Repository navigation
Description
Design doc: files/designs/code-id-replication-admission.md in the keep-the-four-repos-healthy goal workspace. Audit at origin/main 73c0ec7466291b164b00f2fb3c911c2769997de2 — every defect below re-verified present at the cited sites this pass (2026-11-02).
Summary
ReplicationTracker::accept (pkgs/id/src/world/replication.rs) is the receiver-side trust boundary for world-journal replication (F12.13, #806, PR #4447): whatever it accepts, the durable receiver and spectator phases treat as a verified, restorable journal. The module's own rule, recorded when the PR #4447 follow-up closed the undecodable-entry hole, is that a verified cursor never covers data the restore path cannot read. That rule has been enforced leg by leg for the payload — entries must decode (chain_payload), and the payload's own journal header must be exactly one Created header for this world at the envelope's version (validate_payload_journal_header, #4486, landed today). The envelope's own header_version field is still held to neither of the two contracts WorldCore::restore and the journal writer jointly state:
- Range (id(replication): ReplicationTracker accepts journal header_version 0 — accept checks only the upper bound, so a signed Snapshot rebases a v1-v3 chain onto a version WorldCore::restore refuses (1..=3) #4471) — restore accepts a header version only in
JOURNAL_VERSION..=MAX_JOURNAL_VERSION(1..=3,world/core.rs:217).acceptchecks only the upper bound (replication.rs:712), so a signed snapshot withheader_version = 0rebases the tracker onto a chain no binary can restore, andReplicationTracker::newperforms no range check either (a v0 tracker accepts v0 tails). The origin's laptop probe: v0 snapshot accepted,header_version()becomes 0; MAX+1 refused as control. - Monotonicity (id(replication): ReplicationTracker accepts a snapshot with a lower header version — replica silently rolls back and re-admits old-version tails #4487) — journal header versions only ever move forward (the writer bumps the header before appending an entry kind an older binary cannot read,
world/mod.rs:915-946; there is no downgrade path). Tail and Terminal frames enforce this (HeaderVersionChangedon any change); the Snapshot arm does not — it checks only thatcursor_beforeis the chain base for the snapshot's own version and assignsself.header_version = envelope.header_versionunconditionally (replication.rs:739-752). The origin's probe: v1 tail → v2 bump snapshot → v1 tail refused → v1 snapshot accepted → v1 tail accepted. The guard is defeated by inserting one snapshot frame.
Both are one unstated contract with two clauses: the envelope's header version is admitted only if the chain it names could exist — in the restore range, and never behind the chain the tracker already holds.
The contract
A frame is admitted only if its header version names a chain that could exist: inside the restore range
JOURNAL_VERSION..=MAX_JOURNAL_VERSION, and — for a snapshot, the one frame kind allowed to change the chain's version — never lower than the version of the chain the tracker already holds.
Recorded decisions (full reasoning, alternatives and risks in the design doc):
- The range check lives in
accept, for every kind, before any state changes —header_versionoutsideJOURNAL_VERSION..=MAX_JOURNAL_VERSIONis refused with the existingUnsupportedJournalVersionvariant, now applied to both ends. Snapshot, Tail and Terminal alike: a v0 Terminal against a v0 tracker is the same hole as a v0 Snapshot. - Construction is made inert, not redesigned — id(replication): ReplicationTracker accepts journal header_version 0 — accept checks only the upper bound, so a signed Snapshot rebases a v1-v3 chain onto a version WorldCore::restore refuses (1..=3) #4471 offers two endings for
ReplicationTracker::new; this design records the second. With decision 1 inaccept, an out-of-range session version makes the tracker inert: every frame kind carries its ownheader_versionand is range-checked on the envelope, and tails/terminals must additionally equal the tracker's version, which no in-range frame can do against an out-of-range tracker.new's doc states this explicitly. A fallible constructor would ripple to every session bootstrap for a state that cannot arise from a real journal header — the session version comes from the local journal, whose header restore already range-checks. - Snapshot monotonicity gets its own error — a snapshot whose
header_versionis lower than the tracker's currentheader_versionis refused with a dedicated variant (HeaderVersionRegressed), leaving cursor and header version untouched. ReusingHeaderVersionChangedloses: that variant's doc names the tail/terminal fact (any change on a frame kind that may not change version), while a snapshot regression is a different diagnosis — the one frame kind that may move the version moved it the one way journals never move — and the durable receiver's logging will want to tell a downgrading writer bug from a merely desynchronised peer. - Equal and higher stay accepted — same-version snapshots remain the compaction rebase; higher in-range snapshots remain the header-bump rebase (both covered by existing tests). Only the lower direction is refused; no legitimate writer state is excluded, because no code path in the repo ever lowers a journal's header version.
Phased breakdown
Phases adopt the two open origins as sub-issues (no parallels); #4486 is the landed third leg, cited as the embodiment, not a phase. Phase 3 is a new closing-audit issue, gated on Phases 1–2.
- Phase 1 — the restore range at the envelope (adopted: id(replication): ReplicationTracker accepts journal header_version 0 — accept checks only the upper bound, so a signed Snapshot rebases a v1-v3 chain onto a version WorldCore::restore refuses (1..=3) #4471). Decisions 1–2: range-check every kind in
accept;new's inert-tracker doc. (id(replication): ReplicationTracker accepts journal header_version 0 — accept checks only the upper bound, so a signed Snapshot rebases a v1-v3 chain onto a version WorldCore::restore refuses (1..=3) #4471 is currentlystatus/parkedwith a holder — its agent note under this design records the phase structure; the design does not reassign it.) - Phase 2 — snapshot monotonicity (adopted: id(replication): ReplicationTracker accepts a snapshot with a lower header version — replica silently rolls back and re-admits old-version tails #4487). Decisions 3–4: refuse a lower-version snapshot with
HeaderVersionRegressed, state untouched. Independent of Phase 1 in logic; textually adjacent inaccept(whichever lands second rebases a small diff — noted, not gated). - Phase 3 — closing admission inventory + contract statement (new issue, gated on Phases 1–2): the contract statement lands in the
replication.rsmodule doc, and every preconditionWorldCore::restoredemands of a journal is dispositioned against a check inaccept(holds at accept / holds at restore only, with the reason that is safe / gap filed as a named follow-up). Required dispositions: (a) entry-kind version gating — whether a signed in-range snapshot may carry an entry kind that requires a higher header version (header_version_for_entry) on a lower header, and what restore makes of it; (b) the session-bootstrap version source — whereReplicationTracker::new'sheader_versionargument comes from in the serve wiring, and whether decision 2's inert-tracker reasoning holds at that call site, including whether a new session may legitimately start below a previous session's version; (c) Terminal frames carrying an out-of-range version against a tracker set by a pre-fix snapshot (expected covered by Phase 1 — confirm, don't assume); (d) anyacceptcaller that pre-validates versions itself — disposition each as removed, delegated toaccept, or named exception.
Open questions (in the design doc, not filed)
Whether the durable receiver (#806 phase 3) should persist a regression refusal as writer-misconduct evidence or merely log it; whether a future protocol revision wants an explicit reset/rebase frame kind for disaster recovery (snapshot regression is recorded as not that mechanism); the session-restart version question, assigned to Phase 3's disposition (b) to answer from the actual bootstrap code.
Related
- Origins adopted as phases: id(replication): ReplicationTracker accepts journal header_version 0 — accept checks only the upper bound, so a signed Snapshot rebases a v1-v3 chain onto a version WorldCore::restore refuses (1..=3) #4471 (Phase 1), id(replication): ReplicationTracker accepts a snapshot with a lower header version — replica silently rolls back and re-admits old-version tails #4487 (Phase 2).
- Landed sibling leg, cited not adopted: id(replication): ReplicationTracker::accept never checks the payload's own journal header — a snapshot with no header, a foreign world's header, a version-99 header or a duplicate header is verified and advances the cursor, though WorldCore::restore refuses every one #4486 (payload's own journal header, closed 2026-11-02) and the PR feat(id): F12.13 replication wire contract + crypto primitives (#806 phase 1) #4447 decode leg in
chain_payload. - Sibling designs in the same crate: Design: reconstruction is admission — validate authority-bearing inputs where they are rebuilt (pkgs/id) #2898 / Design: reconstruction is admission, continued — carried state, checked succession, and every restore's guards (pkgs/id follow-on to #2898) #4000 (journal reconstruction is admission — the restore side of this same rule), Design: a signed artifact is validated as strictly as it is built — one receipt contract for pkgs/id artifacts #3231 (signed-artifact receipt), Design: one guarded door — every gossip claim enters through its table (pkgs/id presence + peers) #4502 (gossip admission). This design is the replication-receipt gate; it does not re-audit the journal seams Design: reconstruction is admission — validate authority-bearing inputs where they are rebuilt (pkgs/id) #2898/Design: reconstruction is admission, continued — carried state, checked succession, and every restore's guards (pkgs/id follow-on to #2898) #4000 own.
- Feature epic: id: F12.13 journal replication (read replicas over a signed stream) #806 (F12.13 replication; phases 2–5 build on this contract).
Acceptance criteria
- Every acceptance leg named in the design audit is either implemented with tests or explicitly dispositioned on this issue: a verified cursor never covers journal data the restore path cannot read (headers, chain payload, and each leg the audit enumerates).
- Regression tests reproduce each audited defect against the pre-fix code and pass after the fix.
- The design doc (
files/designs/code-id-replication-admission.mdin the goal workspace) and this issue agree at landing; any leg deferred is filed as a linked follow-up issue, not dropped.
Activity
dezren39 commentedon Nov 2, 2026
agent-note:v1 — design architect, pass 40 (2026-11-02)
Design #4592 originated this pass from the replication-admission audit at origin/main
73c0ec74:ReplicationTracker::acceptholds the envelope'sheader_versionto neither the restore range (#4471) nor monotonicity (#4487), while the payload-side legs of the same rule already landed (chain_payloaddecode check, PR #4447;validate_payload_journal_header, #4486, closed today).Structure: Phase 1 = #4471 (adopted), Phase 2 = #4487 (adopted), Phase 3 = #4593 (new closing audit, gated on Phases 1–2, triage packet → pilot task 6481). All three are linked as sub-issues. Full design:
files/designs/code-id-replication-admission.mdin the keep-the-four-repos-healthy goal workspace; register entry added (status: proposed).dezren39 commentedon Nov 2, 2026
Intake review
Design issue from the design-architect audit at origin/main 73c0ec7, re-verified leg by leg in the issue body; companion to #806 / PR #4447 (the replication trust boundary it extends). Still live: no merged PR references this issue (filed today). Risks: the cursor/restore invariant spans several legs — a partial fix that covers only the named leg re-creates the hole the PR #4447 follow-up already closed once. Suggested approach: implement leg-by-leg with one failing test per leg before the fix, in the order the audit lists them.
🤖 Agent note · filed
docs(id): design note for #4630 — bound the checkpoint at write time
19 remaining items
dezren39 commentedon Nov 4, 2026
🤖 Agent note · handed off
opencodedezren39 commentedon Nov 4, 2026
🤖 Agent note · handed off
dezren39 commentedon Nov 4, 2026
agent-note:v1
event: handed_off
task: 8414 (repo-health-code-pilot; repo-worker-code; lane code-y)
at: 2026-11-04T16:42Z
found_via: atomic queue claim (lane code-y)
verified: Issue #4592 OPEN, no hard-stop labels; claim labels already synced (queue/claimed, status/in-progress, work/assigned). Execution-lane assignment for task 8414 is opencode — the bound opencode claimant session owns the triage work; this lane does not work it directly. Attempts 22/24 after repeated unlaunched handoffs.
next: Claimant launches the bound opencode session for task 8414.
dezren39 commentedon Nov 4, 2026
🤖 Agent note · handed off
dezren39 commentedon Nov 5, 2026
🤖 Agent note · handed off