Skip to content

Summary

FLAG-037 CANCELLED_BY_ENGINE layer delivered per Atlas ruling 2026-04-21 and your pre-code approval the same day. Three commits, 5 new tests, regression clean on the touched surface. S46 phantom-fill loop is closed: the reconciler can no longer infer a fill on an order the engine explicitly cancelled.

Patch bundle

Location: 08 Patches/fix-reconciler-disappeared-order-conservative-ext/

  • 0001-fix-models-state_manager-CANCELLED_BY_ENGINE-status-.patch (C5)
  • 0002-fix-reconciler-execution_engine-CANCELLED_BY_ENGINE-.patch (C6)
  • 0003-test-reconciler-CANCELLED_BY_ENGINE-guard-5-mandator.patch (C7)

Diff stat (main..HEAD):

 neo_engine/ledger_reconciler.py              |  66 +++-
 neo_engine/main_loop.py                      |  38 +++
 neo_engine/models.py                         |  18 +-
 neo_engine/state_manager.py                  |  68 ++++
 tests/test_reconciler_cancelled_by_engine.py | 466 +++++++++++++++++++++++++++
 5 files changed, 654 insertions(+), 2 deletions(-)

What changed

C5 — Schema + enum + method (f28fd6c)

neo_engine/models.py: - OrderStatus.CANCELLED_BY_ENGINE = "CANCELLED_BY_ENGINE" — British spelling preserved per Atlas spec; distinct from existing US-spelled CANCELED (cancellation confirmed on-ledger). - Order.cancelled_at: Optional[str] — ISO 8601, set by the engine the moment it decides to cancel (not the ledger-confirmation timestamp). - Order.cancel_reason: Optional[str] — free-form context ("degraded_entry_cancel_all", "shutdown_cancel", etc.).

neo_engine/state_manager.py: - _ensure_column(conn, "orders", "cancelled_at", "TEXT") and the same for cancel_reason, added to initialize_database(). Idempotent; legacy rows stay NULL. - _row_to_order extended with the defensive if "col" in row.keys() else None pattern for both new columns so rows fetched from an un-migrated DB still construct cleanly. - mark_cancelled_by_engine(order_id, *, cancelled_at=None, reason=None) — dedicated intent-verb method (per your ruling, not folded into update_order_status). Single transaction: writes status, cancelled_at, cancel_reason, and refreshes updated_at.

C6 — cancel_all integration + reconciler guard (5122226)

neo_engine/main_loop.py_cancel_all_live_orders: - For each live order in the iteration, the engine now calls self._state.mark_cancelled_by_engine(order.id, reason=context) before self._gateway.submit_offer_cancel(order.offer_sequence). Per your ruling, DB write first. - Failure posture: - DB write raises → log ERROR, skip gateway submit, continue to next order. The unmarked order will fall through the ambiguous-disappeared age gate on the next tick — which is the correct conservative outcome if we failed to persist intent. - DB write succeeded, gateway submit raises → log ERROR, continue. The CANCELLED_BY_ENGINE status protects the order from the phantom-fill path regardless of whether the OfferCancel tx reaches the ledger this tick.

neo_engine/ledger_reconciler.py: - _handle_disappeared_active_order — CANCELLED_BY_ENGINE guard added as the first check, ahead of cancel_tx_hash and the age gate. Atlas canonical evaluation order now reads as:

1. CANCELLED_BY_ENGINE   → never phantom-fill; emit RECONCILER_SKIP_ENGINE_CANCEL
2. cancel_tx_hash set    → existing cancel-race short-circuit
3. Age-threshold gate    → existing FLAG-037 C2 logic
The skip path emits a structured INFO log (RECONCILER_SKIP_ENGINE_CANCEL — disappeared order was cancelled by engine; skipping phantom-fill path) with order_id, offer_sequence, cancelled_at, cancel_reason. No anomaly row is written (skip is expected behavior, not an anomaly). - _get_orders_for_reconciliation now includes OrderStatus.CANCELLED_BY_ENGINE alongside ACTIVE / PARTIALLY_FILLED / CANCEL_PENDING. Per your ruling this is defense-in-depth, not filter- only bonus: it ensures the guard actually runs (and emits its log) on the post-cancel tick, including after a restart. - _reconcile_order dispatches CANCELLED_BY_ENGINE orders directly into _handle_disappeared_active_order as the first branch, bypassing the ACTIVE/PARTIALLY_FILLED offer lookup. The guard fires regardless of whether the offer is still present on the ledger (e.g. OfferCancel still in flight).

C7 — 5 mandatory tests (0e74b13)

tests/test_reconciler_cancelled_by_engine.py — all five Atlas-required tests, Windows-safe fixture (StateManager.close() before TemporaryDirectory.cleanup()):

