test(registry-lifecycle): order the pump's exit before anything samples B Golden #272 r2 attempt 4 (25e60015) red this cell at :514 with `held 0 -> 1, seats 1` in 11.594 s — passing at 10.8/10.5/23.3 s on a1-a3. Not a budget: the re-apply bound immediately above it PASSED. It is an ORDERING defect in the rig. The pump was spawned with a bare `thread::spawn` whose JoinHandle was dropped on the floor, and stopped by a flag alone. The drains below it converge on A's and B's gauges, which prove the observable state is clean — they do NOT prove the pump THREAD is done. `run_peer_pump` reads the flag at the top of its loop, so a round already in flight when the flag is set runs to completion, and its last feed can land on B AFTER the gauges have read clean: one held row plus one in-flight seat arriving after `b_held_before` is sampled. That is the observed signature exactly. (a) The handle is kept, and the pump signals an mpsc channel AFTER `run_peer_pump` returns. The rig does `recv_timeout(PUMP_EXIT_BUDGET)` then joins, immediately after the stop flag and BEFORE the drains and the sample. The prior comment here argued AGAINST joining, and it was right about the thing it named: a bare UNBOUNDED join can hang, and a hung test reports nothing at all — strictly worse than the assertion it protects. What it missed is that bounded and joined are SEPARABLE, so the choice was never join-or-converge. The comment is rewritten to record that rather than silently overridden. Two properties of the bound, stated so a future red is read correctly: - PUMP_EXIT_BUDGET bounds a ROUND, and a pathological round can chain several 30 s reads across five legs. An expiry therefore names a LEG-STUCK RIG, never a product verdict — the failure says so and says not to raise the budget to make it go away. - The join after a proven exit is prompt BY CONSTRUCTION: the thread has already signalled that `run_peer_pump` returned. PUMP_EXIT_BUDGET mirrors the product's private `PUMP_PEER_IO_TIMEOUT` (30 s), the bound on any single reply read — the pump splits its carrier at construction so a black-holed peer cannot wedge the loop. The mirror is a second literal by construction and is declared, not hidden: the doc comment says IF THAT CONSTANT MOVES, MOVE THIS ONE, and drift makes the budget wrong rather than silently permissive because the failure names it. SEED (post-publish thin PR, deliberately not in this rider, which stays test-only): make the product const `pub`, or add a `pub` accessor, and delete the mirror. (b) The :514 failure now prints B's stream table and per-family registry row counts. THE CONDITION IS UNTOUCHED — only the message grew. The gauges are TOTALS, so `held 0 -> 1, seats 1` said a row arrived without saying which. `NetStreamInfo` carries no family field (family is a registry-row concept here), so the counts sit beside the table rather than in it. A probe that fails renders as `(stream table UNREADABLE: …)`, never as an empty table: absent and unreadable must not render alike. Format arguments are evaluated only on the failure path. (c) Audit of the same file for other flag-stopped-never-joined actors sampled by gauges: the pump was the only one. Five stop flags; `dispatch_gen1` joins, `dispatch_gen2` joins, and both single-dispatcher tests join. The broker serve thread has no flag — it is the rig's server. The genuinely un-joinable case stays as-is and documented: the dispatch loop spawns per-row workers and exposes no handle, so seats-to-zero remains the observable there. (d) Predicates and both directional assertions are unchanged. Co-authored by: hertz Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01WAw9XAcTR8oSwzYPsqZoY2