The project decision is the GNU Affero General Public License, version 3 or (at your option) any later version. - "license": "AGPL-3.0-or-later" in all 14 Ekklesia-owned package.json files. - README: the License section states AGPL v3 or any later version (SPDX AGPL-3.0-or-later) and carries the FSF's standard grant: "either version 3 of the License, or (at your option) any later version", with its warranty disclaimer and pointer to the license. - Module template README and module development guide: AGPL-3.0-or-later for Ekklesia and official modules. LICENSE is unchanged: it remains the canonical AGPLv3 text. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Ekklesia module template
This is the smallest supported Ekklesia module product. It proves the static manifest,
compiled runtime and schema entrypoints, capability registration, a module-owned Drizzle table, a
generated module-owned migration, logical storage-bucket declarations, and one permissioned HTTP
route (GET /records, declared in register) that reads that table through
context.services.database — the platform-provided ModuleDatabase facade, never a
module-constructed Drizzle instance or a raw pg client. Migration execution and storage
provisioning are intentionally not included.
It also ships two independently optional React roles: a primary/management UI at src/ui/index.tsx
and a full-viewport display at src/display/index.tsx. One invocation of pnpm build compiles each
declared role to the exact artifact in entrypoints.ui and entrypoints.display.artifact
(ADR 0023). Both default-export
the same role-neutral defineModuleUi({ id, version, mount }) ABI and receive the same
ModuleUiHostContext; there is deliberately no mode flag. The manifest entrypoint and the host route
choose the role.
Either role can be omitted independently. To remove only the display example, delete src/display/
and entrypoints.display (and remove example.display.view if nothing else uses it). To keep the
module backend-only, also delete src/ui/, tsconfig.ui.json, tsconfig.ui-test.json,
tests/module-ui.test.ts, the ui entrypoint, and the React/module-UI dependencies. The builder
succeeds with an empty artifact list when neither role is declared.
tests/module-ui.test.ts builds both artifacts from source, validates them with
parseModuleUiExport, mounts each into a real DOM, and unmounts it. Ekklesia mounts the primary UI
inside its Dashboard shell and serves the display without Dashboard chrome at the stable
/display/example page. See
Declaring UI entrypoints.
To create a module inside this repository:
- Copy this directory to
modules/<module-id>. - Rename the package in
package.jsonand removeprivatewhen it is ready for publication. - Update the ID, name, version, namespaces, compatibility ranges, and bucket requests in
ekklesia.module.json. - Keep the manifest capability versions and dependency ranges aligned with the contracts the module actually implements and consumes.
- Replace
src/server/index.tswith backend behavior only. ItsdefineModuleRuntimeID and version must match the manifest; do not duplicate the rest of the manifest in TypeScript. Declare HTTP routes only inregister, against a permission the manifest already declares;start,stop, and every route handler — neverregister— receivecontext.services.database, a typedModuleDatabaseview over the platform's one PostgreSQL pool. - Replace the pure Drizzle declarations in
src/schema/tables.tsand keep its table prefix aligned with the module ID. Keep thedefineModuleSchemaownership wrapper insrc/schema/index.tsaligned too, and never import another module's schema. - Update
drizzle.config.ts, generate forward migrations withpnpm migration:generate --name=lowercase_namefrom the module directory, and review the SQL. Keep each generated tag indrizzle/meta/_ekklesia.json, classify it asnoneorapproval-required, and change that file'smoduleIdwhen renaming the template. - Run
pnpm install,pnpm build,pnpm modules:validate,pnpm migrations:validate, andpnpm verifyfrom the repository root. The declared production entrypoint is the compileddist/server/index.js, not source TypeScript; the schema entrypoint is likewise compiled todist/schema/index.js.
That modules/<module-id> directory is where a module is developed. An installation never runs
it from there: Ekklesia's image contains no modules, and an installation installs a self-contained
copy of the module into its module storage
(An installed module is self-contained,
ADR 0048). Copying dist/ alone is not enough, because the compiled server code imports the Module
SDK and Drizzle by name. After building, package the module with the canonical packager, which
bundles the backend into one deterministic Ekklesia Module Package (ADR 0049):
pnpm modules:pack --module templates/module --target any --out <directory> \
--source-revision "$(git rev-parse HEAD)"
The module owns its feature screens and display renderer. Do not add platform navigation, the
containing shell, global theme, authentication screens, or top-level routing; those belong to
surfaces and Core. A module UI asks its host for relative API paths and realtime state, and never
hardcodes Ekklesia routes. The display permission is enforced by the host before it serves the page
or descriptor; context.permissions remains a presentation-only hint.
Keep imports side-effect-free, register capabilities in register, acquire resources in start,
and release them in stop. Never construct a Drizzle instance, read DATABASE_URL, or reach for a
raw pg client — context.services.database is the only supported way to query this module's own
tables, and it is never available during register.
The migration directory uses the pinned stable Drizzle Kit 0.31.10 PostgreSQL journal and
snapshot format. Migrations are forward-only and may modify only this module's objects. Offline
validation treats SQL as opaque bytes, calculates SHA-256 from the exact contents, and never opens a
database or executes SQL. Destructive declarations are review metadata, not automatic SQL
classification or approval.
License
The template's package.json declares "license": "AGPL-3.0-or-later", the license of Ekklesia
and of official Ekklesia modules (see the repository README and
LICENSE). A module started from the template begins with that declaration; an
official Ekklesia module keeps it.