# Test Key assertion
1 test_degraded_cancel_then_reconcile_skips_phantom_fill engine.record_full_fill not called; no anomaly row; RECONCILER_SKIP_ENGINE_CANCEL emitted exactly once
2 test_ambiguous_disappearance_routes_through_age_gate Age-gate path unchanged — young→phantom-fill, old→held; no skip log on either path
3 test_restart_continuity_fresh_reconciler_still_skips Fresh StateManager + fresh LedgerReconciler on the same DB file still skips; proves the guard rides on persistent SQL state
4 test_mixed_cancelled_and_ambiguous_processes_only_ambiguous One CANCELLED_BY_ENGINE + one ambiguous young; exactly one phantom-fill (ambiguous), exactly one skip log (cancelled)
5 test_truth_gate_preserved_zero_inventory_delta 4-order batch all CANCELLED_BY_ENGINE → zero record_full_fill calls, engine signal stays RUNNING, one skip log per order

Regression

Scoped to the touched surface:

$ python -m pytest tests/test_reconciler_conservative.py \
    tests/test_reconciler_cancelled_by_engine.py \
    tests/test_ledger_reconciler.py \
    tests/test_config.py \
    tests/test_state_manager.py -q

121 passed in 2.74s

Pre-existing failures (not this branch)

tests/test_execution_engine.py and tests/test_xrpl_gateway.py have pre-existing TypeError: OrderSizeConfig.__init__() missing 1 required positional argument: 'max_size_pct_of_portfolio' failures across ~100 tests. I verified (git stash no-op on untracked test file, run against committed branch code) that these failures are present on the branch's committed code and reproduce the same TypeError. They are config-signature drift unrelated to FLAG-037 — flagging per your standing instruction.


Standing-instruction compliance (your Apr 14 rules)

  1. No pre-created branch during investigation. ✅ — branch fix/reconciler-disappeared-order-conservative-ext was created only when I was ready to commit C5. Investigation was on throwaway state.
  2. No *.patch glob in PowerShell. The apply block below uses Get-ChildItem ... | Sort-Object Name | ForEach-Object { git am $_.FullName }.
  3. Defensive git branch -D before git checkout -b. Included below.

Apply instruction (PowerShell)

Run in Katja's VS Code terminal from C:\Users\Katja\Documents\NEO GitHub\neo-2026\:

# 1. From main, ensure clean working tree and up-to-date
git checkout main
git pull
git status  # should be clean

# 2. Defensive branch delete (standing rule #3) + create fresh
git branch -D fix/reconciler-disappeared-order-conservative-ext 2>$null
git checkout -b fix/reconciler-disappeared-order-conservative-ext

# 3. Apply the three patches in order (standing rule #2 — no glob)
Get-ChildItem "C:\Users\Katja\Documents\Claude Homebase Neo\02 Projects\NEO Trading Engine\08 Patches\fix-reconciler-disappeared-order-conservative-ext" -Filter "*.patch" | Sort-Object Name | ForEach-Object { git am $_.FullName }

# 4. Verify
git log --oneline main..HEAD
#   expected, top-to-bottom:
#     test(reconciler): CANCELLED_BY_ENGINE guard — 5 mandatory tests (FLAG-037)
#     fix(reconciler,execution_engine): CANCELLED_BY_ENGINE guard in cancel_all + reconciler skip path (FLAG-037)
#     fix(models,state_manager): CANCELLED_BY_ENGINE status + cancelled_at field on orders table (FLAG-037)

python -m pytest tests/test_reconciler_conservative.py tests/test_reconciler_cancelled_by_engine.py tests/test_ledger_reconciler.py tests/test_config.py tests/test_state_manager.py -q
#   expected: 121 passed

If any patch fails mid-apply, run git am --abort and flag it — do not try to push through.


Local main drift — acknowledgement

Per your note: I developed this branch atop my local main, which may be a few commits behind Katja's canonical tree at apply time. The three patches only touch:

  • neo_engine/models.py (one enum + two Order fields, both additive)
  • neo_engine/state_manager.py (schema migration via _ensure_column, new mark_cancelled_by_engine method, defensive reads in _row_to_order)
  • neo_engine/main_loop.py (_cancel_all_live_orders — surgical edit inside the existing loop body)
  • neo_engine/ledger_reconciler.py (new guard block, filter extension, dispatch branch — additive to existing logic)
  • tests/test_reconciler_cancelled_by_engine.py (new file)

No conflicts expected against any of the other Phase 7.3 merges already in main (anchor/drift/corridor guards, FLAG-041, FLAG-042, FLAG-044, startup-mode-reset). If git am fuzz appears I'll recut from the canonical main.


Open items that should route behind this merge

  • FLAG-037 closure on the Open Flags board — after merge + one clean live session, I can draft the closure note for Katja.
  • FLAG-040 (WAC correction) — Atlas's pre-code spec said this lands post-FLAG-037. Ready to start investigation on your say-so.
  • FLAG-038 / FLAG-039 — not touched by this branch.

Ready for code review.

— orion