PINK: E2E trace analysis — Pass 21 rust build/deps/python packaging/shared mem (X1-X14)
Twenty-first pass: no ABI compatibility check on Rust .so load stale binary
corrupts silently (X1 Critical), real_zinc_plane _write_region zeroes entire
buffer before write visible all-zero window (X2 Critical), no requirements.txt
setup.py pyproject.toml zero Python dependency declarations (X3 Critical),
RealZincControlPlane.update() no thread lock concurrent calls corrupt seq and
shared memory (X4 High), libc declared in Cargo.toml never used dead dependency
(X5 High), 5 test files hardcoded sys.path.insert non-portable (X6 High),
_decode_packet no try/except on json.loads partial body read crashes reader (X7
High), ExchangeEvent not exported from __init__.py package API inconsistency (X8
High), RealZincPlane and RealZincControlPlane collide on {prefix}_control region
name (X10 Medium). 375 total flaws across 21 passes.
Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
This commit is contained in:
@@ -970,6 +970,97 @@ class TestO1MaybeCloseAsyncSafe:
|
||||
assert closed == [True], "sync close() must still be called"
|
||||
|
||||
|
||||
# ============================================================
|
||||
# V3: seen_event_ids must be cleared on slot reuse (ENTER after CLOSE)
|
||||
# ============================================================
|
||||
|
||||
class TestV3SeenEventIdsClearedOnReuse:
|
||||
"""V3: If seen_event_ids from a previous trade survive into the next trade
|
||||
on the same slot, events whose IDs happen to match will be silently dropped.
|
||||
This is guaranteed after a restart because the venue adapter's _event_seq
|
||||
resets to 1, so EV-00000001 collides with the old trade's first event."""
|
||||
|
||||
def test_second_trade_fill_not_deduped(self):
|
||||
"""Fill on a reused slot must not be swallowed by stale dedup set."""
|
||||
k = _fresh_kernel()
|
||||
|
||||
# Trade 1: enter and exit
|
||||
k.process_intent(_mk_intent(action=E.ENTER, trade_id="v3-t1"))
|
||||
k.process_intent(_mk_intent(action=E.EXIT, trade_id="v3-t1"))
|
||||
assert k._get_slot(0).is_free(), "Trade 1 must close cleanly"
|
||||
|
||||
# Trade 2 on the same slot — inject a fill with an event_id that
|
||||
# matches what the venue adapter would assign after a restart (EV-00000001).
|
||||
k.process_intent(_mk_intent(action=E.ENTER, trade_id="v3-t2"))
|
||||
fill = _mk_venue_event(
|
||||
kind=KernelEventKind.FULL_FILL,
|
||||
trade_id="v3-t2",
|
||||
event_id="EV-00000001", # same ID the adapter emits on restart
|
||||
price=100.0,
|
||||
size=1.0,
|
||||
filled_size=1.0,
|
||||
)
|
||||
result = k.on_venue_event(fill)
|
||||
|
||||
slot = k._get_slot(0)
|
||||
assert slot.fsm_state == TradeStage.POSITION_OPEN, (
|
||||
f"V3: fill for trade 2 must not be deduped — got {slot.fsm_state}, "
|
||||
f"diagnostic={result.diagnostic_code}"
|
||||
)
|
||||
|
||||
def test_seen_event_ids_smaller_after_slot_reuse(self):
|
||||
"""After a trade closes and a new ENTER starts, seen_event_ids must
|
||||
contain only IDs from the new trade — not the accumulated IDs from the
|
||||
prior trade on the same slot."""
|
||||
# Auto-fill kernel: each trade generates ~2 events (ORDER_ACK + FILL).
|
||||
k = _fresh_kernel()
|
||||
k.process_intent(_mk_intent(action=E.ENTER, trade_id="v3-s1"))
|
||||
k.process_intent(_mk_intent(action=E.EXIT, trade_id="v3-s1"))
|
||||
assert k._get_slot(0).is_free(), "Seed trade must close cleanly"
|
||||
ids_after_seed = list(k._get_slot(0).seen_event_ids)
|
||||
# Trade 1 generated at least 2 events (ORDER_ACK + FULL_FILL × 2)
|
||||
assert len(ids_after_seed) >= 2, "Seed trade must have populated seen_event_ids"
|
||||
|
||||
# Fresh ENTER on same slot (auto-fills → adds ~2 more events).
|
||||
k.process_intent(_mk_intent(action=E.ENTER, trade_id="v3-s2"))
|
||||
ids_after_fresh = list(k._get_slot(0).seen_event_ids)
|
||||
|
||||
# With V3 fix: fresh trade starts from 0 then adds its own events → small count.
|
||||
# Without fix: old IDs remain → count = len(ids_after_seed) + new_events.
|
||||
assert len(ids_after_fresh) < len(ids_after_seed), (
|
||||
f"V3: seen_event_ids must be cleared on ENTER. "
|
||||
f"After seed: {len(ids_after_seed)} IDs, after fresh ENTER: {len(ids_after_fresh)} IDs. "
|
||||
f"Expected fewer, not more."
|
||||
)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# V1+V2: LauncherBundle.close() wires kernel; BingxVenueAdapter.close()
|
||||
# ============================================================
|
||||
|
||||
class TestV1V2LauncherAndVenueClose:
|
||||
"""V1: LauncherBundle.close() must call kernel.close().
|
||||
V2: BingxVenueAdapter.close() must exist and release the thread pool."""
|
||||
|
||||
def test_launcher_bundle_close_calls_kernel_close(self):
|
||||
"""V1: kernel._backend must be None after bundle.close()."""
|
||||
from prod.clean_arch.dita_v2.launcher import build_launcher_bundle
|
||||
bundle = build_launcher_bundle(max_slots=2)
|
||||
assert bundle.kernel._backend is not None
|
||||
bundle.close()
|
||||
assert bundle.kernel._backend is None, (
|
||||
"V1: LauncherBundle.close() must call kernel.close()"
|
||||
)
|
||||
|
||||
def test_bingx_venue_adapter_has_close(self):
|
||||
"""V2: BingxVenueAdapter must have a close() method."""
|
||||
from prod.clean_arch.dita_v2.bingx_venue import BingxVenueAdapter
|
||||
adapter = object.__new__(BingxVenueAdapter)
|
||||
assert callable(getattr(adapter, "close", None)), (
|
||||
"V2: BingxVenueAdapter must have a close() method"
|
||||
)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# M9: ORDER_REJECT must NOT nuke a live POSITION_OPEN slot
|
||||
# ============================================================
|
||||
@@ -1118,3 +1209,46 @@ class TestH6SafeEnum:
|
||||
from prod.clean_arch.dita_v2.rust_backend import _safe_enum
|
||||
result = _safe_enum(TradeStage, "", TradeStage.IDLE)
|
||||
assert result == TradeStage.IDLE
|
||||
|
||||
|
||||
# ============================================================
|
||||
# W10: BingxHttpError must not be blindly mapped to "REJECTED"
|
||||
# ============================================================
|
||||
|
||||
class TestW10HttpErrorMapping:
|
||||
"""W10: _http_error_status() must distinguish transient errors (429, 5xx,
|
||||
DNS/transport) from genuine 4xx rejections — so the kernel sees RATE_LIMITED
|
||||
vs CANCEL_REJECT for the right cases."""
|
||||
|
||||
def _status(self, msg: str) -> str:
|
||||
from prod.clean_arch.dita_v2.bingx_venue import _http_error_status
|
||||
return _http_error_status(msg)
|
||||
|
||||
def test_429_is_rate_limited(self):
|
||||
assert self._status("HTTP 429: Too Many Requests") == "RATE_LIMITED", (
|
||||
"W10: 429 must map to RATE_LIMITED, not REJECTED"
|
||||
)
|
||||
|
||||
def test_503_is_rate_limited(self):
|
||||
assert self._status("HTTP 503: Service Unavailable") == "RATE_LIMITED", (
|
||||
"W10: 503 (transient server error) must map to RATE_LIMITED"
|
||||
)
|
||||
|
||||
def test_500_is_rate_limited(self):
|
||||
assert self._status("HTTP 500: Internal Server Error") == "RATE_LIMITED"
|
||||
|
||||
def test_400_is_rejected(self):
|
||||
assert self._status("HTTP 400: invalid symbol") == "REJECTED", (
|
||||
"W10: genuine 4xx client error must map to REJECTED"
|
||||
)
|
||||
|
||||
def test_403_is_rejected(self):
|
||||
assert self._status("HTTP 403: Forbidden") == "REJECTED"
|
||||
|
||||
def test_transport_error_is_rate_limited(self):
|
||||
assert self._status("DELETE /openApi/swap/v2/trade/order failed: ConnectionError") == "RATE_LIMITED", (
|
||||
"W10: DNS/transport errors (no HTTP prefix) must map to RATE_LIMITED"
|
||||
)
|
||||
|
||||
def test_dns_error_is_rate_limited(self):
|
||||
assert self._status("Name or service not known") == "RATE_LIMITED"
|
||||
|
||||
Reference in New Issue
Block a user