jonnify·bcs /bcs/trading/log-and-ledger/replay-equals-live

B8.2 · dive 2 — replay equals live

State is the fold of the log.

The master invariant of the platform reads: one instrument's book is reconstructable as a fold over an append-only event log. State is not a thing kept beside the log — state is the fold of the log. Recovery is therefore replay, not a feature bolted on afterward. The as-built EchoCache.Journal already proves the posture on the job lane, and the PROPOSED Exchange.Book inherits a certified mechanic rather than inventing one.

Source: replay(j, conn) and apply_and_remember are real Elixir at echo/apps/echo_cache/lib/echo_cache/journal.ex, taught in content/bcs4.4.md, recorded in bcs_rung_4_4_check.out (PASS 6/6). The per-instrument Exchange.Book is PROPOSED (docs/trading/).

The restart-and-remember stepper. Fold a name to v5, stop both the table and the journal, reopen against an empty L1, then replay an old version and a newer one — the H3 record's own values, the verdict computed from the applied memory.
L1 cache (table) v5 journal · applied (in the file) v5 a name folded to v5; both stores agree
readout · Fold the name to v5, then full-stop both stores. After the stop L1 is empty but the applied memory survives in the file — a replayed old version answers :remembered_stale from the journal alone; the newer v6 passes through.
§1

The journal remembers what the cache forgot

bcs_rung_4_4_check.out · H3 · verbatim

The applied table lives in the file, so it survives the table, the node, and the bus. After a full restart with an empty L1, a replayed old version must answer :remembered_stale from the journal alone — no cache row consulted, none created — while a genuinely newer version passes through and updates the memory. The committed gate:

content/echo_data/runtimes/elixir/bcs_rung_4_4_check.out · the cross-restart memoryfrozen
derive (memory): the applied table lives in the file, so it survives the table, the node, and the bus; after a full restart with an empty L1, a replayed old version must answer :remembered_stale from the journal alone -- no cache row consulted, none created -- while a genuinely newer version passes through and updates the memory
H3 memory ok -- the journal remembered v5 across a full stop of table and journal; the replayed old version answered :remembered_stale without touching the cache -- the table's verdict counters did not move and no row appeared -- and the genuinely newer v6 passed through and became the new last word
PASS 6/6

Without this memory, an empty L1 makes every replayed message look fresh; with it, staleness is a fact about the lane, not about whichever rows happen to be resident. The memory check runs before the cache: apply_and_remember consults the journal first and answers :remembered_stale without an ETS lookup, because the journal's word outlives the cache's rows — the gate proves the verdict counters do not move and no row appears.

§2

The bus dies; the lane replays

bcs_rung_4_4_check.out · H4 · verbatim

The bus is volatile by D-2, so a bus restart erases queued coherence jobs. The loss drill stages exactly that: 50 intents recorded and enqueued, 20 applied before the loss, the lane's queue keys flushed. Replay must re-enqueue exactly the 30 uncovered intents in seq order, and a consumer must drain them to a remembered count of 50.

The loss-drill ledger. Over the fixed 50 / 20 / 30: record 50, apply some, flush the bus, then replay. The bars are the journal's coverage arithmetic — uncovered re-enqueued, applied closing at 50 of 50.
record 50 intents to begin
readout · Record 50 intents, apply 20, then flush the bus — D-2's loss. Replay re-enqueues only the uncovered intents in seq order; the committed record reads exactly 30 re-enqueued and the applied memory closing at 50 of 50.
content/echo_data/runtimes/elixir/bcs_rung_4_4_check.out · the loss drillfrozen
derive (loss): D-2 keeps the bus volatile, so a bus restart erases queued coherence jobs; 50 intents recorded and enqueued, 20 applied before the loss, the lane's queue keys flushed -- replay must re-enqueue exactly the 30 uncovered intents in seq order, and a consumer must drain them to a remembered count of 50
H4 loss ok -- the bus restart erased the queue and the journal replayed the lane back: exactly 30 uncovered intents re-enqueued in seq order under their recorded job ids, the consumer drained them, and the applied memory closed at 50 of 50 names holding their final versions
PASS 6/6

Replay selects by coverage — every intent whose name lacks an applied version at least as new — so the 20 already-applied intents are not re-sent and the 30 lost ones are, with no acknowledgment bookkeeping anywhere on the hot path. Replay reuses the recorded job ids, so the bus answers :duplicate for whatever it still holds, which the record counts in the open.

§3

Coverage, not acknowledgment

docs/trading/trading.specs.md · the master invariant

The same predicate drives replay and the dedup floor: an intent is uncovered when its name carries no applied version at least as new. The hot path pays no per-intent completion write — there is no acknowledgment row to maintain. This is the design's reach: the replay property — folding the log reproduces live state — is the Chapter 4.4 posture re-gated per book (the master invariant's mechanics). What the journal proves on the job lane, the PROPOSED Exchange.Book reuses for its instrument's log: kill the book, restart, replay equals live.

# recovery beside the bus: re-enqueue every intent not covered by the applied memory,
# in seq order, reusing recorded job ids so admission dedup absorbs whatever it holds.
{:ok, %{replayed: n, deduplicated: m}} = Journal.replay(j, conn)
§4

Recovery is replay — the alternatives weighed

content/bcs4.4.md · the design's reasoning

The posture is chosen against named alternatives. A WAL inside the bus — the obvious place to put durability, and the one the law forbids: the bus stays volatile by D-2, and durability lives in a file the bus has never heard of, which keeps the bus a fan-out primitive and not a log. A dual-write to a store, then the bus — a crash between the two writes loses the message or announces a write that never happened; the outbox closes both seams by recording the intent first and relaying after. A canonical store the book reads back on every command — a round trip on the hot path, where the fold of an in-memory log answers for free.

Recovery as replay costs a price this module measures in dive 3 — 524 µs against the bare lane's committed 148 µs end to end — and that price buys an outbox, a last word per name, and a replay that survives the bus. The fold that is live state is written in the functional core the platform is built in (/elixir); the bus the log moves to at milestone B is the stream lanes of /echomq. The compaction that keeps the outbox small and the regulated ledger are B8.2.3 · The Double-Entry Ledger.

§5

References

sources · related

Sources

Related