The exec contract
One scenario table folded through the real order FSM against every covered venue's real event mapper — the machine-checked version of emitter-split, no-silent-vanish and exactly-one-terminal.
The venue-adapter execution contract spans every bridge crate and lives in none of them, so its
harness is hosted one layer down, in crates/vike-bridge-core/tests/bridge_conformance.rs. Before
it existed the contract was verified ad hoc, per venue. Now it is one scenario table run against
every covered venue's real, fixture-tested event_mapper, folded through the real
vike_exec::ManagedOrder FSM — the same code the live core folds through. Only the venue-native
JSON frames and the mapper are venue-specific; the scenarios, the folds and the assertions are
shared.
The three clauses under test
The file's header states them as the contract:
- Emitter split — Rust emits
OrderSubmittedsynchronously at submit; the venue emitsOrderAccepted,OrderRejected,OrderCanceledand the fill events. - No silent vanish — a dead venue path must synthesize a terminal
OrderRejected. - Exactly one terminal — the FSM must reach exactly one of
Filled,Canceled,Rejected,DeniedorExpired. Illegal or out-of-order events are dropped idempotently, never corrupting the single terminal.
The oracle is not a re-implementation. ContractOrder wraps the real FSM and mirrors
ExecutionEngine::on_event's thin per-order policy: the bare-fill versus wrap-fill split, trade-id
dedup so a reconnect replay never double-counts, and a counter for the one genuinely bad case — a
terminal event that failed to apply while the order was still live. That counter must end at zero
in every healthy scenario, and the terminal count must end at exactly one.
The four scenarios
- Lifecycle — submit, accept, partial fill, full fill reaches
Filledwith one terminal. - TransportDeath — a dead venue path still synthesizes a terminal, driven through the exact
shared seam each venue really uses:
ExecActorfor command venues,resolve_ambiguous_submitfor REST-poll venues. - CancelAfterClose — a late venue
OrderCanceledreplay and a lateOrderCancelRejectedadvisory arriving after the order already closed are both dropped without corrupting the single terminal. - ReconnectMidOrder — driven through the real shared pump
run_user_data_foreverwith a scripted drop and reconnect that replays the mid-order partial: no duplicate terminal, no double-counted fill, no lost terminal.
The two axes
ExecKind selects which no-vanish seam TransportDeath exercises — CommandActor (a dead thread
synthesizes OrderRejected) or RestPoll (an ambiguous or timed-out submit resolves through the
shared resolve_ambiguous_submit).
FillShape is the orthogonal axis, and it exists because fill granularity genuinely differs.
Crypto venues stream Cumulative partials — each piece a distinct OrderPartiallyFilled before the
terminal OrderFilled. The FX, CFD and equity venues fill Whole: one execution reports the full
quantity, and their real mappers have no partial-fill path at all. Alpaca even folds a
"partial_fill" SSE event into OrderFilled. Lifecycle and ReconnectMidOrder branch on this so a
whole-fill venue is exercised faithfully instead of being asserted into a state it can never emit;
TransportDeath and CancelAfterClose are already shape-agnostic.
A doc comment is not a declaration
ExecKind's own comment records why it names no venue list: the list that used to live there named
four of the seven CommandActor venues, was written when the table held six, and never grew when
three more joined. FillShape is the counter-example it points at — its variants name venues, but
each name carries the reason (that venue's real mapper has no partial-fill path), so it reads as
evidence rather than as a roster somebody must keep in sync.
Covered and deferred
covered_bridges() returns the venues in the table; DEFERRED records the rest with a reason each.
No coverage fraction is written anywhere — the two lists partition the roster, so their own lengths
are the count, and coverage_matrix prints the whole grid under --nocapture together with the
deferral reasons, so what did not run is explicit. The
roster gate then holds the partition exact
against vike_model::VENUES.
Deferring a venue: name the shape, never a feature gate
The FXCM row is the worked example, and its removal is recorded in DEFERRED's own doc comment.
The old reason read "behind the fxcm feature; default build is an Unavailable stub — no real
event_mapper is compiled to drive". Every clause of that was false: the crate declares its module
tree with no cfg at all, map_fxcm_event was always compiled in every build, and the feature
gates nothing but build.rs. The obstacle was one keyword, mod instead of pub mod, behind a
reason nobody re-read for as long as it sounded plausible. A feature is a claim about a build, and a
build is exactly the thing a stale reason stops describing.
FXCM is now covered, and its row is instructive for the opposite reason: its "frames" are JSON envelopes the C++ shim builds, its accept is the synchronous return of a placement FFI call, and its comment states plainly what a green row does not say — nothing about the shim, the FFI, or any ForexConnect behaviour behind it, because no CI machine has ever linked that SDK. The row covers the venue's pure layer, which is where its last four recorded defects lived.
Frames from real wire
A venue's frame methods may source their template from that venue's committed sanitized capture
under crates/bridges/<venue>/tests/fixtures/captured/, written by the venue's opt-in capture smoke
and consumed here through captured_template. The harness loads the first captured frame as a
structure template — the real envelope, field set and value types — and patches only the
scenario-driven leaves through the type-preserving patch_f64 and patch_str. Absent a fixture it
falls back to the hand-authored frame, byte-identical to before. The coverage printout names which
venues sourced captured templates, so what ran against real wire is never a guess.
Related: the order lifecycle, the ExecutionClient seam, test doubles.
Testing
The six tiers of verification in the Vike Trader workspace — unit, property, conformance, gate, smoke and latency — and what each one is the only tier able to prove.
The market-data contract
Three L2-book invariants — monotonic sequence, gap triggers a resnapshot, derived L1 matches the book — run against every covered venue's real book normalizer.