Skip to content

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.

  1. 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.

  2. 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_event accepts a set committed all-or-nothing (one transaction, one possession, one encounter scope), so co-produced events land together or not at all.

  3. The non-append surface is a small closed set, each operation a narrow, specially-audited entry point because it is not a pure append:

  4. 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.
  5. Author-scoped export (ADR-0019) — reads + packages signed bytes, emitting an append-only audit event recording blast radius.
  6. 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.

  1. 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_event call. The wire contract is untouched by drafts, so they need not — and must not — sit on the immutable log.

  2. submit_event is the in-DB convergence of every write-time seam, executed atomically in order:

  3. 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.
  4. Authorize — access-control enforcement (§7).
  5. 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.
  6. 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.
  7. Apply hard-policy gates — the ADR-0010 suppressing owner-gate; the §5.9 sensitivity auto-grade + seal-time safety-projection derivation.
  8. 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.
  9. Idempotent appendINSERT … ON CONFLICT (uuid) DO NOTHING; AFTER INSERT trigger projections (§9.4).
  10. Return the assigned UUID/HLC and honest assembly state.

  11. 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.

  12. The authoring path and the apply path are distinct and must not be conflated. submit_event is 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.