docs: document module database access
CI / verify (pull_request) Successful in 27s
CI / infrastructure-smoke (pull_request) Successful in 20s
CI / migration-smoke (pull_request) Successful in 29s

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.
This commit is contained in:
ekklesia
2026-07-25 21:50:03 -05:00
parent d1b9efd4d2
commit c38b572778
15 changed files with 584 additions and 91 deletions
+16 -12
View File
@@ -396,15 +396,19 @@ Every response sets `Cache-Control: no-store`.
`apps/api/src/database.ts`'s `createApiDatabase(databaseUrl, options)` is the one place `apps/api`
touches PostgreSQL connection setup. It constructs exactly one validated, timeout-bounded
`pg.Pool` (`createValidatedPostgresPool`, `@ekklesia/core/database`) and wraps it with two
independent typed Drizzle views — `createIdentityDrizzleView(pool)` and
`createAuthorizationDrizzleView(pool)` — rather than two separate pools. `close()` on the returned
handle is the only thing that ever calls `pool.end()`; neither domain view owns or closes the pool
itself. `apps/api/src/server.ts` calls `createApiDatabase` once during `startApi()`, builds the
identity runtime from `.identity` and the `AuthorizationStore`/`AuthorizationEvaluator` from
`.authorization` off that same handle, and closes the handle exactly once — on graceful shutdown,
and on any startup failure after the pool was already constructed (mirroring the existing
identity-only failure-cleanup path from [API](api.md#graceful-shutdown)).
`pg.Pool` (`createValidatedPostgresPool`, `@ekklesia/core/database`) and wraps it with three
independent typed Drizzle views — `createIdentityDrizzleView(pool)`,
`createAuthorizationDrizzleView(pool)`, and `createModuleDatabaseView(pool)` (the `ModuleDatabase`
facade module code queries through `context.services.database`; see
[API](api.md#module-database-access) and
[Module development](../module-development/README.md#database-access)) — rather than three
separate pools. `close()` on the returned handle is the only thing that ever calls `pool.end()`;
no domain view owns or closes the pool itself. `apps/api/src/server.ts` calls `createApiDatabase`
once during `startApi()`, builds the identity runtime from `.identity`, the
`AuthorizationStore`/`AuthorizationEvaluator` from `.authorization`, and the frozen
`ModuleRuntimeServices` from `.module` off that same handle, and closes the handle exactly once —
on graceful shutdown, and on any startup failure after the pool was already constructed (mirroring
the existing identity-only failure-cleanup path from [API](api.md#graceful-shutdown)).
The existing per-domain factories (`createIdentityDrizzleDatabase`,
`createAuthorizationDrizzleDatabase`), which each construct and own a private pool, are
@@ -413,9 +417,9 @@ unaffected and remain available for standalone/testable use — the CLI bootstra
the authorization schema and has no reason to share a pool with anything else.
`apps/api/tests/lifecycle.test.ts` proves this composition explicitly: exactly one
`new Pool(...)` call for a full `startApi()` lifecycle (covering both identity and authorization
usage), and exactly one `pool.end()` call regardless of how many concurrent `shutdown()` callers
there are or whether the failure happened during startup.
`new Pool(...)` call for a full `startApi()` lifecycle (covering identity, authorization, and
module database usage), and exactly one `pool.end()` call regardless of how many concurrent
`shutdown()` callers there are or whether the failure happened during startup.
## Tests