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:
- 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. - 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.
- 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 authoritativeScope: 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-quoteThe 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 voidThe 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 --> BoundThe 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 zeroThe 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 fillThe 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_completeCollateral (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 streamOrder 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 shapeThe 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 frozen11. 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 ranA 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_abortedThe 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 stallA 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:
- A design — the allowed transitions for implementers.
- A query — "what state was entity X in at offset K?" has one computable answer: fold to K.
- 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.