Layer State Model — C4 State Diagrams per Layer #

Companion to architecture.md (Level 3 layers), flows.md (the events that drive every transition), and model-plugin-layer.md (§9 lifecycle). This is the C4 state level: one section per architecture layer, each showing the layer as an immutable consumer/producer of time-travelable streams.

The state law (governs every diagram below) #

Every stateful entity in the system obeys three rules, with no exceptions:

  1. State is a fold. S(K) = fold(F, S₀, events[0..K]) over the layer's declared input streams. State at any offset K is reconstructable by replaying to K — that is time travel, and it is why every diagram below is as much a query (what was true at K?) as a design.
  2. Transitions happen only by appended events. No state ever changes by edit, clock, or side effect. Corrections, voids, and reversals are new events driving new transitions — history only grows.
  3. Every transition label names the stream event that causes it. If a transition cannot name its event, it cannot exist. This is what makes the diagrams executable documentation: replaying the named events through the fold reproduces the exact state trajectory, byte-identically.

Master map:

Layer Consumes Produces Stateful entities
Edge external world (impure) match log, order log, control stream source/match ingest state
Stream Core every stream (cross-cutting) hash chains; the footer's reserved checkpoint/state-root slot (no producer — §2's scope note) and derived, discardable snapshots stream segment state
Replay match log; for the house-line families also the run manifest's recorded TERMS and the family's house sleeve's model ARTIFACT (the first house sleeve's on a single-family run, the sleeve TAGGED to the family on a board) — recorded initial state evaluated through the LineSetter seam on a match's opening snapshot (SC-13 rule 5; the dependency is compile-time inverted: pricing implements replay's seam, the runtime wires it) game-state stream, lifecycle stream market lifecycle (per claim series)
Pricing game-state stream (the extractor node), feature stream, control stream probability streams model lifecycle (plugin §9), per-tick emission, the dota2 extractor's per-match side binding and the race extractor's RaceTracker, the minute extractors' MinuteTracker and the house extractors' HouseFold + the line the setter chose (§4)
Market-Making probability streams (house sleeves) or recorded order-log quoting ingress (external sleeves), own fills quote streams quote state
Exchange quote streams, order log (minus the quoting-ingress records, which drive Market-Making), settlements, lifecycle market-event stream retail order state, book fold
Clearing lifecycle stream, market-event stream, order log (account_created), match log (SC-1 kills recompute / SC-7 match completion / SC-8 race crossings, SC-9 minute settlements, SC-10 side-minute, SC-11 gold-lead and SC-12 tally settlements, and SC-13 the house-line markets' LAST frames before the completion, through the title's projector) settlement stream position state, collateral state
Interface any stream in the run's directory via the gateway — game-state, market-event, settlement first among them (§8) order-log events (via gateway) gateway session cursors (§8) — non-authoritative
Cross-layer root logs + run manifest reports, verified-run records run lifecycle (§11)
Allocation the epoch log (root: the epoch gateway's records — the operator's actions and the partner chain's final blocks), the included runs' manifests and sealed settlement + market-event streams the allocation stream (derived) epoch state (§12; allocation-market.md)
Risk the allocation stream's desk records and the epoch log's desk_topup (the desk); the run's control root (the sleeve); the run's match-log feed records (the feed) — (the desk fold read by R6 at the next inclusion, by the inclusion edge and by the desk watch, whose reason-2 sleeve suspensions ride the other runs' control roots; the sleeve fold by the quote fold); the feed fold by R7, whose pulls are quote_batch records desk credit state, sleeve quoting state, run feed state (§13; risk-engine.md)
Verification every stream (the two in-process taps read the collateral folds' live state instead — §9 note) violation/control events (violation-event emission deferred — §9 note) invariant verdict state
Offline match log, probability + settlement streams reports, artifacts test-split / evaluation state

1. Edge Layer — source ingest state (per match) #

Contract
Consumes the impure world: demo files or archive tables (batch — event-catalog §6.1/§6.12) or broadcast fragments (live)
Produces match log events, watermarks, lifecycle events
State = fold of its own emitted match-log events (checkpointed fragment cursor)
Time travel re-parse from the recorded fragments/file → identical log; resume = read own log tail
stateDiagram-v2
    [*] --> Complete: batch parse of a finished source — demo or archive tables (the batch path)
    [*] --> Discovered: match_discovered appended
    Discovered --> Live: match_live appended
    Live --> Live: fragment parsed — events plus watermark appended
    Live --> Stale: watermark age exceeds threshold · GRID live — feed_stalled appended
    Stale --> Live: backfill complete — fresh watermark appended · GRID live — feed_recovered appended
    Live --> Complete: canonical final confirmation appended — match_complete/v1 (Canonical)
    Stale --> Complete: canonical final confirmation appended — match_complete/v1 (Canonical)
    Complete --> [*]
    note right of Stale: downstream consequence is Replay layer Halted on a watermark path — no version advance means no market · on GRID live the run's feed pause, every quote pulled (risk-engine R7)
Diagram source
stateDiagram-v2
    [*] --> Complete: batch parse of a finished source — demo or archive tables (the batch path)
    [*] --> Discovered: match_discovered appended
    Discovered --> Live: match_live appended
    Live --> Live: fragment parsed — events plus watermark appended
    Live --> Stale: watermark age exceeds threshold · GRID live — feed_stalled appended
    Stale --> Live: backfill complete — fresh watermark appended · GRID live — feed_recovered appended
    Live --> Complete: canonical final confirmation appended — match_complete/v1 (Canonical)
    Stale --> Complete: canonical final confirmation appended — match_complete/v1 (Canonical)
    Complete --> [*]
    note right of Stale: downstream consequence is Replay layer Halted on a watermark path — no version advance means no market · on GRID live the run's feed pause, every quote pulled (risk-engine R7)

GRID-live note (the CS2 Series Events WebSocket capture: the d2-grid binary's live subcommand, crates/grid-edge/src/live_capture.rs, event-catalog §6.13 rules 12–13): the Live → Complete edge IS match_complete/v1 (finality Canonical) — on this path it fires from two TRIGGERS inside the one producing transformer (§2): GRID's game-decided event (series-ended-game / team-won-game → Cs2Event::MatchEnd, the GRID CS2 events-mapping note §2.1) when observed mid-stream, and otherwise Cs2LiveDriver::close, which synthesizes the same record when the transport closes cleanly — the server's Close frame, answered best-effort by the client's mandatory Close reply (RFC 6455 §5.5.1, flushed under a bounded write deadline; its failure never blocks the seal) — and then seals the capture, unless the series' last validity toggle reads invalidated or a feed stall of either source is open with no match end observed (event-catalog §6.13 rule 12 (d)), in which case close synthesizes no match_complete and refuses (Ineligible::InvalidatedSeries on an invalidated series; on an open stall Ineligible::FeedStalled { feed_source }, event-catalog §6.13 rule 12 (d)), seals nothing, and the .partial stays unsealed exactly as in the transport-failure case (event-catalog §6.13 rule 13(a)). GRID's FEED framing (grid-started-feed / grid-ended-feed) is context, never a canonical event. A transport failure mid-stream (a peer that vanishes without a Close, a reset) is NOT that edge: the capture appends feed_stalled/v1 (the events source), aborts with the error, and the .partial segment stays unsealed — never a silently truncated "complete" match log — and there is no tail-resume on this path: a retry is a fresh dial from fromSequenceNumber=0; the abandoned .partial is never resumed. This path emits no watermark, so the Replay layer's Open ↔ Halted (§3) has no producer here; Live ↔ Stale has one, the capture's feed-health records (event-catalog §6.13 rule 12 (d)): Live → Stale is feed_stalled/v1 — no envelope for the capture's rule, a failed roster poll for a pending round, or a transport or IO error — and Stale → Live is feed_recovered/v1, appended before the fresh envelope's records once the continuity rule holds (or once the retried poll succeeds). The capture records its rule first, feed_watch_armed/v1, before any envelope. The consequence of Stale here is the run's feed pause (risk-engine.md §3 R7), never a market halt (flows.md Flow 4's deviation note). Of the contract table above, the Time-travel row's "resume = read own log tail", the Produces row's "watermarks" and the State-fold row's "checkpointed fragment cursor" do not apply to this path.

Immutability note: a parsing correction is a revision event referencing the superseded offset, never a rewrite — the demo/broadcast bytes and the emitted log are both permanent.


2. Stream Core — segment state (per stream) #

Contract
Consumes append requests from its producing transformer (exactly one producer per stream)
Produces the stream itself: offsets and the hash chain (plus the footer's reserved state_root slot, sealed ABSENT by every production writer — the scope note below)
State = fold of appended events: h(i) = H(h(i−1) ‖ canonical_bytes(event_i))
Time travel any offset addressable; state roots at checkpoints would make prefixes provable without recomputation — a prefix is proved by recomputing the chain (architecture.md scope ledger)
stateDiagram-v2
    [*] --> Open: stream created — lineage recorded (parents, offsets, transformer hash)
    Open --> Open: event appended — hash chain extends
    Open --> Checkpointed: checkpoint event — merkleized state root committed
    Checkpointed --> Open: appends continue past the checkpoint
    Open --> Sealed: producing source complete or run ended
    Checkpointed --> Sealed: producing source complete or run ended
    Sealed --> [*]
    note right of Sealed: sealed segments are immutable forever — snapshots stay derivable and discardable, never authoritative
Diagram source
stateDiagram-v2
    [*] --> Open: stream created — lineage recorded (parents, offsets, transformer hash)
    Open --> Open: event appended — hash chain extends
    Open --> Checkpointed: checkpoint event — merkleized state root committed
    Checkpointed --> Open: appends continue past the checkpoint
    Open --> Sealed: producing source complete or run ended
    Checkpointed --> Sealed: producing source complete or run ended
    Sealed --> [*]
    note right of Sealed: sealed segments are immutable forever — snapshots stay derivable and discardable, never authoritative

Scope: the Checkpointed edge has no producer — no checkpoint event exists and every writer seals with the state root absent. The edge is kept for the verifiable-order-book design, the intended producer of the reserved footer slot (architecture.md scope ledger); a stream's only committed digest is its hash chain, and "Sealed" is reached from Open.

Forking note: a fork is (parent id, fork offset, own events) — copy-on-write, sharing all ancestry by reference. Every offset of every segment is a legal fork point (architecture.md, Timelines).


3. Replay Layer — market lifecycle (per claim series: a CS2 team-round, a LoL / Dota2 / synth match, one line — a race line or a minute line (kills, side, gold, tally) — of a LoL / Dota2 match, or a LoL / Dota2 match's HOUSE-LINE market — the final total kills or the kill handicap over the line the house sets) #

Contract
Consumes match log (cursor = the only clock); the house-line families also read the run manifest's recorded terms and the family's house sleeve's model artifact (the first house sleeve's on a single-family run, the family's tagged sleeve's on a board) through the LineSetter seam — recorded initial state, never a stream (SC-13 rule 5)
Produces game-state stream (state_version = offset), lifecycle stream
State = fold of match-log events up to the cursor (a house-line market's line: also the recorded terms and artifact — a pure function evaluated once, on the opening snapshot)
Time travel place the cursor at any offset → the exact market state at that instant

On a BOARD this machine runs once PER COLUMN — one LifecycleFold per family over the ONE projector's game-state stream, every fold seeing every barrier and advancing only on its own family's, all of them appending through the one writer to the one lifecycle stream in the board's canonical order (event-catalog §6.9). The stream law holds: one writer per stream, N folds.

stateDiagram-v2
    [*] --> Pending: epoch observed in game-state stream (CS2 — the round's first snapshot, match-winner — the forwarded match envelope, match_discovered or roster_side/v1 on the betty frame log, synth_discovered on a synth log · race / minute lines — every line of the match on that envelope · house line — the match's OPENING snapshot, the open_minute-th, with the line the setter chose on it, never the envelope)
    Pending --> Open: market_open — barrier requires settlement_complete (or void_complete) of the prior epoch OF ITS FAMILY (CS2 epoch = round · race / minute lines — the prior line of the same match · house line — the prior MATCH's settlement · on a board the fold advances only on its own family's barrier)
    Open --> Halted: stale watermark or data gap (live path)
    Halted --> Open: fresh watermark — bookmakers re-quote
    Open --> Closed: epoch end forwarded in game-state stream (CS2 — canonical round_end, match-winner — match_complete or synth_complete, race — the per-frame snapshot on which a side reaches the line, state/v2 on LoL · the roster-summed player_frame on Dota2 · minute line — the snapshot whose ordinal IS the line's minute, over or under · gold-lead line — the same snapshot, over or under by the team's lead · tally line — the same snapshot, over or under by the team's count of the kind · house line — match_complete, over or under by the LAST snapshot's total or lead)
    Pending --> Closed: epoch end forwarded before the open barrier lifted (CS2 — round_end, match-winner — match_complete or synth_complete, race — a kills jump crossing a later line on the frame that closed the live one) — the market NEVER opens (opening after the epoch ended would trade a known outcome)
    Halted --> Closed: round_end forwarded during a data gap — the claim window ends with the round (CS2 only — no halt producer for the match-winner family)
    Closed --> Settled: settlement events then settlement_complete appended
    Open --> Voided: round_restored (the trigger is the live path, out of scope — demo preflight excludes these rounds — the money path is SC-V's unwind), or an intra-match line's tie / unreached line
    Pending --> Voided: round_restored before the open barrier lifted, or an intra-match line voided while still awaiting its barrier — the match completing with it unreached, or a same-frame tie at or past it
    Halted --> Voided: round_restored during a data gap, or an intra-match line's tie / unreached line striking during one
    Closed --> Voided: round_restored, or an intra-match line never reached before the match ended (settlement-contract SC-V — the epoch closed with the match)
    Settled --> Voided: round_restored — reversal events (out of scope)
    Voided --> [*]: void_complete — the replayed round opens as a NEW market id (a voided race line is never re-observed)
    Settled --> [*]
Diagram source
stateDiagram-v2
    [*] --> Pending: epoch observed in game-state stream (CS2 — the round's first snapshot, match-winner — the forwarded match envelope, match_discovered or roster_side/v1 on the betty frame log, synth_discovered on a synth log · race / minute lines — every line of the match on that envelope · house line — the match's OPENING snapshot, the open_minute-th, with the line the setter chose on it, never the envelope)
    Pending --> Open: market_open — barrier requires settlement_complete (or void_complete) of the prior epoch OF ITS FAMILY (CS2 epoch = round · race / minute lines — the prior line of the same match · house line — the prior MATCH's settlement · on a board the fold advances only on its own family's barrier)
    Open --> Halted: stale watermark or data gap (live path)
    Halted --> Open: fresh watermark — bookmakers re-quote
    Open --> Closed: epoch end forwarded in game-state stream (CS2 — canonical round_end, match-winner — match_complete or synth_complete, race — the per-frame snapshot on which a side reaches the line, state/v2 on LoL · the roster-summed player_frame on Dota2 · minute line — the snapshot whose ordinal IS the line's minute, over or under · gold-lead line — the same snapshot, over or under by the team's lead · tally line — the same snapshot, over or under by the team's count of the kind · house line — match_complete, over or under by the LAST snapshot's total or lead)
    Pending --> Closed: epoch end forwarded before the open barrier lifted (CS2 — round_end, match-winner — match_complete or synth_complete, race — a kills jump crossing a later line on the frame that closed the live one) — the market NEVER opens (opening after the epoch ended would trade a known outcome)
    Halted --> Closed: round_end forwarded during a data gap — the claim window ends with the round (CS2 only — no halt producer for the match-winner family)
    Closed --> Settled: settlement events then settlement_complete appended
    Open --> Voided: round_restored (the trigger is the live path, out of scope — demo preflight excludes these rounds — the money path is SC-V's unwind), or an intra-match line's tie / unreached line
    Pending --> Voided: round_restored before the open barrier lifted, or an intra-match line voided while still awaiting its barrier — the match completing with it unreached, or a same-frame tie at or past it
    Halted --> Voided: round_restored during a data gap, or an intra-match line's tie / unreached line striking during one
    Closed --> Voided: round_restored, or an intra-match line never reached before the match ended (settlement-contract SC-V — the epoch closed with the match)
    Settled --> Voided: round_restored — reversal events (out of scope)
    Voided --> [*]: void_complete — the replayed round opens as a NEW market id (a voided race line is never re-observed)
    Settled --> [*]

The feed pause is not a lifecycle state. A stall of the run's feed (risk-engine.md §3 R7; §13's feed machine) leaves every market Open: no lifecycle record is written, the pull — a zero-level quote batch — empties the book of quotes, under R7 v2 the stall record's tick cancels every resting retail order (retail_cancelled{feed_stall}), and an order stamped in the stall, deferred to the fresh tick, meets a book empty of quotes and of retail orders and ends order_unfilled when it sorts before the fresh house batch (v < f), at f = v following that batch and free to fill against it, never resting and never market_halted — Open → Halted stays the CS2 watermark rule.

Same-step open (every family): the fold's observation step runs the settlement-barrier walk at once (replay::lifecycle::fold's observe ends in the barrier walk; its settlement input runs the same walk), so [*] → Pending → Open fires in the ONE lifecycle step of the epoch's observation whenever every prior epoch of the family is already Settled (or retired by its void_complete) AND the fold is not halted — a halt defers every open to the resume step (the Halted → Open row above is the resume's own edge for markets halted while Open; a still-Pending market opens there on the ordinary Pending → Open edge) — the first epoch's barrier vacuously. For the CS2 round family that step is the round's FIRST game-state snapshot (a state/v1 or state/v2 record whose round_no the policy has not seen; no round-start record reaches the fold — the §6.2 forwarding set carries round_end / round_restored / watermark / revision only, event-catalog §6.2), so a round's two markets open at that snapshot's tick — on the Flow 3 log round 17's open rides its first snapshot at ts 3131.00 (the fixture's recorded replay_ts 313_100 — the docs' seconds convention, event-catalog §4), the log's second record (the figure the lifecycle gate pins). The prior round's settlement_complete is folded by then under the runtime's fixed step order: the round_end tick emits the settlement, and the NEXT tick folds it into the lifecycle barrier in its pre-drain, BEFORE that tick's advance — so any later tick's observation sees the prior epoch Settled. When the barrier is still outstanding at the observation (a settlement group in flight, the settlement-lag race named below), the market stays Pending and opens on the barrier's own step — the ordinary Pending → Open edge, emitted by the fold's settlement input. The race-line and minute-line families below state the same rule.

The settlement-lag race: the Pending → Closed, Halted → Closed, Pending → Voided, and Halted → Voided edges exist because a round can end (or be restored) while its markets still await the prior epoch's settlement barrier or sit in a data-gap halt. The claim window ends with the round regardless of market phase, and a market whose round ended before its barrier lifted must never open; a late settlement_complete finds it Closed (or, in the restore race, Voided) — never Pending — so nothing resurrects it.

The match-winner family (settlement-contract SC-7): the SAME machine and the SAME fold serve a LoL / Dota2 / synth match. Epoch = the match (its discovery ordinal in the game-state stream); one market per match on the model's perspective side; [*] → Pending → Open on the forwarded match envelope (match_discovered, roster_side/v1 on the betty frame log — event-catalog §6.11 rule 11 —, or synth_discovered on a synth log, §6.18) (the first epoch's barrier is vacuous; a later match's open waits on the prior match's settlement_complete exactly as a round's does); Open → Closed on the forwarded match_complete (synth_complete on synth; settlement-contract SC-7). The Halted and Voided edges have no producer for this family: no title's feed carries a watermark or a restore, so the policy emits neither signal — the edges stay drawn for the machine, unreachable for the family.

The race-to-kills family (settlement-contract SC-8): the SAME machine and the SAME fold serve a LoL or Dota2 match's race lines. Epoch = match ordinal × line count + line index — SEQUENTIAL lines as consecutive epochs: every line's market is created Pending on the forwarded match envelope (match_discovered; roster_side/v1 on the betty frame log), the lowest opens at once (the run's first epoch's barrier is vacuous) and each higher line opens on the prior line's settlement_complete OR void_complete — the barrier row above, unchanged. Open → Closed on the first per-frame snapshot whose per-side kills reach the line (lol/state/v2's tallies; a dota2/player_frame/v1's per-slot counters summed through the bound roster — the higher count wins a double cross); Open → Voided (and Pending → Voided) on an equal-count crossing (a tie) or, for every line still unreached, on the forwarded match_complete — the Voided edges' intra-match causes drawn above, with SC-V's unwind as the money path; Closed → Voided has no producer for the race (a crossed line has left the fold's line loop and is never voided); Pending → Closed when a kills jump crosses a later line on the very frame that closed the live one (both close in line order; the later never opens). A voided line leaves the live set on void_complete and is never re-observed — the "replayed round" of the terminal edge has no race counterpart. Halted has no producer for this family (neither the LoL nor the betty feed carries a watermark).

The kills-at-minute family (settlement-contract SC-9): the SAME machine and the SAME fold serve a match's MINUTE lines through the same frame-kills seam, with one difference in the closing edge and one fewer void cause. Epoch = match ordinal × line count + line index as for the race; every line's market is created Pending on the forwarded envelope, the lowest opens at once, each later line opens on the prior line's settlement_complete OR void_complete. Open → Closed on the snapshot whose ORDINAL within the match is the line's minute — the frame that IS the minute — whichever side the total falls on (OVER and UNDER are both settlements: the partition total ≥ N + 1 has no push, so this family never voids on its settling frame); Open → Voided (and Pending → Voided) only on the forwarded match_complete with the minute unreached — the tie edge has no producer here. Pending → Closed has no producer either: minutes strictly ascend, so at most one line settles per frame and the next is Pending its barrier on that frame — and the lines rule keeps a QUOTE WINDOW (SC-9 rule 5: the first minute ≥ 2, each later minute ≥ the previous + 3) so the next line is quoted on at least two frames and an order on the first merges before the close; a line one minute past the previous would open on the barrier and close inside one tick, never quoted, so the rule refuses it (NoQuoteWindow). Closed → Voided has no producer for this family either (the fold voids only lines at or after the live one), and Halted none (neither feed carries a watermark). A voided line is retired on void_complete as for the race.

The SIDE kills-at-minute family (settlement-contract SC-10): the minute machine verbatim over lines that each name a TEAM — the closing edge reads that team's own count instead of the total (Open → Closed on the frame that IS the line's minute, OVER or UNDER by the team's count), every other edge and void cause the minute family's; the lines rule adds the team wire and keeps one line per minute (two teams at one minute would leave the second market unquotable — SC-10 rule 5), so a run interleaves the teams across ascending minutes. It is the SAME fold (MinuteTracker, generic over the judged count), not a second machine.

The GOLD-LEAD-at-minute family (settlement-contract SC-11): the same machine a third time over lines that name a TEAM and a GOLD amount — the closing edge reads that team's gold LEAD (its gold minus the other side's, saturating at 0) on the frame that IS the line's minute, OVER on N + 1 or more, UNDER otherwise (a lead equal to the line UNDER); the frame the fold reads is the title's GOLD read (FrameSource<Gold> — the fold is generic over WHAT the frame yields, MinuteJudged::Quantity). Every other edge, void cause and the lines rule (one line per minute, the teams interleaved) are the side family's; the gold line is capped at 100,000.

The TALLY-at-minute families (settlement-contract SC-12): the same machine over lines that name a TEAM and a COUNT of a KIND (towers taken; dragons or barons slain — the kind is the family text, one generic family replay::tally_at_minute over the kind tag) — the closing edge reads that team's count off the title's frame READER's tally (FrameSource<Tally>: the forwarded structure_destroyed/v1 / objective_slain/v1 events folded per match — the LoL reader machine and the dota2 reader machine of §4) on the frame that IS the line's minute, OVER on N + 1 or more, UNDER otherwise (a count equal to the line UNDER); which events that frame SEES is the title's ordering rule (LoL: the minute's events precede its frame; Dota2: a tower at the frame's own tick counts from the next frame — SC-12 rule 3). Every other edge, void cause and the lines rule are the side family's; the count is capped at 20; Dota2 offers towers only.

The HOUSE-LINE families (settlement-contract SC-13): the SAME machine over ONE market per match per family (replay::house_line — total_kills, kill_handicap, one generic family over the kind tag), with the creating edge moved off the envelope. Epoch = the match's discovery ordinal (the match winner's spelling). [*] → Pending on the match's OPENING snapshot — the run manifest's house_line open_minute-th, by the frame fold's ordinal — with the line the LineSetter chose on that snapshot (the pricing layer's setter over the run's house model, SC-13 rule 5); the barrier (the prior match's settlement_complete) is folded by then under the tick order — the prior completion's tick emits its settlement, the NEXT tick's pre-drain folds it into the lifecycle, and the next match's envelope and then its open_minute-th snapshot (open_minute ≥ 1) are at least two ticks later — so Pending → Open fires in the same lifecycle step (vacuous for the run's first match). Open → Closed on the forwarded match_complete, the outcome read off the LAST snapshot before it (the fold's own last — the frame fold releases it at the completion, so the house fold keeps it). A match completing before its opening snapshot creates NO market: no Pending, no void, no unwind. Voided has no producer for this family (the partition has no tie; a created market always settles), Halted none (neither feed carries a watermark), and Pending → Closed none under the tick order above (the market opens in the step that creates it; the generic edge stays drawn as the safety net, and the policy test runs a two-match log through the lifecycle fold to pin the headroom — replay/tests/house_line_policy.rs). The kind-neutral fold (HouseFold<T> — the frame fold plus the opening flag and the last frame) is driven by the policy, the extractors and the clearing source alike; the setter value is ONE, Arc-shared by the policy and the extractor (the runtime bind) — one RULE, evaluated by each holder on the same opening frame with the same terms, so the descriptor's line and the priced line are equal by purity (the serve test pins the equality).

The two barriers (settlement_complete before next open, void_complete before reopen) make balance availability a function of stream order, never of scheduler timing (flows.md Flows 3 and 5).


4. Pricing Layer — per-tick emission (per model, per market, per state_version — the run manifest's title AND its board select the emitters (one per family on the board; a one-entry board is the single-family case): CS2's per-team kills markets, a LoL / Dota2 match-winner market, a LoL / Dota2 line — a race line or a minute line — or a LoL / Dota2 house-line market whose line the setter chose) #

Contract
Consumes feature stream (schema-versioned — on a board a model consumes only the records carrying its own column's feature_schema_id), control stream (bindings; manifest = recorded initial state, promotions supersede at next open — plugin §11), lifecycle stream (market_open — the plugin-§9 effect timer; every market_* marker folds the position-consistent status view that decides the market_resumed deferred suspension pull — plugin §11)
Produces probability streams (live + shadow), model_output_invalid events on the derived model_health stream (event-catalog §6.10 writer split)
State = fold of control stream (which models exist and their lifecycle state; manifest = recorded initial binding — plugin §11); models themselves are stateless per tick; the dota2 extractor's per-match side binding (below — an extractor state that spans ticks — the race extractor's RaceTracker, the minute extractors' MinuteTracker — the side family's too — and the house extractors' HouseFold with the line the setter chose on the opening snapshot are the others)
Time travel probability at any (model, state_version) recomputable from artifact hash + feature event — lineage pins the recipe

Model lifecycle state is owned by model-plugin-layer.md §9 (Registered → Validated → Shadow → Live → Suspended, with the approver as a declared parameter) — not duplicated here. The per-tick emission machine:

stateDiagram-v2
    [*] --> Invoked: feature vector at state_version V of the model's OWN column's grid consumed (a model's column is the one whose extractor emits its recorded feature_schema_id · a grid no column emits refuses at bind · a record off the model's grid halts, BenchError ForeignGrid)
    Invoked --> Raw: predict returns a RawDistribution
    Invoked --> Invalid: panic caught — reason panic
    Raw --> Constrained: wrapper truncates below current kills and renormalizes exactly
    Raw --> Constrained: binary families (match winner, race to kills, kills at minute, side kills at minute, gold lead at minute, towers / dragons / barons at minute, total kills / kill handicap — the house lines) — validated only, no domain truncation (model-plugin-layer §7 two paths — the title's live path and the §10 backtest's)
    Raw --> Invalid: validation fails — wrong sum or bounds
    Constrained --> Emitted: probability event appended with model_id, wrapper_version, V
    Invalid --> Silent: model_output_invalid appended to model_health stream — NO probability event
    Emitted --> [*]
    Silent --> [*]: bound bookmaker stays silent this tick — no-quote beats bad-quote
Diagram source
stateDiagram-v2
    [*] --> Invoked: feature vector at state_version V of the model's OWN column's grid consumed (a model's column is the one whose extractor emits its recorded feature_schema_id · a grid no column emits refuses at bind · a record off the model's grid halts, BenchError ForeignGrid)
    Invoked --> Raw: predict returns a RawDistribution
    Invoked --> Invalid: panic caught — reason panic
    Raw --> Constrained: wrapper truncates below current kills and renormalizes exactly
    Raw --> Constrained: binary families (match winner, race to kills, kills at minute, side kills at minute, gold lead at minute, towers / dragons / barons at minute, total kills / kill handicap — the house lines) — validated only, no domain truncation (model-plugin-layer §7 two paths — the title's live path and the §10 backtest's)
    Raw --> Invalid: validation fails — wrong sum or bounds
    Constrained --> Emitted: probability event appended with model_id, wrapper_version, V
    Invalid --> Silent: model_output_invalid appended to model_health stream — NO probability event
    Emitted --> [*]
    Silent --> [*]: bound bookmaker stays silent this tick — no-quote beats bad-quote

The line families: the manifest's board (model-plugin §12 — its entries, FamilyLines; one emitter per column) selects the emitters beside the title; the race extractors (LolRaceFeatureExtractor / Dota2RaceFeatureExtractor, model-plugin §8 lol/race-v1 / dota2/race-v1) drive the same RaceTracker the lifecycle policy drives, so their emission is GATED by the race fold: one vector per snapshot (state/v2; player_frame/v1) for the LIVE line, none for the envelope, the completion, or a snapshot with no line left. The dota2 race extractor reads the match's sides off ITS OWN fold's frame reader (the roster binding below, carried by the reader the race fold owns) — one binding, never a second copy — and every minute extractor prices a snapshot on the fold's OWN frame value (MinuteTracker:: last_frame), never on a second read of the snapshot. The fold steps FIRST: the frame that crosses a line prices the NEXT line while that line's market is still Pending its barrier (the settlement_complete / void_complete just emitted reaches the lifecycle at the next tick's pre-drain), so that one quote finds no open market and is dropped — no-quote beats bad-quote — and a new line's first quote arrives on the following snapshot. The minute extractors (LolMinuteFeatureExtractor / Dota2MinuteFeatureExtractor, model-plugin §8 lol/minute-v1 / dota2/minute-v1) are the same machine on the minute fold (MinuteTracker, the same shared frame fold): the vector prices the live line with the minutes to go read off the frame ORDINAL, the settling frame prices the NEXT line (Pending its barrier — dropped), and nothing is emitted once the last line is decided. The side-minute extractors (LolSideMinuteFeatureExtractor / Dota2SideMinuteFeatureExtractor, model-plugin §8 lol/side-minute-v1 / dota2/side-minute-v1) are the same machine again over side lines: the vector carries the live line's TEAM and that team's own count in place of the total. The gold-lead extractors (LolGoldLeadFeatureExtractor / Dota2GoldLeadFeatureExtractor, model-plugin §8 lol/gold-lead-v1 / dota2/gold-lead-v1) are the same machine over gold lines, the fold reading gold: the vector carries the live line's team and that team's SIGNED lead and margin. The tally extractors (LolTallyFeatureExtractor<K> / Dota2TallyFeatureExtractor<K>, model-plugin §8 lol/<kind>-v1 / dota2/towers-v1) are the same machine over tally lines, the fold reading the reader's tally: the vector carries the live line's team and that team's count of the kind and margin. The line extractors' live-line machine, per match (the race's edges named; the minute families' "crossing" is the frame that IS the line's minute, whichever side the judged count falls on):

