Add a typed ModuleDatabase facade over the API-owned Drizzle/PostgreSQL connection.
Provide frozen ModuleRuntimeServices to module start, stop, and authorized HTTP handlers while keeping registration declarative and database-free.
Preserve one physical runtime pool shared by Identity, Authorization, and installed modules.
Add real PostgreSQL integration coverage for lifecycle queries, authorized handlers, transactions, explicit migrations, error sanitization, and shutdown ordering.
Add a type-checked module-template example and document the architecture in ADR 0016.
Architecture
ModuleDatabase exposes the supported Drizzle operations:
select
selectDistinct
selectDistinctOn
insert
update
delete
execute
transaction
Modules access it through context.services.database. They do not receive a pg.Pool, connection string, $client, migration runner, or platform schema registry.
Registration remains database-free. The same frozen database facade is supplied to module lifecycle hooks and HTTP handlers.
Migrations remain an explicit deployment operation and never run during application startup.
Normal shutdown ordering is:
Drain and close HTTP.
Stop the module runtime while database access remains available.
Close the PostgreSQL pool.
Verification
Root: 41 tests
Contracts: 111 tests
Module SDK: 5 tests
Module template: 5 tests
Core: 338 tests
API: 145 tests
Total unit tests: 645
PostgreSQL integration: 114 tests
pnpm verify: passed
pnpm test:migrations:integration: passed
Doctor: READY WITH WARNINGS
pnpm migrations:validate: passed
git diff --check: clean
Production migration contents and checksums remain unchanged. No production schema, manifest contract, manifestVersion, dependency, lockfile, route contract, or Hono-boundary changes were introduced.
Commits
0e308eb9 — feat: define module database access
d1b9efd4 — test: prove module database access with PostgreSQL
c38b5727 — docs: document module database access
## Summary
* Add a typed `ModuleDatabase` facade over the API-owned Drizzle/PostgreSQL connection.
* Provide frozen `ModuleRuntimeServices` to module `start`, `stop`, and authorized HTTP handlers while keeping registration declarative and database-free.
* Preserve one physical runtime pool shared by Identity, Authorization, and installed modules.
* Add real PostgreSQL integration coverage for lifecycle queries, authorized handlers, transactions, explicit migrations, error sanitization, and shutdown ordering.
* Add a type-checked module-template example and document the architecture in ADR 0016.
## Architecture
`ModuleDatabase` exposes the supported Drizzle operations:
* `select`
* `selectDistinct`
* `selectDistinctOn`
* `insert`
* `update`
* `delete`
* `execute`
* `transaction`
Modules access it through `context.services.database`. They do not receive a `pg.Pool`, connection string, `$client`, migration runner, or platform schema registry.
Registration remains database-free. The same frozen database facade is supplied to module lifecycle hooks and HTTP handlers.
Migrations remain an explicit deployment operation and never run during application startup.
Normal shutdown ordering is:
1. Drain and close HTTP.
2. Stop the module runtime while database access remains available.
3. Close the PostgreSQL pool.
## Verification
* Root: 41 tests
* Contracts: 111 tests
* Module SDK: 5 tests
* Module template: 5 tests
* Core: 338 tests
* API: 145 tests
* Total unit tests: 645
* PostgreSQL integration: 114 tests
* `pnpm verify`: passed
* `pnpm test:migrations:integration`: passed
* Doctor: `READY WITH WARNINGS`
* `pnpm migrations:validate`: passed
* `git diff --check`: clean
Production migration contents and checksums remain unchanged. No production schema, manifest contract, `manifestVersion`, dependency, lockfile, route contract, or Hono-boundary changes were introduced.
## Commits
1. `0e308eb9` — feat: define module database access
2. `d1b9efd4` — test: prove module database access with PostgreSQL
3. `c38b5727` — docs: document module database access
Introduce ModuleDatabase (a typed, restricted Drizzle query surface) and the frozen
ModuleRuntimeServices boundary in @ekklesia/contracts, and thread the same services object
through Core's module start/stop lifecycle hooks and apps/api's authorized module HTTP handler
context. apps/api builds the facade via a new core/src/modules/database.ts view (createModuleDatabaseView)
over the one existing shared PostgreSQL pool, alongside the identity and authorization views, and
wires it into startApi's fixed startup order ahead of Identity/Authorization construction.
Registration stays database-free; a module reaches its database only through an authorized start,
stop, or HTTP handler invocation.
Add a real installed-module fixture (core/tests/fixtures/module-database-integration/db-facade)
with its own manifest, Drizzle schema, drizzle-kit-generated migration, and permissioned HTTP
routes, then exercise it end to end in a new apps/api PostgreSQL integration suite: explicit
migration application before the module runtime starts (never auto-created), start/stop/HTTP
handlers sharing one ModuleDatabase facade by reference, authorized read/mutation/transaction
commit and rollback against real PostgreSQL, denied/rejected-origin/OPTIONS/HEAD requests never
reaching the module or its table, and both a module-caught and an uncaught real database failure
sanitizing exactly as pass 1 established. Registered alongside the existing suites in
vitest.migrations.integration.config.ts; no production code, schema, or migration changed.
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.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
ModuleDatabasefacade over the API-owned Drizzle/PostgreSQL connection.ModuleRuntimeServicesto modulestart,stop, and authorized HTTP handlers while keeping registration declarative and database-free.Architecture
ModuleDatabaseexposes the supported Drizzle operations:selectselectDistinctselectDistinctOninsertupdatedeleteexecutetransactionModules access it through
context.services.database. They do not receive apg.Pool, connection string,$client, migration runner, or platform schema registry.Registration remains database-free. The same frozen database facade is supplied to module lifecycle hooks and HTTP handlers.
Migrations remain an explicit deployment operation and never run during application startup.
Normal shutdown ordering is:
Verification
pnpm verify: passedpnpm test:migrations:integration: passedREADY WITH WARNINGSpnpm migrations:validate: passedgit diff --check: cleanProduction migration contents and checksums remain unchanged. No production schema, manifest contract,
manifestVersion, dependency, lockfile, route contract, or Hono-boundary changes were introduced.Commits
0e308eb9— feat: define module database accessd1b9efd4— test: prove module database access with PostgreSQLc38b5727— docs: document module database access