ADR-0022 — The validated submit surface: the node's write path¶
Context¶
ADR-0021 put the safety/compatibility floor in the
database and named the validated submit surface as the unbypassable entry point for all writes: UIs
and external clients never INSERT into event tables; they call validated submit functions that the DB
role-grant model confines them to. ADR-0021 then made that surface's completeness the load-bearing
bet — every legitimate write must be expressible through it, or UIs are pushed to raw access and the
bypass re-opens — but left the surface itself unspecified.
The hard part is a paradox in ADR-0021's own words: the surface must be small (the smallest possible audited trusted base, principle 8) yet complete (cover every legitimate write). A function per clinical event type would be neither — it would sprawl, and it would have to grow every time a new event type is added, violating additive-only evolution (ADR-0012) and bloating the audited surface. Two observations dissolve the paradox:
- Cairn is append-only (principle 1), so almost every write is the same operation — append a validated, signed event. Clinical events, identity events (§5.7), actor events (§3.12), demographic assertions (§4.1), the overlay streams (sensitivity, responsibility-tags, acknowledgments, trust/recall markers, the ADR-0020 visibility-suppression overlay), federation peering (ADR-0017), and audited configuration acts (ADR-0010) are all appends of a signed event. The only writes that are not appends are a small, enumerable set.
- The write path is where a long list of write-time seams the spec has accumulated all converge — the authorship stamp (ADR-0008), clash detection (ADR-0003), the seal-time safety projection (§5.9), the suppressing owner-gate (ADR-0010), the legibility-twin derivation (ADR-0012), and canonicalize+sign (ADR-0011/ADR-0015). The submit surface is not a new mechanism — it is the named convergence point of these seams.
Decision¶
Specify the submit surface as one generic validated-append, plus a closed set of non-append operations — small by construction, complete by genericity. Canonical home: language-substrate §9.6. No new founding principle.
-
One generic validated-append function, not one per type.
submit_event(envelope, body)is generic in signature and type-validated by dispatch to a per-(event_type, schema_version)validator held in the trusted layer and registered additively (ADR-0012). A new event type adds a validator behind the same door — the surface does not grow. Closed-enum membership (event_type, the role enum) is still enforced. This single move is what makes the surface simultaneously small and complete. -
Atomic multi-event submission. A clinical action routinely co-produces several events — the ADR-0020 order + note-line, or an event together with its overlay.
submit_eventaccepts a set committed all-or-nothing (one transaction, one possession, one encounter scope), so co-produced events land together or not at all. -
The non-append surface is a small closed set, each operation a narrow, specially-audited entry point because it is not a pure append:
- Erasure / key-custody redistribution (ADR-0005) — crypto-shred / seal / escrow. It destroys or re-custodies a key rather than appending content, though it emits an append-only erasure-declaration audit event; the irreversible rung carries the ADR-0020 forced-rationale gate.
- Author-scoped export (ADR-0019) — reads + packages signed bytes, emitting an append-only audit event recording blast radius.
- Blob byte-tier put (§6.6, §3.14) —
content-addressed bytes on the separate byte plane, content-verified on store. (The reference is
an ordinary
submit_event; only the bytes take this door.)
Everything not in this closed set is submit_event. Reads/projections are SELECT on projection
views (the §9.5
grant), outside the surface; an audited read such as break-glass key-use
(§5.9)
emits its audit through the append path.
-
Drafts are adjacent, not part of it. Pre-commit drafts (§3.10) are local, mutable, never-synced node state behind a separate validated (but not append-only) local API; commit turns a draft into a
submit_eventcall. The wire contract is untouched by drafts, so they need not — and must not — sit on the immutable log. -
submit_eventis the in-DB convergence of every write-time seam, executed atomically in order: - Authenticate the author — verify an author-attestation token
(ADR-0008), yielding the contributor set and
the authorship-confidence grade (attested / asserted / unattributed). The DB session is never
trusted as the author (
session.user ≠ event.author, §3.10) — this is why a direct-DB client cannot forge authorship: it cannot forge the token. - Authorize — access-control enforcement (§7).
- Validate the envelope — required typed fields, scope-key shape, contributor-set well-formedness,
closed-enum membership, and the §3.6
Tier-1 ceiling
t_effective ≤ t_recorded. - Validate the body — dispatched per
(event_type, schema_version)(ruling 1); demographic coherence (§4.2/§5.2); §3.6 Tier-2 clash → flag, never resolve. - Apply hard-policy gates — the ADR-0010 suppressing owner-gate; the §5.9 sensitivity auto-grade + seal-time safety-projection derivation.
- Canonicalize + twin + sign — deterministic-CBOR canonical bytes, the mandatory plaintext legibility twin derived at write-time (ADR-0012), the signature (ADR-0011/ADR-0015), and the content digest.
- Idempotent append —
INSERT … ON CONFLICT (uuid) DO NOTHING;AFTER INSERTtrigger projections (§9.4). -
Return the assigned UUID/HLC and honest assembly state.
-
Signing must be reachable from the in-DB path — a direct consequence of ADR-0021's floor-in-DB. If signing lived only in the L2 Rust layer, a direct-DB caller could not produce a signed event and the in-DB floor would be incomplete, contradicting ADR-0021. Therefore the node's trusted base includes the database process (fat Postgres, ADR-0001): signing is performed in-DB (pgrx + node-keystore access) by default, or delegated to a co-located trusted signer the in-DB submit invokes — but never required to sit in L2. The author-attestation token and the signer are the two pieces of trusted base the submit path binds.
-
The authoring path and the apply path are distinct and must not be conflated.
submit_eventis the authoring path (this node mints, validates, signs, appends). The apply path (§6.1/§9.4) ingests already-signed events from peers — it verifies signature/content-address and idempotently appends, but never re-signs and never re-runs authorship/authz (a foreign node's attestation stands). Both terminate in the same idempotent append; only authoring signs.
Consequences¶
- Easier. The surface is provably small — one append door + a three-member non-append set — yet complete: genericity (ruling 1) covers every event type, and the append-only taxonomy enumerates the categories so the completeness claim is checkable. Completeness is maintainable: a new event type is an additive validator, never a new door. Concentrating every write-time seam at one in-DB function gives the smallest-possible audited surface (principle 8) and puts every seam the spec has named (ADR-0003/0005/0006/0008/0010/0011/0012/0015/0020) in one reviewable place.
- Harder / new trusted surface. That one in-DB function becomes the most safety-critical code in the system (mis-sign, mis-attribute, mis-validate, or mis-gate → corruption or leak). It must be optimised for reviewer-legibility and is the prime pgrx candidate (ADR-0002). The author-attestation-token verification and the in-DB signer / keystore access are the two sub-seams to get right; the validator-dispatch registry must itself be additive-only and tamper-evident.
- The bet (refining ADR-0021's). That the generic-append + closed-non-append-set surface stays complete as the clinical model grows — that no future legitimate write is neither an append nor a member of the closed non-append set. We would know the bet is wrong if such an operation appears (forcing a new door and growing the surface) — which would itself be a useful signal that the data model has grown a non-append-shaped concept worth scrutinising before it is admitted.
- Policy-neutral (principle 9). The surface is mechanism; which writes a deployment permits, and the hard-policy gates it activates at step 5.5, are configuration. No new event stream; no new founding principle — this refines ADR-0021 and ADR-0001.