Orion Tasking — FLAG-037: Reconciler Conservative Phantom Fill¶
To: Orion (he/him)
From: Vesper (she/her)
CC: Katja (Captain), Atlas (he/him)
Date: 2026-04-19
Branch: fix/reconciler-disappeared-order-conservative
Priority: HIGH — next in sequence after Directional Drift Guard (merged)
Context¶
The reconciler's current logic when an order disappears from the open set with no cancel tx: assume fill → apply phantom fill → log action_taken='phantom_fill_applied' to reconciler_anomaly_log.
Post-S41 analysis of the anomaly log revealed two distinct cases:
Case 1 — S41 fills (rows 3–5): Orders 11–19 seconds old. Reconciler correctly detected 3 real fills in the first 24 seconds of session. Phantom fill application was correct. Inventory math closes exactly on-chain.
Case 2 — S38 stale orders (rows 1–2): Orders ~86,000 seconds old (~24 hours). Placed during S38, offers sat live on XRPL ledger during the session gap, consumed by the market overnight. Phantom fill happened to be correct — but only verifiable after the fact. Blindly auto-filling a 24-hour-old order is dangerous.
Atlas ruling (Apr 19): Implement age-based conservative handling. Young orders (< threshold) → current phantom fill behavior. Old orders (≥ threshold) → do NOT auto-fill. Log as held_pending_review. Surface alert. Require resolution.
Spec (Atlas-locked Apr 19)¶
Age threshold: 300 seconds (configurable in YAML).
Case 1 — Young order (age < age_threshold_seconds):
Current behavior unchanged. Apply phantom fill. Log action_taken='phantom_fill_applied' to reconciler_anomaly_log. No change to existing code path.
Case 2 — Old order (age ≥ age_threshold_seconds):
- Do NOT apply phantom fill
- Do NOT update inventory / fills table
- Log action_taken='held_pending_review' to reconciler_anomaly_log with full context (order_id, age_seconds, side, quantity_rlusd, xrp_equivalent)
- Emit a WARNING log: [RECONCILER] HELD_PENDING_REVIEW — order_id=<id> age=<age>s ≥ threshold=<threshold>s — manual resolution required
- Enter DEGRADED mode via _enter_degraded_mode("reconciler_held_pending_review") — cancel all orders, stop quoting, continue observation. The engine must not continue trading with unresolved stale-order exposure.
One-shot per session: If DEGRADED is already active (e.g., a guard fired first), the held_pending_review log and WARNING still emit — the state record is always written. But _enter_degraded_mode is idempotent and will no-op if already in DEGRADED.
YAML Config Block¶
Add under strategy: in all three config files:
reconciler_conservative:
enabled: true
age_threshold_seconds: 300 # orders older than this are held, not auto-filled
Pre-Code Investigation Required¶
Before writing any code, investigate and report on the following:
Q1 — Phantom fill decision point. Locate the exact method and line number in ledger_reconciler.py where the current phantom fill decision is made. What is the condition check? What data is available at that point (order dict fields, timestamps)?
Q2 — Order age calculation. What timestamp field is available on the disappeared order that can be used to calculate age? Is it created_at, submitted_at, or something else? What data type (ISO string, Unix float, epoch int)? Confirm how to compute age_seconds = now - order_timestamp.
Q3 — reconciler_anomaly_log write path. Confirm the existing writer method signature for reconciler_anomaly_log. What fields does it accept? Is action_taken a free-text string or an enum? Confirm that adding action_taken='held_pending_review' requires no schema change.
Q4 — _enter_degraded_mode availability. The reconciler runs inside the main loop engine context. Confirm that _enter_degraded_mode is accessible from the reconciler's call site, or whether the held_pending_review signal needs to be returned up the call stack to the main loop for DEGRADED entry. Identify the cleanest pattern given the existing architecture.
Q5 — Config access in reconciler. How does the reconciler currently access config values? Is the config object passed directly, or accessed via the engine? Confirm the pattern so age_threshold_seconds can be wired correctly.
Report findings before writing any code.
Implementation Guidance¶
Age calculation: Use wall-clock time.time() as "now". Parse the order's timestamp field to a comparable value. If the field is an ISO string, use datetime.fromisoformat() and .timestamp(). Age in seconds = time.time() - order_timestamp_unix.
Guard placement: The age check should wrap the existing phantom fill logic. If age < threshold → current behavior (no change). If age ≥ threshold → held_pending_review path. The threshold check should happen before the phantom fill is applied, not after.
held_pending_review log fields: Same shape as existing reconciler_anomaly_log rows, with action_taken='held_pending_review'. Include order_id, age_seconds, side, quantity_rlusd, xrp_equivalent, and optionally a threshold_seconds context field.
DEGRADED entry: Use reason string "reconciler_held_pending_review". This gives operators immediate context at restart about why the session degraded.
Persist-failure safety: If the reconciler_anomaly_log write fails, swallow the exception (log at ERROR) and still enter DEGRADED. The safety function (stopping trading) must not be blocked by a logging failure. Same pattern as anchor guard and drift guard.
enabled: false path: If the config block is disabled, the age check is skipped entirely — revert to current phantom fill behavior for all orders regardless of age. Useful for testing / debugging without the conservative gate active.
Test Requirements¶
Minimum 8 tests for this branch:
- Young order (age < threshold) → phantom fill applied, no DEGRADED
- Old order (age ≥ threshold) →
held_pending_reviewlogged, DEGRADED entered, no phantom fill - Old order → WARNING log emitted with correct order_id and age
enabled: false→ old order treated as young (current behavior), no held_pending_review- Persist failure on
held_pending_reviewwrite → swallowed, DEGRADED still entered - Exactly at threshold (age == threshold) → held_pending_review (boundary: ≥ threshold triggers)
- Multiple disappeared orders in one tick — one young, one old → phantom fill for young, held_pending_review for old
- (Bonus)
reconciler_anomaly_logrow written with correctaction_takenand session_id populated - (Bonus) Windows teardown fix — use
addCleanuppattern for any integration tests using realStateManager
Commit Plan (suggested)¶
feat(config): ReconcilerConservativeConfig dataclass + YAML defaults (FLAG-037)fix(reconciler): age-based conservative phantom fill — held_pending_review path (FLAG-037)test(reconciler): conservative phantom fill — 8+ tests (FLAG-037)
Constraints¶
- Do not modify the phantom fill path for young orders — current behavior must be preserved exactly
held_pending_reviewmust never silently drop — it must always log + DEGRADED, even if DB write fails- Parameters configurable in YAML — no hardcoded thresholds
- Use existing
_enter_degraded_modeinfrastructure - Windows teardown fix required in any integration tests using real
StateManager(useaddCleanuppattern) - No reconciler logic changes beyond the age threshold gate
Deliverable¶
Standard format: branch name, commit list with hashes and messages, test count and pass rate, pre-code investigation findings, any deviations from spec flagged explicitly. Vesper reviews before merge.
— Vesper