Skip to content

6. Synchronisation Layer

6.1 Mechanism

  • Transport-agnostic, resumable, delta-based protocol over HTTPS; optional store-and-forward via removable media ("sneakernet sync") for fully disconnected sites.
  • Build, don't adopt. Built on PostgreSQL logical decoding as the change-capture primitive, with a thin custom Rust sync service implementing scoping, filtering, priority, and idempotent apply. The service ships and applies events; it does not merge or resolve conflicts — because the clinical log is append-only and immutable, syncing the source of truth is INSERT-only, idempotent (UUIDv7 PK), scoped set-union; there are no row-level clinical conflicts to resolve. The DB guarantees that "apply" is safe (language-substrate §9.4); the daemon stays thin. Projection winner tiebreaks compare TEXT keys under COLLATE "C" (byte order) so the display winner converges across a federation of mixed default collations (ADR-0045). Honest limit of that claim: it holds among nodes running the same projection code. Additive-only evolution governs the wire (§6.5), not projection semantics — a change to a winner rule alters displayed clinical state fleet-inconsistently under the no-lockstep-upgrade doctrine, with no event recording it. The working convention (already de facto in ADR-0036/0037/0038): winner-rule changes are ADR-gated and travel the §7.6 code plane like any behavior change.

Note

pgactive / SymmetricDS are references, not dependencies. They exist to resolve row-level conflicts (last-writer-wins and similar) — a problem Cairn designed away — and their default policies can violate invariants (LWW on a demographic = silent data loss, forbidden by §4). Borrow their patterns (logical-decoding plumbing, store-and-forward / sneakernet) without a hard runtime dependency, honoring the vendor-independence mission. See ADR-0001.

  • Per §9, this safety-critical service is Rust and/or in-database, never a dynamic language.
  • Sync scopes are declarative subscription predicates that govern automatic replication — an administrative prefetch default, not an access boundary. They are versioned, auditable, and normally evaluated at the parent; but a node may acquire any record it has legitimate need for, even out of scope and even with the parent unreachable (audited). Scope governs what a node pulls by default, never what it is permitted to hold. See §6.4.
  • Bandwidth discipline: compression, binary diffs, and attachments synced lazily by reference on a separate, resource-isolated byte tier (§6.6).
  • Upstream priority order: new clinical events and audit events first; identity events (link/repudiate/reattribute) high priority; attachment bytes last — and on a budget that can never starve the above (§6.6).

6.2 Consistency model

  • Eventual consistency with causal ordering (HLC) within a patient record.
  • Every projection displays a freshness indicator ("last synced with parent 4 h ago") — a first-class UI requirement. Backup health is the same honest-assembly fact applied to durability ("last successful backup N h ago"; medium full / detached / failing) — a node running without a recovery net must say so (security §7.10).
  • Honest assembly state. The chart is always a best-effort assembly of currently-available parts, and must say so as a first-class clinical fact. Beyond freshness it surfaces known-missing parts when it can detect them (the parent advertised 5 episodes, only 3 arrived; a sibling is reachable but unsynced) and, when fully partitioned, signals that parts may exist beyond the island. Making absence visible is a safety gain with no paper equivalent — on paper the other ward's notes are simply, invisibly absent. See §6.4 and ADR-0004.
  • The notification inbox is honest-assembly applied to alerts. A notification is a local projection over locally-available events (identity §5.12, data-model §3.11); a trigger may still be on another node, so "all caught up / inbox zero" is never claimed across a partition. The honest ceiling mirrors the erasure ceiling: "to this node's knowledge, you have seen everything relevant."

6.3 Failure modes (designed-for)

