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
+20 -1
View File
@@ -1,4 +1,5 @@
import { defineModuleRuntime } from "@ekklesia/module-sdk";
import { records } from "../schema/tables.js";
export const recordsCapability = Object.freeze({
kind: "example-records",
@@ -7,7 +8,25 @@ export const recordsCapability = Object.freeze({
export default defineModuleRuntime({
id: "example",
version: "0.1.0",
register({ capabilities }) {
register({ capabilities, http }) {
capabilities.provide("example.records", recordsCapability);
// The accepted module database boundary: `context.services.database` — provided only to an
// authorized handler, never to `register` — is this module's one way to reach PostgreSQL. It
// is a fully typed Drizzle query surface over the module's own table (`../schema/tables.js`),
// never a raw `pg` client, a connection string, or a Drizzle instance this module built
// itself. `example.records.read` is declared in `ekklesia.module.json`, exactly like every
// other permission a module route enforces.
http.route({
method: "GET",
path: "/records",
permission: "example.records.read",
async handler(_request, context) {
const rows = await context.services.database
.select({ id: records.id, value: records.value })
.from(records);
return Response.json(rows);
},
});
},
});