Matching Engine — Specification #

The third "genuinely hard node." Companion to architecture.md (Exchange Layer), state-layers.md §5–§6 (quote and order state machines — owned there, not duplicated here), event-catalog.md (§6.5–§6.7 — every emitted event), and settlement-contract.md.

Authority boundary (thesis-aligned): this engine decides best compatible orders by price-time priority — nothing else. Funded-ness lives in the collateral view (the alignment note's phase 1 extracts it as the Risk Engine); truth lives in the oracle; payouts live in the clearing house.

Contents: 1 Numbers · 2 Time and sequencing · 3 Book structure · 4 Quote-batch transaction · 5 Taker matching · 6 Collateral accounting · 7 Determinism and invariants · 8 Edge cases · 9 Tests


1. Numeric representation #

Quantity Unit Type Range
Price mill ($0.001 — the 0.1-cent tick) u16 1..999 for orders/quotes (0 and 1000 are settlement-only prices)
Money mill i64 balances, costs, collateral
Size claim unit u32 1..MAX_ORDER_SIZE (run-manifest parameter)
  • Settlement prices land on-grid by construction: a claim family's payout rule is a PayoutVector — one price per outcome class, every entry on the 0..=1000 mill grid (stream-core units; the family's values live in its title module). CS2 kills-over-5: k/5 = 0, 200, 400, 600, 800, 1000 mills; a match-winner family: 0, 1000.
  • All arithmetic is i64; worst case (price × size) ≤ 1000 × 2³² < 2⁴² — no overflow, no widening needed.
  • UI displays cents by default and shows the third decimal only when a price uses it — grid precision is a bookmaker feature, not a retail burden.

2. Time and sequencing — the C2 rule, pinned #

