"""Drive-loop scar-tissue tests — each proves a PINK incident cannot recur. Async without pytest-asyncio: sync tests drive coroutines via ``asyncio.run`` (deterministic, zero config). Mutation litmus per test in the docstring — break the guard, a test goes RED. Run: /home/dolphin/siloqy_env/bin/python3 -m pytest prod/exec_unified/test_drive_loop.py -q """ from __future__ import annotations import asyncio from decimal import Decimal from prod.exec_unified.contract import ExecutionRequest, Side, UrgencyClass from prod.exec_unified.drive_loop import ( DriveLoop, Resolved, ResubmitPlan, SlotView, build_working_order, ) from prod.exec_unified.router import ExecutionMethod, decide from prod.exec_unified.working import Action, WorkingOrder, WorkingRegistry FLAT = SlotView(trade_id="", stage="IDLE", size=Decimal(0)) def _open(tid: str, size="1") -> SlotView: return SlotView(trade_id=tid, stage="POSITION_OPEN", size=Decimal(size)) class FakeClock: def __init__(self, t: float = 1000.0) -> None: self.t = t def __call__(self) -> float: return self.t class FakePort: """Scriptable ExecPort. ``pump_effects`` installs a new slot on each pump (to simulate a fill surfacing during a round-trip). ``positions`` / ``lof`` drive the requote gate.""" def __init__(self, clock: FakeClock, slot: SlotView, *, positions=None, last_own_fill: float = -1e9) -> None: self._clock = clock self.slot = slot self.positions = positions or [] self.lof = last_own_fill self.pump_effects: list[SlotView] = [] self.submits: list[ResubmitPlan] = [] self.cancels: list[WorkingOrder] = [] self.cancel_raises = False self.positions_raises = False def clock(self) -> float: return self._clock() def slot_view(self) -> SlotView: return self.slot def last_own_fill_at(self) -> float: return self.lof async def pump(self) -> None: if self.pump_effects: self.slot = self.pump_effects.pop(0) async def cancel(self, wo: WorkingOrder) -> None: self.cancels.append(wo) if self.cancel_raises: raise RuntimeError("cancel boom") async def open_positions(self) -> list[dict]: if self.positions_raises: raise RuntimeError("probe boom") return self.positions async def submit(self, plan: ResubmitPlan) -> None: self.submits.append(plan) def _wo(action: Action, tid="req-0", *, max_reprices=0, cross_on_expiry=False, attempt=0, clock_t=1000.0, deadline=999.0) -> WorkingOrder: return WorkingOrder( request_id=tid, base_request_id="req", asset="BTCUSDT", side=Side.BUY if action is Action.ENTER else Side.SELL, action=action, limit_price=Decimal("100"), deadline=deadline, created=clock_t, attempt=attempt, meta={"max_reprices": max_reprices, "cross_on_expiry": cross_on_expiry}, ) def _loop(port: FakePort, clock: FakeClock) -> tuple[DriveLoop, WorkingRegistry]: reg = WorkingRegistry(clock) return DriveLoop(port, reg), reg # ── after_submit classification ────────────────────────────────────────────── def test_immediate_fill_not_registered(): clock = FakeClock() port = FakePort(clock, _open("req-0")) # slot already shows the entry filled loop, reg = _loop(port, clock) res = loop.after_submit(_wo(Action.ENTER), rejected=False) assert res == "immediate_fill" assert len(reg) == 0 # not registered — nothing to sweep def test_rejected_postonly_registers_and_expires_now(): # A post-only reject registers AND is pulled to expire immediately (one shared path). clock = FakeClock() port = FakePort(clock, FLAT) loop, reg = _loop(port, clock) wo = _wo(Action.ENTER) res = loop.after_submit(wo, rejected=True) assert res == "working_reject" assert reg.working("req-0") is not None assert reg.expired() == [wo] # deadline pulled to now → resolvable # ── handle_expired: the core races ─────────────────────────────────────────── def test_fill_races_cancel_resolves_as_fill(): # THE core race: quote fills during the cancel round-trip. Must classify FILLED, not # cancel-and-retry. Mutation: skip the post-cancel re-classify (step 5) -> RED. clock = FakeClock() port = FakePort(clock, FLAT) port.pump_effects = [FLAT, _open("req-0")] # 2nd pump (post-cancel) surfaces fill loop, reg = _loop(port, clock) reg.register(_wo(Action.ENTER)) res = asyncio.run(loop.handle_expired(reg.working("req-0"))) assert res == Resolved.FILLED_AFTER_TTL assert reg.working("req-0") is None # cleared, not left working assert port.submits == [] # NO retry/market after a fill def test_exit_never_strands_escalates_to_market(): # An exit that didn't maker-fill MUST cross. Mutation: return SKIP for EXIT -> RED. clock = FakeClock() port = FakePort(clock, _open("req-0")) # position still open after cancel loop, reg = _loop(port, clock) reg.register(_wo(Action.EXIT)) res = asyncio.run(loop.handle_expired(reg.working("req-0"))) assert res == Resolved.ESCALATED_MARKET assert len(port.submits) == 1 assert port.submits[0].method is ExecutionMethod.TAKER assert port.submits[0].reduce_only is True # exits reduce, never flip def test_acquire_entry_miss_abandons(): # ACQUIRE: a missed entry is free — never chase, never cross (§4-1). Uses the REAL # routing decision. Mutation: make cross_on_expiry True for ACQUIRE -> RED. clock = FakeClock() port = FakePort(clock, FLAT) loop, reg = _loop(port, clock) req = ExecutionRequest(request_id="req", asset="BTCUSDT", side=Side.BUY, size=Decimal("1"), urgency=UrgencyClass.ACQUIRE) wo = build_working_order(request_id="req-0", base_request_id="req", asset="BTCUSDT", side=Side.BUY, action=Action.ENTER, limit_price=Decimal("100"), decision=decide(req), clock=1000.0, ttl_s=-1.0) reg.register(wo) res = asyncio.run(loop.handle_expired(reg.working("req-0"))) assert res == Resolved.ABANDONED assert port.submits == [] # abandoned — no cross, no chase def test_entry_retry_within_budget_uses_fresh_id(): # Mechanism test: a policy that allows entry chase retries with a FRESH per-attempt id # (audit H4). Mutation: reuse the same request_id on retry -> assert on fresh id RED. clock = FakeClock() port = FakePort(clock, FLAT) loop, reg = _loop(port, clock) reg.register(_wo(Action.ENTER, max_reprices=1, attempt=0)) res = asyncio.run(loop.handle_expired(reg.working("req-0"))) assert res == Resolved.RETRIED assert len(port.submits) == 1 plan = port.submits[0] assert plan.request_id == "req-1" and plan.attempt == 1 # fresh id, linked to parent assert plan.base_request_id == "req" assert plan.method is ExecutionMethod.MAKER def test_entry_miss_slot_busy_never_double_enters(): # Raced remainder fill occupied the slot after cancel. Must NOT re-enter. # Mutation: drop the slot-busy guard (step 8) -> RED (a submit would appear). clock = FakeClock() port = FakePort(clock, FLAT) port.pump_effects = [FLAT, _open("other-trade")] # slot busy with a DIFFERENT trade loop, reg = _loop(port, clock) reg.register(_wo(Action.ENTER, max_reprices=1)) res = asyncio.run(loop.handle_expired(reg.working("req-0"))) assert res == Resolved.SKIPPED_SLOT_BUSY assert port.submits == [] # ── the fail-safe requote gate ─────────────────────────────────────────────── def test_requote_blocked_by_recent_own_fill(): # REST reconcile lags WS fills — inside the hot window, requote is refused (double-entry # 2026-06-10). Mutation: drop the hot-window check -> RED. clock = FakeClock(1000.0) port = FakePort(clock, FLAT, last_own_fill=998.0) # 2 s ago, < 5 s window loop, reg = _loop(port, clock) reg.register(_wo(Action.ENTER, max_reprices=1)) res = asyncio.run(loop.handle_expired(reg.working("req-0"))) assert res == Resolved.REQUOTE_BLOCKED assert port.submits == [] def test_requote_blocked_by_live_position(): clock = FakeClock(1000.0) port = FakePort(clock, FLAT, positions=[{"positionAmt": "0.5"}]) loop, reg = _loop(port, clock) reg.register(_wo(Action.ENTER, max_reprices=1)) res = asyncio.run(loop.handle_expired(reg.working("req-0"))) assert res == Resolved.REQUOTE_BLOCKED assert port.submits == [] def test_requote_fails_safe_on_probe_error(): # Ambiguity is not permission. A probe error blocks the requote. Mutation: return True # on exception -> RED. clock = FakeClock(1000.0) port = FakePort(clock, FLAT) port.positions_raises = True loop, reg = _loop(port, clock) reg.register(_wo(Action.ENTER, max_reprices=1)) res = asyncio.run(loop.handle_expired(reg.working("req-0"))) assert res == Resolved.REQUOTE_BLOCKED def test_entry_retry_allowed_when_flat_and_cold(): # The positive control: flat venue, no recent fill, budget remains → retry proceeds. clock = FakeClock(1000.0) port = FakePort(clock, FLAT, positions=[], last_own_fill=-1e9) loop, reg = _loop(port, clock) reg.register(_wo(Action.ENTER, max_reprices=1)) res = asyncio.run(loop.handle_expired(reg.working("req-0"))) assert res == Resolved.RETRIED assert len(port.submits) == 1 # ── sweep robustness ───────────────────────────────────────────────────────── def test_already_resolved_short_circuits(): clock = FakeClock() port = FakePort(clock, FLAT) loop, reg = _loop(port, clock) wo = _wo(Action.ENTER) # NOT registered res = asyncio.run(loop.handle_expired(wo)) assert res == Resolved.ALREADY_RESOLVED def test_sweep_survives_one_bad_order(): # BI-3: an expiry-handler exception drops THAT order (logged) and the sweep continues. clock = FakeClock(1000.0) port = FakePort(clock, _open("req-0")) # exit still open → escalate path port.cancel_raises = False loop, reg = _loop(port, clock) # good exit (escalates) + a poison order whose cancel raises mid-handle reg.register(_wo(Action.EXIT, tid="req-0")) results = asyncio.run(loop.resolve_expired()) assert Resolved.ESCALATED_MARKET in results # the healthy one still resolved def test_cancel_failure_does_not_abort_handling(): # A cancel that raises is logged, not fatal — the exit still escalates to MARKET. clock = FakeClock(1000.0) port = FakePort(clock, _open("req-0")) port.cancel_raises = True loop, reg = _loop(port, clock) reg.register(_wo(Action.EXIT)) res = asyncio.run(loop.handle_expired(reg.working("req-0"))) assert res == Resolved.ESCALATED_MARKET assert len(port.cancels) == 1 # cancel was attempted