PINK: E2E trace analysis — Pass 12 sync/async wider scope (O1-O11)
Twelfth pass: _maybe_close asyncio.run silently skips close from async context (O1), _pick_live_symbol missing await crashes on coroutine iteration (O3), _run() pool .result() no timeout — backend hang freezes process (O5), KernelSlotView.__getattr__ N FFI calls for N fields no caching (O8), DITAv2LauncherBundle no __del__ leaks resource tree (O9), ExecutionKernel no close() — __del__ only cleanup (O10), __setattr__ triggers 5 persistence side effects undocumented (O11). 254 total flaws. Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
This commit is contained in:
@@ -4547,3 +4547,275 @@ The `_snap_lock` protects `_last_snapshot` only during writes (line 269-271). Th
|
|||||||
| M | Pass 10 (Runtime/TestBugs/FSM/Persistence/Metrics) | 18 | 3 | 7 | 5 | 3 | 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 |
|
| N | Pass 11 (Async/Sync Seams/Locks/Threading) | 10 | 4 | 1 | 3 | 1 | 1 |
|
||||||
| **Total** | | **243** | **20** | **67** | **70** | **58** | **28** |
|
| **Total** | | **243** | **20** | **67** | **70** | **58** | **28** |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## PASS 12 — SYNC/ASYNC WIDER SCOPE (launcher, generators, streams, FFI, tests)
|
||||||
|
|
||||||
|
### O1: `_maybe_close()` calls `asyncio.run()` without checking for a running event loop — close/disconnect silently skipped
|
||||||
|
|
||||||
|
**File:** `launcher.py:270-274`
|
||||||
|
|
||||||
|
```python
|
||||||
|
def _maybe_close(obj):
|
||||||
|
...
|
||||||
|
if inspect.isawaitable(result):
|
||||||
|
try:
|
||||||
|
asyncio.run(result)
|
||||||
|
except RuntimeError:
|
||||||
|
pass # SILENT — coroutine never executed
|
||||||
|
```
|
||||||
|
|
||||||
|
When `_maybe_close()` is called from any context that already has a running event loop (which includes all async tests, any `async def main()` orchestrator, or any code path that imports and runs `DITAv2LauncherBundle` inside an async context), `asyncio.run(result)` raises `RuntimeError: asyncio.run() cannot be called from a running event loop`. The `except RuntimeError: pass` swallows it — the close/disconnect method **never executes**.
|
||||||
|
|
||||||
|
Affected resources when called from async context:
|
||||||
|
- `RealZincPlane.close()` — never called → 3 shared memory regions leaked
|
||||||
|
- `RealZincControlPlane.close()` — never called → 1 shared memory region leaked
|
||||||
|
- `BingxVenueAdapter` has neither `close()` nor `disconnect()` — N/A
|
||||||
|
- `InMemoryZincPlane` has no close — N/A
|
||||||
|
|
||||||
|
The `DITAv2LauncherBundle.close()` method calls `_maybe_close(self.venue)`, `_maybe_close(self.zinc_plane)`, `_maybe_close(self.control_plane)` — if any of these have async close/disconnect methods, they're all silently skipped when called from async context.
|
||||||
|
|
||||||
|
This means: in any async deployment (which is the only deployment pattern — tests, and presumably production via `asyncio.run()` at top level), **shared memory regions are never explicitly closed**. They rely on process exit cleanup.
|
||||||
|
|
||||||
|
**Severity: High**
|
||||||
|
|
||||||
|
### O2: `async def connect()` shims in all test generators call sync `venue.connect()` without `await` — misleading pattern
|
||||||
|
|
||||||
|
**Files:** `gen_live_tests.py:143-146`, `gen2.py:332-333`, `_gen_test.py:70` (via Shim/Shim pattern)
|
||||||
|
|
||||||
|
```python
|
||||||
|
# All three test harnesses have this pattern:
|
||||||
|
async def connect(self, initial_capital=0):
|
||||||
|
self.kernel.venue.connect() # sync method, no await
|
||||||
|
```
|
||||||
|
|
||||||
|
`BingxVenueAdapter.connect()` (bingx_venue.py:301) is a **sync** `def` that returns `bool`. It internally calls `self._run(result())` which under a running event loop submits to the thread pool and blocks with `.result()`. The `async def connect()` wrapper is misleading — it's `async` but immediately calls a sync method that will **block the event loop** for the HTTP round-trip duration.
|
||||||
|
|
||||||
|
The caller's perspective: `await runtime.connect()` should yield the event loop. Instead, it blocks until the BingX HTTP call inside `connect()` completes (via `_run()`'s thread pool path).
|
||||||
|
|
||||||
|
**Severity: Medium**
|
||||||
|
|
||||||
|
### O3: `gen_live_tests.py:171` — `_contract_rows(client)` NOT awaited in `async def _pick_live_symbol` — silent failure
|
||||||
|
|
||||||
|
**File:** `gen_live_tests.py:171**
|
||||||
|
|
||||||
|
```python
|
||||||
|
async def _pick_live_symbol(client):
|
||||||
|
rows = _contract_rows(client) # MISSING await! _contract_rows is async def
|
||||||
|
...
|
||||||
|
pos_rows = [r for r in rows if ...]
|
||||||
|
```
|
||||||
|
|
||||||
|
`_contract_rows` is `async def` (line 69). Without `await`, `rows` is a **coroutine object**, not the actual data. The subsequent iteration `for r in rows` would iterate over a coroutine object — in Python 3.12+, coroutines raise `TypeError: 'coroutine' object is not iterable` when iterated.
|
||||||
|
|
||||||
|
This function is called from `_run_scenario` (line 260) and `_run_pink_live_roundtrip` (line 297). If either path reaches `_pick_live_symbol`, it crashes with `TypeError`. This bug may not have manifested in practice if the code paths that call `_pick_live_symbol` are rarely exercised or if the test generator's output file hasn't been regenerated recently.
|
||||||
|
|
||||||
|
**Severity: High**
|
||||||
|
|
||||||
|
### O4: `test_exchange_event_seam_parity.py` uses deprecated `asyncio.get_event_loop().run_until_complete()`
|
||||||
|
|
||||||
|
**File:** `test_exchange_event_seam_parity.py:243,264**
|
||||||
|
|
||||||
|
```python
|
||||||
|
snap = asyncio.get_event_loop().run_until_complete(mock.account_snapshot()) # line 243
|
||||||
|
asyncio.get_event_loop().run_until_complete(asyncio.wait_for(_collect(), timeout=2.0)) # line 264
|
||||||
|
```
|
||||||
|
|
||||||
|
`asyncio.get_event_loop()` is **deprecated** in Python 3.12+ (raises `DeprecationWarning`). If no running event loop exists at call time, it creates a new loop and sets it as the current event loop — which can cause subtle issues when multiple event loops are active. The modern pattern is `asyncio.run()`.
|
||||||
|
|
||||||
|
These are the only two places in the workspace that use the deprecated `get_event_loop().run_until_complete()` pattern.
|
||||||
|
|
||||||
|
**Severity: Medium**
|
||||||
|
|
||||||
|
### O5: `_run()` thread pool has no timeout on `.result()` — if backend hangs, calling thread hangs forever
|
||||||
|
|
||||||
|
**File:** `bingx_venue.py:236**
|
||||||
|
|
||||||
|
```python
|
||||||
|
return pool.submit(asyncio.run, result).result() # NO timeout
|
||||||
|
```
|
||||||
|
|
||||||
|
`concurrent.futures.Future.result()` has an optional `timeout` parameter. None is set here. If the thread pool worker hangs (e.g., the `asyncio.run()` call in the worker gets stuck on a never-responding HTTP request, a deadlocked coroutine, or an infinite loop), the calling thread blocks **forever** on `.result()`.
|
||||||
|
|
||||||
|
If the calling thread is the event loop thread (Path B), the entire event loop is frozen indefinitely. No WS messages, no keepalive tasks, no timer events. The system is completely dead.
|
||||||
|
|
||||||
|
The `_backend_snapshot()` method has a 5-second timeout for its `threading.Event.wait()`, but the actual `_call_backend("refresh_state", ...)` that runs inside the thread pool has no timeout. The HTTP client (`BingxHttpClient`) may have its own default timeout (typically 30-60 seconds for `aiohttp`), but there's no fallback if it hangs beyond that.
|
||||||
|
|
||||||
|
**Severity: High**
|
||||||
|
|
||||||
|
### O6: MockVenueAdapter never exercises the thread-pool bridge — all CI tests use mock venue, bridge untested
|
||||||
|
|
||||||
|
**Files:** `mock_venue.py` vs `bingx_venue.py`
|
||||||
|
|
||||||
|
`MockVenueAdapter.submit()` is pure sync — it does `return self._events_from_submit(...)` with no awaitables, no thread pools. `BingxVenueAdapter.submit()` is a sync-bridge that goes through `_run()` → `pool.submit(asyncio.run, ...).result()`.
|
||||||
|
|
||||||
|
All 35+ tests in `test_flaws.py` use `MockVenueAdapter`. All generated live tests use `BingxVenueAdapter` but are rarely executed (require live exchange credentials and API key env vars). The thread-pool bridge — including:
|
||||||
|
- Thread creation and lifecycle
|
||||||
|
- `asyncio.run()` inside pool workers
|
||||||
|
- Event loop per HTTP call
|
||||||
|
- Thread pool exhaustion handling
|
||||||
|
- Exception propagation through `.result()`
|
||||||
|
|
||||||
|
— is **never exercised in CI**. If the bridge has a bug (e.g., the `asyncio.run()` inside the pool worker corrupts shared state, or thread-safety issues in `aiohttp`), it surfaces only in production.
|
||||||
|
|
||||||
|
**Severity: Medium**
|
||||||
|
|
||||||
|
### O7: `BingxUserStream._keepalive_loop` and `_rotation_sentinel` are fire-and-forget tasks — unhandled exceptions silently lost
|
||||||
|
|
||||||
|
**File:** `bingx_user_stream.py:105-112**
|
||||||
|
|
||||||
|
```python
|
||||||
|
keepalive_task = asyncio.create_task(self._keepalive_loop(listen_key), name="lk_keepalive")
|
||||||
|
rotation_task = asyncio.create_task(self._rotation_sentinel(), name="lk_rotation")
|
||||||
|
```
|
||||||
|
|
||||||
|
Both are created with `create_task()` and tracked for later cancellation, but **not supervised during normal operation**. If `_keepalive_loop` raises an exception that's not caught by its internal `try/except` (e.g., a `asyncio.CancelledError` variant, or a `RuntimeError` from the HTTP layer), the exception is stored in the `Task` object. If `.result()` or `.exception()` is never called on that `Task`, the exception is logged by the asyncio event loop as `"Task exception was never retrieved"` — a warning message, but no structured error handling.
|
||||||
|
|
||||||
|
`_rotation_sentinel` has no exception handling in its body — it just does `await asyncio.sleep(secs)` and returns. It can't raise an exception unless the event loop is shut down during its sleep (in which case `CancelledError` is raised, which is properly handled in the `finally` block).
|
||||||
|
|
||||||
|
**Severity: Low**
|
||||||
|
|
||||||
|
### O8: `KernelSlotView.__getattr__` makes a ctypes call per attribute — each read triggers Rust FFI and is not cached
|
||||||
|
|
||||||
|
**File:** `rust_backend.py:422-426**
|
||||||
|
|
||||||
|
```python
|
||||||
|
def __getattr__(self, name: str) -> Any:
|
||||||
|
slot = self._snapshot() # FFI call → Rust serialize → JSON parse → TradeSlot
|
||||||
|
if hasattr(slot, name):
|
||||||
|
return getattr(slot, name)
|
||||||
|
raise AttributeError(name)
|
||||||
|
```
|
||||||
|
|
||||||
|
Every attribute access on a `KernelSlotView` — including `slot.size`, `slot.fsm_state`, `slot.trade_id`, `slot.active_entry_order`, etc. — does a full JSON round-trip to the Rust kernel:
|
||||||
|
1. Python calls `_get_rust().get_slot_json(self._backend, slot_id)`
|
||||||
|
2. ctypes calls Rust `dita_kernel_get_slot_json`
|
||||||
|
3. Rust serializes the entire `TradeSlot` to a JSON string
|
||||||
|
4. ctypes returns the C string pointer
|
||||||
|
5. Python calls `_take_string(raw)` → `text.decode("utf-8")`
|
||||||
|
6. Python calls `json.loads(text)` → dict
|
||||||
|
7. `_slot_from_payload(dict)` → new `TradeSlot` dataclass
|
||||||
|
8. `getattr(slot, name)` → read the one field from the new object
|
||||||
|
|
||||||
|
Accessing 5 fields on a `KernelSlotView` (e.g., `slot.size`, `slot.fsm_state`, `slot.entry_price`, `slot.active_entry_order`, `slot.trade_id`) does 5 FFI round-trips. The deserialized `TradeSlot` is created and immediately discarded for each access.
|
||||||
|
|
||||||
|
The `_snapshot()` method (line 435) calls `self._kernel._get_slot(self._slot_id)` which does the full FFI round-trip. There is no caching of the deserialized `TradeSlot` between successive accesses. This is an N+1 performance issue — accessing N fields costs N FFI calls instead of 1.
|
||||||
|
|
||||||
|
**Severity: Medium**
|
||||||
|
|
||||||
|
### O9: `DITAv2LauncherBundle` has no `__del__` — bundle that's garbage collected leaks its entire resource tree
|
||||||
|
|
||||||
|
**File:** `launcher.py:64-95**
|
||||||
|
|
||||||
|
```python
|
||||||
|
@dataclass
|
||||||
|
class DITAv2LauncherBundle:
|
||||||
|
kernel: ExecutionKernel
|
||||||
|
control_plane: ControlPlane
|
||||||
|
projection: HazelcastProjection
|
||||||
|
zinc_plane: ZincPlane
|
||||||
|
venue: VenueAdapter
|
||||||
|
|
||||||
|
def close(self) -> None:
|
||||||
|
_maybe_close(self.venue)
|
||||||
|
_maybe_close(self.zinc_plane)
|
||||||
|
_maybe_close(self.control_plane)
|
||||||
|
```
|
||||||
|
|
||||||
|
No `__del__` method. If a bundle is garbage collected without an explicit `close()` call:
|
||||||
|
- The Rust kernel's `KernelHandle` is freed by `ExecutionKernel.__del__` (if GC runs)
|
||||||
|
- If `RealZincPlane` was in use, its `close()` is never called → 3 shared memory regions leaked
|
||||||
|
- If `RealZincControlPlane` was in use, its `close()` is never called → 1 shared memory region leaked
|
||||||
|
- The projection (Hazelcast) client connection is never closed
|
||||||
|
- The venue adapter's thread pool executor is never shut down
|
||||||
|
|
||||||
|
If the bundle is created and dropped in a loop (e.g., per-test setup/teardown), shared memory regions accumulate until the system runs out of `/dev/shm/` space.
|
||||||
|
|
||||||
|
**Severity: Medium**
|
||||||
|
|
||||||
|
### O10: ExecutionKernel has no `close()` — `__del__` is the only cleanup path for the Rust handle
|
||||||
|
|
||||||
|
**File:** `rust_backend.py:519-525**
|
||||||
|
|
||||||
|
```python
|
||||||
|
def __del__(self) -> None:
|
||||||
|
backend = getattr(self, "_backend", None)
|
||||||
|
if backend is not None:
|
||||||
|
try:
|
||||||
|
_get_rust().destroy(backend)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
No `close()` method exists on `ExecutionKernel`. The `DITAv2LauncherBundle.close()` doesn't touch the kernel (it calls `_maybe_close` on venue, zinc_plane, and control_plane only). The Rust `_backend` handle is only freed when `__del__` runs during garbage collection.
|
||||||
|
|
||||||
|
If the kernel is part of a reference cycle (K3/K6 — `Kernel → KernelStateView → KernelSlotView → Kernel`), `__del__` may be delayed indefinitely until the cycle GC runs. During that delay, the Rust `KernelHandle` is alive but unreachable — its memory is leaked until GC.
|
||||||
|
|
||||||
|
**Severity: Medium**
|
||||||
|
|
||||||
|
### O11: `KernelSlotView.__setattr__` triggers 5 side effects including durable writes — undocumented
|
||||||
|
|
||||||
|
**File:** `rust_backend.py:428-453**
|
||||||
|
|
||||||
|
```python
|
||||||
|
def __setattr__(self, name: str, value: Any) -> None:
|
||||||
|
...
|
||||||
|
slot = self._snapshot()
|
||||||
|
setattr(slot, name, value)
|
||||||
|
self._kernel._set_slot(slot) # triggers: Rust FFI write + state refresh
|
||||||
|
# + account.observe_slots
|
||||||
|
# + projection.write_slot
|
||||||
|
# + zinc_plane.write_slot
|
||||||
|
```
|
||||||
|
|
||||||
|
Setting any attribute on a `KernelSlotView` — even something trivial like `slot.some_metadata_field = "test"` — triggers 5 side effects: Rust FFI write to the kernel, `KernelStateView.refresh()`, `account.observe_slots()`, `projection.write_slot()`, and `zinc_plane.write_slot()`. The method name `__setattr__` gives no indication that setting a field triggers durable writes across multiple persistence layers.
|
||||||
|
|
||||||
|
There is no read-only view that prevents accidental mutation. Any code that holds a `KernelSlotView` reference and assigns a field bypasses all FSM guards and directly mutates the Rust kernel state.
|
||||||
|
|
||||||
|
**Severity: Medium**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Pass 12 Summary
|
||||||
|
|
||||||
|
| # | Flaw | Layer | Severity |
|
||||||
|
|---|------|-------|----------|
|
||||||
|
| O1 | `_maybe_close()` asyncio.run without loop guard — close/disconnect silently skipped from async context | Launcher | **High** |
|
||||||
|
| O2 | `async def connect()` shims call sync `venue.connect()` without await — blocking pattern | Test | Medium |
|
||||||
|
| O3 | `_contract_rows(client)` NOT awaited in `_pick_live_symbol` — silent coroutine iteration crash | Test | **High** |
|
||||||
|
| O4 | `test_exchange_event_seam_parity.py` uses deprecated `get_event_loop().run_until_complete()` | Test | Medium |
|
||||||
|
| O5 | `_run()` thread pool `.result()` has no timeout — backend hang freezes process indefinitely | Venue | **High** |
|
||||||
|
| O6 | MockVenueAdapter never exercises thread-pool bridge — bridge untested in CI | Venue | Medium |
|
||||||
|
| O7 | `_keepalive_loop`/`_rotation_sentinel` fire-and-forget tasks — exceptions silently lost | Stream | Low |
|
||||||
|
| O8 | `KernelSlotView.__getattr__` makes N FFI calls for N attribute accesses — no caching | Bridge | Medium |
|
||||||
|
| O9 | `DITAv2LauncherBundle` no `__del__` — GC'd bundle leaks entire resource tree | Launcher | Medium |
|
||||||
|
| O10 | `ExecutionKernel` no `close()` — Rust handle only freed by unpredictable `__del__` | Bridge | Medium |
|
||||||
|
| O11 | `KernelSlotView.__setattr__` triggers 5 persistence side effects — read-only view missing | Bridge | Medium |
|
||||||
|
|
||||||
|
### Pass 12 Severity
|
||||||
|
|
||||||
|
| Severity | Count |
|
||||||
|
|----------|-------|
|
||||||
|
| **High** | 3 (O1, O3, O5) |
|
||||||
|
| Medium | 7 (O2, O4, O6, O8, O9, O10, O11) |
|
||||||
|
| Low | 1 (O7) |
|
||||||
|
|
||||||
|
### Combined Catalog (All 12 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 |
|
||||||
|
| **Total** | | **254** | **20** | **70** | **73** | **60** | **28** |
|
||||||
|
|||||||
@@ -26,7 +26,8 @@
|
|||||||
| L | Pass 9 (Contracts/Events/Network/FFI/Diffs) | 16 | 0 | 4 | 8 | 4 | 0 |
|
| 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 |
|
| 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 |
|
| N | Pass 11 (Async/Sync Seams/Locks/Threading) | 10 | 4 | 1 | 3 | 1 | 1 |
|
||||||
| **Total** | | **243** | **20** | **67** | **70** | **58** | **28** |
|
| O | Pass 12 (Sync/Async Wider Scope) | 11 | 0 | 3 | 7 | 1 | 0 |
|
||||||
|
| **Total** | | **254** | **20** | **70** | **73** | **60** | **28** |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -326,6 +327,26 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## O-Series: Sync/Async Wider Scope (Launcher, Generators, Streams, FFI, Tests) (Pass 12)
|
||||||
|
|
||||||
|
*Full detail in TRACE doc under "PASS 12 — SYNC/ASYNC WIDER SCOPE."*
|
||||||
|
|
||||||
|
| # | Flaw | Layer | Severity |
|
||||||
|
|---|------|-------|----------|
|
||||||
|
| O1 | `_maybe_close()` asyncio.run without loop guard — close skipped from async context | Launcher | **High** |
|
||||||
|
| O2 | `async def connect()` shims call sync venue.connect() without await — blocking | Test | Medium |
|
||||||
|
| O3 | `_contract_rows(client)` NOT awaited — `_pick_live_symbol` iterates coroutine = crash | Test | **High** |
|
||||||
|
| O4 | Deprecated `get_event_loop().run_until_complete()` in test file | Test | Medium |
|
||||||
|
| O5 | `_run()` thread pool .result() no timeout — backend hang freezes process | Venue | **High** |
|
||||||
|
| O6 | MockVenueAdapter never exercises thread-pool bridge — untested in CI | Venue | Medium |
|
||||||
|
| O7 | `_keepalive_loop`/`_rotation_sentinel` fire-and-forget — exceptions silently lost | Stream | Low |
|
||||||
|
| O8 | `KernelSlotView.__getattr__` N FFI calls for N fields — no caching | Bridge | Medium |
|
||||||
|
| O9 | `DITAv2LauncherBundle` no `__del__` — GC'd bundle leaks resource tree | Launcher | Medium |
|
||||||
|
| O10 | `ExecutionKernel` no `close()` — Rust handle only freed by unpredictable __del__ | Bridge | Medium |
|
||||||
|
| O11 | `KernelSlotView.__setattr__` triggers 5 persistence side effects — no read-only view | Bridge | 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."*
|
||||||
|
|||||||
Reference in New Issue
Block a user