Futures Trading 6.2.5
Latest11 September 2026
This release has upgrade notes. Read them before updating — they describe behaviour changes that need your attention.
Futures Trading v6.2.5
Release Date: September 11, 2026 Tags: ORDERS, CANCELLATION, MATCHING-ENGINE, ORDERBOOK
Overview
A cancelled futures order could be put back into the matching queue by a read taken before it was cancelled, leaving a matchable copy of an order whose funds had already been refunded.
This is the defect that was found and fixed in the spot engine, ported out of this
one before it bit. It has the same two halves here as there, and one of them is
worse: backed-levels.ts reads "this order is in the queue" as proof that a price
level is backed, so a ghost order props up a ghost level that outlives every
sweep — the reconciler looks at the level, sees it backed, and declines to remove
it, for as long as the process lives.
Nothing here needs a migration and no figure changes. If you have ever seen a price level on a futures book that no order explains and that a restart clears while a running sweep will not, this is the shape of it.
Requires Ecosystem v6.5.2 if you run it — the same fix on the spot side — and Core v6.7.8 underneath.
Update Instructions
pnpm updatorNo table is added, nothing runs by hand, and no setting changes. The matching engine and the cancel doors are server-side: the backend restart is the whole of it.
- Update Ecosystem to 6.5.2 in the same window if you run it. The spot engine carries the original of this defect and the narrowing of its tombstone; the two engines are independent, so neither fix reaches the other.
- A restart clears any ghost order already resident in memory. There is nothing to run and nothing stored to repair — the queue is process memory, not a table.
Upgrade Notes
How a cancelled order came back
initializeOrders adopts every row getAllOpenOrders() returns straight into the
matching queue, and that read is a snapshot. The cancel doors write the terminal
row first and evict the order from the queue afterwards — and "afterwards" was up to
three MySQL round trips later. A boot, or a lease promotion, whose read straddled
that gap adopted an order the ledger no longer had.
Two separate faults lived in that window:
- Anything that threw in between — the wallet 404, the settlement transaction — left the order resident for ever, matchable against a refund that had already happened.
- A snapshot taken before the terminal write could re-adopt the order even after the eviction had run, because the eviction cannot reach a read that was already in flight.
The first is fixed by evicting the instant the row stops existing. The second cannot be — a splice does not reach a read taken earlier — so it needs a record that the cancellation completed, which is what the tombstone is.
This is the engine's own existing precedent rather than a new pattern:
expireUnfilledOrder already removed from the queue and marked the order before
writing its close.
Fixed
A cancelled order could be revived by a read taken before it was cancelled
- Fixed the single-order cancel door to remove the order from the matching queue
and record its cancellation the moment the ledger row becomes terminal, rather than
roughly three MySQL round trips later.
removeFromQueueis synchronous, costs no read, and asserts no lease — so it is correct on a follower, where the queue is empty and it is a no-op — which is why it is safe to do immediately. The eviction at the end of the route is unchanged and is now a no-op, and the book refresh and cycle kick still happen only on the success path. - Fixed cancel-all to record each order as it goes, at the moment that order's row stops existing, rather than at the per-symbol eviction the loop reaches much later.
- Fixed the engine to skip adopting any order whose cancellation has already completed. The record is a tombstone with a one-minute life and a bounded table, swept as it is read.
- Fixed both call sites so that in-memory bookkeeping can never fail a cancel whose ledger write has already landed. The cancellation stands either way; without the tombstone the behaviour degrades to exactly what shipped before it.
Why this engine got a tombstone and not the spot engine's cancel claim
The spot engine guards this window with a cancel claim, which also keeps a price level counted as backed for the seconds a cancel is in flight. This engine has no such window to protect — its cancel door removes the order and writes the row without an intervening await on a wallet — so it gets the tombstone alone.
One structure, one job. Porting the claim as well would have added a second thing to reason about on a path that does not need it.