# UPDATE-WEDGE round-3 RIG VERDICT — net-runtime starvation seam FALSIFIED (todlando, 2026-07-09)

Report-before-fix per `docs/UPDATE-WEDGE-2-ROUND3-RULING.md` §7. Rig lives in the
`uwedge2` worktree: `crates/spt-daemon/tests/net_worker_starve.rs` + a throwaway
DIAGNOSTIC probe on `crates/spt-daemon/src/nethost.rs` (`active_dial_tasks()`,
`net_canary_age_ms()`, `dial_permits_available()` — no REQ, marked throwaway).

## (a) Mechanism confirmed? — NO. FALSIFIED, robustly, across both dead-peer shapes.

The ruling's decider (§5): do dead-peer `submit_dial`s HOLD/serialize the broker
net runtime's 2 worker threads (starving the reactor that services LOCAL loopback
`rc` I/O), or do the QUIC awaits YIELD? **They yield.**

Rig: dialer with a 3s QUIC-op bound; fire K=12 concurrent `submit_dial`s straight
onto the broker's own `NetHost` (the exact pump path); read the reactor-liveness
canary (a runtime task stamping epoch-ms every 25ms — a stale stamp == workers
monopolized).

| dead-peer shape | K in-flight | peak canary age | dial permits free |
|---|---|---|---|
| proof-hang (accepts QUIC handshake, black-holes the seed-proof — netbroker.rs dead peer) | 12 | **32 ms** | 12 |
| unreachable (id-only `addr_for_node_hex`, no path — the field's "peer unresponsive" ecb39e40/cfd9fc55 shape) | 12 | **33 ms** | 12 |

12 in-flight dead-peer dials (≫ the 2 workers) held the canary at **32–33 ms** vs
the **15 000 ms** stall-evict threshold — ~450× headroom. Both connect shapes
(proof-read-hang AND relay-less/discovery-off unreachable-connect) yield cleanly;
2 workers multiplex 12 dead dials without perturbation. The 24-permit dial
semaphore is never the limiter (12 free throughout). Locked as passing asserts so
a future non-yielding regression re-fires the investigation.

## (b) Boundary map

- **Loopback `rc` I/O** (`open_stream` attach @nethost.rs:1226, `send_stream`
  controller-output @1301) is driven by `bounded_block_on` → `runtime.handle()
  .block_on(fut)` on a **broker DISPATCH/reply thread** (NOT a net worker). The
  future's `tokio::io::duplex` readiness is serviced by the 2-worker net reactor —
  **PROVEN healthy under a 12-deep dead-peer dial burst** (canary fresh).
- **Dead-peer connects** (`submit_dial` @nethost.rs:1036) run as `runtime.spawn`
  tasks on the 2 workers, bounded by `quic_op_timeout`. They are cooperative
  (yield at every await); 12 coexist with zero reactor impact.
- **The field stall-evict** is *"controller writer blocked >15000ms on a
  suspended/black-holed **brain conn**"*. The blocked writer is the broker → BRAIN
  IPC path; it unblocks only when the **brain READS/drains** its side. That drain
  is the brain main loop's job — it is **not** on the broker net runtime at all.

So the net runtime is EXONERATED as the wedge seam. The `submit-dial exceeded the
10s bound` field lines are the *expected, benign* dead-peer timeout — not evidence
of runtime saturation. The wedge coupling lives at the **brain main-loop drain**.

## (c) Does shape C suffice? — MOOT. C's (and A's) premise is false.

Both ruled shapes target net-worker starvation by a dial burst:
- **C** (brain bounds/staggers dead-peer dial *requests*) — there is no burst-driven
  worker starvation to relieve; staggering dials changes nothing about the brain's
  drain.
- **A** (isolate loopback I/O onto its own runtime) — the loopback lane is already
  proven un-starved by peer work; isolating it fixes a starvation that isn't there.

**Do NOT mint `REQ-BRAIN-DIAL-BOUND-NO-STARVE` or `REQ-NET-LOOPBACK-NO-PEER-STARVE`.**
Neither closes the field wedge.

## Re-open — where the wedge actually is (HYPOTHESIS for doyle's next ruling)

The stall is the **brain main loop failing to DRAIN controller output sinks for
>15s**. The net runtime is clean, so a peer op stalls the drain only if it runs ON
/ blocks the brain's main (drain) loop. Two sub-candidates, both inside the
v0.30.3 (counter-54) regression window the field pinned:

1. **counter-54 reap-drive (ce053dc) — PRIME suspect for the STEADY-STATE
   doyle-only wedge.** v0.30.3 added "brain self-drives the controller reap" in the
   main loop (every ~500ms). If that drive does a blocking net/IPC op or contends a
   lock with the peer path, it stalls the very drain it shares a thread with — with
   NO respawn — which is exactly the never-before-v0.30.3 steady-state wedge.
2. **Startup sequencing (respawn severity) — the round-2-deferred stagger.** The new
   brain runs net-consumer setup + initial peer dials interleaved with entering its
   drain loop; resumed controllers go undrained across the dead-peer-dial window →
   stall-evict on respawn.

Next rig (needs doyle's authorization — brain+broker+PTY stack, `brain_decouple`
template + a real pump round with a dead peer): assert whether (1) the counter-54
reap-drive and/or (2) startup sequencing blocks the brain from draining an
actively-streaming controller >15s. That is the real decider now. The pure-NetHost
Stage-1 rig has done its job: it removed the net runtime from suspicion.

## Runner

`cargo nextest run -p spt-daemon --test net_worker_starve` (or `--test-threads=1`).
Hermetic (relays disabled, no discovery, loopback QUIC); no SPT_HOME/PTY needed.
