PINK: E2E trace analysis — Pass 18 rust test gaps/accounting/FFI types (U1-U14)

Eighteenth pass: R2 compares cumulative vs last-fill realized PnL broken after
2nd fill (U3 Critical), R4 compares open_notional vs used_margin fundamentally
different quantities (U4 Critical), on_venue_event/apply_fill no NaN guards
price/size propagates NaN (U6 Critical), order_type/limit_price sent to Rust
no fields silently dropped (U1 High), VenueEventStatus expects
"CANCEL_REJECTED" typo fails deserialization (U2 High), R3 skipped when
len(e.positions)==0 silent false negative (U5 High), zero Rust tests for
ORDER_REJECT/PARTIAL_FILL/TERMINAL_STATE guard (U7 High), safe_float returns
NaN/Inf contradicts _safe (U8 Medium), _scan_slots uses metadata leverage not
slot.leverage (U9 Medium). 333 total flaws across 18 passes.

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
This commit is contained in:
Codex
2026-06-02 14:47:36 +02:00
parent 66b403ff7d
commit 94078ee8fe
2 changed files with 416 additions and 1 deletions

View File

@@ -6385,3 +6385,394 @@ The backup `rust_backend.py` lacks the Rust FFI integration, has no `_first_inva
| S | Pass 16 (Error Handling/Arithmetic/Test Infra) | 16 | 4 | 7 | 5 | 0 | 0 | | S | Pass 16 (Error Handling/Arithmetic/Test Infra) | 16 | 4 | 7 | 5 | 0 | 0 |
| T | Pass 17 (Unsafe Review/Dead Code/Build/Protocols) | 14 | 0 | 5 | 5 | 4 | 0 | | T | Pass 17 (Unsafe Review/Dead Code/Build/Protocols) | 14 | 0 | 5 | 5 | 4 | 0 |
| **Total** | | **319** | **27** | **95** | **92** | **64** | **37** | | **Total** | | **319** | **27** | **95** | **92** | **64** | **37** |
---
## PASS 18 — RUST TEST GAPS, ACCOUNTING RECONCILIATION BUGS, FFI TYPE MISMATCHES
### U1: Rust `KernelIntent` has no `order_type` or `limit_price` fields — Python sends them, serde silently drops them
**Files:** `rust_backend.py:375-377` (Python sends), `_rust_kernel/src/lib.rs:439-456` (Rust receives)
Python's `_intent_to_payload()` serializes two fields that the Rust `KernelIntent` struct does not have:
```python
# Python sends (rust_backend.py:375-377):
"order_type": getattr(intent, "order_type", "MARKET"),
"limit_price": float(getattr(intent, "limit_price", 0.0) or 0.0),
```
```rust
// Rust receives (lib.rs:439-456) — no order_type or limit_price fields:
struct KernelIntent {
timestamp: DateTime<Utc>,
intent_id: String,
trade_id: String,
slot_id: i64,
asset: String,
side: TradeSide,
action: KernelCommandType,
reference_price: f64,
target_size: f64,
leverage: f64,
exit_leg_ratios: Vec<f64>,
reason: String,
metadata: Map<String, Value>,
stage: TradeStage,
// order_type — MISSING
// limit_price — MISSING
}
```
Serde's default behavior ignores unknown fields during deserialization. Both `order_type` and `limit_price` are transmitted across the FFI boundary and **silently thrown away**. Any downstream logic in Rust that depends on these fields is dead code. The Python `KernelIntent` dataclass declares them with defaults (`"MARKET"`, `0.0`), and they're used in Python-side `_first_invalid_intent_field()` (which checks `limit_price` for NaN), but the Rust kernel never sees them.
**Impact:** If the Rust kernel were to use `order_type` to distinguish MARKET from LIMIT orders (which would be needed for realistic exchange interaction), the field exists in Python but is never delivered. This was clearly designed to be added to Rust but the addition was never completed.
**Severity: High**
### U2: Rust `VenueEventStatus` deserializer expects `"CANCEL_REJECTED"` — Python sends `"CANCELED_REJECTED"`, deserialization fails
**Files:** `_rust_kernel/src/lib.rs:269-278` (Rust deserializer), Python `contracts.py` `VenueEventStatus` enum
The Rust `VenueEventStatus` custom deserializer has a typo in one of its string literals:
```rust
// Rust deserializer (lib.rs:269-278):
"CANCEL_REJECTED" => Ok(VenueEventStatus::CANCELED_REJECTED),
// ^ no D — TYPO
```
The string literal `"CANCEL_REJECTED"` is missing the D after L. The enum variant is correctly named `CANCELED_REJECTED` (with a D), and Python's `VenueEventStatus.CANCELED_REJECTED.value` produces `"CANCELED_REJECTED"` (with a D).
When Python sends a `VenueEvent` with `status=CANCELED_REJECTED`, the JSON contains `"status": "CANCELED_REJECTED"`. Rust's deserializer tries to match `"CANCELED_REJECTED"` against the string `"CANCEL_REJECTED"` — which fails. Serde returns an error: `"invalid VenueEventStatus: CANCELED_REJECTED"`. The `on_venue_event()` returns an error diagnostic.
**Impact:** Any venue event with status `CANCELED_REJECTED` fails deserialization on the Rust side. The event is rejected with `INVALID_EVENT_PARSE` instead of being processed normally. This means CANCEL_REJECT events (which are important FSM signals — they tell the kernel that a cancel was rejected by the exchange) are silently discarded rather than being used to transition the FSM.
**Note on usage:** The mock venue does not produce `CANCELED_REJECTED` events in the current test suite. The bingx live venue adapter (`bingx_venue.py`) maps exchange cancel responses but may use a different status string. This bug is dormant until a live exchange returns a cancel-rejected status — at which point the venue event is silently dropped with an error diagnostic.
**Severity: High**
### U3: R2 reconciliation compares cumulative `k.realized_pnl` against single-last-fill `e.last_fill_realized_pnl` — broken after 2+ fills
**File:** `account.py:459-473`
```python
# K side (line 296):
self._k_realized += safe_float(realized_pnl, 0.0) # ACCUMULATES all fills
# E side (line 299):
self._e_last_fill_realized_pnl = e_fill_realized_pnl # ONLY the LAST fill
# R2 comparison (line 460):
delta_r2 = abs(k.realized_pnl - e.last_fill_realized_pnl)
```
After the first fill:
- `k.realized_pnl = fill_1_pnl` (e.g., 10.0)
- `e.last_fill_realized_pnl = fill_1_pnl` (10.0)
- `delta_r2 = 0` ✅ OK
After the second fill:
- `k.realized_pnl = fill_1_pnl + fill_2_pnl` (e.g., 10.0 + 15.0 = 25.0)
- `e.last_fill_realized_pnl = fill_2_pnl` (15.0 — only the last fill)
- `delta_r2 = |25.0 - 15.0| = 10.0`
- With `realized_rounding = 0.05`, `10.0 > 0.05` → **ERROR**
After every fill beyond the first, R2 fires ERROR because the K accumulator includes all fills but the E value is reset to only the most recent fill. This is a fundamental design flaw — K and E track realized PnL differently (K accumulates, E replaces), and the comparison is apples-to-oranges.
**Fix:** Either `e.last_fill_realized_pnl` must be accumulated (add each new fill to the previous total) on the Python side, or R2 should compare only the per-fill delta (which would require storing per-fill values on both sides).
**Severity: Critical**
### U4: R4 reconciliation compares `k.open_notional` against `e.used_margin` — fundamentally different quantities
**File:** `account.py:488-498`
```python
# K side (line 411):
open_notional += abs(slot.size) * mark # Σ |qty| · mark_price
# E side (line 370):
self._e_used_margin = _safe(...) # exchange-reported used margin
# R4 comparison (line 490):
delta_notional = abs(k.open_notional - e.used_margin)
```
**K open_notional** = sum of absolute position notional values: `|size| × mark_price`. This is a gross market exposure measure.
**E used_margin** = exchange-reported margin used: `Σ notional / leverage`, possibly adjusted for cross-margin, risk weighting, position tiers, and maintenance margin requirements. This is a margin requirement measure.
For a 100 USDT position at 10x leverage:
- K open_notional = 100 × mark = **100 USDT**
- E used_margin = 100 / 10 = **10 USDT** (approximately, ignoring cross-margin effects)
- delta = |100 - 10| = **90 USDT**
- With `capital_epsilon = 1e-4` → `90 > 1e-4` → instantly exceeds even the WARN band
R4 produces ERROR on every position with any leverage > 1x. The only time R4 would pass is with 1x leverage on a single position (where notional = used_margin approximately). This makes R4 **completely broken as designed**.
**Fix:** R4 should compare like-with-like: either compare K open_notional against E open_notional (if available from exchange), or compare K used_margin against E used_margin. The current comparison of notional vs margin is meaningless.
**Severity: Critical**
### U5: R3 skipped when `len(e.positions) == 0` — K has open positions but E reports none, silent false negative
**File:** `account.py:478`
```python
if len(e.positions) > 0: # guard — only run R3 when E reports positions
if k.open_positions != len(e_pos_map):
return "ERROR", ...
```
If the exchange reports zero positions (corresponds to exchange-side position clear, connection loss, or initial state) while K has open positions (slots in `POSITION_OPEN`), R3 is **entirely skipped**. The position count mismatch is not detected.
This is asymmetric: if K=0 and E=1 (E has a position K doesn't know about), R3 fires because `len(e.positions)=1 > 0` and `k.open_positions=0 != 1`. But if K=1 and E=0, the guard prevents detection. The position that exists only in K's view is invisible to reconciliation.
**Scenario:** The exchange liquidates a position (or it expires) and sends no explicit event. K still thinks the position is open. The reconciler is called with E reporting 0 positions. R3 is skipped. R1 capital divergence may catch it eventually (if the trade had PnL), but if PnL is zero, R1 delta is also zero → **no detection at all**.
**Severity: High**
### U6: `on_venue_event` and `apply_fill` have no NaN/Inf guards on incoming venue event fields — NaN price/size propagate unchecked
**File:** `_rust_kernel/src/lib.rs` — `on_venue_event()`, `apply_fill()`
The `apply_fill()` function uses `event.price`, `event.size`, `event.filled_size` directly in arithmetic without finiteness checks:
```rust
// apply_fill (approximately):
slot.entry_price = event.price; // NaN → stored directly
let realized = realized_pnl(&slot, event.price, fill_size); // NaN → NaN PnL
slot.realized_pnl += realized; // NaN accumulates
slot.size = (slot.size - fill_size).max(0.0); // NaN - x = NaN, .max(0.0) = 0.0 (safe for size)
```
The `realized_pnl()` function (line 1121) guards `entry_price <= 0.0` but **NaN passes through** (IEEE 754: `NaN <= 0.0` is `false`). The `mark_price()` function (line 395) guards `price.is_finite()` on input but `entry_price` can be set to NaN by `apply_fill` before `mark_price` is called.
**Trigger path:** If a venue event arrives with `price = NaN` (corrupted exchange data, deserialization of malformed JSON, or a bug in the venue adapter), `apply_fill` stores NaN into `entry_price`. Every subsequent `realized_pnl()` and `mark_price()` call produces NaN. Once NaN enters `slot.realized_pnl`, it propagates to `k_realized_pnl` → `k_capital` → all financial computations.
The Rust-side `apply_fill_settled()` (line 761) has `if realized_pnl.is_finite()` — but this only guards the `account` level, not the slot level. The slot-level `realized_pnl` has already been corrupted.
**Severity: Critical**
### U7: Rust kernel has zero tests for ORDER_REJECT, PARTIAL_FILL, TERMINAL_STATE guard, STALE_STATE_RECONCILING guard, RATE_LIMITED, or MARK_PRICE transitions
**File:** `_rust_kernel/src/lib.rs` — `mod tests` (32 tests total)
The 32 Rust unit tests cover ExchangeFeeConfig (7), AccountState fill_settled (5), predicted→settled (2), JSON dispatch (3), dedup (3), snapshot save/restore (4), capital_frozen (4), and one FSM transition test (`enter_then_ack_fill`).
**FSM transitions with ZERO Rust test coverage:**
| Transition | Rust test | Python FFI test | Overall |
|-----------|-----------|-----------------|---------|
| ORDER_REJECT → IDLE | ❌ | ❌ | **Uncovered** |
| PARTIAL_FILL → ENTRY_WORKING / EXIT_WORKING | ❌ | ❌ (no explicit FSM state check) | **Uncovered** |
| FULL_FILL → POSITION_OPEN / CLOSED | ❌ | ✅ (test_flaws.py) | Python only |
| CANCEL_ACK → IDLE (entry) | ❌ | ✅ (TestFlaw2) | Python only |
| CANCEL_ACK → POSITION_OPEN (exit) | ❌ | ✅ (TestFlaw2) | Python only |
| CANCEL_REJECT → POSITION_OPEN | ❌ | ✅ (TestI15) | Python only |
| TERMINAL_STATE guard | ❌ | ❌ | **Uncovered** |
| STALE_STATE_RECONCILING guard | ❌ | ❌ | **Uncovered** |
| RATE_LIMITED response | ❌ | ❌ | **Uncovered** |
| MARK_PRICE (unrealized_pnl update) | ❌ | ❌ | **Uncovered** |
| SLOT_BUSY rejection | ❌ | ✅ (incidental) | Weak |
| ENTER with capital frozen | ❌ | ✅ (TestCapitalFrozen) | Python only |
| EXIT on IDLE slot | ❌ | ✅ (incidental) | Weak |
| Multi-leg exit FSM | ❌ | ✅ (TestFlaw4) | Python only |
The most critical gap: **ORDER_REJECT** — if the exchange rejects an entry order, the kernel should transition back to IDLE (or emit a diagnostic). This path exists in the Rust code (line ~1525: `KernelEventKind::ORDER_REJECT` match arm) but has **zero test coverage** in Rust or Python.
**Severity: High**
### U8: `safe_float()` in `utils.py` returns NaN/Inf instead of default — contradictory behavior with `_safe()` in `account.py`
**File:** `utils.py:13-19`, `account.py:229-233`
```python
# utils.py safe_float():
def safe_float(value, default=0.0):
try:
out = float(value)
except Exception:
return default
if not math.isfinite(out):
return out # BUG: returns NaN/Inf unchanged!
return out
# account.py _safe():
def _safe(v, default=0.0):
try:
f = float(v)
return f if math.isfinite(f) else default # CORRECT: returns default
except:
return default
```
`safe_float()` returns the non-finite value (NaN/Inf) when encountered, while `_safe()` correctly returns the default. Both functions serve the same purpose (safe float conversion) but have **opposite behavior** for non-finite inputs.
`safe_float()` is used in `AccountProjectionV1.observe_slots()` (line 56) which feeds data into the legacy reconciliation path. If an exchange returns NaN for a price or size, `safe_float()` propagates NaN into the accounting state rather than defaulting to 0.0.
**Fix:** `safe_float()` should return `default` when the value is non-finite, matching `_safe()`'s behavior.
**Severity: Medium**
### U9: `_scan_slots()` uses `slot.metadata.get("leverage")` not `slot.leverage` — wrong leverage source for used_margin computation
**File:** `account.py:414-416`
```python
metadata = slot.metadata or {}
lev = max(1.0, _safe(metadata.get("leverage", slot.leverage if hasattr(slot, "leverage") else 1.0), 1.0))
```
The used_margin computation reads leverage from `slot.metadata.get("leverage")` rather than `slot.leverage`. The slot's own `.leverage` field is only used as a fallback if metadata doesn't have the key.
The Rust kernel sets `slot.leverage` from the intent's leverage field during `process_intent()` (line ~1258). It does NOT write leverage into `slot.metadata`. The metadata is populated from `intent.metadata` which is a generic dict that may or may not contain a "leverage" key.
**Result:** Unless the calling algo explicitly places leverage in `intent.metadata["leverage"]`, `_scan_slots` uses the default `1.0` for `lev`, regardless of what `slot.leverage` actually is. The used_margin computation is wrong for any slot with leverage ≠ 1x.
**This affects R5 (used_margin comparison).** If the slot has 10x leverage but `_scan_slots` computes with 1x, `k.used_margin` is 10x larger than it should be, producing a false R5 ERROR.
**Severity: Medium**
### U10: `AccountState` serializes `k_fees_paid` but Rust manually injects JSON key `"k_net_fees"` — two keys for same value
**File:** `_rust_kernel/src/lib.rs:1144-1148` (Rust), `rust_backend.py:907` (Python)
Rust's `on_account_event()` manually injects an additional JSON key into the account event result:
```rust
obj.insert("k_net_fees".to_string(), json!(self.account.k_fees_paid));
```
The serde-serialized `AccountState` already contains the field `k_fees_paid` (from `#[derive(Serialize)]`). This creates two JSON keys (`"k_net_fees"` and `"k_fees_paid"`) holding the same value.
Python's `ExecutionKernel.snapshot()` reads `"k_fees_paid"` from the deserialized account data — it never reads `"k_net_fees"`. The injected key is dead data on the wire. This is not a functional bug but represents confusion about which key names are canonical.
**Severity: Low**
### U11: 10+ `AccountState` fields transmitted across FFI but never read by Python — wasted bandwidth, confusion risk
**File:** `_rust_kernel/src/lib.rs` (Rust serializes), `rust_backend.py` (Python reads)
Fields serialized by Rust's `AccountState` serde and transmitted across FFI but **never read** by Python:
- `seed_capital` — initial capital, available from config
- `k_taker_fees` — individual taker fee bucket
- `k_maker_fees` — individual maker fee bucket
- `k_maker_rebates` — individual rebate bucket
- `fee_config` — entire `ExchangeFeeConfig` struct (5 subfields: calibration_ratio, taker_rate, maker_rate, etc.)
- `last_predicted_fee` — most recent predicted fee value
- `last_calibration_ratio` — most recent calibration ratio
- `seen_account_event_ids` — entire `IndexSet<String>` (1024 entries at capacity)
These fields are serialized to JSON, sent across the FFI boundary via CString, and **silently discarded** by Python. The most wasteful is `seen_account_event_ids` — a 1024-element set of strings that is transmitted on every snapshot read but never used on the Python side.
**Severity: Low**
### U12: `_order_from_payload()` overwrites `internal_trade_id` with enclosing slot's `trade_id` — loses order-level distinction
**File:** `rust_backend.py:302-310,334-335`
```python
def _order_from_payload(payload: dict, trade_id: str) -> VenueOrder:
return VenueOrder(
internal_trade_id=trade_id, # OVERWRITES with slot's trade_id
...
)
# Called as (line 334):
active_exit_order=_order_from_payload(order_dict, trade_id=str(payload.get("trade_id", ""))),
```
The `_order_from_payload()` function takes `trade_id` from the caller and uses it as the order's `internal_trade_id`. The JSON payload's own `internal_trade_id` field is **ignored**. If Rust's `TradeSlot.to_dict()` serializes the order with its own `internal_trade_id` (which may differ from the slot's `trade_id` — e.g., a slot that was re-entered after a cancel), the per-order ID is silently replaced with the slot-level ID.
This affects the Python-side `VenueOrder.internal_trade_id` field — it will always match the slot's `trade_id` rather than the order's original ID. Any Python code that relies on `internal_trade_id` to distinguish between entry and exit orders, or to track orders across cancel/re-enter cycles, gets wrong data.
**Note:** T8 covers the same bug in `real_zinc_plane.py`. This is the same bug in `rust_backend.py` — the FFI path. The bug exists in both code paths.
**Severity: Medium**
### U13: Reconciliation has no independent third reference — any divergence affecting both K and E equally is invisible
**File:** `account.py:442-510`
Every R-rule compares K (kernel-computed state) against E (exchange-reported state). If both K and E share a common error source — stale mark price, wrong position count, outdated wallet balance — the delta is small and reconciliation reports OK.
**Specific blind spots:**
1. **R1**: If both `k.capital` and `e.wallet_balance` are wrong by the same amount in the same direction (e.g., both show 9,800 but true capital is 10,000), `abs(9800 - 9800) = 0` → OK
2. **R4**: If both K open_notional and E used_margin are inflated by the same stale mark price, the delta is small → OK (but read U4 — these are fundamentally different quantities, so this blind spot is theoretical)
3. **R3**: If both K and E report 2 positions but the positions have wrong sizes or entry prices, the count matches → OK (no per-position quality comparison)
No cross-check against a third data source (independent market data feed, broker API, or blockchain) exists.
**Severity: Medium**
### U14: `lot_step` declared in `ReconcileConfig` but never used anywhere — dead config field
**File:** `account.py:220`
```python
@dataclass
class ReconcileConfig:
...
lot_step: float = 0.001 # DEAD — never referenced in _classify or _scan_slots
```
The `lot_step` field is declared with a default of `0.001` but is never read by any code path. It was intended for per-position quantity comparison (R6 mentioned in a comment) but that rule was never implemented. A developer configuring reconciliation might set `lot_step` expecting it to affect behavior, but it has no effect.
**Severity: Low**
---
## Pass 18 Summary
| # | Flaw | Layer | Severity |
|---|------|-------|----------|
| U1 | `order_type`/`limit_price` sent to Rust, no serde fields — silently dropped | FFI | **High** |
| U2 | Rust `VenueEventStatus` expects `"CANCEL_REJECTED"` (typo) — `"CANCELED_REJECTED"` fails | Rust | **High** |
| U3 | R2 compares cumulative K realized vs single-last-fill E realized — broken after 2nd fill | Accounting | **Critical** |
| U4 | R4 compares K open_notional vs E used_margin — fundamentally different quantities | Accounting | **Critical** |
| U5 | R3 skipped when `len(e.positions)==0` — K has positions but E reports none, silent | Accounting | **High** |
| U6 | `on_venue_event`/`apply_fill` no NaN guards on venue event price/size — NaN propagates | Rust | **Critical** |
| U7 | Zero Rust tests for ORDER_REJECT, PARTIAL_FILL, TERMINAL_STATE, etc. | Test | **High** |
| U8 | `safe_float()` returns NaN/Inf instead of default — contradicts `_safe()` | Bridge | Medium |
| U9 | `_scan_slots` uses `metadata.get("leverage")` not `slot.leverage` — wrong leverage source | Accounting | Medium |
| U10 | Rust injects `"k_net_fees"` key alongside serde's `k_fees_paid` — duplicate key | Bridge | Low |
| U11 | 10+ AccountState fields transmitted across FFI but never read by Python | FFI | Low |
| U12 | `_order_from_payload()` overwrites `internal_trade_id` with slot's `trade_id` | Bridge | Medium |
| U13 | No independent third reference — symmetrical K=E errors invisible | Accounting | Medium |
| U14 | `lot_step` declared in ReconcileConfig but never used — dead field | Accounting | Low |
### Pass 18 Severity
| Severity | Count |
|----------|-------|
| **Critical** | 3 (U3, U4, U6) |
| **High** | 4 (U1, U2, U5, U7) |
| Medium | 4 (U8, U9, U12, U13) |
| Low | 3 (U10, U11, U14) |
### Combined Catalog (All 18 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 |
| J | Pass 7 (Test Infra/Data/Rust/Env/Conn) | 16 | 0 | 7 | 7 | 2 | 0 |
| K | Pass 8 (Observability/Memory/Time/DeadCode) | 23 | 2 | 7 | 7 | 1 | 6 |
| L | Pass 9 (Contracts/Events/Network/FFI/Diffs) | 16 | 0 | 4 | 8 | 4 | 0 |
| M | Pass 10 (Runtime/TestBugs/FSM/Persistence/Metrics) | 18 | 3 | 7 | 5 | 3 | 0 |
| N | Pass 11 (Async/Sync Seams/Locks/Threading) | 10 | 4 | 1 | 3 | 1 | 1 |
| O | Pass 12 (Sync/Async Wider Scope) | 11 | 0 | 3 | 7 | 1 | 0 |
| P | Pass 13 (FFI Safety/Dangling Pointers/Coverage) | 9 | 1 | 3 | 3 | 1 | 1 |
| Q | Pass 14 (Serde Edges/Backup Diffs/Market Data) | 12 | 0 | 4 | 3 | 2 | 3 |
| R | Pass 15 (Resource Leaks/Trust Boundaries/Security) | 14 | 2 | 6 | 3 | 2 | 1 |
| S | Pass 16 (Error Handling/Arithmetic/Test Infra) | 16 | 4 | 7 | 5 | 0 | 0 |
| T | Pass 17 (Unsafe Review/Dead Code/Build/Protocols) | 14 | 0 | 5 | 5 | 4 | 0 |
| U | Pass 18 (Rust Test Gaps/Accounting/FFI Types) | 14 | 3 | 4 | 4 | 3 | 0 |
| **Total** | | **333** | **30** | **99** | **96** | **64** | **37** |

