Reconciliation
How a live mount proves its own books against the venue's own reports — the pass, the health gate, and which venues reconcile.
A live core's books are built from messages. Messages get lost, arrive while the process is down, or arrive twice. Reconciliation is the periodic proof that the local book still matches what the venue itself says — and, where it does not, a classified disagreement with a policy attached.
The engine is pure. crates/vike-exec/src/recon/mod.rs's header states the split: diff turns
venue reports plus a LocalView into Divergences, resolve turns those plus a ReconPolicy
into a Recon, and both are pure functions with no I/O. That is what makes golden-fixture
tests and the three-way journal cross-check possible at all — crates/vike-exec/src/recon/types.rs
says so in its own first line.
What one pass fetches
crates/vike-exec/src/recon/client.rs's ReconClient is the venue-facing report seam, and it is
blocking REST. Three fetches are required — fetch_order_status_reports, fetch_fill_reports,
fetch_position_status_reports. Two more default to Ok(None), so an unwired client stays inert
rather than failing a pass:
fetch_balance— the venue's absolute cash truth. A venue that reports none still has its orders, fills and positions diffed.fetch_fee_rates— the account-actual maker/taker schedule, overridden by binance, bybit, okx, deribit and aster — the last on its spot lane only, its perp lane staying inert. Unwired, the caller keeps the staticvike_model::fee_schedule_fordefault.
fetch_mass_status composes the three required fetches into one MassStatus bundle in a fixed
order (orders → fills → positions); a venue with a genuine atomic bulk endpoint may override it,
and that doc records that no current venue does. run_pass composes fetch → diff → resolve in
one function — the offline twin the golden fixtures run. The live driver reaches the same three
stages across two threads instead, which the next section describes.
Where the work runs
crates/vike-core/src/recon_manager.rs's header names the design reports-in, compute-on-fold.
The manager runs on its own thread and does only the blocking fetch — it must never touch the
p99 < 10 µs fold. It then enqueues the raw reports as Command::ReconcileReports; the fold thread
reads its own local_view(), runs the pure diff and resolve, and folds each synthesized event
through the same publish_to path real venue events take
(crates/vike-core/src/runtime/mod.rs's reconcile_reports). Diff and resolve therefore sit on
the same thread as the state they read: no cross-thread snapshot, no staleness window.
The pass deliberately avoids ApplySnapshot
reconcile_reports explains why it does not route through Command::ApplySnapshot:
ExecutionEngine::apply_snapshot's order reap terminalizes any live local order absent from
snapshot.open_orders, so pushing an all-empty-except-balance snapshot through that arm would
cancel every live order on the venue as a side effect. Balance is seeded directly onto
Account::balance instead — the narrowest path that updates cash without touching positions or
the order registry.
Cadence, and why repetition is safe
Three cadences share one pass (ReconManager::run_startup_pass): a startup pass after
startup_delay; an on-demand pass whenever a bridge pokes the reconcile trigger, typically wired
to a reconnect; and ReconConfig::interval, which re-runs the same pass on a fixed cadence.
ReconConfig::audit_interval is a fourth arm that runs no reconcile pass at all — it pokes the
core's existing stuck-order watchdog waker instead.
That makes idempotency load-bearing rather than convenient, and the header says where it comes
from: diff dedups fills by trade_id against the engine's seen_trade_ids, so a tick that finds
nothing new folds nothing new.
The health gate is per venue
ReconManager::should_reconcile is consulted before each venue's leg of every pass — startup,
trigger and interval alike. With no probe wired it returns true; otherwise it asks the probe for
that venue and suppresses the leg on ReconHealth::Degraded. Suppression defers that venue only:
it sets no pending flag and never blocks a different healthy venue in the same pass.
The probe itself is built in crates/vike-ops/src/reconcile_config.rs's build_recon_config over
a per-venue map of feed-status handles, so a bybit feed gap suppresses only bybit's leg. A venue
with no entry in the map reads Healthy — an ungated venue is never blocked, which is how the
exec-only venues (no market feed of their own) keep reconciling. health_from_feed_status is
fail-soft: only an error state, or a disconnected one whose status string literally says so, reads
Degraded.
Which venues reconcile
The mount is the composition root, and crates/vike-mount/src/lib.rs's header describes a layout
of three kinds, not two:
build_recon_clientis a thin dispatch over the bybit, okx and binance bridges' ownrecon_clientfactories.- Hyperliquid is built by
crates/vike-mount/src/hyperliquid.rs'shyperliquid_live_client, which returns the exec client and theReconClienttogether — it calls the bridge'srecon_clientonly once itsinstrumentsfetch has resolved the product, which the shared dispatch cannot do without a second network round trip. - deribit, aster, cTrader, Alpaca, IG, OANDA — and, behind the
ibkrandpolymarketfeatures, IBKR and Polymarket — call their own factory inline inmake_engine's arm, each needing a non-REST, bespoke-signer or non-generic-credentials handshake the shared dispatch cannot produce.
A paper venue returns None and reconciles nothing, and there are two independent ways to be one.
The per-venue arming ceiling (policy.venues.<venue>) is read first, above the credential read:
a paper venue returns the paper client having loaded no credential and opened no socket. Only
behind it does the older rule apply — absent credentials are the live gate. Both, in that order,
are the risk and arming page's subject.
Five of those inline factories (deribit, cTrader, IG, OANDA, IBKR) perform a blocking
authenticated handshake just to construct the client, so the mount gates them on its resolved
reconcile verdict through recon_if_enabled: with reconciliation off they are never called and
the venue does no authenticated network work at mount. That gate is an explicit parameter and
deliberately not "does this venue have a reconnect trigger" — the trigger is wired for four venues
only, so inferring the global gate from it would silently stop reconciling those five.
The output an operator sees
Events the policy auto-applies fold immediately. Everything held becomes a ReconAlert carrying
proposed_events — the events resolve would have folded — that an operator confirm folds
later through the same publish path, byte-identically to what an immediate fold would have
produced (confirm_recon). Which is which is the
policies page's subject, and each kind has a page.
A held divergence is a state, not an event
crates/vike-core/src/runtime/recon_held.rs's header carries the measurement that forced this:
under a hold-everything policy a divergence nothing heals is re-held every interval forever, and
the live box logged 396 identical warnings on 2026-08-25 and 345 on 2026-08-24, every one of
them the same bybit external-position divergence. The log now announces transitions — one line
when a divergence enters the held set, one when it leaves — with a much slower periodic summary so
a standing backlog stays visible without being repeated at pass cadence. The held store has the
same shape: a re-raise refreshes its row in place, so it is bounded by distinct divergence
identities rather than by uptime.