Part of #553 (group 1 — independent; no relayhistory work required). The reader swap in group 2 is only safe if today's ledger output is pinned first.
Problem
burn's correctness for a reader swap is defined by the rows the ledger ends up with, not by the parser's in-memory structs. The existing guards are partial:
crates/relayburn-cli/tests/golden.rs snapshots CLI stdout for a handful of invocations over tests/fixtures/cli-golden/ledger.jsonl — it locks presentation, not ledger rows.
crates/relayburn-sdk/src/reader/{claude,codex,opencode}/tests.rs assert struct fields per fixture — they will be deleted with the readers at cutover, taking their expectations with them.
ledger/fingerprint.rs is the silent-collapse hazard: turn_content_fingerprint (:97-115) = sha256("{ts}|{model}|{input+output}|{cacheRead}|{cache5m+cache1h}|{first tool args_hash[..4]}")[..16]; if a new source derives ts, message_id, or args_hash differently, turns are either duplicated (different turn_id_fingerprint, same content) or collapsed. turn_index is pass-local (claude/incremental.rs:719, codex/incremental.rs:929, opencode.rs:220) and must not be part of any identity.
Scope
- Ledger snapshot harness
crates/relayburn-sdk/tests/ledger_snapshots.rs: for every fixture in tests/fixtures/{claude,codex,opencode} (27 + 8 + 5) and every multi-file case (original-session+fork-branch-a+fork-branch-b; cross-file-parent; sidechain-*; nested-subagent with its sidecars), run ingest_all into a temp ledger with IngestRoots pinned to a temp HOME, then dump every DERIVABLE_TABLES row (ledger/schema.rs:18-30: turns, compactions, relationships, tool_result_events, user_turns, sessions, inferences) plus content (content.sqlite) as canonical JSON (sorted keys, rows ordered by primary key, record_json parsed and re-serialized stably) to tests/snapshots/ledger/<case>.json. Also snapshot the archive_state.upstream_cursors_json shape and source_fingerprint per case. UPDATE_SNAPSHOTS=1 regenerates.
- Incremental equivalence: for each JSONL fixture, ingest in 1 pass vs. in N passes (append line-by-line or in 3 chunks, re-running
ingest_all between) and assert identical snapshots. This pins the in-progress/back-off cursor semantics (claude/incremental.rs:646-664, codex/incremental.rs:790-843) that a relayhistory-backed source must reproduce.
- Identity vectors: a table-driven test listing, for each fixture turn,
(source, session_id, message_id, ts, turn_id_fingerprint, turn_content_fingerprint) — the values the group-2 adapter must reproduce exactly. Same for tool_result_event (tool_use_id, event_index) and user_turns.user_uuid (note the synthesized Codex/OpenCode form "{session}:{preceding||start}->{following}", codex.rs:746-769, opencode.rs:965-973).
burn state parity --against <other-ledger-home> [--session id] [--json]: a maintenance verb in crates/relayburn-cli/src/commands/state.rs (keep it under state per AGENTS.md) that diffs two ledgers table-by-table by primary key and reports: rows only in A, only in B, same key but different record_json (with a field-level diff), and summary totals (turns, tokens by model, cost). Backed by an SDK function in query_verbs/state.rs so tests and the group-2 parity harness can call it. This is the tool that will prove the swap on a real machine ("confirm an operation against its effect, never its return value").
- Add the git canonicalization vectors (
git@github.com:Org/Repo.git, https://github.com/org/repo, ssh://git@host:2222/org/repo.git, no-remote, worktree) to reader/git.rs tests so relayhistory's project_key helper can be checked against the same expectations.
Acceptance
cargo test -p relayburn-sdk --test ledger_snapshots passes; snapshots committed; CI runs it.
- Incremental-equivalence test covers every JSONL fixture.
burn state parity --against on two ledgers built from the same fixtures reports zero differences; on a ledger where one turn's ts is shifted by 1 ms it reports exactly one turns row differing and one turn_content_fingerprint change.
AGENTS.md "Ledger schema" bullet mentions the snapshot harness as the thing to update when the on-disk shape changes.
Out of scope
Any behaviour change; comparing against relayhistory (group 2).
Part of #553 (group 1 — independent; no relayhistory work required). The reader swap in group 2 is only safe if today's ledger output is pinned first.
Problem
burn's correctness for a reader swap is defined by the rows the ledger ends up with, not by the parser's in-memory structs. The existing guards are partial:
crates/relayburn-cli/tests/golden.rssnapshots CLI stdout for a handful of invocations overtests/fixtures/cli-golden/ledger.jsonl— it locks presentation, not ledger rows.crates/relayburn-sdk/src/reader/{claude,codex,opencode}/tests.rsassert struct fields per fixture — they will be deleted with the readers at cutover, taking their expectations with them.ledger/fingerprint.rsis the silent-collapse hazard:turn_content_fingerprint(:97-115) =sha256("{ts}|{model}|{input+output}|{cacheRead}|{cache5m+cache1h}|{first tool args_hash[..4]}")[..16]; if a new source derivests,message_id, orargs_hashdifferently, turns are either duplicated (differentturn_id_fingerprint, same content) or collapsed.turn_indexis pass-local (claude/incremental.rs:719,codex/incremental.rs:929,opencode.rs:220) and must not be part of any identity.Scope
crates/relayburn-sdk/tests/ledger_snapshots.rs: for every fixture intests/fixtures/{claude,codex,opencode}(27 + 8 + 5) and every multi-file case (original-session+fork-branch-a+fork-branch-b;cross-file-parent;sidechain-*;nested-subagentwith its sidecars), runingest_allinto a temp ledger withIngestRootspinned to a temp HOME, then dump everyDERIVABLE_TABLESrow (ledger/schema.rs:18-30:turns, compactions, relationships, tool_result_events, user_turns, sessions, inferences) pluscontent(content.sqlite) as canonical JSON (sorted keys, rows ordered by primary key,record_jsonparsed and re-serialized stably) totests/snapshots/ledger/<case>.json. Also snapshot thearchive_state.upstream_cursors_jsonshape andsource_fingerprintper case.UPDATE_SNAPSHOTS=1regenerates.ingest_allbetween) and assert identical snapshots. This pins the in-progress/back-off cursor semantics (claude/incremental.rs:646-664,codex/incremental.rs:790-843) that a relayhistory-backed source must reproduce.(source, session_id, message_id, ts, turn_id_fingerprint, turn_content_fingerprint)— the values the group-2 adapter must reproduce exactly. Same fortool_result_event(tool_use_id, event_index)anduser_turns.user_uuid(note the synthesized Codex/OpenCode form"{session}:{preceding||start}->{following}",codex.rs:746-769,opencode.rs:965-973).burn state parity --against <other-ledger-home> [--session id] [--json]: a maintenance verb incrates/relayburn-cli/src/commands/state.rs(keep it understateperAGENTS.md) that diffs two ledgers table-by-table by primary key and reports: rows only in A, only in B, same key but differentrecord_json(with a field-level diff), and summary totals (turns, tokens by model, cost). Backed by an SDK function inquery_verbs/state.rsso tests and the group-2 parity harness can call it. This is the tool that will prove the swap on a real machine ("confirm an operation against its effect, never its return value").git@github.com:Org/Repo.git,https://github.com/org/repo,ssh://git@host:2222/org/repo.git, no-remote, worktree) toreader/git.rstests so relayhistory'sproject_keyhelper can be checked against the same expectations.Acceptance
cargo test -p relayburn-sdk --test ledger_snapshotspasses; snapshots committed; CI runs it.burn state parity --againston two ledgers built from the same fixtures reports zero differences; on a ledger where one turn'stsis shifted by 1 ms it reports exactly oneturnsrow differing and oneturn_content_fingerprintchange.AGENTS.md"Ledger schema" bullet mentions the snapshot harness as the thing to update when the on-disk shape changes.Out of scope
Any behaviour change; comparing against relayhistory (group 2).