"""V1: ObserveOnlyVenue — refusal + delegation contract.""" from __future__ import annotations import asyncio import sys sys.path.insert(0, "/mnt/dolphinng5_predict") sys.path.insert(0, "/mnt/dolphinng5_predict/nautilus_dolphin") import pytest from prod.clean_arch.violet.observe_guard import ObserveOnlyVenue, ObserveOnlyViolation class _Inner: def __init__(self): self.connected = False self.calls = [] def submit(self, intent): self.calls.append(("submit", intent)) return ["should-never-happen"] async def submit_async(self, intent): self.calls.append(("submit_async", intent)) return ["should-never-happen"] def cancel(self, order, reason=""): self.calls.append(("cancel", order)) return [] async def cancel_async(self, order, reason=""): self.calls.append(("cancel_async", order)) return [] async def connect(self): self.connected = True async def subscribe(self): if False: # pragma: no cover — generator shape yield None @pytest.mark.asyncio async def test_submit_and_cancel_raise_and_never_reach_inner(): inner = _Inner() guard = ObserveOnlyVenue(inner) with pytest.raises(ObserveOnlyViolation): guard.submit({"x": 1}) with pytest.raises(ObserveOnlyViolation): guard.cancel(object()) with pytest.raises(ObserveOnlyViolation): await guard.submit_async({}) with pytest.raises(ObserveOnlyViolation): await guard.cancel_async(object()) assert inner.calls == [] @pytest.mark.asyncio async def test_attribute_get_delegates(): inner = _Inner() guard = ObserveOnlyVenue(inner) assert guard.connected is False await guard.connect() assert inner.connected is True and guard.connected is True def test_attribute_set_delegates_to_inner(): """The runtime does `venue._kernel_ref = kernel` — assignment must land on the wrapped adapter, not the guard.""" inner = _Inner() guard = ObserveOnlyVenue(inner) sentinel = object() guard._kernel_ref = sentinel assert getattr(inner, "_kernel_ref") is sentinel assert not hasattr(type(guard), "_kernel_ref") def test_wrapped_mock_venue_full_kernel_drive_never_submits(): """Real ExecutionKernel over a wrapped MOCK venue: an ENTER intent is refused at the venue boundary; the kernel's venue-submit-failure rollback converts the refusal into a synthetic REJECT (slot returns to IDLE) and the inner venue NEVER sees the order.""" from prod.clean_arch.dita_v2.launcher import build_launcher_bundle from prod.clean_arch.dita_v2.contracts import ( KernelCommandType, KernelIntent, TradeSide, TradeStage, ) from datetime import datetime, timezone bundle = build_launcher_bundle(venue_mode="MOCK", max_slots=1) kernel = bundle.kernel inner = kernel.venue submitted = [] orig_submit = inner.submit inner.submit = lambda *a, **k: submitted.append(a) or orig_submit(*a, **k) kernel.venue = ObserveOnlyVenue(inner) intent = KernelIntent( timestamp=datetime.now(timezone.utc), intent_id="i-1", trade_id="T-OBS-1", slot_id=0, asset="BTCUSDT", side=TradeSide.SHORT, action=KernelCommandType.ENTER, reference_price=100.0, target_size=1.0, leverage=1.0, exit_leg_ratios=(1.0,), reason="test", metadata={}, stage=TradeStage.INTENT_CREATED, ) outcome = kernel.process_intent(intent) assert submitted == [] # nothing reached the venue assert kernel.slot(0).is_free() # FSM rolled back to IDLE assert any( "VENUE_SUBMIT_ERROR" in str(e.reason) for e in outcome.emitted_events ), "expected the synthetic REJECT from the guard refusal" if __name__ == "__main__": raise SystemExit(pytest.main([__file__, "-v"]))