ADR-0014: Open Core Architecture — Plugin-based Modules
- Status: Accepted
- Date: 2026-07-03
Context — ADR-0014
The project needs a sustainable business model while preserving the open-source core. The target market (churches in Brazil) segments naturally: small churches use the free core; larger churches pay for premium modules (nuclei/cells, check-in, events).
The challenge is how to decouple modules from the core so that:
- Modules can be developed, versioned, and distributed independently
- The core compiles and runs without any module installed
- Licensing is enforced per-module at runtime (BSL for modules, MIT for core)
- Adding a new module requires minimal changes to the core
Decision — ADR-0014
Open Core with plugin registry. The core (barateza/neemias, MIT + CLA) exposes a Plugin interface in packages/plugin-registry/. Modules (barateza/neemias-modules, BSL) implement this interface and self-register at import time via registerPlugin().
Plugin interface — 6 hooks cover all integration points:
interface Plugin {
id: string; // license entitlement key
name: string; // display name
version: string; // semver
minCoreVersion: string; // compatibility gate
registerDexieStores?: (db) => void; // IndexedDB tables
registerWorkerRoutes?: (route, mw) => void; // API routes + middlewares
registerReactRoutes?: () => Route[]; // React Router paths
registerI18n?: () => Translations; // i18n namespaces
registerPermissions?: () => PermMap; // RBAC keys
registerMigrations?: () => Migration[]; // D1 SQL
}License enforcement is defense-in-depth:
- Frontend: React routes gated by
licenseHas(plugin.id)(IndexedDB-stored license key) - Worker:
verifyLicenseKey()middleware on every module route (Ed25519 offline check) - Dexie: operations gated by the same license check
Repository separation:
| Repo | License | Visibility |
|---|---|---|
barateza/neemias | MIT + CLA | Public |
barateza/neemias-modules | BSL 1.1 | Private (public after MVP release) |
The plugin-registry package lives in the MIT repo. Modules live in the BSL monorepo. They are never published to the public npm registry — the code is bundled directly into the Worker + SPA at build time. All modules are shipped in the bundle; license keys activate them at runtime.
Module activation flow:
- All modules are bundled into the Worker + SPA
- At boot, all plugins self-register via
registerPlugin()(side-effect import) - Core iterates
getPlugins()and wires hooks (routes, i18n, permissions, stores) - License key (signed JWT, Ed25519) is verified offline by frontend and Worker
- Only modules present in the key's
entitlementsarray are active
Consequences — ADR-0014
Benefits:
- Clean separation: core knows nothing about modules except the
Plugininterface - Independent versioning: modules can release on their own cadence
- Zero configuration for churches: one-click deploy ships all modules
- Licensing is cryptographic (Ed25519 signatures), not trust-based
- Adding a module = 1 import line in
router.ts+ 1 inmain.tsx
Tradeoffs:
- All modules in the bundle increase payload (~50-100 KB per module)
- Module extraction requires discipline: schemas split between core and module
- License gate is checked at multiple layers (frontend + Worker + Dexie) — slight duplication
- Plugin registry is mutable global state (acceptable for serverless; reset in tests)
Alternatives Considered — ADR-0014
| Option | Rejected because |
|---|---|
| npm packages (public) | Would expose BSL code to trivial npm install without license enforcement |
| Git submodules | Fragile in CI; sync issues; poor DX |
| Runtime plugin marketplace | Over-engineering for MVP; adds network dependency for license checks |
| Fully closed source | Loses community adoption; the core's value is being open |
| Fully open source | No revenue path; ChMS market in Brazil is proprietary |
References — ADR-0014
- ../../product/prd.md
- ../sdd.md — Open Core section
- .specs/features/059-open-core-design/spec.md — Full decision record (21 decisions)
- https://github.com/barateza/neemias/issues/156 — Exploratory issue
- https://github.com/barateza/neemias-modules — Module monorepo (BSL)
- CLA.md — Contributor License Agreement (MIT + CLA model)
- Requirement IDs: FR-OpenCore, NFR-license, NFR-modularity