Add ADR 0016 recording the accepted module database access design (ModuleDatabase, the frozen ModuleRuntimeServices boundary, one pool/one facade/one services-object identity, explicit migrations, and the trust model), with narrow cross-reference corrections to ADR 0006 and ADR 0015 where their original text is now inaccurate. Sweep docs/development/api.md, docs/development/authorization.md, docs/module-development/README.md, docs/development/testing.md, and docs/development/identity.md for the new database boundary and remaining Phase 4C staleness; correct the stale "Phase 4C is next" roadmap paragraph in platform-architecture.md and the matching gap in README's current-status narrative. Extend the module template with a permissioned GET /records route reading through context.services.database, using the canonical createModuleTable helper, with matching template tests.
209 lines
12 KiB
Markdown
209 lines
12 KiB
Markdown
# Ekklesia
|
|
|
|
Ekklesia is an open, modular church technology platform for composing backend capabilities
|
|
into independently developed dashboards, websites, mobile apps, kiosks, displays, and other
|
|
surfaces.
|
|
|
|
## Current status
|
|
|
|
Phase 3 is complete, and Phase 4A (Core identity foundation), Phase 4B (identity HTTP transport),
|
|
Phase 4C (Core authorization), Phase 5A (module authorization integration), Phase 5B (module HTTP
|
|
transport), and Phase 5C (module database access) have landed. The repository includes
|
|
public module contracts, independently versioned capabilities, static module manifests,
|
|
deterministic automatic discovery, compiled server loading, a staged capability registry,
|
|
dependency-ordered lifecycle with rollback, module-owned Drizzle table contracts, deterministic
|
|
schema aggregation, and a cloneable backend-only module template. Migration artifacts have
|
|
module-owned Drizzle contracts, deterministic offline discovery, explicit destructive-change
|
|
declarations, and exact checksums. Core provides read-only applied-history planning and a locked,
|
|
whole-batch transactional PostgreSQL runner with exact external destructive approval matching. A
|
|
hermetic live PostgreSQL suite proves history, rollback, statement breakpoints, advisory-lock
|
|
concurrency and release, exact destructive approvals, and sanitization against the production
|
|
adapter. A Core-owned `pnpm doctor` dashboard combines the existing static discovery and read-only
|
|
migration-planning services into one concise readiness report.
|
|
|
|
Phase 4A added Core-owned identity foundations: a Core-owned Better Auth database schema
|
|
(`ekklesia_core_user`, `ekklesia_core_session`, `ekklesia_core_account`,
|
|
`ekklesia_core_verification`), a first-class Core-owned migration source that coexists with
|
|
module-owned migrations under the exact same migration system, minimal opaque-identity contracts
|
|
in `@ekklesia/contracts`, and an explicit Better Auth construction boundary in Core. See
|
|
[Identity](docs/development/identity.md) and
|
|
[ADR 0010](docs/architecture/decisions/0010-core-identity-and-better-auth-adoption.md).
|
|
|
|
Phase 4B adds identity HTTP transport: a minimal Hono-based `apps/api` composition root
|
|
(`pnpm api:start`) mounts Better Auth at `/api/auth/*` and exposes `GET /api/v1/principal` behind
|
|
Core's own Fetch-compatible identity runtime — Core still does not depend on Hono, and modules
|
|
still cannot reach any of this. Public email/password registration is intentional; every account
|
|
receives identical lowest-privilege baseline status and signup order never grants elevation. See
|
|
[API](docs/development/api.md) and
|
|
[ADR 0011](docs/architecture/decisions/0011-identity-http-transport-and-public-enrollment.md).
|
|
**No email verification, password recovery, or Dashboard exists yet** — that, along with
|
|
production approval-provider composition, jobs, physical storage provisioning, surfaces, and
|
|
feature modules, remains later vertical slices. Public registration is not yet ready for a real
|
|
public launch.
|
|
|
|
Phase 4C adds Core's authorization foundation on top of identity: a schema, evaluator, and store
|
|
for platform owners, explicit grants, and relationship-derived grants; a smallest-possible
|
|
immutable permission registry; an explicit, single-use, concurrency-safe platform-owner bootstrap
|
|
command (`pnpm authorization:bootstrap-owner`, never HTTP-exposed, never inferred from signup
|
|
order); and, in `apps/api`, reusable Hono authorization enforcement plus a narrowly scoped
|
|
`/api/v1/grants` HTTP API for managing *explicit* grants only, requiring an exact trusted `Origin`
|
|
on every mutation. Roles/groups/teams/tenants, an owner-management HTTP endpoint, and service
|
|
accounts/API tokens remain later work. See [Authorization](docs/development/authorization.md) and
|
|
[ADR 0013](docs/architecture/decisions/0013-core-authorization-and-owner-bootstrap.md).
|
|
|
|
Phase 5A wires module-declared permissions into that same authorization system: a version-3 module
|
|
manifest declares permissions as complete `{id, baseline, description}` declarations (never a bare
|
|
string), and `apps/api` discovers every installed module and composes one immutable platform
|
|
permission registry — Core's own declarations plus every module's — before constructing its
|
|
database pool, Better Auth instance, or HTTP server. A module's declared permission is then
|
|
enforceable and grantable through the exact same `requirePermission` middleware and
|
|
`/api/v1/grants` HTTP API Core's own permissions already use, with `core.*` reserved and never
|
|
claimable by a module. `pnpm doctor` and `pnpm modules:validate` both validate permission
|
|
composition. Installing, removing, or editing a module currently requires an API restart; removing
|
|
one makes its permission unavailable to new checks and new grants without deleting or revoking any
|
|
historical grant or audit row. See
|
|
[Authorization](docs/development/authorization.md#module-permission-composition),
|
|
[Module development](docs/module-development/README.md), and
|
|
[ADR 0014](docs/architecture/decisions/0014-module-permission-declarations-and-platform-registry.md).
|
|
|
|
Phase 5B mounts a module's own HTTP routes in production. A module registers framework-neutral
|
|
Fetch (`Request`/`Response`) route handlers during its existing `register` hook; Core collects them
|
|
into an immutable snapshot alongside its capabilities, and `apps/api` discovers modules exactly
|
|
once, starts the module runtime from that same discovered inventory, and mounts its published
|
|
routes under `/api/v1/modules/<module-id>` — after every module has registered and started
|
|
successfully, so no request can reach a module before startup completes. Every route is enforced
|
|
through the identical `requirePermission` middleware and composed permission registry Core's own
|
|
routes use (a module can never supply its own baseline), state-changing methods require a trusted
|
|
`Origin`, and a handler failure or non-`Response` return becomes a fixed, sanitized `500` — never a
|
|
raw error, stack, path, or permission id. Relationship-derived grant creation remains later work.
|
|
See [API](docs/development/api.md#module-http-routes),
|
|
[Module development](docs/module-development/README.md#http-routes), and
|
|
[ADR 0014](docs/architecture/decisions/0014-module-permission-declarations-and-platform-registry.md#deferred).
|
|
|
|
Phase 5C gives a module's own lifecycle hooks and HTTP handlers typed access to their own
|
|
PostgreSQL tables. `context.services.database` — a restricted, typed `ModuleDatabase` view over
|
|
Drizzle's `select`/`selectDistinct`/`selectDistinctOn`/`insert`/`update`/`delete`/`execute`/
|
|
`transaction`, never a raw `pg` client, pool, or connection string — is available to `start`,
|
|
`stop`, and every authorized route handler, and reuses the one PostgreSQL pool `apps/api` already
|
|
owns; registration remains declarative and database-free. Modules continue to own and import their
|
|
own Drizzle schemas and generated migrations, applied explicitly and never automatically at
|
|
startup. See [API](docs/development/api.md#module-database-access),
|
|
[Module development](docs/module-development/README.md#database-access), and
|
|
[ADR 0016](docs/architecture/decisions/0016-module-database-access.md).
|
|
|
|
## Quick start
|
|
|
|
Prerequisites: Node.js 24 LTS, pnpm 11.12, and Docker with Compose.
|
|
|
|
```bash
|
|
pnpm install --frozen-lockfile
|
|
cp .env.example .env
|
|
pnpm infrastructure:up
|
|
pnpm modules:validate
|
|
pnpm migrations:validate
|
|
pnpm identity:schema:validate
|
|
pnpm doctor
|
|
pnpm migrations:plan
|
|
pnpm test:migrations:integration
|
|
pnpm verify
|
|
```
|
|
|
|
`pnpm doctor` builds the workspace first, then reports the supported Node.js runtime, installed
|
|
modules and dependency order, schema and migration inventories, optional read-only PostgreSQL
|
|
planning, and the current object-storage integration state. It never applies migrations, creates a
|
|
history table, starts module servers, or performs storage operations. Schema discovery does import
|
|
compiled schema-definition entrypoints; those entrypoints are contractually side-effect-free.
|
|
|
|
With no installed modules and no `DATABASE_URL`, the current repository produces a report shaped
|
|
like this:
|
|
|
|
```text
|
|
Ekklesia Doctor
|
|
|
|
Platform
|
|
✓ Runtime version supported
|
|
Active Node.js satisfies the declared supported range.
|
|
|
|
Modules
|
|
✓ Module discovery complete
|
|
0 modules installed
|
|
✓ Dependency graph valid
|
|
Dependency order is empty.
|
|
✓ Permission registry composed
|
|
1 permission registered
|
|
|
|
Data definitions
|
|
✓ Schemas validated
|
|
0 modules · 0 tables
|
|
✓ Migrations validated
|
|
2 migrations · checksums valid
|
|
core:0000_identity_core_tables · f8f2086ef06f
|
|
core:0001_authorization_core_tables · 830e1fbc26d0
|
|
|
|
PostgreSQL
|
|
○ Connection check skipped
|
|
DATABASE_URL is not configured.
|
|
○ Migration plan skipped
|
|
A validated migration inventory and configured PostgreSQL connection are required.
|
|
|
|
Identity
|
|
○ Identity configuration skipped
|
|
Core identity schema defines 4 tables (ekklesia_core_account, ekklesia_core_session, ekklesia_core_user, ekklesia_core_verification).
|
|
Identity runtime configuration (signing key, base URL, trusted origins) is not set.
|
|
|
|
Object storage
|
|
○ Check unavailable
|
|
Core storage integration has not been implemented yet.
|
|
|
|
Result: READY WITH WARNINGS
|
|
```
|
|
|
|
Checks have stable pass, warning, failure, or skipped states. Warnings and skipped optional checks
|
|
produce `READY WITH WARNINGS` and exit zero; any failure produces `NOT READY` and exit one. The
|
|
dashboard is human-readable local guidance, not a replacement for automated CI evidence.
|
|
|
|
`pnpm migrations:plan` uses `DATABASE_URL` from the uncommitted `.env` and is read-only.
|
|
`pnpm migrations:apply` uses the same validated inventory but mutates PostgreSQL under Core's
|
|
advisory lock and one transaction. Review the plan before applying. Destructive migrations are
|
|
blocked by default and require an exact externally supplied approval; there is no force flag.
|
|
|
|
The runner has both deterministic unit coverage through a narrow database port and a separate live
|
|
PostgreSQL integration suite. The live command creates and destroys disposable Docker
|
|
infrastructure; it remains separate from the Docker-free `pnpm verify` gate.
|
|
|
|
`pnpm identity:schema:validate` regenerates Better Auth's canonical schema with the pinned local
|
|
CLI into a temporary location and structurally compares it against the checked-in
|
|
`core/src/identity/schema.ts`, then removes the temporary output. It requires no `DATABASE_URL`
|
|
and is part of `pnpm verify`. See [Identity](docs/development/identity.md) for the full upgrade
|
|
workflow.
|
|
|
|
`pnpm api:start` builds the workspace, then starts the identity HTTP transport
|
|
(`GET /api/v1/principal`, Better Auth mounted at `/api/auth/*`) against an already-migrated
|
|
`DATABASE_URL` — it never applies migrations itself. See [API](docs/development/api.md) for
|
|
required configuration, routes, and a full request/response transcript.
|
|
|
|
Run `pnpm infrastructure:smoke` for an isolated end-to-end PostgreSQL and authenticated S3 check.
|
|
|
|
The development services bind only to loopback:
|
|
|
|
- PostgreSQL: `127.0.0.1:5432`
|
|
- S3-compatible object storage: `127.0.0.1:8333`
|
|
|
|
The values in `.env.example` are development-only and must never be reused in a deployed
|
|
environment.
|
|
|
|
## Architecture
|
|
|
|
- [Platform architecture](docs/architecture/platform-architecture.md)
|
|
- [Repository boundaries](docs/architecture/repository-boundaries.md)
|
|
- [Repository workflow](docs/development/repository-workflow.md)
|
|
- [Testing](docs/development/testing.md)
|
|
- [Identity](docs/development/identity.md)
|
|
- [API](docs/development/api.md)
|
|
- [Authorization](docs/development/authorization.md)
|
|
- [Module development](docs/module-development/README.md)
|
|
- [Architecture decisions](docs/architecture/decisions/README.md)
|
|
|
|
No open-source license has been selected yet. A license should be added only after the project
|
|
owner makes that decision.
|