View File

@@ -32,7 +32,8 @@
| R | Pass 15 (Resource Leaks/Trust Boundaries/Security) | 14 | 2 | 6 | 3 | 2 | 1 | | R | Pass 15 (Resource Leaks/Trust Boundaries/Security) | 14 | 2 | 6 | 3 | 2 | 1 |
| S | Pass 16 (Error Handling/Arithmetic/Test Infra) | 16 | 4 | 7 | 5 | 0 | 0 | | S | Pass 16 (Error Handling/Arithmetic/Test Infra) | 16 | 4 | 7 | 5 | 0 | 0 |
| T | Pass 17 (Unsafe Review/Dead Code/Build/Protocols) | 14 | 0 | 5 | 5 | 4 | 0 | | T | Pass 17 (Unsafe Review/Dead Code/Build/Protocols) | 14 | 0 | 5 | 5 | 4 | 0 |
| **Total** | | **319** | **27** | **95** | **92** | **64** | **37** | | U | Pass 18 (Rust Test Gaps/Accounting/FFI Types) | 14 | 3 | 4 | 4 | 3 | 0 |
| **Total** | | **333** | **30** | **99** | **96** | **64** | **37** |
--- ---
@@ -462,6 +463,29 @@
--- ---
## U-Series: Rust Test Gaps, Accounting Reconciliation Bugs, FFI Type Mismatches (Pass 18)
*Full detail in TRACE doc under "PASS 18 — RUST TEST GAPS, ACCOUNTING RECONCILIATION BUGS, FFI TYPE MISMATCHES."*
| # | Flaw | Layer | Severity |
|---|------|-------|----------|
| U1 | `order_type`/`limit_price` sent to Rust, no serde fields silently dropped | FFI | **High** |
| U2 | Rust `VenueEventStatus` expects `"CANCEL_REJECTED"` (typo) `"CANCELED_REJECTED"` fails deserialization | Rust | **High** |
| U3 | R2 compares cumulative K realized vs single-last-fill E realized broken after 2nd+ fill | Accounting | **Critical** |
| U4 | R4 compares K open_notional vs E used_margin fundamentally different quantities | Accounting | **Critical** |
| U5 | R3 skipped when `len(e.positions)==0` K has positions but E reports none, silent | Accounting | **High** |
| U6 | `on_venue_event`/`apply_fill` no NaN guards on venue event price/size NaN propagates | Rust | **Critical** |
| U7 | Zero Rust tests for ORDER_REJECT, PARTIAL_FILL, TERMINAL_STATE guard, etc. | Test | **High** |
| U8 | `safe_float()` returns NaN/Inf instead of default contradicts `_safe()` | Bridge | Medium |
| U9 | `_scan_slots` uses `metadata.get("leverage")` not `slot.leverage` wrong leverage | Accounting | Medium |
| U10 | Rust injects `"k_net_fees"` key alongside serde's `k_fees_paid` duplicate | Bridge | Low |
| U11 | 10+ AccountState fields transmitted across FFI but never read by Python | FFI | Low |
| U12 | `_order_from_payload()` overwrites `internal_trade_id` with slot's trade_id | Bridge | Medium |
| U13 | No independent third reference symmetrical K=E errors invisible | Accounting | Medium |
| U14 | `lot_step` declared in ReconcileConfig but never used dead field | Accounting | Low |
---
## 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."*