stateDiagram-v2
    [*] --> NoMatch
    NoMatch --> Line: the forwarded match envelope (match_discovered · roster_side/v1) — every line Pending, the lowest live
    NoMatch --> Halt: a snapshot before the envelope — FrameWithoutMatch (on the betty frame log the envelope is the roster · SidesUnbound is a LIVE match's frame before its roster)
    Line --> Emitted: a snapshot with the live line unreached — one vector on the perspective side (race_threshold = the live line · the minute family's line_minute / line_kills / minutes_to_go)
    Emitted --> Line
    Line --> Line: a snapshot crossing the live line (the minute family — the snapshot whose ordinal IS the line's minute) — the fold advances, the vector prices the NEXT line (still Pending its barrier)
    Line --> Done: the last line crossed or tied — nothing to price
    Line --> Halt: a frame index not strictly above the last — FrameOrder
    Line --> Halt: a frame inside the live match before its binding record — SidesUnbound (a per-slot title under a deaths-block envelope)
    Line --> Halt: a binding record contradicting the bound sides — SidesRebound (per-slot titles)
    Line --> Halt: a forwarded objective event the title's reader refuses (a LoL tower without its owning side) or a side's tally leaving u64 — TallyOverflow (every family's fold, the reader steps on every record)
    Done --> NoMatch: forwarded match_complete
    Line --> NoMatch: forwarded match_complete — the unreached lines void
Diagram source
stateDiagram-v2
    [*] --> NoMatch
    NoMatch --> Line: the forwarded match envelope (match_discovered · roster_side/v1) — every line Pending, the lowest live
    NoMatch --> Halt: a snapshot before the envelope — FrameWithoutMatch (on the betty frame log the envelope is the roster · SidesUnbound is a LIVE match's frame before its roster)
    Line --> Emitted: a snapshot with the live line unreached — one vector on the perspective side (race_threshold = the live line · the minute family's line_minute / line_kills / minutes_to_go)
    Emitted --> Line
    Line --> Line: a snapshot crossing the live line (the minute family — the snapshot whose ordinal IS the line's minute) — the fold advances, the vector prices the NEXT line (still Pending its barrier)
    Line --> Done: the last line crossed or tied — nothing to price
    Line --> Halt: a frame index not strictly above the last — FrameOrder
    Line --> Halt: a frame inside the live match before its binding record — SidesUnbound (a per-slot title under a deaths-block envelope)
    Line --> Halt: a binding record contradicting the bound sides — SidesRebound (per-slot titles)
    Line --> Halt: a forwarded objective event the title's reader refuses (a LoL tower without its owning side) or a side's tally leaving u64 — TallyOverflow (every family's fold, the reader steps on every record)
    Done --> NoMatch: forwarded match_complete
    Line --> NoMatch: forwarded match_complete — the unreached lines void

The dota2 side binding (the first extractor state that spans ticks; event-catalog §6.11.1, model-plugin §8; ONE fold, ingest::dota2::roster_side::RosterBinding, driven by the match-winner extractor AND by the shared frame fold's reader — replay::dota2:: Dota2FrameReader, the binding plus the objective tally — under the race and the minute families — each line extractor reads the bound sides off its own fold): per match, over the game-state stream.

stateDiagram-v2
    [*] --> Unbound
    Unbound --> Bound: roster_side/v1 — the match envelope binds the ten slots' sides · the tally resets (nothing counted before a match's roster reaches its lines)
    Unbound --> Unbound: forwarded structure_destroyed/v1 (kind Tower) before the bind — credited, then wiped by the roster's reset
    Bound --> Bound: roster_side/v1 with identical sides — idempotent re-announcement
    Bound --> Unbound: forwarded match_complete (either block) — released for the next match · the tally resets
    Unbound --> Unbound: forwarded match_discovered (either block) — the tally resets
    Bound --> Bound: forwarded structure_destroyed/v1 (kind Tower) — the OTHER side than the owner credited one tower, CHECKED
    Bound --> Halt: a side's tower tally leaving u64 — TallyOverflow
    Unbound --> Halt: player_frame/v1 before the bind — RosterUnbound (SidesUnbound through EVERY frame read of the shared fold — kills, gold and the tally read, which needs no sides but takes the seam's halt), never a positionally assumed side
    Bound --> Halt: roster_side/v1 with contradicting sides — RosterRebound (SidesRebound through the race fold · a different match announced while one is live halts in the lifecycle fold first, SC-7 rule 4)
    Bound --> Emitted: player_frame/v1 — one feature vector on the radiant perspective (the match-winner extractor · under a line family emission is the live-line machine above)
    Emitted --> Bound
Diagram source
stateDiagram-v2
    [*] --> Unbound
    Unbound --> Bound: roster_side/v1 — the match envelope binds the ten slots' sides · the tally resets (nothing counted before a match's roster reaches its lines)
    Unbound --> Unbound: forwarded structure_destroyed/v1 (kind Tower) before the bind — credited, then wiped by the roster's reset
    Bound --> Bound: roster_side/v1 with identical sides — idempotent re-announcement
    Bound --> Unbound: forwarded match_complete (either block) — released for the next match · the tally resets
    Unbound --> Unbound: forwarded match_discovered (either block) — the tally resets
    Bound --> Bound: forwarded structure_destroyed/v1 (kind Tower) — the OTHER side than the owner credited one tower, CHECKED
    Bound --> Halt: a side's tower tally leaving u64 — TallyOverflow
    Unbound --> Halt: player_frame/v1 before the bind — RosterUnbound (SidesUnbound through EVERY frame read of the shared fold — kills, gold and the tally read, which needs no sides but takes the seam's halt), never a positionally assumed side
    Bound --> Halt: roster_side/v1 with contradicting sides — RosterRebound (SidesRebound through the race fold · a different match announced while one is live halts in the lifecycle fold first, SC-7 rule 4)
    Bound --> Emitted: player_frame/v1 — one feature vector on the radiant perspective (the match-winner extractor · under a line family emission is the live-line machine above)
    Emitted --> Bound

The LoL frame reader (replay::lol::LolFrameReader — the LoL analogue of the binding above, carried by the same shared frame fold under every LoL family; event-catalog §6.15.1): per match, the running objective tally off the forwarded events, every transition its own event.

stateDiagram-v2
    [*] --> Idle
    Idle --> Counting: forwarded match_discovered/v1 — the tally starts from zero
    Counting --> Counting: forwarded structure_destroyed/v1 of kind Tower — the OTHER side than the owner credited one tower, CHECKED (plates · inhibitors · the nexus count nothing)
    Counting --> Counting: forwarded objective_slain/v1 whose NAME is a drake · elderDragon (a dragon) or baron (a baron) — the slayer's side credited, CHECKED (any other name counts nothing)
    Counting --> Read: state/v2 snapshot — the tally AT this minute (the minute's events precede it) read by FrameSource of Tally
    Read --> Counting
    Counting --> Halt: a Tower with no owning side — Title(MissingField(SD_SIDE)) · a torn forwarded body — its own decode halt · a torn state/v2 snapshot — its frame-index decode halt (frame_index_from_record) · a side's tally leaving u64 — TallyOverflow
    Counting --> Idle: forwarded match_complete/v1 — released, the next match starts from zero
Diagram source
stateDiagram-v2
    [*] --> Idle
    Idle --> Counting: forwarded match_discovered/v1 — the tally starts from zero
    Counting --> Counting: forwarded structure_destroyed/v1 of kind Tower — the OTHER side than the owner credited one tower, CHECKED (plates · inhibitors · the nexus count nothing)
    Counting --> Counting: forwarded objective_slain/v1 whose NAME is a drake · elderDragon (a dragon) or baron (a baron) — the slayer's side credited, CHECKED (any other name counts nothing)
    Counting --> Read: state/v2 snapshot — the tally AT this minute (the minute's events precede it) read by FrameSource of Tally
    Read --> Counting
    Counting --> Halt: a Tower with no owning side — Title(MissingField(SD_SIDE)) · a torn forwarded body — its own decode halt · a torn state/v2 snapshot — its frame-index decode halt (frame_index_from_record) · a side's tally leaving u64 — TallyOverflow
    Counting --> Idle: forwarded match_complete/v1 — released, the next match starts from zero

The house-line families: the house extractors (pricing::feature::HouseFeatureExtractor<T, K> on lol/total-v1 / lol/handicap-v1 / dota2/total-v1 / dota2/handicap-v1, model-plugin §8) drive the SAME HouseFold the lifecycle policy and the clearing source drive, and add the one step no earlier extractor had: on the match's OPENING snapshot they SET the line — through the ONE HouseLineSetter the runtime bind shares with the policy (the run's house model evaluated per candidate of the terms, P(OVER) nearest one half; settlement-contract SC-13 rule 5) — and price it on that same snapshot (the market is created and opened in that lifecycle step, so the first quote arrives on the opening frame, unlike the line families' first quote on the frame after the barrier); every later snapshot prices the same line with the running total or signed lead; nothing is emitted before the opening, after the completion, or for a match that never opens. The extractor state that spans ticks is the fold plus the line the setter chose; the setter itself is stateless — a pure function of the snapshot, the fold and the recorded artifact.

stateDiagram-v2
    [*] --> NoLine: the forwarded match envelope (match_discovered · roster_side/v1) — the fold discovers the match
    NoLine --> NoLine: a snapshot before the open_minute-th (state/v2 · player_frame/v1) — nothing emitted
    NoLine --> Priced: the open_minute-th snapshot — the shared setter SETS the line off the run's house model and the vector for it is emitted
    Priced --> Priced: a later snapshot — the same line, the running total or signed lead
    Priced --> NoLine: forwarded match_complete — the line cleared, nothing emitted
    NoLine --> NoLine: forwarded match_complete before the opening — nothing emitted, no line was set
    NoLine --> Halt: the setter refuses the opening snapshot (HouseSetError — a model refusal, a grid the snapshot cannot fill)
    Priced --> Halt: an opened snapshot with no live line (LiveMissing — structurally unreachable, typed) · the frame fold's own halts
    Halt --> [*]
Diagram source
stateDiagram-v2
    [*] --> NoLine: the forwarded match envelope (match_discovered · roster_side/v1) — the fold discovers the match
    NoLine --> NoLine: a snapshot before the open_minute-th (state/v2 · player_frame/v1) — nothing emitted
    NoLine --> Priced: the open_minute-th snapshot — the shared setter SETS the line off the run's house model and the vector for it is emitted
    Priced --> Priced: a later snapshot — the same line, the running total or signed lead
    Priced --> NoLine: forwarded match_complete — the line cleared, nothing emitted
    NoLine --> NoLine: forwarded match_complete before the opening — nothing emitted, no line was set
    NoLine --> Halt: the setter refuses the opening snapshot (HouseSetError — a model refusal, a grid the snapshot cannot fill)
    Priced --> Halt: an opened snapshot with no live line (LiveMissing — structurally unreachable, typed) · the frame fold's own halts
    Halt --> [*]

5. Market-Making Layer — quote state (per quote) #

Contract
Consumes its model's probability stream (house sleeve) or its recorded forecast_submit/ladder_submit order-log ingress (external sleeve — the same fold, driven per the architecture.md external drive rule); market-event stream (own fills fold back, own quote echoes reconcile the resting mirror, every sleeve's quote_accepted advances the folded book version); lifecycle stream (market_created + event-catalog §6.9 markers); settlement stream (own payouts and own void_refunds staged, applied into free at settlement_complete / void_complete — the market's posted leaves the headroom and, under R1 v2, a realized loss comes off it; the market retires from the fold)
Produces quote stream, tagged with state_version
State = fold of own fills (inventory, posted risk, realized cash) + own resting mirror + market statuses/epochs + staged payouts and void refunds, plus latest probability event (house — an external sleeve is driven per record, nothing retained between records; the architecture.md quote fold's step-8 headroom mirror needs exactly the engine's admission inputs)
Time travel the quote ladder at version V is a pure function of (probability@V, fold to V); external: of (the recorded submission, fold to its merge slot)
stateDiagram-v2
    [*] --> Computed: fold consumes its drive input — probability event at version V (house) or recorded forecast_submit / ladder_submit at its merge slot, V = the version its stamp names (external)
    Computed --> Submitted: quote batch appended — tagged V, cancel_all_prior
    Submitted --> Resting: matching engine accepts — collateral pre-check passed
    Submitted --> Rejected: admission fails — quote_rejected appended (capacity, invalid, market_closed, or stale_version)
    Resting --> PartiallyFilled: fill event at version V
    PartiallyFilled --> Filled: remaining size fills
    Resting --> Filled: full fill event
    Resting --> Expired: game-state advances past V — automatic, structural
    PartiallyFilled --> Expired: version advance — remainder dies
    Resting --> Cancelled: cancel in the next batch
    Filled --> [*]
    Expired --> [*]
    Rejected --> [*]
    Cancelled --> [*]
    note right of Expired: zero stale-state fills is not a policy — no path exists from an old version to a fill
Diagram source
stateDiagram-v2
    [*] --> Computed: fold consumes its drive input — probability event at version V (house) or recorded forecast_submit / ladder_submit at its merge slot, V = the version its stamp names (external)
    Computed --> Submitted: quote batch appended — tagged V, cancel_all_prior
    Submitted --> Resting: matching engine accepts — collateral pre-check passed
    Submitted --> Rejected: admission fails — quote_rejected appended (capacity, invalid, market_closed, or stale_version)
    Resting --> PartiallyFilled: fill event at version V
    PartiallyFilled --> Filled: remaining size fills
    Resting --> Filled: full fill event
    Resting --> Expired: game-state advances past V — automatic, structural
    PartiallyFilled --> Expired: version advance — remainder dies
    Resting --> Cancelled: cancel in the next batch
    Filled --> [*]
    Expired --> [*]
    Rejected --> [*]
    Cancelled --> [*]
    note right of Expired: zero stale-state fills is not a policy — no path exists from an old version to a fill

The Submitted → Rejected edge is the ENGINE's transition and stays drawn — but for a sleeve driven by the quote fold (architecture.md, the quote fold) all four reject reasons are unreachable by construction (status checked, ladder valid, stale ticks skipped, shrink shared) once the BM step runs at its merge slot — every record whose merge key precedes the house batches' slot (tick_ts, class 2) folds into the mirror and the engine first —, so the fold treats an own rejection as proof its mirror diverged and halts rather than absorbing it (halt, never guess). The guarantee extends to external sleeves: the gateway pre-validates ladder structure with the engine's own check before a record exists, and the stamp-named-version rule (architecture.md external drive rule 1) makes staleness a fold-side pre-skip — so a client can post garbage at the wire and only ever receive a wire reject, never create a recorded batch the engine refuses.

Out of scope (the alignment note's phase 1): quotes enter via the signed-quote gateway and reserve collateral atomically per batch (the real-world alignment note §3) — the state machine gains a Reserved refinement, not a new shape.


6. Exchange Layer — retail order state (IOC and resting) #

Contract
Consumes quote streams, order log (orders + account_created + the operator records subaccount_opened / instruction_relayed; the two §6.6 quoting-ingress schemas are Market-Making's, diverted before the engine), settlement stream (collateral feedback), lifecycle stream
Produces market-event stream (accepts, fills, cancels, rests, retail cancels, rejections — stale, funds, closed, halted, self-cross, position, resting cap — and instruction refusals)
State = fold of market events + settlements (order book with its retail fragments, collateral view, and the per-subaccount fold state below)
Time travel the book at any offset = fold to that offset; disputes are recomputed, not argued
stateDiagram-v2
    [*] --> Recorded: appended to order log — seq, ts, quoted_version (an order/v1, or an instruction_relayed whose signed bytes carry them). This is the moment it becomes history
    Recorded --> RejectedInstruction: instruction_rejected appended — bad_instruction, foreign_account, bad_signature or stale_nonce (matching-engine §5 steps 1–4), judged at its merge position (matching-engine §5)
    Recorded --> RejectedNoResting: instruction_rejected no_resting_order appended — a verified cancel naming no resting order of its subaccount, its nonce consumed, judged at its merge position (matching-engine §5)
    Recorded --> Normalized: the recorded order/v1 or instruction_relayed folded — complement transform — buy Lower at p becomes sell Higher at 1−p (a relayed instruction once its signature verifies and its fresh nonce is consumed)
    Normalized --> Sequenced: the same record at its merge position — deterministic merge — quadruple (replay_ts, class, stream, offset) places it after same-ts quotes
    Sequenced --> Admitted: the same record passes the collateral-view check (own fold, never a ledger query) — its next appended event is a fill, retail_rested or order_unfilled
    Sequenced --> RejectedClosed: market not open (closed, voided, or pre-open, or, for a relayed place, never created) — order_rejected market_closed appended (matching-engine §8)
    Sequenced --> RejectedHalted: market halted at admission — order_rejected market_halted appended (matching-engine §8)
    Sequenced --> RejectedStale: order_rejected stale_version appended — book version advanced past quoted_version
    Sequenced --> RejectedSelfCross: would cross a resting order of the same customer key — order_rejected self_cross appended
    Sequenced --> RejectedFunds: order_rejected insufficient_funds appended — insufficient balance for cost — for a subaccount, balance less reservations below the cost at the named price plus the declared fee term
    Sequenced --> RejectedRestingCap: a GTC place whose remainder would rest beyond the subaccount's max_resting_orders — order_rejected resting_cap appended (matching-engine §5 step 8)
    Sequenced --> RejectedPosition: the held-units guard broken — a subaccount sell, or a buy while a sell of the other side rests, beyond the held units — order_rejected insufficient_position appended
    Admitted --> Filled: crosses best price — fill events appended
    Admitted --> PartiallyFilled: fill events appended for part of the size — the remainder of the IOC, or a GTC stamped at or below a stall record's ts (R7 v2), cancels with no further event
    Admitted --> Unfilled: order_unfilled appended — no crossable depth, the IOC, or a GTC stamped at or below a stall record's ts (R7 v2), cancels entirely
    Admitted --> Resting: GTC remainder, after any fills, stamped above every stall record's ts (R7 v2) — retail_rested appended, its reservation taken
    Resting --> Resting: fill as maker — fill appended, the reservation consumed at the order's price
    Resting --> Filled: the remainder fills as maker — fill appended
    Resting --> Cancelled: retail_cancelled appended — customer (a verified cancel instruction), close or void (the drain at the marker's merge position), or feed_stall (R7 v2 at the stall record's tick, a halted market's at its resume), the remainder's reservation released
    Filled --> [*]: terminal at its last fill
    PartiallyFilled --> [*]: terminal at its last fill
    Unfilled --> [*]: terminal at order_unfilled
    Cancelled --> [*]: terminal at retail_cancelled
    RejectedInstruction --> [*]: terminal at instruction_rejected
    RejectedNoResting --> [*]: terminal at instruction_rejected
    RejectedClosed --> [*]: terminal at order_rejected
    RejectedHalted --> [*]: terminal at order_rejected
    RejectedStale --> [*]: terminal at order_rejected
    RejectedSelfCross --> [*]: terminal at order_rejected
    RejectedFunds --> [*]: terminal at order_rejected
    RejectedPosition --> [*]: terminal at order_rejected
    RejectedRestingCap --> [*]: terminal at order_rejected
    note right of Resting: only a subaccount's GTC place instruction rests (under R7 v2 none stamped at or below a stall record's ts) — version expiry and a sleeve's cancel_all_prior never touch it (matching-engine §4)
Diagram source
stateDiagram-v2
    [*] --> Recorded: appended to order log — seq, ts, quoted_version (an order/v1, or an instruction_relayed whose signed bytes carry them). This is the moment it becomes history
    Recorded --> RejectedInstruction: instruction_rejected appended — bad_instruction, foreign_account, bad_signature or stale_nonce (matching-engine §5 steps 1–4), judged at its merge position (matching-engine §5)
    Recorded --> RejectedNoResting: instruction_rejected no_resting_order appended — a verified cancel naming no resting order of its subaccount, its nonce consumed, judged at its merge position (matching-engine §5)
    Recorded --> Normalized: the recorded order/v1 or instruction_relayed folded — complement transform — buy Lower at p becomes sell Higher at 1−p (a relayed instruction once its signature verifies and its fresh nonce is consumed)
    Normalized --> Sequenced: the same record at its merge position — deterministic merge — quadruple (replay_ts, class, stream, offset) places it after same-ts quotes
    Sequenced --> Admitted: the same record passes the collateral-view check (own fold, never a ledger query) — its next appended event is a fill, retail_rested or order_unfilled
    Sequenced --> RejectedClosed: market not open (closed, voided, or pre-open, or, for a relayed place, never created) — order_rejected market_closed appended (matching-engine §8)
    Sequenced --> RejectedHalted: market halted at admission — order_rejected market_halted appended (matching-engine §8)
    Sequenced --> RejectedStale: order_rejected stale_version appended — book version advanced past quoted_version
    Sequenced --> RejectedSelfCross: would cross a resting order of the same customer key — order_rejected self_cross appended
    Sequenced --> RejectedFunds: order_rejected insufficient_funds appended — insufficient balance for cost — for a subaccount, balance less reservations below the cost at the named price plus the declared fee term
    Sequenced --> RejectedRestingCap: a GTC place whose remainder would rest beyond the subaccount's max_resting_orders — order_rejected resting_cap appended (matching-engine §5 step 8)
    Sequenced --> RejectedPosition: the held-units guard broken — a subaccount sell, or a buy while a sell of the other side rests, beyond the held units — order_rejected insufficient_position appended
    Admitted --> Filled: crosses best price — fill events appended
    Admitted --> PartiallyFilled: fill events appended for part of the size — the remainder of the IOC, or a GTC stamped at or below a stall record's ts (R7 v2), cancels with no further event
    Admitted --> Unfilled: order_unfilled appended — no crossable depth, the IOC, or a GTC stamped at or below a stall record's ts (R7 v2), cancels entirely
    Admitted --> Resting: GTC remainder, after any fills, stamped above every stall record's ts (R7 v2) — retail_rested appended, its reservation taken
    Resting --> Resting: fill as maker — fill appended, the reservation consumed at the order's price
    Resting --> Filled: the remainder fills as maker — fill appended
    Resting --> Cancelled: retail_cancelled appended — customer (a verified cancel instruction), close or void (the drain at the marker's merge position), or feed_stall (R7 v2 at the stall record's tick, a halted market's at its resume), the remainder's reservation released
    Filled --> [*]: terminal at its last fill
    PartiallyFilled --> [*]: terminal at its last fill
    Unfilled --> [*]: terminal at order_unfilled
    Cancelled --> [*]: terminal at retail_cancelled
    RejectedInstruction --> [*]: terminal at instruction_rejected
    RejectedNoResting --> [*]: terminal at instruction_rejected
    RejectedClosed --> [*]: terminal at order_rejected
    RejectedHalted --> [*]: terminal at order_rejected
    RejectedStale --> [*]: terminal at order_rejected
    RejectedSelfCross --> [*]: terminal at order_rejected
    RejectedFunds --> [*]: terminal at order_rejected
    RejectedPosition --> [*]: terminal at order_rejected
    RejectedRestingCap --> [*]: terminal at order_rejected
    note right of Resting: only a subaccount's GTC place instruction rests (under R7 v2 none stamped at or below a stall record's ts) — version expiry and a sleeve's cancel_all_prior never touch it (matching-engine §4)

Every terminal state is an appended market event — including the rejections, which is what makes "zero stale fills" auditable rather than asserted. A cancel instruction is no order of its own: verified with a fresh nonce, it drives its target's Resting → Cancelled, or ends instruction_rejected{no_resting_order} when the target is not a resting order of its subaccount (matching-engine §5). A subaccount opening whose key fails the strict parse ends instruction_rejected{bad_key} and opens nothing (event-catalog §6.6).

Fold state per subaccount — the exchange fold's homes for what the machine above reads (matching-engine §5–§6):

State Home and the events that move it
operator and customer key the subaccount_opened record — fixed for the run; a key failing the strict parse leaves no subaccount (bad_key)
balance the opening balance, then its fills' tranche cash and its payout and void-refund rows — the one balance the ledger also folds (state-layers §7)
last consumed nonce absent until the first verified instruction with a fresh nonce; becomes that nonce at matching-engine §5 step 4 and moves by no other event, a later refusal of the same instruction included
cash reservations Σ remaining × p_named over its resting buys under retail_fee_rule v1 "no fee" (a later fee version with a per-fill or per-order term states its reservation term, matching-engine §6) — taken at retail_rested, consumed by each fill of the resting order, released by retail_cancelled; exchange-only admission state, never ledger cash
unit reservations per (market, side Y): Σ remaining over its resting sells of Y — the same three events; with an incoming sell of Y and Σ remaining over its buys of the other side (resting and incoming), the terms the held-units guard bounds by the units of Y held whenever a sell of Y rests or comes in (matching-engine §6)
resting orders per order id (its instruction_relayed offset): market, book side, price, remaining — from retail_rested to its last fill or its retail_cancelled

A retail position lives in its market's pot — a retail leg's cash in the pot from its fill, a sleeve leg's carried collateral futarchy-system.md §8 convention (b) — and is untouched by a financing-cycle boundary; Σ withdrawn, the handoff withdrawals of futarchy-system.md §8 (R47), is zero inside the exchange fold (matching-engine §6).


7. Clearing Layer — position and collateral state #

Contract
Consumes lifecycle stream (market terms + phase markers), market-event stream (fills), order log (account_created and subaccount_opened — balances start from deposits; an opening whose key fails the strict parse credits nothing, the exchange fold's rule applied independently, event-catalog §6.6), match log (title records for the outcome recompute: SC-1 kill counting, SC-2 cutoff — the round_end's reported counts are diagnostic only; under the race family the layer re-derives the game-state stream INTERNALLY — a mirrored replay fold, the title's own projector over the same match-log records, not a new stream edge — and reads the crossing frames off it, SC-8 rule 6)
Produces settlement stream (payouts, void_refunds, settlement_complete, void_complete — each barrier naming its claim family on a board, since epochs are family-local and several families share one match; absent on a single-family run); reversals on the SETTLED-then-voided path, out of scope — settlement-contract SC-V rule 4)
State = fold of the consumed streams (authoritative balances, positions, market pots); ONE COLUMN per claim family — an outcome source and the family's text — every title record observed by every column (each mirrors its own projector and fold), a column's finals keyed (column, epoch), a market_created routed to its family's column or refused by name (UnknownFamily); the title outcome source's recorded outcomes live from their finality to their epoch's settlement_complete — the barrier retires them with the epoch (flows.md Flow 3; the void_complete barrier retires the same way, releasing nothing because a voided epoch never recorded an outcome — SC-8 rule 3 / SC-V), so the OUTCOME SOURCE's working set is the epochs in flight, not the run's history, for every title that retires (CS2 retires nothing — its post-cutoff tail still folds into the last settled round's diagnostic, so its map is bounded by a match's rounds); the fold's own settled_epochs / settled_markets stay for the run on purpose — SC-V rule 4's refusal of a void after settlement keys on them
Time travel the ledger at any offset recomputable by anyone with the logs — the audit story (this is why fill/v1 records the taker's direction: without it, buyer and seller are not reconstructible downstream)

Vocabulary: the machines below speak the matching-engine §6 tranche/pot model — "locked collateral" is not a term of this document. Cash a party pays on an opening tranche sits in the market pot (pot = 1000 mills × open interest at every offset); posted is the party's cumulative at-risk attribution (payments − receipts), not a separate money bucket.

Position (per party — account or sleeve — per market):

stateDiagram-v2
    [*] --> Flat
    Flat --> OpenPos: fill event — the open tranche pays into the market pot
    OpenPos --> OpenPos: further fills — closes settle at the trade price, opens pay in
    OpenPos --> Flat: closing fill zeroes net — realized PnL already in balance
    OpenPos --> SettledPaid: payout staged — applied with settlement_complete, the pot pays net × the owner-side settlement price
    OpenPos --> VoidedPos: void_refund rows — the party's posted returns from the pot (settlement-contract SC-V) — reversal events only for a SETTLED market (out of scope), or void_value redemption (per claim-series terms, the growth path's D5)
    SettledPaid --> [*]: settlement_complete — the epoch's positions clear
    VoidedPos --> [*]: void_complete
Diagram source
stateDiagram-v2
    [*] --> Flat
    Flat --> OpenPos: fill event — the open tranche pays into the market pot
    OpenPos --> OpenPos: further fills — closes settle at the trade price, opens pay in
    OpenPos --> Flat: closing fill zeroes net — realized PnL already in balance
    OpenPos --> SettledPaid: payout staged — applied with settlement_complete, the pot pays net × the owner-side settlement price
    OpenPos --> VoidedPos: void_refund rows — the party's posted returns from the pot (settlement-contract SC-V) — reversal events only for a SETTLED market (out of scope), or void_value redemption (per claim-series terms, the growth path's D5)
    SettledPaid --> [*]: settlement_complete — the epoch's positions clear
    VoidedPos --> [*]: void_complete

Collateral (the money's own state machine):

stateDiagram-v2
    [*] --> Free: balance credited — deposit or payout
    Free --> InPot: fill event — the open tranche pays the trade-price side into the pot
    InPot --> Free: closing fill — the pot pays the exposure at the TRADE price
    InPot --> Free: settlement_complete applies the epoch's staged payouts — the pot drains at the settlement price
    InPot --> Free: void_complete applies the epoch's staged void_refunds — each party's posted returns, the pot drains to zero (settlement-contract SC-V)
    note right of InPot: pot = 1000 mills × open interest at every offset (matching-engine §6) — settlement exactly funded at any grid price, checked by a tap, not trusted
    note right of Free: posted (payments − receipts) attributes at-risk cash per party for headroom and display — the alignment note's phase 1 refines quote reservation into Reserved and Drawn (out of scope)
Diagram source
stateDiagram-v2
    [*] --> Free: balance credited — deposit or payout
    Free --> InPot: fill event — the open tranche pays the trade-price side into the pot
    InPot --> Free: closing fill — the pot pays the exposure at the TRADE price
    InPot --> Free: settlement_complete applies the epoch's staged payouts — the pot drains at the settlement price
    InPot --> Free: void_complete applies the epoch's staged void_refunds — each party's posted returns, the pot drains to zero (settlement-contract SC-V)
    note right of InPot: pot = 1000 mills × open interest at every offset (matching-engine §6) — settlement exactly funded at any grid price, checked by a tap, not trusted
    note right of Free: posted (payments − receipts) attributes at-risk cash per party for headroom and display — the alignment note's phase 1 refines quote reservation into Reserved and Drawn (out of scope)

The collateral-view equality checker asserts the Exchange layer's fold and this layer's fold agree at every shared offset outside an in-flight settlement OR void group (matching-engine §6: a group applies atomically — the ledger at its trigger record, the exchange at the settlement_complete or void_complete barrier; between a group's first payout or refund row and its barrier the folds are legitimately mid-transaction) — two implementations of one fold definition, continuously reconciled. (Neither implementation may call the other's code: an independent check that computes its own expected value through the code under test checks nothing.)


8. Interface Layer — pure projector (no state machine) #

Contract
Consumes any stream in the run's directory, via the gateway — game-state, market-event, and settlement first among them (the hello_ack directory serves every stream of the run, root logs included, gateway-protocol.md §2 — streams are immutable and read-only, so the wider read surface changes no write path)
Produces order-log events (user actions through the input gateway — the impure edge)
State = fold of nothing owned — the screen is a materialized view of consumed streams
Time travel close the app, replay the streams, get the identical screen — pixel-for-pixel derivable

Deliberately stateless: the trading screen holds no authoritative state, so it needs no state diagram. Its one write path (order submission) becomes history at the order-log append (§6, Recorded), not in the browser.

8.1 Gateway session (per connection) #

The one interface-layer entity with a state machine — and its entire state is a set of read cursors plus a write path. Nothing authoritative lives in a session: drop it, reconnect, re-subscribe from the last delivered offsets, and the client is byte-identically restored. Sessions are how humans borrow the streams' time-travel property.

Note: the Authenticated state name is historical — binding is by recorded existence, not credential verification (gateway-protocol §3 rule 2), an operator session's included; the customer instructions an operator session relays are signed and verified in the exchange fold (§6), never at the session.

stateDiagram-v2
    [*] --> Connected: websocket open
    Connected --> Authenticated: account bound — account_created in the order log, or existing account id
    Authenticated --> Authenticated: order / forecast / ladder submitted, or on an operator session a subaccount opening / relayed instruction — gateway stamps (replay_ts, seq), appends to order log, acks with seq
    Authenticated --> Authenticated: control frame — register, shadow, promote, suspend, backtest card — appends to the control ROOT log, acks with its own seq
    Authenticated --> Subscribed: subscribe per stream from offset K (0, head, or a resume cursor)
    Subscribed --> Streaming: events push as subscribed streams advance
    Streaming --> Streaming: order / forecast / ladder / control frame submitted — same write paths from any authenticated state
    Streaming --> Dropped: connection lost — no state lost, only cursors forgotten client-side
    Dropped --> Connected: reconnect
    note right of Subscribed: session state = cursors only. The order ack carries seq so the client can correlate its terminal event (fill, unfilled, rejected) from the market-event stream
Diagram source
stateDiagram-v2
    [*] --> Connected: websocket open
    Connected --> Authenticated: account bound — account_created in the order log, or existing account id
    Authenticated --> Authenticated: order / forecast / ladder submitted, or on an operator session a subaccount opening / relayed instruction — gateway stamps (replay_ts, seq), appends to order log, acks with seq
    Authenticated --> Authenticated: control frame — register, shadow, promote, suspend, backtest card — appends to the control ROOT log, acks with its own seq
    Authenticated --> Subscribed: subscribe per stream from offset K (0, head, or a resume cursor)
    Subscribed --> Streaming: events push as subscribed streams advance
    Streaming --> Streaming: order / forecast / ladder / control frame submitted — same write paths from any authenticated state
    Streaming --> Dropped: connection lost — no state lost, only cursors forgotten client-side
    Dropped --> Connected: reconnect
    note right of Subscribed: session state = cursors only. The order ack carries seq so the client can correlate its terminal event (fill, unfilled, rejected) from the market-event stream

Order submission is valid from ANY authenticated state, not only Streaming — a classic pull-only client (gateway-protocol.md §3 rule 3b) trades without ever subscribing; the diagram above draws the write path at both points. The quoting ingress extends the same write path to forecast_submit/ladder_submit (gateway-protocol §3 rule 3c) — one log, one stamp counter, one ack shape; the first accepted quoting append binds its external sleeve to the session's account for the run. The pull read (fetch/v1) is a pure query and appears in no state machine: it retains no cursor and transitions nothing. The subscribe transition's "offset K (0, head, or a resume cursor)" enumeration is not exhaustive: an offset beyond head is a valid forward cursor — delivery begins when the stream reaches it (gateway-protocol §3 rule 3).


9. Verification Layer — invariant verdict state (per checker) #

Contract
Consumes every stream relevant to its invariant (the two in-process taps read fold state instead — note below)
Produces violation events; the model health tap may emit auto-suspend control events (the layer's one sanctioned writer — control events only, never market or money events)
State = fold of consumed events + assertion results
Time travel a violation carries its exact offset — replay to K reproduces it deterministically (git-bisect over time)
stateDiagram-v2
    [*] --> Holding: tap subscribed from offset zero
    Holding --> Holding: event consumed — invariant asserted and passes
    Holding --> Violated: assertion fails at offset K — violation event records K
    Violated --> [*]: run marked failed — zero-tolerance KPI broken
    note right of Violated: solvency, stale-fill, settlement, collateral-view equality all share this shape
Diagram source
stateDiagram-v2
    [*] --> Holding: tap subscribed from offset zero
    Holding --> Holding: event consumed — invariant asserted and passes
    Holding --> Violated: assertion fails at offset K — violation event records K
    Violated --> [*]: run marked failed — zero-tolerance KPI broken
    note right of Violated: solvency, stale-fill, settlement, collateral-view equality all share this shape

The determinism harness is the same machine at run granularity: Holding while output hashes match the re-run, Violated at the first diverging offset.

Violation-event emission deferral (out of scope). The in-process taps (solvency, collateral-view equality) reach Violated as typed fold state carrying the exact offset, and the runtime halts on the first violation (§11 Running → Failed) — but no violation EVENT is appended to a stream. The appended form matters when taps run out-of-process (Flow 6's operator harness) and belongs to that harness, together with its schema row in event-catalog §6; without it the halt error is the recorded anchor (at most one violation ever exists per run by construction — the run stops at the first). The model-health tap's auto-suspend control events remain a separately recorded deferral (model-plugin-layer.md §14).

The same deferral applies to the CONSUMPTION side: the two in-process taps (solvency, collateral-view equality) do not subscribe to the streams this row names — they read the two collateral folds' own live state through public accessors, plus the runtime's own Σ-deposits recount for solvency's conservation arm (folded from the same account_created records, computed by neither fold under test): solvency after every merged record and at the tick boundary, equality at the same points outside an in-flight settlement or void group (matching-engine §6). The stream-fed, out-of-process form belongs to Flow 6's harness. The swept universe is the folds' live entries themselves, not a party × market list handed down by the runtime: for pots and positions an absent entry reads as the fold's own default (empty pot, flat position), so the sweep is equivalent to any superset universe, and a market no fill touched satisfies matching-engine §6's pot = 1000 × OI as 0 = 0. Balances are the exception — both folds learn their parties from the same records, so a party live in one fold only is itself the violation, never a default read; the one case the live-state universe does not see is a party the records declared that NEITHER fold holds (clearing/tests/equality.rs::assert_folds_agree keeps that declared-list check; at run time such a party surfaces as the solvency conservation arm (when its balance is nonzero) or as the folds' own UnknownAccount halt on first use).


10. Offline Layer — evaluation and test-split state #

Contract
Consumes match log (training split), probability + settlement streams (evaluation)
Produces model artifacts (hashed), report cards, the held-out evaluation report
State = fold of the run manifest + consumed streams — reports are reproducible artifacts, not opinions
Time travel any report regenerable from (manifest, stream offsets, evaluator version)
stateDiagram-v2
    [*] --> SealedSplit: test split defined in its own manifest — invisible to admission gates
    SealedSplit --> GateRun: formal spec gate executed — exactly once per frozen candidate
    GateRun --> ReportFrozen: held-out report generated, hash recorded in lineage
    ReportFrozen --> [*]
    note right of SealedSplit: admission gate 4 and shadow report cards use the validation split only — the exam stays sealed until the candidate is frozen
Diagram source
stateDiagram-v2
    [*] --> SealedSplit: test split defined in its own manifest — invisible to admission gates
    SealedSplit --> GateRun: formal spec gate executed — exactly once per frozen candidate
    GateRun --> ReportFrozen: held-out report generated, hash recorded in lineage
    ReportFrozen --> [*]
    note right of SealedSplit: admission gate 4 and shadow report cards use the validation split only — the exam stays sealed until the candidate is frozen

11. Cross-layer — run lifecycle #

Contract
Consumes run manifest (identity), root logs; for an allocation-citing run, the epoch log's run_included and run_verdict rows
Produces verified-run record, published evaluation report
State = fold of the manifest plus every stream the run produced
Time travel the run is its manifest + root logs — same inputs, same run, byte for byte, forever
stateDiagram-v2
    [*] --> Defined: run manifest frozen — seeds, model hashes, pinned versions, split definition — and, for a run inside an allocation epoch, its sleeve ceilings equal to (caps_rule v1) or in (0, V_i] (the caps_rule v2 shared-line chain) the epoch's allocation record's V_i, with the record's reference (§12, allocation-market §8)
    Defined --> Running: streams opened with lineage (for an allocation-citing run, its inclusion DERIVED from the sealed epoch log — the inclusion constructor from_epoch, the recorded run_included row with its offset and the fold's allocation record, one typed input — admitted at the start, under a document whose risk chain names the desk-period chain (R5–R6b) served with a control root (ControlRootRequired, checked in the scheduler's included constructor, which every serve path calls — the cross-run stop's path in, risk-engine.md §5), every other allocation-citing run keeping its lineage and ids, under the caps_rule v2 (shared-line) chain the row's sublimits equal to the manifest's sleeves and ceilings and the line rule re-derived from the log's run_included and run_verdict rows, and the row's offset in every stream's lineage, allocation-market §8), replay cursor starts
    Running --> Completed: final match replayed — last settlement_complete appended
    Running --> Failed: any zero-tolerance tap reaches Violated (§9), or any typed halt — a fold refusing to guess on corrupted history (event-catalog §4), or a construction invariant the fold checks rather than assumes (architecture.md external drive rule 3) · on a board, a promotion across columns or a registration of a grid no column emits — model-plugin §9
    Completed --> Verified: determinism harness re-run — every output stream hash byte-identical
    Completed --> Failed: hash mismatch at some offset — bisectable
    Verified --> Published: evaluation report frozen, hash recorded in lineage
    Failed --> [*]
    Published --> [*]
    note right of Defined: run_id = hash of the frozen manifest — a run is identified by what defines it, not by when it ran
Diagram source
stateDiagram-v2
    [*] --> Defined: run manifest frozen — seeds, model hashes, pinned versions, split definition — and, for a run inside an allocation epoch, its sleeve ceilings equal to (caps_rule v1) or in (0, V_i] (the caps_rule v2 shared-line chain) the epoch's allocation record's V_i, with the record's reference (§12, allocation-market §8)
    Defined --> Running: streams opened with lineage (for an allocation-citing run, its inclusion DERIVED from the sealed epoch log — the inclusion constructor from_epoch, the recorded run_included row with its offset and the fold's allocation record, one typed input — admitted at the start, under a document whose risk chain names the desk-period chain (R5–R6b) served with a control root (ControlRootRequired, checked in the scheduler's included constructor, which every serve path calls — the cross-run stop's path in, risk-engine.md §5), every other allocation-citing run keeping its lineage and ids, under the caps_rule v2 (shared-line) chain the row's sublimits equal to the manifest's sleeves and ceilings and the line rule re-derived from the log's run_included and run_verdict rows, and the row's offset in every stream's lineage, allocation-market §8), replay cursor starts
    Running --> Completed: final match replayed — last settlement_complete appended
    Running --> Failed: any zero-tolerance tap reaches Violated (§9), or any typed halt — a fold refusing to guess on corrupted history (event-catalog §4), or a construction invariant the fold checks rather than assumes (architecture.md external drive rule 3) · on a board, a promotion across columns or a registration of a grid no column emits — model-plugin §9
    Completed --> Verified: determinism harness re-run — every output stream hash byte-identical
    Completed --> Failed: hash mismatch at some offset — bisectable
    Verified --> Published: evaluation report frozen, hash recorded in lineage
    Failed --> [*]
    Published --> [*]
    note right of Defined: run_id = hash of the frozen manifest — a run is identified by what defines it, not by when it ran

A paused run. A run whose feed is stalled (§13's feed machine; risk-engine.md §3 R7) stays Running: the pause is a fold of the match log's feed records, not a state of this machine. A capture that ends while a source is stalled with NO match end observed leaves its run Running with no further tick — no seal, no verdict row — and the epoch's window ends it run_unjudged at the grace end (futarchy-system.md §9 C25); a match end observed in an envelope completes and seals the match as it does today, a stall still open at that moment ending with the run (§13).

The rebuilt run. A run that reached Failed from Completed — its local report disagreeing with the replay of its root logs — is REBUILT under the v3 chain's window (futarchy-system.md §4 B7 v5, §9 C99): the same manifest re-folded over the same root logs is a new lifecycle instance of the same run id, Defined through Verified, and its verified-run record is the epoch log's second run_verdict row for the run; the Failed --> [*] edge ends the first instance, not the run's evidence. A run that reached Failed from Running never rebuilds: the same root logs re-fold to the same halt.

A disk fault. A derived-stream create, write, fsync or seal that the filesystem fails — inside the run, at its start, or in the determinism harness's re-run — is no transition of this machine: the same root logs re-fold to no halt, so it is neither Running --> Failed nor Completed --> Failed. The staging directory is discarded, no verdict row is recorded, and the attempt's Running instance leaves no record, so the run is Defined again before a serve, and stays Completed before a judge — served or judged again once the disk reads and writes (futarchy-system.md §7 "The cycle runner", exit 2).


12. Allocation Layer — epoch state (per epoch document) #

Contract
Consumes the epoch log (root — epoch_opened, run_included, run_verdict, block_final, stake_recorded, stake_withdrawn, stake_moved, desk_topup; one writer, the epoch gateway: an impure edge tailing the partner chain's FINAL blocks, loading the operator's epoch document and recording the operator's inclusions and the harness's verdicts — its operator half d2-cycle, which also records the post-close chain rows from the recorded tail until the chain reader exists, futarchy-system.md §7), and — after a run's inclusion — that run's manifest and its SEALED lifecycle, settlement and market-event streams at the run-sealed offset, its manifest's sleeve set, and the harness's inventory of sealed runs citing the allocation (B7's missing-row check) (allocation-market.md §5, §9)
Produces the allocation stream (derived — document_checked, book_closed, raw_share, capped_share, allocation, return_observed, window_closed, balance_series (1_000_053, minted — the 19 October note's snapshots derived from the runs, futarchy-system.md §4 B6b; built as a function, placed in the v2 chain (declared_chain_v2, step 3j-1) and folded by the composition's v2 branch (step 3j-2)), utilization, winsorized, estimate, feasible_set, settlement, pool_liability, stake_payout, participation_yield (1_000_054, minted — futarchy-system.md §4 B14b; built as a function, placed in the v2 chain (declared_chain_v2, step 3j-1) and folded by the composition's v2 branch (step 3j-2)), epoch_settled, epoch_aborted; under a v2 document capped_share/v2, allocation/v2, utilization/v2, winsorized/v2, feasible_set/v2 and settlement/v2 in their v1 counterparts' places with balance_series after window_closed and the participation_yield rows before epoch_settled — allocation-market.md §5's v2 layout, the stream the composition's v2 branch writes, task 143 step 3j-2; allocation-market.md §5 — one record per block of the chain, futarchy-system.md §4); document_checked … allocation and epoch_aborted (1_000_036..=1_000_041), return_observed (1_000_042), the log's stake_moved (1_000_043), window_closed (1_000_044), utilization (1_000_045), winsorized (1_000_046), estimate (1_000_047), feasible_set (1_000_048), settlement (1_000_049 — minted with the certificate's verifier, its producer the solver); pool_liability 1_000_050 (B13), stake_payout 1_000_051 (B14 — the region extended to ..=1_000_052), epoch_settled 1_000_052 (B15 — the v1 region's last id; every record of the v1 chain minted); capped_share/v2 1_000_055 and allocation/v2 1_000_056 minted by B3 v2 and B4 v2 (task 143 step 3b), balance_series/v1 1_000_053 by B6b (step 3c), utilization/v2 1_000_057 by B8 v3 (step 3d), winsorized/v2 1_000_058 by B9 v2 (step 3e), feasible_set/v2 1_000_059 by B11 v2 (step 3g), settlement/v2 1_000_060 by B12 v2 (step 3h; built as a function, placed in the v2 chain (declared_chain_v2, step 3j-1) and folded by the composition's v2 branch (step 3j-2)), participation_yield/v1 1_000_054 by B14b (step 3i — the region complete, C61) (event-catalog §6.17, §7 rule 4); under a v3 document (declared_chain_v3()) balance_series/v2 (1_000_069) and settlement/v3 (1_000_070) in their v2 counterparts' roles, at allocation-market.md §5's v3 offsets (+1, +4), and no winsorized and no estimate — allocation-market.md §5's v3 layout, both ids minted by their first producers; under a document whose risk chain names R5–R6b (the v3 document's) the desk records margin_called (1_000_062), margin_cleared (1_000_063), liquidated (1_000_064) and defaulted (1_000_065), the desk-period chain's, stand among B6's rows in the stream's one total order, every later offset shifted by the desk records before it (risk-engine.md §11; §13)
State = fold of the epoch log, then the included runs' sealed streams in their run_included order — the log's order, the only one two observers both hold (allocation-market.md §9); the epoch document itself is stored in the epoch store (futarchy-system.md §11 W10) and immutable from epoch_opened on — every parameter the notes call precommitted is a field of its config snapshot, every rule a rule identity of its chain, both under its system hash, never a code constant (CLAUDE.md core laws 7(b) and 8; futarchy-system.md §3); the fold is the CHAIN of futarchy-system.md §4 — the document check and fifteen declared blocks (under v2 seventeen, B6b and B14b among them — eighteen elements with the document check, placed in the v2 chain declared_chain_v2, step 3j-1, and folded end to end by the composition's v2 branch, step 3j-2): twelve (under v2 thirteen, B6b's balance_series added) emit one allocation-stream record each, B6 one return_observed per Verified run, B5 (the ceiling route) acts on the run side at Defined, B14 emits one payout row per net position and, under v2 and v3, B14b one yield row per participant with a positive yield; under v3 B9 and B10 are absent — fifteen declared blocks, sixteen elements with the document check (declared_chain_v3()), their two records not written
Time travel the epoch at any offset of its two streams is recomputable by anyone holding the log, the document and the runs' root logs — the determinism harness re-runs the fold as it re-runs every run; the only time in the layer is the chain's block height and the runs' replay_ts

The epoch (one machine per epoch_id; allocation-market.md §6):

stateDiagram-v2
    [*] --> Declared: epoch_opened / document_checked
    Declared --> Declared: block_final
    Declared --> Open: stake_recorded
    Open --> Open: stake_recorded / stake_withdrawn / stake_moved / block_final
    Declared --> Aborted: epoch_aborted (every chain, before allocation)
    Open --> Aborted: epoch_aborted (every chain, before allocation)
    Open --> Closed: book_closed
    Closed --> Implemented: raw_share / capped_share / allocation
    Implemented --> Implemented: run_included / run_verdict / return_observed / block_final / desk_topup / margin_called / margin_cleared / liquidated / defaulted
    Implemented --> Observed: window_closed
    Implemented --> Aborted: epoch_aborted (v1–v3)
    Observed --> Settled: balance_series / utilization / [winsorized / estimate — v1/v2] / feasible_set / settlement / pool_liability / stake_payout / participation_yield / epoch_settled (v1–v3)
    Settled --> [*]
    Aborted --> [*]
    Observed --> Carrying: epoch_settled (v4, a carried line of it locked)
    Observed --> CapitalReleased: epoch_settled (v4, no carried line of it locked)
    Implemented --> Carrying: epoch_aborted (v4, a carried line of it locked)
    Implemented --> CapitalReleased: epoch_aborted (v4, no carried line of it locked)
    Carrying --> Carrying: run_verdict / carry_settled / block_final
    Carrying --> CapitalReleased: run_verdict{verified} / carry_settled (the last locked line of it)
    CapitalReleased --> [*]
Diagram source
stateDiagram-v2
    [*] --> Declared: epoch_opened / document_checked
    Declared --> Declared: block_final
    Declared --> Open: stake_recorded
    Open --> Open: stake_recorded / stake_withdrawn / stake_moved / block_final
    Declared --> Aborted: epoch_aborted (every chain, before allocation)
    Open --> Aborted: epoch_aborted (every chain, before allocation)
    Open --> Closed: book_closed
    Closed --> Implemented: raw_share / capped_share / allocation
    Implemented --> Implemented: run_included / run_verdict / return_observed / block_final / desk_topup / margin_called / margin_cleared / liquidated / defaulted
    Implemented --> Observed: window_closed
    Implemented --> Aborted: epoch_aborted (v1–v3)
    Observed --> Settled: balance_series / utilization / [winsorized / estimate — v1/v2] / feasible_set / settlement / pool_liability / stake_payout / participation_yield / epoch_settled (v1–v3)
    Settled --> [*]
    Aborted --> [*]
    Observed --> Carrying: epoch_settled (v4, a carried line of it locked)
    Observed --> CapitalReleased: epoch_settled (v4, no carried line of it locked)
    Implemented --> Carrying: epoch_aborted (v4, a carried line of it locked)
    Implemented --> CapitalReleased: epoch_aborted (v4, no carried line of it locked)
    Carrying --> Carrying: run_verdict / carry_settled / block_final
    Carrying --> CapitalReleased: run_verdict{verified} / carry_settled (the last locked line of it)
    CapitalReleased --> [*]

On a caps_rule v4 chain (futarchy-system.md §4 "The v4 chain") the epoch log runs on past its terminal row: a run included under the epoch and not Verified below its deadline is CARRIED, its line locked, and its verdict rows, its carry_settled/v1 row (event-catalog §6.16) and the block_final rows follow until the last carried line is released — the CAPITAL RELEASE, allocation-market.md §8b's released on that chain. The Settled and Aborted states are the v1, v2 and v3 chains' terminals, and a v4 epoch's abort before its allocation (no run, so no carried line); on the v4 chain the terminal row itself enters Carrying when a carried line is locked and CapitalReleased when none is; the successor epoch's machine may meanwhile run from epoch_opened (it opens once this epoch's allocation is recorded).

Each arrow names the records that cause it, and the conditions are in allocation-market.md §6: the book closes at the pinned block height with every pool seeded and positive and aborts there if any pool is unseeded or empty; the window is a fixed span of heights after the close and closes at h*, the first final height at or above its deadline by whose height every included run has its recorded verdict (on a v1, v2 or v3 chain; on the v4 chain under carry_scoring_rule v1 every run due there — a carried run of the predecessor — has its Verified verdict, the epoch's own unverified runs carried, futarchy-system.md §4 B7 v6) (futarchy-system.md §9 C37; under B7 v5, the v3 chain's, its recorded Verified verdict — a Failed verdict keeps the window pending until the rebuilt record's Verified row, C99), and no later than the grace end (an unjudged run's abort there is decided at the first final above it), aborting instead when too few distinct matches were observed (B7 v2 and v3; under B7 v4 and v5 (B7 v5 the v3 chain's), such a window closes and settles on the stake-split fallback, taxed — futarchy-system.md §9 C93) or an included run is still unjudged at the grace end (under B7 v5 also one whose latest verdict there is Failed, run_unrecovered); the settling step appends the balance series (built as a function, placed in the v2 chain (declared_chain_v2, step 3j-1) and folded by the composition's v2 branch (step 3j-2)), the utilization, under v1/v2 the winsorized matrix's hash and the estimate, the feasible set, the settlement, the pool liabilities (B13 — on the raw share, exact, refusing a book, share or settlement that disagrees with the document and a document pinning an unbuilt rule version) and one payout per net position (B14 — B1's positions in first-row order, each ⌊r_ij P_i / R_i⌋ floored once from its pool's exact liability, rounding_rule v1; the stored liabilities' signs, conservation and net identity Σ_i P_i = (1 − τ_pool) R re-held, a document pinning v2 refused) and the settlement record (B15 — the tax and the residue floored once, the one rounding charge at most one unit per payout row, the four amounts re-adding to R; the stored liabilities' identities and the payouts re-held first) before the epoch closes. A second epoch_opened, an inclusion whose manifest disagrees with the allocation, or a settlement whose certificate fails, or whose bytes the re-fold does not reproduce, or a payout list whose bytes B14's re-fold does not reproduce, is corrupted history — a typed refusal (CertificateError, the reproduction taps settlement_reproduces and payouts_reproduce), never a guess.

Nothing in this layer moves money: a stake is a recorded fact citing the chain's entry, a payout is a published row the chain's vault pays from (allocation-market.md §1, §4). The run lifecycle (§11) meets this layer at one point — the frozen manifest's sleeve ceilings and its allocation_ref — so a run is still "its manifest + root logs", and the allocation that set its ceilings is itself a recorded fact of the run's definition.


13. Risk Layer — the desk, the sleeve and the feed #

Contract
Consumes the allocation stream's desk records (margin_called, margin_cleared, liquidated, defaulted) and the epoch log's desk_topup rows (the desk); the run's control root — sleeve_suspended, sleeve_resumed (the sleeve); the run's match log's feed records — feed_watch_armed, feed_stalled, feed_recovered (the feed)
Produces nothing — the desk fold is read by R6 (the next inclusion), by the inclusion edge, by the desk watch (risk-engine.md §8 E7, whose reason-2 frames each other unsealed run's gateway appends to that run's control root) and by the gateway's start-time derivation; the sleeve fold by the quote fold and the gateway's sleeve mirror; the feed fold by R7, whose pulls are quote_batch records on the sleeves' quote streams
State = fold of the desk: the allocation stream in its order, per bookmaker pool per epoch; the sleeve: the run's control root at its merge class, by the runtime's control consumer's sleeve fold, partitioned out of the control stream before the model registry (risk-engine.md §5); the feed: the match log's feed records in their ticks, by the scheduler's feed fold, one machine per source
Time travel the desk at any offset of the allocation stream is recomputable from the epoch log, the document and the included runs' root logs (§12); the sleeve and the feed at any offset of the run are recomputable from the run's manifest and root logs (§11)

The desk (one machine per bookmaker pool per epoch; risk-engine.md §5):

stateDiagram-v2
    [*] --> Quoting: allocation (the pool's V_i above zero)
    Quoting --> MarginCalled: margin_called
    MarginCalled --> Quoting: margin_cleared
    Quoting --> Liquidated: liquidated
    MarginCalled --> Liquidated: liquidated
    Liquidated --> Defaulted: defaulted
    Quoting --> [*]: epoch_settled / epoch_aborted
    MarginCalled --> [*]: epoch_settled / epoch_aborted
    Liquidated --> [*]: epoch_settled / epoch_aborted
    Defaulted --> [*]: epoch_settled / epoch_aborted
Diagram source
stateDiagram-v2
    [*] --> Quoting: allocation (the pool's V_i above zero)
    Quoting --> MarginCalled: margin_called
    MarginCalled --> Quoting: margin_cleared
    Quoting --> Liquidated: liquidated
    MarginCalled --> Liquidated: liquidated
    Liquidated --> Defaulted: defaulted
    Quoting --> [*]: epoch_settled / epoch_aborted
    MarginCalled --> [*]: epoch_settled / epoch_aborted
    Liquidated --> [*]: epoch_settled / epoch_aborted
    Defaulted --> [*]: epoch_settled / epoch_aborted

The triggers of each record are risk-engine.md §4 (R5, R6, R6b) and the effects of each state are its §5; since t_l ≤ 1, defaulted never precedes liquidated; when both trip on one row it follows it, and under the shared line a later return_observed row of another run of the desk may trip it alone, so the desk passes through Liquidated.

The sleeve (one machine per sleeve per run; risk-engine.md §5):

stateDiagram-v2
    [*] --> Quoting: streams opened (the run's Defined to Running, §11)
    Quoting --> SuspendedOperator: sleeve_suspended (operator)
    SuspendedOperator --> Quoting: sleeve_resumed (the operator's record only)
    Quoting --> SuspendedLiquidated: sleeve_suspended (liquidated)
    SuspendedOperator --> SuspendedLiquidated: sleeve_suspended (liquidated)
    Quoting --> [*]: last settlement_complete (the run's seal, §11)
    SuspendedOperator --> [*]: last settlement_complete (the run's seal, §11)
    SuspendedLiquidated --> [*]: last settlement_complete (the run's seal, §11)
Diagram source
stateDiagram-v2
    [*] --> Quoting: streams opened (the run's Defined to Running, §11)
    Quoting --> SuspendedOperator: sleeve_suspended (operator)
    SuspendedOperator --> Quoting: sleeve_resumed (the operator's record only)
    Quoting --> SuspendedLiquidated: sleeve_suspended (liquidated)
    SuspendedOperator --> SuspendedLiquidated: sleeve_suspended (liquidated)
    Quoting --> [*]: last settlement_complete (the run's seal, §11)
    SuspendedOperator --> [*]: last settlement_complete (the run's seal, §11)
    SuspendedLiquidated --> [*]: last settlement_complete (the run's seal, §11)

The records' producer is the operator gateway, the control root's one writer: reason 0 operator from the operator's own frames (risk-engine.md §8 E3), reason 2 liquidated from the desk watch's frames and from the gateway's start-time derivation (risk-engine.md §8 E7), reason 1 — once reserved for a feed stall — retired and refused. SuspendedOperator is risk-engine.md §5's and the gateway's Suspended(operator), SuspendedLiquidated their Suspended(liquidated). SuspendedLiquidated admits no resume to the run's seal (risk-engine.md §12 C105): a recorded sleeve_resumed of it halts the fold (ResumeAfterLiquidated), as do a duplicate suspension (SleeveAlreadySuspended), a resume of a quoting sleeve (SleeveNotSuspended), an undeclared sleeve (SleeveUnknown) and a reason-2 record where reason 2 is not admitted (reason 2 is admitted only on a run whose inclusion's document names R6 v1; elsewhere SleeveReasonUndeclared, code 14 at the wire; risk-engine.md §12 C105). A pull parked on a halted market fires once at that market's resume, a later sleeve_resumed notwithstanding. The effects of each state are risk-engine.md §5; under a document naming R9, the run that liquidated a desk was stopped by R9, a refusal in the exchange and not a state of this machine (§3).

The feed (one machine per source of the run's lineage tag, under a manifest carrying a feed_rule; risk-engine.md §3 R7, §5):

stateDiagram-v2
    [*] --> Live: feed_watch_armed (the match log's first record, the manifest's rule)
    Live --> Stalled: feed_stalled (the source's slot empty) — the pull at the stall record's tick, and under R7 v2 retail_cancelled feed_stall per resting retail order
    Stalled --> Live: feed_recovered (the open stall's last_seq, the continuity rule re-derived)
    Live --> [*]: last settlement_complete (the run's seal, §11)
    Stalled --> [*]: last settlement_complete (the run's seal, §11) — a match end observed in an envelope during a state-poll stall
Diagram source
stateDiagram-v2
    [*] --> Live: feed_watch_armed (the match log's first record, the manifest's rule)
    Live --> Stalled: feed_stalled (the source's slot empty) — the pull at the stall record's tick, and under R7 v2 retail_cancelled feed_stall per resting retail order
    Stalled --> Live: feed_recovered (the open stall's last_seq, the continuity rule re-derived)
    Live --> [*]: last settlement_complete (the run's seal, §11)
    Stalled --> [*]: last settlement_complete (the run's seal, §11) — a match end observed in an envelope during a state-poll stall

A sleeve of the run quotes only while every source is Live. A first record that is not the armed record and an armed record at a later offset halt the fold (FeedWatchUnarmed), an armed or stall record whose rule is not the manifest's halts it (FeedRuleDisagrees), and a recovery with no open stall of its source, a second stall of a stalled source, a stall or recovery of a source the tag does not declare and a recovery that does not re-derive halt it (FeedRecoveryUnverified). The machine is neither the market lifecycle (§3: the markets stay Open) nor the sleeve's (no control-root record); a run whose capture ends while a source is Stalled with no match end observed runs no further tick (§11): a clean socket close during an open stall synthesizes no match_complete and refuses to seal (event-catalog §6.13 rule 12 (d)), while a match end observed in an envelope completes and seals the match, the open stall ending with the run. From the match end the capture writes no feed record, so the machine holds its state to the seal.


Reading this doc #

Each diagram is simultaneously three things, because of the state law:

  1. A design — the allowed transitions for implementers.
  2. A query — "what state was entity X in at offset K?" has one computable answer: fold to K.
  3. A test — replay the named events and assert the trajectory; any divergence is a determinism violation with an exact offset.

If a change adds a transition that cannot name its stream event, or a state that is not a fold of declared inputs, the change is wrong by construction — that is the property this document exists to protect.