Implementation (L3)
1. Read-through on miss¶
A read is served from the ReadCache on a hit or read through to the far node on a miss; the cached entry is then kept coherent by the down-channel. The cache holds data, not the ability to answer queries: messages and rows that flowed back, never a query engine. This is the line between "compute at the authority" and "cache what flowed back" — a near node never replicates the store and runs queries locally.
A split runtime serves a mail-list view whose rows are the authority server's computed query over an empty local store (crates/posthaste-server/tests/backend_link_split.rs::remote_runtime_serves_a_mail_list_view_from_the_backend); undo-history (current_summary) reads through too.
2. Retaining policy and coherence-by-eviction¶
A cached row/message is held until the down-channel says it changed, not until a TTL expires. The coherence task consumes AuthorityServerLink::subscribe and evicts a message's entry on any base assertion, so a cached summary is never stale (the next read re-fetches). Eviction under storage pressure is a separate policy decision (drop an entry; the next read re-fetches), distinct from coherence. A split runtime gets the retaining cache + the coherence task; co-located stays passthrough.
3. Split-process proofs¶
The seam is proven end to end with no mock on either side:
- Write path — a
Remoteruntime forwardsmessage.destroyacross HTTP into the authority server's authoritative store (backend_link_split,// spec: docs/replication/backend-link/L1#3-the-backendapi-contract).destroyis used becausesetFlaggedStateneeds a local read (undo-history) a split runtime cannot do until the read path lands — the test that surfaced the read dependency concretely. - Read path — the read twin: a
Remoteruntime serves a mail-list view from the authority server over the link, and readscurrent_summarythrough, lifting the write-test blocker.
4. Co-located no-op short-circuits¶
The bundled deployment is byte-for-byte unchanged because every split-only mechanism degenerates co-located:
- the read policy is passthrough (retain nothing, always read through) — no redundant cache;
- the up-channel confirms synchronously, so the
AuthorityServerPendingSetis empty andapply_pending_set_overlayis a no-op; build_snapshot's pending-set fold short-circuits on an empty pending set.
This is "the same code, collapsed" (// spec: docs/replication/backend-link/L3#4-co-located-no-op-short-circuits), not a special case — co-located and split run one path with the split-only work gated to a non-empty pending set / retaining policy.
5. Hardening (partial)¶
[::state partial plan=eph/DESIGN-L2-deployment-topology#3-split-runtime-hardening]
The split-runtime path works but is not yet productized. Remaining: eviction under storage pressure (cache bounds/LRU), RuntimeCoverage reporting what a split runtime holds, reconnect/snapshot recovery on the down-channel, carrying the account id on assertions to avoid cross-account over-eviction, and sibling view replicas (detail/conversation) beyond the mail-list. Role-move optimism (moveToRole, into which the archive/trash sugar collapsed) is not folded yet (it needs account role→mailbox resolution).
6. Multi-runtime fan-in (partially realized)¶
[::state partial plan=eph/DESIGN-L2-deployment-topology#4-multi-runtime-fan-in]
The multi-runtime topology is specified at the seam contract (L1 §3.1: AuthorityServerLinkId, the per-runtime registry, settlement-routed-to-origin-runtime, per-runtime-idempotency, runtime-credential-per-runtime) and the mechanism (replication L1 §5.2, §10: per-near-node watermark, the multi-runtime hosted-authority-server preset). Realization is partial:
- ✅ a per-runtime registry on the
AuthorityServerfar node (AuthorityServerLinkId→ mutation-id pairing + settlement sink), mirroringposthaste-runtime/src/far_end/links.rs'smutations_by_client_id—AuthorityServer::forward_mutation_fordedups by(AuthorityServerLinkId, ClientMutationId)and assigns theRuntimeMutationId(S1). The co-locatedLocalAuthorityServerforwards under a minted id (the X = 1 case). - ✅ per-runtime credential authentication in
link_router, derivingAuthorityServerLinkId—LinkAuth::PerRuntimecarries thetoken → AuthorityServerLinkIdmap; the middleware resolves the presented token and threads the id intoforward_mutation_for(S2). There is no shared-bearer mode. - ✅ emitting
AuthorityServerFrame::Settlementon the down-channel and routing it to the originating runtime's stream (broadcastBaseunchanged) —AuthorityServer::forward_mutation_foremits a Settlement onto a per-runtime sink;LocalAuthorityServer::subscribe_formerges the broadcastBasewith that runtime's Settlements (S3). The near node currently retires its pending set viaBaseabsorption (the state-before-event rule) and ignoresAuthorityServerFrame::Settlement; consuming Settlement for explicit retirement (esp. theFailedcase) is a near-node follow-up, not a far-node routing concern. - ✅
(AuthorityServerLinkId, ClientMutationId)dedup atforward_mutation— folded into step 1 (S1).
The co-located path is the X = 1 case (a minted id, no auth), so these additions leave colocated-unchanged intact.
S4 (per-runtime reconnect/resume) is realized: a reconnecting runtime recreates its settlement channel and gets a fresh receiver — future Settlements resume (disconnect-window Settlements are lost; the near node reconciles via Base + the up-channel error). Bounded retention (S1) and the failed-mutation reject (so a retry re-applies) are in place. Remaining: the near node currently retires its pending set via Base absorption + the up-channel error and ignores AuthorityServerFrame::Settlement; consuming it for explicit retirement is the open item.
7. Assertions¶
| ID | Sev. | Assertion |
|---|---|---|
| cache-is-coherent-base | MUST | A cached entry is held until the down-channel asserts it changed (evict-on-assertion), not until a TTL; eviction under pressure is a separate policy concern. |
| caches-data-not-queries | MUST | A near node caches the data that flowed back (messages and rows), not a query engine; new queries/pages are read-through to the authority. |
| split-proven-no-mock | SHOULD | The remote transport and the far-node link_router are proven to meet over loopback HTTP for both the write and read paths with no mock on either side. |