The incomplete vector, pending→unmatched, and channel-agnostic transitions
Incomplete vector at placement (no-guesswork)
The size vector is filled progressively: Betfair's placement response carries only sizeMatched; the
remaining / lapsed / cancelled breakdown arrives later via order-sync/stream, and voided at
settlement. deriveOrderMatchStatus therefore uses the reconciliation deficit as the
completeness signal (ADR-0003):
deficit = requested − (matched + remaining + lapsed + cancelled + voided)
deficit ≈ 0 → vector complete → classify the remainder into its terminal buckets
deficit > dust → the deficit is UNRESOLVED / pending → shown as "pending", NEVER fabricated
deficit < −dust → over-sum → throw FINANCIAL_INTEGRITY
At placement (matched=60, rest unreported): deficit = 40 → "partially matched ₹60 · ₹40 pending".
Once sync reports lapsed=40, it becomes "₹40 lapsed". The terminal remainder buckets populate only
when the provider actually reports them — this is mandatory, not optional (the user's directive:
lossless + no-guesswork, always).
pending is removed as a display state; unmatched covers it
There is no separate pending display status. Both "created/sent, not yet on the book" and
"on the book, nothing matched yet" display as unmatched — for the user, both mean the same:
your stake isn't matched. (The DB lifecycle status may still distinguish pending/submitted as
infra — ADR-0002 — but the derived Display Status collapses them to unmatched.)
Channel-agnostic transition handling
deriveOrderMatchStatus runs identically whether the size vector was last updated by the Betfair
stream (ocm/uo), the Betfair REST fallback (listCurrentOrders), or the Bifrost
queue — it reads persisted columns, not a channel. The obligation this places on the ingestion
side: whichever channel writes the size vector must write the same normalized fields, so the
status is stable across a stream→poll failover. (This is the substrate for the later polling→streaming
pivot; the status function itself needs no change.)
Lay bets — no change
Verified against nettingEngine.ts (betType: 'back' | 'lay', lay liability −S×(O−1) into the
exposure vector, DOMAIN_INTEGRITY throw on unknown betType) and orderService.calcLayLiability
(stake × (odds−1)). The size vector is in stake terms for both sides, so
deriveOrderMatchStatus is side-agnostic; liability is a derived display figure the product already
shows. No status-logic or netting change for lay.