Failure Behaviour
Internet down Facility operates on facility server; queues outbound
Intranet down Department server is local master for its scope
Department server down Workstations operate standalone on mirrored scope
Node destroyed (has a parent/peer) Re-provision from parent; only unsync'd local events are at risk → aggressive upward sync priority
Node destroyed (solo, no peer) Restore from the sealed local backup + recovery secret; new supersede-linked identity, erasures replayed on restore — see security §7.10, ADR-0026
Peer offers an event of a type this node cannot classify Not a refusal — admit-and-defer (ADR-0056, §6.5). The event is stored verbatim, re-propagated and rendered by the skeleton twin, but produces no projection rows and confers no power until the code plane classifies it. Live on the clinical plane since 2026-07-29 (#265/#266): the admitted-uninterpreted state is recorded explicitly, and reclassification re-adjudicates before it reprojects. Honest status — the node/actor plane still fail-closes on a node event type it cannot map (#301), so a carrier node is still a propagation barrier for future trust-plane types; that door needs a carried-not-interpreted row shape of its own.
Peer offers an event this node's floor genuinely refuses (malformed, bad signature, unenrolled signer, floor-rejected) Refusal + durable re-offer — the residual contract, once unknown types are excluded from it (ADR-0056). The bytes are quarantined verbatim by digest, the refusal is answered legibly, and the sync watermark does not advance past unresolved refusals (a quarantine floor pins it) — so an upgraded floor later admits what an older one refused, and nothing is silently dropped. The one deliberate exception: an operator ack (acked = TRUE) is a recorded human decision that permanently excludes a penned event from re-offer and quota — never an automatic drop. (Mechanism today in db/020db/022.) Honest status — the pen is narrower than this row implies: it holds unverifiable bytes only, so a door refusal on verifiable bytes persists nothing (#267); the clinical plane freezes its watermark instead and still exits success (#270), while the node plane skips-and-advances and re-offers only on a periodic full sweep (#268).
Hostile-but-credentialed peer streams unique invalid bytes Honest limit: the quarantine pen stores verbatim bytes per digest with no cap or expiry today, so an enrolled-but-malicious peer can grow it without bound — a denial-of-storage exposure accepted at the current direct-pairwise trust tier, bounded only by peer admission (§7.7) and the operator ack path above. A cap/expiry would be a policy rung on the same mechanism (principle 9), not a new mechanism.
Two partitioned nodes each locally-validly enrolled conflicting actor-registry state (one actor_id/two keys, or one key/two actor_ids) Admit-and-dispute. The actor-stream apply door admits both signed events (custody total, never a refusal of verifiable history) and the conflict surfaces as a derived disputed state — no winner picked, permissions withheld, content still flowing with candidate-set attribution — until a human adjudication ceremony (supersede) resolves it. Never auto-resolved. See §6.9, ADR-0054

6.4 Scope is a prefetch hint, not an authority

Resolves former open question §11.3 — see ADR-0004.

A patient moves ED→ICU mid-partition: who reassigns the sync scope while the parent is unreachable? The question dissolves once scope is understood as administration, not ownership. Nobody owns the record — it is the sum of autonomous, signed parts written by different professionals at different places and times, assembled from those parts when it can be. A "transfer" reassigns nothing; it merely gives the receiving node reason to assemble the patient, so that node acquires the parts:

  • from a sibling on the same LAN (the common "internet-down, intranet-up" case);
  • carried with the patient on the device that travels with them (store-and-forward / sneakernet, §6.1) in a total partition — the digital transfer hand-off, paper-parity-exact;
  • or from the parent on reconnect.

Because acquisition is INSERT-only, idempotent set-union (data-model §3.1) it is always safe, and the parent — when reachable — ratifies and audits rather than gates. Two asymmetries make this work:

  • Granting scope is urgent and edge-authorized; revoking is lazy and parent-mediated. Lacking a needed chart is a safety and paper-parity failure; holding an extra copy slightly longer is harmless. So a node never moves a scope (a dangerous mutation needing an authority) — it only ever adds an interest, and surplus copies are garbage-collected later by the parent.
  • Access follows legitimate need + audit, not pre-granted permission. On paper, the chart travels with the patient and nobody phones records for permission; the receiving clinician must read and write now. The digital equivalent is break-the-glass acquisition that is recorded — strictly better than paper, which leaves no trace.

The surviving requirement is honest assembly-state disclosure (§6.2). Interactions deferred elsewhere: garbage collection of surplus copies touches retention/erasure (§11.5). Legitimate-need acquisition of sensitive episodes is resolved (§11.8, ADR-0006): replication is never the confidentiality boundary — a safety-relevant sensitive episode replicates unconditionally (the chart must be able to warn about, and break glass on, content that arrived), and confidentiality is enforced downstream at key-custody and visibility, never by withholding the row. Abstracting a sensitive episode's semantic scope key to an opaque token makes this automatic: the prefetch predicate can no longer select on it, so it falls back to replicate-everything-for-this-patient (identity §5.9).

6.5 Schema evolution: two planes and lossless forwarding

Resolves former open question §11.4 — see ADR-0012. Event-format invariants: data-model §3.13; the distribution plane: security §7.6.

A fleet of offline nodes upgrades unevenly and carries permanent, unbounded version skew. Cairn keeps evolution safe by routing it through two deliberately separate planes that run at different speeds:

  • The sync plane (this section) carries signed, immutable clinical events — set-union, AP, skew-tolerant, and never executable code. The event format evolves here, fleet-wide, forward-compatibly (data-model §3.13).
  • The distribution plane (security §7.6) carries code/DDL/extensions — per-node, per-architecture, threshold-signed under a channel's chained trust root and verified before install (ADR-0055), delivered online or by sneakernet (§6.1). Syncing a native extension binary over the clinical mesh is forbidden — it would be a remote-code-execution channel into every node (principle 8).

The decoupling is the whole game: the schema/extension version at a node only ever has to match that node's own schema, never the version of events arriving from elsewhere. Forward-compatible event format is precisely what lets the two planes move independently, so there is no lockstep fleet upgrade.

  • Lossless forwarding (invariant). A node receiving an event authored under a newer, unseen schema stores, re-propagates, and exports it byte-for-byte — never rejecting, dropping, down-converting, or re-serializing it (which would break its signature and the round-tripping of fields the node cannot read). The synced/exported artifact is always the untouched original; the node's human-readable view of it is a local projection (§6.2), never confused with the source, and a future upgrade simply re-derives it correctly. This is honest-assembly applied to format skew: a node always carries what it cannot yet interpret, and renders it down the data-model §3.13 legibility ladder (coarseness varies; existence never disappears). The tolerance window is infinite for custody, best-effort for understanding.
  • The invariant covers unknown types, not merely unknown fields (ADR-0056). An event whose event_type this node has no code to classify is admitted uninterpreted, not refused: stored verbatim, re-propagated, exported, and rendered down the same ladder by the mechanical skeleton twin — while producing no projection rows and conferring no power. Classification (additive-vs-suppressing, ADR-0010) is never guessed; it arrives with the code plane, and the node then reprojects, granting power only where the floor is satisfied — a deferred event that proves to be suppressing without a valid attestation stays powerless and is flagged. Reclassification re-adjudicates the deferred door gates first and only then reprojects the survivors, via the §9.6 generic reprojection mechanism (ADR-0057), whose cairn_replay_eligible seam makes it structurally unable to grant power to a not-yet-re-adjudicated event. Custody is total; interpretation is deferred; power is earned. The authoring door is deliberately the opposite: a node may carry a type it cannot classify but may never author one (ADR-0051's strict-submit/lenient-apply, applied to types). Without this, a node carrying a chart across version skew (§6.1 sneakernet) would acquire nothing past the first unseen type — the partition case the topology exists for. The deferred state is recorded explicitly, never inferred from the absent classification — the marker, not the missing row, is what reclassification consumes — and an event that fails re-adjudication keeps that marker with its refusal recorded, so it stays powerless and legible rather than silently promoted. Status: shipped on the clinical plane 2026-07-29 (#265/#266), so the invariant above is true as written for clinical events. The node/actor plane's door still fail-closes on a type it cannot map (#301) — the same argument applies there and the asymmetry is a known gap, not a design.

6.6 Attachments: the lazy byte tier

Resolves former open question §11.6 — see ADR-0013. The attachment-reference shape and rendition set are data-model §3.14.

Binary attachments (imaging, scans, waveforms, audio, video; data-model §3.14) are orders of magnitude larger than event bodies and must never travel with them. They sync on a separate, lazy, resource-isolated byte tier: the reference rides the eager event plane, the bytes follow lazily, on a budget that can never displace clinical and audit sync.

  • Reference-eager, byte-lazy. The signed event carries the attachment reference (content digest + descriptor), so a node knows the attachment exists the moment the event arrives. The bytes are fetched on demand (a clinician opens the viewer → foreground, legitimate-need fetch, §6.4) or prefetched in the background at lowest priority — never blocking the eager plane.
  • The availability floor — resource isolation, not just queue priority. Priority ordering alone is insufficient: an in-flight gigabyte head-of-line-blocks the channel. (The motivating failure — a nightly bulk imaging sync that ground a whole deployment to a halt so that emergencies could retrieve no record at all, even with no node replication in play.) Byte transfer is therefore chunked, preemptible, and separately budgeted, so clinical events always interleave between chunks. Blob transfer must never reduce clinical-data availability — availability-over-consistency (principle 5) and paper-parity (principle 3) applied to the transport itself.
  • Byte-replication is opt-in and separately scoped. §6.4's prefetch hint applied to bytes — but the blob prefetch predicate is a separate, much narrower thing than the event-scope predicate. References replicate everywhere; bytes replicate by election. A resource-starved node defaults to references-only, fetch-on-demand (it need not — and often cannot — store every PACS blob); "store all blobs" is just an over-broad predicate. This is the §2 fractal topology: small nodes hold references and fetch from durable blob-holders upstream.
  • Content-addressed swarm fetch. Because a blob self-verifies against the digest in the signed event (data-model §3.14), it can be pulled multi-source, chunked, resumable from any holder — a sibling on the LAN, the parent, or the device carried with the patient (§6.1 sneakernet) — with zero trust in the source. "Carry the film with the patient" is byte-exact and partition-tolerant.
  • Renditions carry their own priority. A logical attachment is a set of content-addressed renditions (raw + lightweight preview + report text); the small rendition can ride along eagerly while the raw stays on-demand, so the chart is legible before the gigabytes land (data-model §3.14).

6.7 Record discovery and the replicated essential-state tier

Resolves national-scale first-contact discovery — see ADR-0016. Depends on the Custodian & Federation Admission spec (§11).

At national scale (≥10⁸ people) no node holds the whole population's records. A patient who has never been seen in this region presents at a small, under-resourced clinic; search-before-create (identity §5.3) finds nothing locally, and the parent hub has never seen them either. The §5.2 rule "match at the lowest tier that sees both registrations" breaks here: the lowest common ancestor is the nation, which can be neither a fat index the clinic queries nor an index the clinic can hold. The conventional fix — a national Master Patient Index — is the surveillance/lock-in capture surface principle 7 forbids. Cairn instead makes discovery a local operation.

  • Two phases of opposite character. Identity discovery ("does a record probably exist for a person like this?") is irreducibly the matcher's fuzzy problem — you cannot content-address a human, which is why ADR-0013's content-addressing does not solve it. Part/locator discovery ("given a UUID, which nodes hold its events?") is content-addressable — a tracker keyed by the immortal patient UUID, the same self-verifying swarm-fetch shape as the §6.6 byte tier.
  • The replicated essential-state tier — a third replication volume between "sync everything" and "scope it." A deliberately tiny, replicate-to-all-federated projection of each person's essential safety set (key demographics + identifiers, active allergies, active medications, problem list / PMH, code-status flag, current-care pointer). It is §6.6's reference-eager, byte-lazy pattern applied to patient existence: the cheap "someone like this may exist over there" hint and the essential snapshot replicate widely; the full longitudinal record and attachments stay scoped and lazy (§6.4), fetched on legitimate need after a match.
  • Current state, not history (the load-bearing boundary). The essential tier replicates only start/stop/change of an essential item — never transaction history. ~77 % of dispensed prescription items are repeats that do not change the current list; dispensing history, observations, vitals, notes, and labs belong to the scoped/lazy full record. This boundary is what keeps the tier affordable (see the sizing in ADR-0016 §8: ~2.5 TB and ~75–150 kbit/s for 100 M people — a commodity SSD and ~1 % of a mediocre satellite link).
  • Discovery is a local matcher query, not a network round-trip. Because every federated node holds the essential snapshot + summary, first-contact matching runs locally and offline against data already present — partition-proof, and with no broadcast of who is being looked for. A hit is an ordinary §5.2 middle-band candidate → human confirmation → link§6.4 acquisition of the full record from the Phase-2 holders. No summary, or a missing comparator, degrades honestly to "no history available" (identity §5.4) — paper-parity exactly.
  • Confidential-essential composes with the safety projection (identity §5.9). An item may be both essential and confidential (a privacy-sensitive drug with a lethal interaction the patient will not disclose). The de-identified safety projection (interaction class + severity, naming nothing) replicates broadly in the essential tier and is itself the actionable fact; the identified item replicates but stays sealed under key-custody, name behind audited break-glass. The patient is kept safe without being outed and without depending on point-of-care disclosure.
  • Bounded by federation admission. Replicating a nation's essential set is lawful only because every holding node is a contracted, accountable custodian (proof of health-system participation + enforceable privacy contract to join the mesh; else it runs isolated). Disclosure is to vetted custodians at region granularity, never a named clinic — the mechanism that makes the unavoidable existence-disclosure tolerable. Specified separately as Custodian & Federation Admission (§11).

6.8 Time-attestation rides the gossip plane

See ADR-0027. The data shape (clock-confidence grade, bracketed t_recorded) is data-model §3.17; the notary/anchor node role is security §7.11.

Trusted time must not require a network round-trip at write time — the genuinely offline node is first-class (§6.2, the ADR-0001 availability floor). The resolution is that offline is a bracket, not a degradation, produced by the same set-union gossip that already moves events — not a separate protocol.

  • Peer cross-attestation gives the lower bound offline. A received anchor (a peer's token, or a notary token a peer forwarded) is a causal lower bound: any event a node authors after receiving an anchor timestamped T has t_recorded.lower ≥ T. No write-time round-trip; the bound rides ordinary inbound sync.
  • Deferred Merkle-root batch notarization gives the upper bound on reconnect — and is the privacy fix. A reconnecting node notarizes the Merkle root of a batch of pending events, not each event, so the anchor learns only "a batch of this size existed by T," never per-event clinic-activity metadata. A single inclusion proof upgrades the clock-confidence grade (data-model §3.17) of every event under that root; the token is overlaid signed data (ADR-0015) that syncs like any other overlay.
  • Honest assembly. A node running on a self-asserted clock, or one whose anchor has not been reachable, surfaces that exactly as it surfaces sync freshness (§6.2) and backup health (security §7.10) — a wide, honestly-graded interval, never a fake-precise timestamp.

6.9 The actor-registry stream

Resolves the 2026-07-15 review finding C4 (issue #205) — see ADR-0054. Registry design: security §7.5; wire shape: data-model §3.12.

Actor-registry events — the full closed security §7.5 algebra, enroll / supersede / revoke / suspend / rotate-key (rotations must travel too, or a peer cannot resolve post-rotation signatures); all signed, content-addressed, HLC-stamped (data-model §3.12) — travel as a distinct stream on the node plane: deny-all trusted-peer admission (security §7.7registry trust is node trust), full replication within the trust neighborhood (the registry is small trust-plane state; full replication is what makes an enrolment or vouch portable), per-peer cursor, and the node plane's quarantine pen + re-offer floor (§6.3) for unverifiable bytes. Pre-wire unsigned registry rows never sync.

The ordering contract is honest, not strict. The registry and clinical streams have independent cursors; no cross-plane ordering is promised. Instead, two rules at the consuming doors (ADR-0054):

  • Content never waits. A clinical event citing a key the local registry does not know yet (or one in dispute) applies normally — availability over consistency — with attribution honestly degraded ("key not yet resolved" / the candidate set, identity §5.10); it re-derives losslessly when the registry catches up.
  • Permissions always wait. An operation where the registry grants authority (a suppression's owner-gate, an attestation's authority check) is penned — delayed, never lost, re-offered — until the cited registry state arrives and is clean; disputed is not clean. Fail-safe by direction: a held suppression keeps the note visible; a held attestation reads un-vouched — never the reverse. Registry uncertainty may withhold a permission, never withhold content.