PINK: E2E trace analysis — Pass 6 deep math/tests/concurrency/security (I1-I22)

Sixth pass: entry-fill accumulation bug (multiple partial fills overwrite
size), crash durability (slot state lost between step 2-5 of process_intent),
seen_event_ids lost on restart (double event processing), idempotency gap
(no newClientOrderId), no graceful degradation, no startup reconcile from
Zinc, Zinc SHM world-readable, KernelSlotView unrestricted write access,
sys.path injection at import time. 22 new flaws. Combined catalog now 160.

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
This commit is contained in:
Codex
2026-06-01 19:01:49 +02:00
parent 1f5a3266c4
commit 9b017e903b
2 changed files with 474 additions and 1 deletions

View File

@@ -2572,3 +2572,444 @@ Monkey-patches Python stdlib `socket.getaddrinfo` to force IPv4 as a workaround
| G | Domain Scans (Pass 4) | 36 | 4 | 11 | 11 | 8 | 2 | | G | Domain Scans (Pass 4) | 36 | 4 | 11 | 11 | 8 | 2 |
| H | Edge Domains (Pass 5) | 22 | 3 | 9 | 5 | 4 | 1 | | H | Edge Domains (Pass 5) | 22 | 3 | 9 | 5 | 4 | 1 |
| **Total** | | **138** | **8** | **30** | **37** | **44** | **19** | | **Total** | | **138** | **8** | **30** | **37** | **44** | **19** |
---
## PASS 6 — MATH, TESTS, CONCURRENCY, RECOVERY, SECURITY
### I1: Entry `apply_fill` sets `slot.size = fill_size` — multiple partial fills overwrite instead of accumulating
**File:** `_rust_kernel/src/lib.rs:798`
```rust
// Entry fill path in apply_fill:
slot.size = fill_size; // DIRECT ASSIGNMENT
slot.initial_size = slot.initial_size.max(fill_size); // max, not sum
```
If a single entry order receives multiple partial fills (e.g., LIMIT order on the book):
- Fill #1: `fill_size = 0.5` → `slot.size = 0.5`, `initial_size = max(0, 0.5) = 0.5`
- Fill #2: `fill_size = 0.3` → `slot.size = 0.3`, `initial_size = max(0.5, 0.3) = 0.5`
After both fills, the actual position is 0.8 but `slot.size` reports 0.3. The position is under-counted by 0.5 — 62.5% error.
The exit path correctly does `slot.size = (slot.size - fill_size).max(0.0)` (subtractive). The entry path should accumulate: `slot.size += fill_size`.
This only manifests with LIMIT orders that receive multiple partial fills over time — a scenario entirely absent from tests (I7).
**Severity: Critical**
### I2: `exit_ratio = 0.0` creates zero-size exit order — slot stuck in EXIT_REQUESTED
**File:** `_rust_kernel/src/lib.rs:467-469`
```rust
let exit_ratio = slot.next_exit_ratio(); // returns 0.0 from exit_leg_ratios=[0.0, ...]
let base_size = if slot.initial_size > 0.0 { ... } else { slot.size };
let exit_size = (base_size * exit_ratio).max(0.0); // = 0.0
```
When `exit_leg_ratios` contains `0.0` in any position, `exit_size = 0.0`. The zero-size exit order is submitted to the venue (`intended_size = 0`). On the fill side, `realized_pnl()` returns 0.0 (guarded by `exit_size <= 0.0`), and `slot.size` is unchanged. The slot stays in `EXIT_REQUESTED` with no means to advance — the leg is consumed but nothing happened. Subsequent exits may eventually handle this, but the zero-size leg is a wasted FSM transition that leaves the slot in a confusing intermediate state.
Also: `NaN` in `exit_leg_ratios` (from `clamp(0.0, 1.0)` not guarding NaN, though serde_json rejects NaN) would produce the same zero-size exit behavior.
**Severity: Medium**
### I3: `entry_price` inconsistency — Python uses falsy check, Rust uses `<= 0.0`
**File:** `contracts.py:88-98` (Python), `_rust_kernel/src/lib.rs:227-228` (Rust)
```python
# Python TradeSlot.mark_price():
self.entry_price = self.entry_price or price # falsy — keeps -0.5, 0.0 replaced
# Rust TradeSlot::mark_price():
if self.entry_price <= 0.0 { self.entry_price = price; } // catches -0.5, replaces it
```
If `entry_price` is negative (possible only via `set_slot_json` direct injection — not from normal trading), Python keeps it and computes `unrealized_pnl` with wrong sign. Rust replaces it. The Python-side `mark_price` is only called from `ExecutionKernel.mark_price()` in rust_backend.py:LOW-1, which never writes back to the Rust kernel — so the Python-side calculation is purely local and the inconsistency has no effect on the Rust kernel's canonical state. However, the `observe_slots` call after `mark_price` re-reads from the Rust kernel, which recomputes PnL correctly. The Python-side mark_price is effectively wasted computation that never feeds back.
**Severity: Informational**
### I4: No Rust unit tests for 99% of kernel functionality
**File:** `_rust_kernel/src/lib.rs:1731-1765`
Only 1 Rust test exists: `enter_then_ack_fill` — creates a 2-slot kernel, submits ENTER, sends ACK, asserts state transitions.
**Not tested in Rust:**
- EXIT, CANCEL, MARK_PRICE, RECONCILE, CONTROL actions
- Any FILL event (PARTIAL, FULL)
- CANCEL_ACK, CANCEL_REJECT, ORDER_REJECT
- RATE_LIMITED handling
- Multi-leg exits
- `consume_exit_leg` edge cases
- `realized_pnl()` formula with boundary values
- `mark_price()` with extreme values
- `resolve_slot()` fallback path
- `reconcile_slots_json` dedup/overflow
- Any C FFI boundary function
- Any serde deserialization failure
- Null pointer handling
No `#[cfg(test)]` module exists — the single test is inline. No Rust integration tests (`tests/` directory).
**Severity: High**
### I5: `MockVenueScenario` rejection flags exist but zero tests use them
**File:** `mock_venue.py:23-35`
```python
@dataclass
class MockVenueScenario:
reject_entries: bool = False
reject_exits: bool = False
cancel_reject: bool = False
```
Three boolean flags to simulate venue rejection of orders. Not a single test in `test_flaws.py` sets any of them to `True`. The `ORDER_REJECT` handler in the Rust kernel's `on_venue_event` exists (lib.rs lines ~1440-1460) but is never exercised by any test.
Similarly, `entry_partial_fill_ratio` and `exit_partial_fill_ratio` exist on `MockVenueScenario` but only one test (`test_cancel_entry_with_partial_fill`) uses partial fills at all — and it only checks `size > 0`, not the full capital-accrual chain.
**Severity: High**
### I6: No LIMIT order test through the full kernel path
The test suite has zero LIMIT orders. The Rust kernel doesn't even contain LIMIT-specific logic — all orders are MARKET. The generated live tests have `limit_does_not_fill` and `limit_immediate_fill` scenario placeholders, but:
- `limit_does_not_fill` uses `reference_price=0.0` (not a real LIMIT order)
- `limit_immediate_fill` uses `target_size=-0.001` (negative size → clamped to 0.0)
Neither scenario actually submits a LIMIT order with `order_type="LIMIT"` and a non-zero `limit_price`. The `_legacy_intent` bug (H7) would convert any LIMIT attempt to MARKET anyway.
The only LIMIT-related code is the Rust kernel's `if intent.order_type == "LIMIT"` branches (lib.rs:503, 1584) which are compile-time dead code — `KernelIntent` doesn't have an `order_type` field that serde would populate.
**Severity: High**
### I7: Three weak/vacuous assertions in `test_flaws.py`
**File:** `test_flaws.py`
1. **Line 512:** `assert order.metadata.get("asset") is not None or order.metadata.get("slot_id") is not None` — mock venue always sets both, this can never fail.
2. **Line 700:** `test_pnl_warning_on_unsettled_reentry` — titled to assert a warning is raised but only checks `r.accepted`. Never checks `diagnostic_code` or verifies the warning was issued.
3. **Line 318:** `assert slot.active_entry_order is None or slot.active_entry_order.status == VenueOrderStatus.FILLED` — the `or` allows two different scenarios to pass, reducing diagnostic power.
**Severity: Low**
### I8: `slot.size = fill_size` entry overfill no guard
**File:** `_rust_kernel/src/lib.rs:798`
Already noted in I1 — entry fill sets `slot.size` directly to `fill_size`. Unlike exit fill which has `(slot.size - fill_size).max(0.0)`, there's no guard against entry overfill (venue fills more than the intended order size). For MARKET orders this is fine (one fill per order), but for LIMIT orders with multiple partial fills, the accumulated fill could exceed `initial_size`.
**Severity: Low** (only relevant with LIMIT + partial fills, which don't exist in the codebase)
### I9: No crash durability — slot state is pure in-memory until step 7 of process_intent
**File:** `rust_backend.py:470-560`
The `process_intent` sequence:
1. validate → 2. Rust FSM → 3. venue.submit() → 4. on_venue_event() → 5. projection → 6. zinc_plane
If the process crashes between steps 2-5, the slot state accumulated in the Rust kernel's in-memory `KernelCore` is **completely lost**. The Rust kernel has no WAL, no journal, no persistent store. On restart, `ExecutionKernel.__init__` creates a fresh `KernelCore` with all slots IDLE.
The crash between step 3 and step 5 is the most dangerous: the exchange has an open order/position, but the kernel has no record of it. On restart:
- The Rust kernel sees `slot.slot_id = IDLE`
- The Zinc slot cache may or may not have the pre-crash state (depends on timing)
- No code on restart loads Zinc state back into the Rust kernel (I14)
- The exchange order lives until it fills (unexpected position) or is manually cancelled
**Concrete example:** `venue.submit()` sends POST to BingX, order placed. HTTP response arrives. `on_venue_event(ORDER_ACK)` transitions slot to `ENTRY_WORKING`. Crash between returning from `on_venue_event` and `zinc_plane.write_slot()`. On restart: slot is IDLE, no active entry order, `_last_settled_pnl` is reset. The exchange has a live ENTRY_WORKING order. Next `process_intent(ENTER)` gets `SLOT_BUSY` because... wait — the fresh kernel doesn't know the order exists, so it sees slot as IDLE and allows a new ENTER. The old order fills on the exchange → double position.
**Severity: Critical**
### I10: `seen_event_ids` lost on restart — events replayed after restart are double-processed
**File:** `_rust_kernel/src/lib.rs:672-683`
`seen_event_ids` is per-slot, per-[`KernelCore`] instance — purely in-process memory. On restart with a fresh `KernelCore`, every slot has `seen_event_ids = Vec::new()`. If events are replayed (from `pump_venue_events()` calling `venue.reconcile()` which re-fetches exchange state):
1. Original run: order fills → `FULL_FILL` with `event_id = "EV-00000042"` → processed, slot → `POSITION_OPEN`
2. Crash
3. Restart: fresh `KernelCore`, `seen_event_ids` empty
4. `pump_venue_events()` fetches same exchange state → new `VenueEvent` objects with new event IDs (adapter's `_event_seq` resets)
5. Rust kernel sees these as novel events — processes them again
6. Position is double-booked, PnL double-settled
The `bingx_venue._event_seq` is an instance-level `itertools.count()` starting from 1. On adapter restart, it resets — so the new event IDs won't match the old ones anyway. Dedup is fundamentally impossible across restarts.
**Severity: Critical**
### I11: No idempotency key (`newClientOrderId`) sent to BingX
**File:** `bingx_venue.py:282-285`, `bingx_direct.py` (external)
BingX supports `newClientOrderId` for order idempotency — sending the same ID twice returns the original order status instead of creating a duplicate. The DITAv2 kernel passes `intent.intent_id` as `decision_id` to the legacy adapter, but there's no guarantee this maps to `newClientOrderId` in the BingX payload.
If the HTTP POST to `/trade/order` times out before the response is read:
1. The order was placed on the exchange
2. `_call_backend` raises a `BingxHttpError` (or similar network exception)
3. `process_intent()` propagates the exception — no retry
4. Next cycle: caller may retry with a new `intent_id`
5. Second POST creates a **second order** on the exchange — duplicate position
Without a client-order-id that persists across retries, the system can create duplicate orders on network timeouts. The exchange has no way to deduplicate.
**Severity: High**
### I12: No graceful degradation for ANY subsystem
Every subsystem failure mode examined:
| Subsystem | Failure | Current behavior |
|-----------|---------|-----------------|
| Zinc SHM init | Corrupted region, OOM | Silent fallback to InMemoryZincPlane (no operator signal) |
| Zinc SHM write | Region overflow, write error | Unhandled exception → kernel crashes |
| Hazelcast write | Cluster unavailable | `.put()` raises → unhandled exception → kernel crashes |
| ClickHouse journal | Sink failure | Exception propagates (no try/except in callers) |
| BingX HTTP | Timeout, rate limit | Exception or REJECTED → slot stuck in ORDER_REQUESTED |
| Rust kernel | Null pointer from FFI | `_take_string` raises RuntimeError → kernel crash |
| Memory pressure | OOM | Process killed by kernel. No signal handler. Zero signal handlers. |
**No subsystem has a graceful degradation path.** No circuit breaker, no retry queue, no fallback to log-only mode, no offline/cached trading mode. Every failure (except the two init-time silent fallbacks) crashes the current kernel operation.
**Severity: High**
### I13: Stray venue event can reactivate a CLOSED slot — no guard
**File:** `_rust_kernel/src/lib.rs:625+`
The `on_venue_event` function has no guard for closed slots:
```rust
fn on_venue_event(&mut self, event: VenueEvent) -> KernelResult {
// ... resolve slot, check duplicates ...
// NO: if slot.closed { return ... }
let prev_state = slot.fsm_state.clone();
match event.kind {
SOME_EVENT_KIND => { /* transitions regardless of closed state */ }
}
}
```
If a stray venue event arrives for a CLOSED slot:
- `ORDER_ACK` → sets `ENTRY_WORKING` — slot re-opens from CLOSED
- `FULL_FILL` → `apply_fill` runs → `slot.size = fill_size`, `fsm_state = POSITION_OPEN`
- `ORDER_REJECT` → clears `trade_id`, `asset`, sets `IDLE` — actually benign reset
A CLOSED slot should be a terminal state that rejects all events. Currently only CANCEL_ACK is harmless on a closed slot; the rest can revive a dead position.
**Severity: High**
### I14: No `reconcile_from_slots` call on startup — Zinc state never loaded into Rust kernel
**Files:** `rust_backend.py:435-465` (init), `real_zinc_plane.py:95-115` (init)
On restart:
1. `RealZincPlane.__init__` reads state from Zinc shared memory into `_slot_cache`
2. `ExecutionKernel.__init__` creates fresh `KernelCore` — all slots IDLE
3. `KernelStateView(self)` reads from the fresh kernel
4. `account.observe_slots([self._get_slot(i) for i in range(max_slots)])` — all slots IDLE
Step 3 and 4 read from the Rust kernel, NOT from Zinc. The Zinc `_slot_cache` populated in step 1 is **never loaded into the Rust kernel**. The `reconcile_on_restart` flag exists in `KernelControlSnapshot` (default `True`) but is never checked anywhere in `ExecutionKernel.__init__` or the launcher.
The system always starts with a blank state even when durable shared memory state exists.
**Severity: High**
### I15: CANCEL_REJECT doesn't clear `active_exit_order` — slot stuck in EXIT_WORKING
**File:** `_rust_kernel/src/lib.rs:1165-1175`
```rust
KernelEventKind::CANCEL_REJECT => {
if slot.fsm_state == TradeStage::EXIT_WORKING {
// stays EXIT_WORKING — no state transition
// active_exit_order remains attached
}
diagnostic_code = KernelDiagnosticCode::CANCEL_REJECTED;
}
```
When the exchange rejects a cancel (typically because the order was already filled or no longer exists), the slot stays in `EXIT_WORKING` with `active_exit_order` still attached. Every subsequent CANCEL attempt hits the same path — the exchange returns "order not found," the kernel sees `CANCEL_REJECT`, and the slot is stuck forever.
If the order was already filled (CANCEL_REJECT means "can't cancel, no longer open"), the slot should check the actual position size and potentially transition to `POSITION_OPEN` or `CLOSED` depending on fill status.
**Severity: Medium**
### I16: Zinc shared memory — world-readable/writable by same-machine processes
**Files:** `real_control_plane.py`, `real_zinc_plane.py`
The Zinc shared memory regions are created with these names:
```python
self.region_name = f"{base}_intent" # e.g., "dita_v2_intent"
self.state_name = f"{base}_state" # "dita_v2_state"
self.control_name = f"{base}_control" # "dita_v2_control"
```
Region names are predictable (prefix defaults to `"dita_v2"`). The `SharedRegion` uses POSIX `shm_open` — the default permissions depend on umask (typically `0644` or `0600`). Any process on the same machine can:
- **Read**: Open the region → `as_buffer()` → `_decode_packet()` → read all slot state, PnL, open orders, control settings
- **Write**: Open the region → forge a packet (`struct.pack("!QQ", seq, len) + json_bytes`) → overwrite slot state, inject fake intents, modify control plane
No access control, no encryption, no integrity check (HMAC/signature) on the wire format. The sequence number is the only ordering mechanism, and it's trivially predictable.
**Severity: High**
### I17: `KernelSlotView` exposes full slot state via unrestricted `__getattr__`/`__setattr__`
**File:** `rust_backend.py:411-460`
```python
class KernelSlotView:
def __getattr__(self, name):
slot = self._snapshot()
return getattr(slot, name) # read ANY field
def __setattr__(self, name, value):
setattr(slot, name, value)
self._kernel._set_slot(slot) # write ANY field — bypasses FSM
```
Any code with a `KernelSlotView` reference can:
- Read all slot fields: `trade_id`, `size`, `entry_price`, `unrealized_pnl`, `realized_pnl`, `seen_event_ids`, `metadata`
- Write all slot fields: `slot_view.realized_pnl = -9999999` — directly manipulates PnL figures flowing into capital settlement
The `_set_slot` call writes through to the Rust kernel without any FSM validation. The entire kernel state is exposed through mutable Python objects with zero access control.
**Severity: High**
### I18: `sys.path.insert(0, ...)` at import time in three production files
**Files:** `real_control_plane.py:14`, `real_zinc_plane.py:22`, `test_flaws.py:13`, `_build_pink_bodies.py:2`, `_gen_test.py:3`
```python
# real_control_plane.py, real_zinc_plane.py — at MODULE LEVEL:
sys.path.insert(0, str(_ZINC_ADAPTER_PATH))
# test_flaws.py, _build_pink_bodies.py, _gen_test.py — at MODULE LEVEL:
sys.path.insert(0, '/mnt/dolphinng5_predict')
```
`sys.path.insert(0, ...)` gives the injected path highest import priority. An attacker with filesystem write access to the inserted path can create a malicious module that shadows a legitimate import (e.g., `zinc.py`, `utils.py`, `typing.py`). When any subsequent `from X import Y` runs, the attacker's module loads with the full privileges of the kernel process.
The production files use a relative path resolution (`Path(__file__).resolve().parents[3] / "zinc" / "adapters" / "python"`), while the test files use a hardcoded absolute path (`'/mnt/dolphinng5_predict'`). Both patterns are dangerous.
**Severity: High**
### I19: `pump_venue_events` re-fetches exchange state that can produce phantom position events
**File:** `bingx_venue.py:395-415`
`reconcile()` calls `_backend_snapshot()` which fetches current positions and open orders from the exchange. The `_events_from_snapshot` method diff-s the current snapshot against the last-known snapshot to produce events:
```python
def _events_from_snapshot(self, before, after):
for symbol, current_pos in after.open_positions.items():
prev_pos = before.open_positions.get(symbol)
if current_pos and (not prev_pos or abs(prev_pos.position_amount) < 1e-12):
# This looks like a new position — emit event
```
If `before` is stale (from `_backend_snapshot` timeout), the diff can produce spurious events. A position that existed before the crash is absent from the stale snapshot → the diff sees it as "new" → emits an entry fill event → Rust kernel processes it as a fresh enter → double position. This compounds with I10 (seen_event_ids lost on restart).
**Severity: High**
### I20: `exit_leg_ratios` no guard against empty list — `next_exit_ratio` returns 1.0
**File:** `contracts.py:196-198`
```python
def next_exit_ratio(self) -> float:
if self.active_leg_index < len(self.exit_leg_ratios):
return self.exit_leg_ratios[self.active_leg_index]
return 1.0
```
If `exit_leg_ratios` is empty (default `(1.0,)` prevents this normally, but the default is only `(1.0,)` in the dataclass), `next_exit_ratio()` returns `1.0`. This is the same as "exit everything" — the `consume_exit_leg` then advances `active_leg_index` to `min(1, 1) = 1`, and `all_legs_done = active_leg_index >= exit_leg_ratios.len()` → `1 >= 0 = true` → slot closes. The empty-ratios edge case is silently handled with `unwrap_or(1.0)`, which happens to be correct — but undocumented.
**Severity: Informational**
### I21: No test for rate-limited events — `RATE_LIMITED` kernel path is dead code
**File:** `_rust_kernel/src/lib.rs` (event handler), `MockVenueScenario.mock_venue.py` (no rate_limit flag)
The Rust kernel has a handler for `KernelEventKind::RATE_LIMITED` (lib.rs lines ~1480-1500). The event flows through the Python bridge's `process_intent()` rate-limit detection (rust_backend.py:585-593). But `MockVenueScenario` has no flag to emit rate-limited events. The only path to trigger `RATE_LIMITED` is from the real BingX adapter — which requires live exchange connectivity.
The entire RATE_LIMITED code path — in both Python and Rust — is untested in CI. Any bug in this path only surfaces in production under rate-limit conditions.
**Severity: Medium**
### I22: Thread pool for `_run` — `max_workers=3` shared across ALL adapter instances
**File:** `bingx_venue.py:236-245**
```python
@classmethod
def _get_executor(cls):
if cls._EXECUTOR is None:
with cls._EXECUTOR_LOCK:
if cls._EXECUTOR is None:
cls._EXECUTOR = ThreadPoolExecutor(max_workers=3, ...)
return cls._EXECUTOR
```
Class-level singleton — all `BingxVenueAdapter` instances share the same 3-thread pool. With the runtime's `step()` calling `submit()` (1 thread) + `_backend_snapshot` (potentially another thread for open orders) + `cancel()` (1 thread in parallel), all 3 threads are consumed. A fourth concurrent call blocks the calling thread at `.result()` indefinitely — freezing the entire event loop.
The pool is never shut down. If a `BingxVenueAdapter` is destroyed, the threads remain running (zombie workers). No `close()`/`disconnect()` path shuts down the executor.
**Severity: Medium**
---
## Pass 6 Summary
| # | Flaw | Layer | Severity |
|---|------|-------|----------|
| I1 | Entry `apply_fill` multiple partial fills overwrite size instead of accumulating | Rust | **Critical** |
| I2 | Zero exit_ratio creates zero-size exit order — slot stuck in EXIT_REQUESTED | Rust | Medium |
| I3 | entry_price inconsistency — Python falsy vs Rust `<= 0.0` gate | Bridge | Info |
| I4 | Only 1 Rust unit test for 1765-line kernel — 99% untested at Rust layer | Rust | **High** |
| I5 | MockVenueScenario rejection flags exist but zero tests use them | Test | **High** |
| I6 | No LIMIT order test through full kernel path | Test | **High** |
| I7 | Three weak/vacuous assertions in test_flaws.py | Test | Low |
| I8 | Entry overfill no guard | Rust | Low |
| I9 | No crash durability — slot state pure in-memory until step 7 of process_intent | Bridge | **Critical** |
| I10 | seen_event_ids lost on restart — events double-processed | Rust | **Critical** |
| I11 | No idempotency key sent to BingX — lost response creates duplicate orders | Venue | **High** |
| I12 | No graceful degradation for ANY subsystem | All | **High** |
| I13 | Stray venue event can reactivate CLOSED slot — no guard | Rust | **High** |
| I14 | No reconcile_from_slots call on startup — Zinc state never loaded into kernel | Restart | **High** |
| I15 | CANCEL_REJECT doesn't clear active_exit_order — slot stuck in EXIT_WORKING | Rust | Medium |
| I16 | Zinc shared memory world-readable/writable by same-machine processes | Zinc | **High** |
| I17 | KernelSlotView unrestricted getattr/setattr — bypasses all FSM guards | Bridge | **High** |
| I18 | sys.path.insert(0) at import time in 3 production files — malicious module loading | Build | **High** |
| I19 | pump_venue_events stale snapshot diff produces phantom position events | Venue | **High** |
| I20 | exit_leg_ratios empty list — next_exit_ratio defaults to 1.0 (undocumented) | Contracts | Info |
| I21 | RATE_LIMITED code path in both Python and Rust is completely untested | All | Medium |
| I22 | Thread pool max_workers=3 shared across all adapter instances — never shut down | Venue | Medium |
### Pass 6 Severity Distribution
| Severity | Count |
|----------|-------|
| **Critical** | 3 (I1, I9, I10) |
| **High** | 9 (I4, I5, I6, I11, I12, I13, I14, I16, I17, I18, I19) |
| Medium | 4 (I2, I15, I21, I22) |
| Low | 2 (I7, I8) |
| Info | 2 (I3, I20) |
### Combined Catalog (All 6 Passes)
| Pass | Focus | Count | Critical | High | Medium | Low | Info |
|------|-------|-------|----------|------|--------|-----|------|
| A | Architectural | 15 | 0 | 2 | 0 | 2 | 11 |
| T | Threading/Atomicity | 9 | 1 | 3 | 3 | 2 | 0 |
| E | E2E Trace (Pass 1) | 26 | 0 | 4 | 10 | 11 | 1 |
| F | Deep E2E (Pass 3) | 30 | 0 | 1 | 8 | 17 | 4 |
| G | Domain Scans (Pass 4) | 36 | 4 | 11 | 11 | 8 | 2 |
| H | Edge Domains (Pass 5) | 22 | 3 | 9 | 5 | 4 | 1 |
| I | Pass 6 (Math/Tests/Recovery/Security) | 22 | 3 | 11 | 4 | 2 | 2 |
| **Total** | | **160** | **11** | **41** | **41** | **46** | **21** |

View File

@@ -20,7 +20,8 @@
| F | Deep E2E (Pass 3) | 30 | 0 | 1 | 8 | 17 | 4 | | F | Deep E2E (Pass 3) | 30 | 0 | 1 | 8 | 17 | 4 |
| G | Domain Scans (Pass 4) | 36 | 4 | 11 | 11 | 8 | 2 | | G | Domain Scans (Pass 4) | 36 | 4 | 11 | 11 | 8 | 2 |
| H | Edge Domains (Pass 5) | 22 | 3 | 9 | 5 | 4 | 1 | | H | Edge Domains (Pass 5) | 22 | 3 | 9 | 5 | 4 | 1 |
| **Total** | | **138** | **8** | **30** | **37** | **44** | **19** | | I | Pass 6 (Math/Tests/Recovery/Security) | 22 | 3 | 11 | 4 | 2 | 2 |
| **Total** | | **160** | **11** | **41** | **41** | **46** | **21** |
--- ---
@@ -161,6 +162,37 @@
--- ---
## I-Series: Math, Tests, Concurrency, Recovery, Security (Pass 6)
*Full detail in TRACE doc under "PASS 6 — MATH, TESTS, CONCURRENCY, RECOVERY, SECURITY."*
| # | Flaw | Layer | Severity |
|---|------|-------|----------|
| I1 | Entry `apply_fill` multiple partial fills overwrite size instead of accumulating | Rust | **Critical** |
| I2 | Zero exit_ratio creates zero-size exit order — slot stuck in EXIT_REQUESTED | Rust | Medium |
| I3 | entry_price inconsistency — Python falsy vs Rust `<= 0.0` gate | Bridge | Info |
| I4 | Only 1 Rust unit test for 1765-line kernel — 99% untested at Rust layer | Rust | **High** |
| I5 | MockVenueScenario rejection flags exist but zero tests use them | Test | **High** |
| I6 | No LIMIT order test through full kernel path | Test | **High** |
| I7 | Three weak/vacuous assertions in test_flaws.py | Test | Low |
| I8 | Entry overfill no guard | Rust | Low |
| I9 | No crash durability — slot state pure in-memory until step 7 of process_intent | Bridge | **Critical** |
| I10 | seen_event_ids lost on restart — events double-processed | Rust | **Critical** |
| I11 | No idempotency key sent to BingX — lost response creates duplicate orders | Venue | **High** |
| I12 | No graceful degradation for ANY subsystem | All | **High** |
| I13 | Stray venue event can reactivate CLOSED slot — no guard | Rust | **High** |
| I14 | No reconcile_from_slots call on startup — Zinc state never loaded into kernel | Restart | **High** |
| I15 | CANCEL_REJECT doesn't clear active_exit_order — slot stuck in EXIT_WORKING | Rust | Medium |
| I16 | Zinc shared memory world-readable/writable by same-machine processes | Zinc | **High** |
| I17 | KernelSlotView unrestricted getattr/setattr — bypasses all FSM guards | Bridge | **High** |
| I18 | sys.path.insert(0) at import time in 3 production files — malicious module loading | Build | **High** |
| I19 | pump_venue_events stale snapshot diff produces phantom position events | Venue | **High** |
| I20 | exit_leg_ratios empty list — next_exit_ratio defaults to 1.0 (undocumented) | Contracts | Info |
| I21 | RATE_LIMITED code path in both Python and Rust is completely untested | All | Medium |
| I22 | Thread pool max_workers=3 shared across all adapter instances — never shut down | Venue | Medium |
---
## H-Series: Edge Domains — Dependencies, Error Handling, Types, Contracts (Pass 5) ## H-Series: Edge Domains — Dependencies, Error Handling, Types, Contracts (Pass 5)
*Full detail in TRACE doc under "PASS 5 — EDGE DOMAINS."* *Full detail in TRACE doc under "PASS 5 — EDGE DOMAINS."*