Three narrow clarifications to the proposed contract. No other decision
changes.
- Outstanding work: module code awaits every operation before its
transaction or savepoint callback returns. A callback that settles
with work outstanding is a scope-usage error and never commits. Core
may let started operations settle before rolling back and releasing
the connection, but waiting never turns into a commit. Executors stay
revoked once their scope has finished.
- One statement per query() call: a correctness and API rule, not a
security boundary. It is upheld by a reliable driver or protocol
mechanism, never a home-grown SQL parser. An implementation that
cannot uphold it stops and reports instead of weakening the ADR.
- Errors: `code` is a stable Ekklesia-owned category and always present.
`sqlState` is optional, present only when PostgreSQL supplied one.
ABI-detected failures (closed scope, invalid parameter, invalid
option) carry a code and no SQLSTATE.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Core and modules exchange PostgreSQL SQL text, bound parameters, result
rows with field metadata, and Core-owned transaction scopes. Never ORM
objects. A module may use Drizzle, Kysely, raw SQL, or another
module-side builder. Core's own ORM becomes an implementation detail.
ADR 0050 decides:
- an explicit correctness/compatibility boundary, not a security
sandbox: no claim for SQL parsing, execution-time prefix checks,
SET ROLE, or PostgreSQL roles;
- opt-in through manifest version 8 `database.abi: 1`, a hard cutover
like every manifest version; a version 8 module without `database`
keeps the legacy ModuleDatabase facade during the deprecation window;
- the executor: query({ text, values, rowMode }) returning
{ command, rowCount, rows, fields }, one executor per module;
- a small parameter set (no Date, no implicit JSON, no identifiers);
- a Core-owned decoding table that loses no precision and does not
depend on time zones (int8, numeric, temporal, and JSON as text;
bytea as Uint8Array);
- a sanitized ModuleDatabaseError { code, sqlState? }, recognised
structurally;
- callback-scoped transactions and Core-named savepoints, revoked when
their callback settles;
- module-side adapters (Drizzle via pg-proxy, raw SQL, later Kysely);
- plain schema metadata for ABI v1 modules instead of Drizzle objects;
- migrations unchanged; packageFormat stays 1; ABI v1 packages omit
transitional.databaseRuntime.
It partly supersedes ADR 0016 and amends ADR 0007 for ABI v1 modules,
and extends ADR 0049. Status Proposed. Accepted ADRs are unchanged. No
runtime, SDK, manifest, packaging, migration, or Signage changes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>