Gateway stamping rule (normative): when an external event (retail order, quoting frame, control action) arrives, the input gateway stamps it with:

  1. replay_ts — the replay timestamp of the latest published game-state event at the moment of arrival (during a halt: the last published state's ts; the order will meet a halted market downstream and be rejected there).
  2. seq — a per-gateway monotonic counter, incremented per accepted append.

Wall-clock time never enters any record. Once stamped and appended, the pair is history: replays use the recorded values, which is what makes "identical user actions → identical fills" literal.

There is no sub-tick clock. replay_ts values only ever come from game-state events; ordering between events sharing a replay_ts is carried entirely by the class and tiebreak below. (Flow 2's order is therefore stamped 3134.75 — the ts of state_version 482 — not some interpolated later instant.)

Genesis edge case: before the first game-state event exists there is no ts to borrow; the one sanctioned stamp that names no game-state event is replay_ts = 0 for records accepted pre-state (in practice account_created, and an operator's subaccount_opened or instruction_relayed — no market exists yet for an order, and a relayed instruction meets its market downstream, §5). Pinned in gateway-protocol.md §4. When the first game-state event is itself at ts 0 (Riot's frame 0), a stamp of 0 also names that state (rule 1), so the pre-state class is indistinguishable from it; the join makes no exception for genesis — every stamp-0 record, pre-state or not, joins the first state-emitting tick whose ts strictly exceeds 0 (gateway-protocol.md §4).

Merge total order (normative): the deterministic merge covers all four of the engine's input streams and orders every event by

(replay_ts, class, stream_id, offset) where class: 0 = lifecycle, 1 = settlement, 2 = quote batch, 3 = external root-log ingress (retail orders — a flat account's order/v1 and an operator's subaccount_opened / instruction_relayed, event-catalog §6.6 —, and the event-catalog §6.6 quoting-ingress records — which ride the same class and recorded keys but drive the market-making fold at their slot instead of entering this engine, gateway-protocol §4; the operator control ROOT log's records ride the same class at their recorded stamps — they drive the model-plugin-layer.md §9 registry fold at their slot, never this engine, and their tick-join rule is strict, pinned in model-plugin-layer.md §11).

Consequences of the class order, at equal replay_ts:

  • Lifecycle first: a market_closed at ts T precedes any quote or order at T — the close-vs-order race (§8) is decided by this line.
  • Settlements second: payouts and their barrier stage into the collateral-view fold before any quote or order at the same instant is admitted against it. (The round-boundary barrier already separates settlement from the next round's flow in time; this class assignment makes the ordering total even at shared timestamps.)
  • Quotes before orders: quotes derived from state V are sequenced before orders stamped while V was current — an order can never trade against a book that hasn't yet reflected the state the order saw.
  • Ties within a class break by (stream_id, offset) — both already total and deterministic.

This quadruple is the entire sequencing rule; there is no rule two.

3. Book structure #

One book per market, holding Higher units only — the complement transform (buy Lower @ p ⇒ sell Higher @ 1000−p) is applied before the merge and is pure rewriting, never stored.

  • Price-indexed arrays, 1001 levels per side; each level a FIFO queue of resting fragments in the order they came to rest (merge order) — a quote fragment (sleeve_id, remaining_size, batch_offset) or a retail fragment (account_id, remaining_size, order_id), its owner a subaccount (event-catalog §6.6) and order_id its instruction_relayed offset.
  • Bookmaker quotes rest, and so does the remainder of a subaccount's GTC place instruction (§5 step 9); a flat account's order/v1 and an IOC instruction never rest (retail IOC per spec §7). Best bid / best ask are derived by scanning the fixed price array (O(1001), constant-size, cache-linear — measured µs-scale); an incremental occupancy index is a recorded optimization, gated on measurement (no observable contract depends on the mechanism, and cursor state inside a fold is derivable state).
  • The book is a fold of the market-event stream — droppable and recomputable at any offset, per the state law.

4. Quote-batch transaction (atomic) #

A quote_batch/v1 at version V from sleeve S processes as one transaction — all effects or none, in this order:

flowchart TB
    A["1 · validate batch<br/>market open · V not below the book version ·<br/>levels on-grid · own bid below own ask (self-cross = reject whole batch)"]
    B["2 · expire stale<br/>remove ALL resting quotes of every sleeve with version below V<br/>emit quote_expired once per affected sleeve"]
    C["3 · collateral check<br/>worst-case liability of S's new levels vs S's headroom<br/>(collateral view fold) — shrink or reject"]
    D["4 · rest levels<br/>emit quote_accepted"]
    E["5 · uncross<br/>if best bid ≥ best ask across sleeves —<br/>match by price-time at the MAKER's price, emit fills<br/>(maker = earlier-sequenced side)"]
    A -->|"invalid / stale"| R["emit quote_rejected — no other effect"]
    A --> B --> C --> D --> E
Diagram source
flowchart TB
    A["1 · validate batch<br/>market open · V not below the book version ·<br/>levels on-grid · own bid below own ask (self-cross = reject whole batch)"]
    B["2 · expire stale<br/>remove ALL resting quotes of every sleeve with version below V<br/>emit quote_expired once per affected sleeve"]
    C["3 · collateral check<br/>worst-case liability of S's new levels vs S's headroom<br/>(collateral view fold) — shrink or reject"]
    D["4 · rest levels<br/>emit quote_accepted"]
    E["5 · uncross<br/>if best bid ≥ best ask across sleeves —<br/>match by price-time at the MAKER's price, emit fills<br/>(maker = earlier-sequenced side)"]
    A -->|"invalid / stale"| R["emit quote_rejected — no other effect"]
    A --> B --> C --> D --> E

Notes: step 2 runs even when step 3 later shrinks the batch — version advance kills stale quotes unconditionally (the "no path from an old version to a fill" property). The same property is why step 1 refuses a batch whose V is below the book version (quote_rejected{stale_version}): two same-ts batches order by (stream_id, offset) in the §2 merge, so a lower-version batch can legally sequence after a higher one — resting it would create fragments below the book version that no later expiry reaches. Step 2's quote_expired is one event per affected sleeve (a sleeve's stale quote set dies as one fact; fragment-level detail is recomputable from the book fold). The batch's own cancel/replacement leg (cancel_all_prior or same-version replace, emitting quote_cancelled) is part of step 2 and likewise stands even when step 3 rejects on capacity — the batch is a replacement, and its cancel leg replaced. Step 5 makes bookmaker-vs-bookmaker matching ordinary CLOB behavior, exactly as the product spec requires; within one sleeve it is impossible because step 1 rejected the batch.

Retail fragments (§3) are not quotes: step 2's version expiry, a batch's cancel_all_prior and its same-version replace touch quote fragments only, so a resting retail order leaves the book by a fill, a signed cancel, the close / void drain or R7 v2's feed-stall cancel alone (§8); step 1's self-cross stays per sleeve. Step 5's uncross may meet a resting retail order: the maker is the earlier-sequenced side, so a batch level crossing a resting retail fragment fills it at the fragment's price, the batch's sleeve the taker and the fragment's subaccount the maker (fill/v1's maker account, event-catalog §6.7).

5. Taker matching (retail IOC and signed instructions) #

Per the §2 sequencing, an admitted order walks the opposite side:

  1. Best price level first; FIFO within the level.
  2. Fill up to min(remaining_order, fragment_size); emit fill/v1 per maker fragment (partial fills are per the spec).
  3. Stop when the order is filled, the book side is exhausted, or the next level no longer crosses (for a signed instruction: lies beyond its limit price, IOC and GTC alike). IOC: any remainder cancels (order_unfilled/v1 if nothing filled). GTC: the remainder rests (§3), retail_rested/v1 — except a place stamped at or below a stall record's ts under R7 v2, whose remainder ends as an IOC remainder does (risk-engine.md §3 R7 v2 (e)).

Admission before matching (per state-layers §6): market open check → stale check (quoted_version < current book version ⇒ order_rejected{stale_version}) → funds check against the collateral view (never a ledger query).

Operator-routed retail — signed instructions. A bettor trades through an operator without the operator controlling the bets or mixing the bettor's money with other accounts. An operator session (gateway-protocol §2 row 14) opens customer subaccounts (subaccount_opened/v1) and relays their customers' signed instructions (instruction_relayed/v1; the instruction's bytes event-catalog §6.6.1); a subaccount's balance, reservations and positions are its own (§6). The fold decides every relayed instruction from its recorded bytes, in this order; the first failing step is the instruction's recorded fate (event-catalog §6.7) and nothing after it runs:

  1. Decode — strict and total: one kind's closed map, every field in its domain, the run id this run's, re-encoding to the identical bytes; else instruction_rejected{bad_instruction}.
  2. Operator — the named subaccount is open (its subaccount_opened admitted, its key strict) and its record names the relaying operator; else instruction_rejected{foreign_account}, the subaccount untouched.
  3. Signature — Ed25519 verify_strict of the recorded signature over the recorded instruction bytes under the subaccount's customer_key; else instruction_rejected{bad_signature} — an unsigned, wrongly keyed or altered instruction (a changed size, side or price is a changed byte).
  4. Nonce — above the subaccount's last consumed nonce (any value while none is consumed); else instruction_rejected{stale_nonce} (a replay), the last consumed nonce unchanged. A verified instruction with a fresh nonce CONSUMES it here — the last consumed nonce becomes it — whatever the later steps decide: a market, stale, self-cross, funds, position, resting-cap or cancel refusal still consumes it, so verified bytes never execute twice. A nonce of u64::MAX leaves no fresh nonce above it, so once it is consumed the subaccount accepts no further instruction — the customer's own signed act.
  5. Market (place) — open; halted ⇒ order_rejected{market_halted}; closed, voided, pre-open or never created ⇒ order_rejected{market_closed} (the gateway does not read the instruction, so the fold is the point that meets a market it never saw — the §8 one-fact rule). A cancel skips this step: it is exempt from the halt, and a closed market's orders are already drained (§8).
  6. Stale (place) — quoted_version below the book version ⇒ order_rejected{stale_version}; the version is signed, so the stale check is the customer's.
  7. Self-cross (place) — a resting retail fragment of any subaccount holding the SAME customer key, under any operator, on the opposite book side at a price the instruction's limit reaches ⇒ the whole instruction is order_rejected{self_cross} and nothing fills (§4 step 1's rule for one sleeve, extended to one customer key).
  8. Funds or position (place) — §6's subaccount rules: a buy below its cost ⇒ order_rejected{insufficient_funds}; a sell, or a buy while a sell of the other side rests, that would break the held-units guard ⇒ order_rejected{insufficient_position}. The funds check runs first, so a buy failing both is insufficient_funds. Then the resting cap: a GTC place whose walk (item 9) would leave a remainder to rest (never the case for a place stamped at or below a stall record's ts under R7 v2) while its subaccount already holds the run's max_resting_orders resting orders (model-plugin-layer §12 MF-17) ⇒ order_rejected{resting_cap}, the whole instruction, nothing filled. The cap bounds one subaccount; the number of subaccounts is unbounded, so the book's resting retail state grows at most one order per recorded instruction_relayed, bounded by the order log's length as every fold's state is. An admission term that overflows the signed-mills range (the cost q × p_named + f, the available balance) is an unfundable order ⇒ order_rejected{insufficient_funds}; an overflow in fill, ledger, exposure or reservation accounting is corrupted history, a halt. The exchange fold holds the run's TOTAL OPENING BALANCE itself (gateway-protocol §5 codes 6 and 21): the sum of the manifest's sleeve ceilings and every account_created and subaccount_opened balance folded so far stays within the signed-mills maximum, and a record lifting it past is corrupted history, a typed halt (EngineError::OpeningTotalExceeded), so a log no gateway admitted halts at that record; the gateway's refusal is the mirror of this rule, never its only holder.
  9. Match (place) — the walk of items 1–3 above, bounded by the limit price; a GTC remainder rests (retail_rested/v1, its reservation taken, §6), an IOC remainder cancels (order_unfilled/v1 when nothing filled) — except a place stamped at or below a stall record's ts under R7 v2, whose remainder ends as an IOC remainder does whatever its time in force (risk-engine.md §3 R7 v2 (e)).

A cancel (kind 1), past step 4, names a resting order of its own subaccount — else instruction_rejected{no_resting_order} (an order filled out, cancelled, drained, another subaccount's, or never an order) — and emits retail_cancelled{customer}: exactly the unfilled remainder's reservation releases, the filled units stay. A flat account's order/v1 keeps the three-step admission above unchanged. A subaccount_opened whose key fails the strict parse is instruction_rejected{bad_key} at its merge position.

The instruction decode and the relayed record's fold are fuzzed (the parser-fuzzing posture of model-plugin-layer §15): crates/exchange/fuzz/fuzz_targets/decode_instruction.rs feeds arbitrary bytes to the step-1 decode — never a panic, and every accepted instruction re-encodes to exactly its bytes — and relay_instruction.rs folds an arbitrary relayed record into a real book — never a halt or a panic, and exactly one recorded refusal or one or more admission records. A standalone cargo-fuzz workspace that is no root-workspace member and runs by an operator's hand, never in CI (the posture crates/grid-edge/fuzz/ set), seeded by examples/seed_corpus.rs from the fixture emitter's real signed instructions.

6. Collateral accounting (netting) #

Per account/sleeve, per market, the collateral view tracks net Higher units n (positive = long), posted at-risk mills, and per market a single escrow pot. Every fill at price p for q units decomposes per party into close-then-open tranches at the net-position sign crossing (the derivation below):

Tranche Cash
Buyer opening long by q @ p pays q × p into the pot (the cost — a funded unit needs nothing more; max payout is prepaid by the pair)
Seller opening short by q @ p pays q × (1000 − p) into the pot (worst case: settlement at 1000)
Seller closing an existing long receives q × p (the long side's exposure at the TRADE price)
Buyer closing an existing short receives q × (1000 − p) (the short side's exposure at the trade price)

The pot therefore moves by exactly 1000 × ΔOI mills (ΔOI = open-interest delta = q − closes), giving the two invariants their exact form: pot = 1000 mills × open interest at every offset (§7 — settlement at any grid price is exactly funded), and Σ balances + Σ pot is constant across any transaction (§9 conservation). Open interest here is Σ max(net, 0) over the market's parties — the long side's units, which is also the short side's Σ max(−net, 0): every fill moves one party +q and the other −q, so Σ net = 0 per market at every offset, and one pot of 1000 mills per open unit therefore funds both legs. A third identity follows from the same table and is what a void relies on: Σ posted over a market's parties = its pot at every offset — each fill's two cash legs sum to −1000 × ΔOI and every party's posted accumulates −cash, so returning each party's posted drains the pot exactly (settlement-contract SC-V).

Why closes settle at the trade price, not the entry basis: releasing "q_closed × entry-locked" is only sound while every annihilating pair's two bases are complementary. When a closing long (basis 440) meets a closing short (basis 530, from a different pair) at a third price 450, basis-release frees 440 + 530 = 970 mills/unit against a pot that holds 1000 per pair — the residual open interest ends over- or under-funded by the basis mismatch. Paying each closer its exposure at the trade price (450 to the long, 550 to the short) releases exactly 1000/unit, keeps every party's lifetime PnL correct (long: −440 + 450 = +10; short: −530 + 550 = +20), and preserves the pot invariant. The "entry-locked" quantity survives as posted — the cumulative at-risk attribution (payments − receipts) used for display and headroom; it may go negative (realized profit expands capacity) under R1 v1, the version before gate G1 — under v2 a negative posted no longer expands the line, the headroom clips it at zero (risk-engine.md §3) — and it is not the pot.

  • Exact netting only (thesis §5 discipline): long and short in the same market net unit-for-unit (the tranche decomposition IS the netting); nothing nets across markets. A sleeve's quote headroom = ceiling − Σ_open max(posted, 0) − Σ reservation of its own resting quotes across every market's book (worst-case fill of every resting level: a bid reserves price × size, an ask reserves (1000 − price) × size — the form the real-world alignment phase refines into reserve→draw→release (the risk engine's admission chain names this step R1; its v2 and the exposure block R4 are risk-engine.md §3); positions don't net across markets, but the ceiling is one budget, so the reservation sum must span all of the sleeve's books) − max(−realized, 0), with realized = free + Σ_open posted (free = the sleeve's collateral-view balance, starting at 0, risk-engine.md §1; R1 v2 since gate G1; v1, the version before, was ceiling − Σ posted − Σ reservation). Sleeve ceilings are run-manifest parameters, their sum within the signed-mills maximum (a manifest whose ceilings sum past it is refused by its own validation, ManifestError::CeilingsOverflow, so no consumer starts the run) (like §1's MAX_ORDER_SIZE; the control stream carries no sleeve-registration vocabulary) — the manifest is a declared determinism root, so ceilings are recorded run inputs, not configuration drift. Their recorded SOURCE under the Allocation Layer is the epoch's allocation record: a bookmaker pool's integer capacity V_i becomes its sleeve's ceiling_mills in every manifest of the window under the caps_rule v1 chain; under the caps_rule v2 (shared-line) chain each manifest's ceiling_mills ≤ V_i is the run's sublimit of the line, the sublimits of a sleeve's live runs summing to at most V_i (futarchy-system.md §4 B5 v3); the manifest cites the record, and the runtime refuses a manifest whose ceilings disagree with it (caps_rule v1) or exceed the line or the live sum (caps_rule v2) — a ceiling change is a quoting restriction between runs, never a pot movement (allocation-market.md §7–§8; the ceiling route, the fold B0–B15 and the composition through epoch_settled — futarchy-system.md §4).
  • Settlement feedback: the engine consumes the §6.8 settlement stream in merge order (class 1, §2). payout records STAGE per market (staging is fold state, not hidden state); their epoch's settlement_complete applies the whole group ATOMICALLY — the group is the markets whose epoch matches AND, when the barrier names a family (a board's), whose series.family matches too: epochs are family-local, so a bare barrier (the single-family run's) retires the epoch's markets of every family and a family-naming one that family's alone — credits every owner, drains each pot to exactly zero (the §6 invariant guarantees Σ payouts = pot; a residue is corrupted history and halts the fold), clears the epoch's positions, and retires its markets. Atomic-at-the-barrier is what keeps the §7 pot-vs-open-interest invariant true at every record boundary — per-record application would break it between the first payout and the barrier. void_refund records stage the same way and their epoch's void_complete applies the group ATOMICALLY (the void barrier names its markets outright, so it needs no family match — matching by id is already family-exact) — Σ refunds must drain the pot to exactly zero (PotNotDrained otherwise; the Σ posted = pot identity above), positions clear, markets retire; a market arriving at the barrier with no rows must be flat (settlement-contract SC-V rule 3). This is also what makes next-epoch balance availability a function of stream order (flows.md Flow 3 barrier note). The engine still only checks admission against its fold — the authoritative ledger folds the same events independently (applying each settlement group atomically at ITS trigger record), and the collateral-view equality checker asserts the two agree at every shared offset outside an in-flight settlement or void group — i.e. at every barrier boundary (between a group's first payout or refund row and its barrier — settlement_complete or void_complete — the two folds are legitimately mid-transaction on different records).
  • The §4 step-3 shrink is greedy in the batch's own level order: each level truncates to the units whose worst-case cost fits the remaining headroom; an all-zero fit is the §8 quote_rejected{capacity} row. The book's stale-check version advances only when a batch rests (a pure cancel included) — a capacity-rejected batch still expires stale quotes (§4 step 2 is unconditional) but leaves the version untouched.

Subaccounts — fully funded, never selling what is not held. A subaccount (event-catalog §6.6) is a party of the tranche table like any account, in the Higher-net frame of §3: its one signed net n per market holds Higher units when n > 0 and Lower units when n < 0 (buy Lower @ p ≡ sell Higher @ 1000 − p). Its admission (§5 step 8) adds rules that bind subaccount instructions only — a flat account's order/v1 and every sleeve keep this section as written, so every recorded log replays unchanged:

  • A BUY of either side is cash-funded in full at the NAMED side's price p_named (the signed limit price, event-catalog §6.6.1): available = balance − Σ reservations ≥ q × p_named + f, f the term of the declared retail_fee_rule (below); else insufficient_funds. A resting buy reserves its remainder's full cost — under v1 "no fee" remaining × p_named; a later fee version with a per-fill or per-order term states its reservation term — even where its fill would first close units of the other side — so the opening-short tranche a flat subaccount's Lower buy reaches pays q × (1000 − (1000 − p_named)) = q × p_named, exactly the buy's prepaid cost, and nothing is borrowed from another account, subaccount or sleeve.
  • The HELD-UNITS GUARD. For a side Y and the other side X, let H_Y be the units of Y held — max(n, 0) for Higher, max(−n, 0) for Lower. Whenever the subaccount has a resting or incoming SELL of Y on a market, after admission Σ remaining of its sells of Y (resting and incoming) + Σ remaining of its buys of X (resting and incoming) ≤ H_Y must hold — so an incoming IOC sell of 11 against 10 held with nothing resting is refused; an instruction that would break it — a sell of Y, or a buy of X while a sell of Y rests — is insufficient_position. A buy of X closes held Y first (the netting is unit for unit), so every unit a sell of Y may still deliver stays held whatever buys of X fill before it, in any fill order. When no sell of Y rests or comes in, buys of X are bounded by cash alone (the bullet above): they close Y and open X. A sell meeting no crossable bid fills nothing (an IOC ends order_unfilled, a GTC rests). Two lines:
    • n = 10 (Higher held), a resting sell of Higher 10, an incoming buy of Lower 5: 10 + 5 > 10 ⇒ insufficient_position. Admitted, the buy could fill first (n = 5) and the resting sell then deliver 10 (n = −5) — five Higher sold that were never held, their short tranche unpaid.
    • n = 10, a resting sell of Higher 4, an incoming buy of Lower 6: 4 + 6 ≤ 10 ⇒ admitted; in either fill order n ends at 0 (the sell first: 6, then the buy closes 6; the buy first: 4, then the sell delivers 4) — nothing oversold.
  • A reservation moves no cash: it is the exchange fold's admission state (state-layers §6), absent from the ledger, so the collateral-view equality (above) compares balances, positions and pots, never reservations. A fill of a resting order consumes its reservation at the fragment's price (the maker's price is the trade price, §4 step 5); a signed cancel, the close / void drain or R7 v2's feed-stall cancel releases exactly the unfilled remainder's reservation (retail_cancelled/v1), the filled units staying.

The three identities need no new term: a retail-vs-retail or retail-vs-sleeve fill is one row of the tranche table per party, so the pot moves by 1000 × ΔOI, Σ net = 0 per market, and Σ posted = pot. Worked example — Alice and Bob are subaccounts (opening balances 100 000 and 50 000 mills), S a supplier sleeve (free 0) whose only resting quote is an ask of 20 @ 600:

Step Alice bal. Alice reserved Bob bal. Bob reserved S free Pot OI
openings 100 000 0 50 000 0 0 0 0
Alice: GTC buy Higher 50 @ 600 — 20 fill against S's ask (Alice pays 20 × 600 = 12 000, S opening short pays 20 × 400 = 8 000), 30 rest 88 000 18 000 (30 × 600) 50 000 0 −8 000 20 000 20
Alice: signed cancel — retail_cancelled{customer}, remaining 30 88 000 0 50 000 0 −8 000 20 000 20
Bob: GTC buy Higher 10 @ 650 — no ask, rests 88 000 0 50 000 6 500 −8 000 20 000 20
Alice: IOC sell Higher 10 @ 650 — fills Bob's bid at 650; Alice closing long receives 6 500, Bob opening long pays 6 500 94 500 0 43 500 0 −8 000 20 000 20
Alice: IOC sell Higher 11 — holds 10 order_rejected{insufficient_position}, nothing moves

The pot is unchanged by the sale (ΔOI = 0: Alice's 10 closed, Bob's 10 opened); Σ balances + pot is 150 000 at every row; Σ posted = Alice 12 000 − 6 500 + Bob 6 500 + S 8 000 = 20 000 = pot; open interest is Alice 10 + Bob 10 = S's 20 short.

The retail fee rule. No fee is set by the notes; the fee is a pinned RULE IDENTITY, retail_fee_rule (version + content hash — CLAUDE.md law 7(b), law 8), in the run manifest (MF-16, model-plugin-layer §12), never a code constant. Its one declared version, v1, is "no fee": f = 0 in every admission above and no fill moves a fee. v1's canonical rule text, its content hash and the table of declared (version, hash) pairs live in model-plugin-layer §12 beside MF-16. v1 "no fee" is a CONFIG default, its source the tokenomics note's "Status for Monday" ("Still open: … fees") and its list of rules published before commitments open ("Before commitments open": "… fee rules …"), replaced only by a dated ruling record, a later version applying from the next epoch (its document's field 79, every run manifest citing that epoch held equal to it; a manifest citing no allocation keeps its own MF-16 pin, changed only by a later manifest; risk-engine.md §7, futarchy-system.md §9 C108). The fee's base, rate, per-fill or per-order application and recipient are a dependency on the economist, declared by a later version from the first epoch that pins it, or from the first manifest pinning it for a run citing no allocation, and paid in the stablecoin unit, mills (the tokenomics note: "fees are paid in stablecoins"); a later version with a per-fill or per-order term states its reservation term and names its fee sink; a run report prints the declared rule beside every fee-dependent figure.

A retail position lives in its market's pot: a retail leg's cash is in the pot from its fill (the tranche table above), and a sleeve leg's carried collateral is futarchy-system.md §8 convention (b). A financing-cycle boundary leaves the position untouched, and the boundary's identity — Σ balances + Σ pots + Σ withdrawn (+ named fee sinks — none under v1 "no fee") — is conserved across it. Σ withdrawn sums the handoff withdrawals of futarchy-system.md §8 (R47); inside the exchange fold it is zero, since no exchange withdrawal event exists and the withdrawals are the futarchy chain's records. futarchy-system.md §8 "Two epochs in the log" is the home of R57's carry rule and its two open conventions.

7. Determinism and invariants #

  • Inputs are one merged, totally-ordered stream (§2); processing is single-threaded per market in merge order; every emitted event's order is a function of input order — nothing else.
  • Structural invariants (checked by taps, not trusted): no resting book is ever crossed after a transaction completes; no fill references a quote version below the book's version at match time (a retail fragment carries no version: its stale check is its admission's, §5); the pot equals 1000 mills × open interest at every offset (§6 — settlement is exactly funded at any grid price, the "covers worst case" form made exact); book fold recomputed from zero equals the incremental book (by re-running the stream and diffing segment chain hashes — the determinism harness; a checkpoint-root form waits on architecture.md's reserved state_root slot, out of scope).
  • Banned inside the engine: wall clock, RNG, floats, iteration over unordered containers — the standard transformer law.

8. Edge cases (pinned) #

Case Rule
Order arrives after market_closed in merge order order_rejected — lifecycle events sequence through the same merge, so "after" is well-defined
Order stamped during halt rejected at admission (market halted) — stamping succeeded, rejection is its recorded fate
Order stamped during a feed stall deferred to the first fresh state tick, where its key (v, class 3) sorts before the house batch's (f, class 2) and it meets a book empty of quotes and of retail orders — the stall's pull cancelled the quotes and, under R7 v2, the stall record's tick cancelled every resting retail order after the pulls (retail_cancelled{feed_stall}, ascending by order id, a halted market's at its resume) — so it ends order_unfilled, no new refusal; a place stamped at or below the stall record's ts never rests, its remainder ending as an IOC remainder does whatever its time in force; the stall is the match log's record, never a market state (risk-engine.md §3 R7 v2 (e)); at an equal sequence (f = v) it follows the fresh house batch and may fill against a quote priced on the fresh state. R7 v1's declared text (risk-engine.md §3 R7 (e), (f)) predates resting retail orders: its "the empty book" means the book empty of quotes, the whole book of a run with no subaccount.
Resting retail orders at a feed stall under R7 v2 cancelled at the stall record's tick after the pulls, one retail_cancelled{feed_stall} per order ascending by order id, each releasing exactly its remainder's reservation (§6) and keeping its filled units, for every operator's subaccounts alike; on a halted market right after its market_resumed, before every class 1–3 record of its stamp, its parked pull and any batch among them; a cancelled order never revives, and a signed cancel naming it is instruction_rejected{no_resting_order}
Order after a sleeve suspension meets the suspended sleeve's empty levels — the suspension's pull cancelled them at class 2 at the record's stamp (a halted market's at its resume) and no later batch of the sleeve rests until a resume; it fills against the other sleeves' resting quotes or a resting retail fragment, or ends order_unfilled, no new reject reason; the suspension is the run's control-root record, never a market state (risk-engine.md §5; under R6 v1 the cross-run stop of a liquidated desk, reason 2, lasting to the run's seal); a position the sleeve accepted before the pull rides to its settlement unchanged
Order for a pre-open (Created) or Voided market, or, for a relayed place, a never-created one order_rejected{market_closed} — "the trading window is not open" is one recorded fact; the lifecycle stream disambiguates for any consumer
Quote batch for a closed market quote_rejected{market_closed}
Quote batch for a pre-open, halted, or voided market quote_rejected{market_closed} — same one-fact rule; quotes get no dedicated halt reason. Status is checked before level inspection, so this covers pure cancels too (a zero-level cancel_all_prior on a halted market is rejected, not the row-below's "valid") — which is exactly why the model-plugin §11 suspension pull DEFERS a halted market's cancel to its resume (an own rejection is mirror divergence)
Quote batch with version below the book version quote_rejected{stale_version}, nothing else happens — see the §4 step-1 note
Batch with zero levels + cancel_all_prior on an OPEN market valid — it is a pure cancel (on a non-open market the row above rejects it first: status precedes level inspection)
Two batches, same sleeve, same version second replaces first (later in merge order; step 2 expires by version, step 1 dedupes by sleeve: newest batch wins)
Sold out (headroom shrinks batch to zero) quote_rejected{capacity}; existing funded units keep trading peer-side per spec §7
Size 0 or off-grid price in an order rejected at gateway validation, never appended — malformed input is not history; inside a signed instruction the same faults are the fold's instruction_rejected{bad_instruction} (§5 step 1 — the gateway does not read the instruction)
A signed cancel on a halted market admitted (§5 — a cancel is exempt from the halt): retail_cancelled{customer}, the reservation released at once; a place instruction on the halted market is order_rejected{market_halted}, its nonce consumed
A place instruction that would cross a resting order of the same customer key order_rejected{self_cross}, the whole instruction — under any operator, so one customer's two subaccounts never trade with each other
A cancel naming an order filled out, cancelled, drained, unknown, or another subaccount's instruction_rejected{no_resting_order}, nothing else — its nonce consumed
A relayed instruction recorded twice (verified bytes replayed) the second is instruction_rejected{stale_nonce} — the first consumed its nonce
A retail cancel and a taker order at one replay_ts both are class 3 on the run's order log (§2), so the order-log offset alone decides: the taker first walks the book with the resting fragment in it — a fragment it fills out is gone and the cancel naming it is instruction_rejected{no_resting_order}, a fragment it fills in part leaves its remainder to the cancel, retail_cancelled{customer} with that remainder —; the cancel first is retail_cancelled{customer}, its remainder's reservation released (§6), and the taker meets what remains of the book — an IOC signed place ends order_unfilled where nothing else crosses its limit, a flat order (no limit) order_unfilled where its side is exhausted, a GTC signed place rests its remainder, retail_rested
A flat order/v1 submitted twice two records, two orders, each decided on its own admission (the second reads order_rejected{stale_version} when a quote batch at a higher state version is accepted between the two, order_rejected{insufficient_funds} when the first's fill leaves the account short, order_unfilled when the first drains the side — a drained side never moves the book version): client_ref is never recorded (gateway-protocol §2 row 3), so no idempotence key exists for a flat order; the nonce of a signed instruction (§5 step 4) is the one idempotence rule, and it binds subaccount instructions only
A buy of one side while a sell of the other side rests, beyond the held units order_rejected{insufficient_position} (§6's held-units guard): with Higher 10 held and a resting sell of Higher 10, a buy of Lower 5 is refused — filled first, it would leave the sell delivering units never held
A GTC place whose remainder would rest beyond the subaccount's max_resting_orders order_rejected{resting_cap} (§5 step 8), the whole instruction, nothing filled — its nonce consumed; with the cap at 2 and two orders resting, a third GTC that crosses nothing is refused, while one that fills in full is admitted (nothing rests)
An instruction relayed by an operator its subaccount's opening does not name instruction_rejected{foreign_account}, the subaccount untouched
market_closed or market_voided arrives for a market with resting fragments every fragment cancels: one quote_cancelled per affected sleeve, ascending, then one retail_cancelled (reason close or void) per resting retail order, ascending by order id, and the book empties — every reservation frees (nothing in a closed book can ever fill: §5 rejects orders, §4 rejects batches — so leaving fragments would consume sleeve headroom across every later market forever). The drain happens at the marker's MERGE POSITION, and the per-sleeve quote_cancelled is what clears a bookmaker's resting mirror — NOT the marker's arrival at the LC-emit step, which precedes the merge. A bookmaker sees the close's STATUS at LC-emit (so its BM step won't quote a closing market) but keeps its resting until this echo, so an order deferred into the closing tick — sorting ahead of the marker in the merge — reconciles against the book the engine still holds (the defer-to-state-tick order rule can place a fill in the closing tick; the premature LC-step clear made it a false FillWithoutResting divergence)
settlement_complete for an epoch whose pot is not exactly zero halt — the §6 invariant guarantees Σ payouts drains the pot; a residue is corrupted history, never smoothed over
void_complete for an epoch whose refund rows do not drain a pot exactly halt — Σ posted = pot is a §6 identity (PotResidue in the ledger, PotNotDrained in the exchange view); a residue is corrupted history (settlement-contract SC-V rule 2)

9. Tests #

  • Golden scenario: the spec's worked example end-to-end (bid $0.44 / ask $0.47 = 440/470 mills, buy 100, settle 3 kills → 600 mills/unit, +$13) — asserting every emitted event byte-for-byte.
  • Property tests: conservation (Σ balances + Σ pot constant across any transaction); never-crossed-at-rest; stale-fill impossibility (generated interleavings of state advances and orders — no fill's version < book version); IOC leaves nothing resting; a subaccount's reservations equal Σ remaining × p_named over its resting buys and Σ remaining over its resting sells at every offset, and no subaccount sells units it does not hold.
  • Determinism: random order/quote interleavings replayed twice → identical market-event stream hashes; then re-merged from logs → identical again.
  • Fixture races: order-vs-version-advance and order-vs-close sequenced both ways through the merge, asserting the documented outcome each way.