gate: and nothing followed it
probehang asserted that the target was dropped on time, which was true of a client that dropped it on time and then ran a reset on a fresh thirty seconds behind the test's back (Codex S2-17). It now asserts what the claim actually needs: the offer was the only thing A posted, it is still the only thing ten seconds later, and A holds that target marked stale — waiting for a roster rather than quietly asking again. Against the round-3 client that reads 2 POSTs and no mark at all. Dispositions in the record (§2d), which also records why the client now tells a gate three things about itself that no DOM shows: boot, rosters and stale. A client that has stopped asking for something looks exactly like one that never needed to.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CzvnuKhLzudvq7jZ7SvVt2
2 files changed,
+41
−0
docs/superpowers/reviews/2026-09-10-slice-2-record.md+25 −0test/browser/call.mjs+16 −0
CI passed — run details
diff --git a/docs/superpowers/reviews/2026-09-10-slice-2-record.md b/docs/superpowers/reviews/2026-09-10-slice-2-record.md| index 6a68232..7f604a2 100644 |
| --- a/docs/superpowers/reviews/2026-09-10-slice-2-record.md |
| +++ b/docs/superpowers/reviews/2026-09-10-slice-2-record.md |
| @@ -253,6 +253,31 @@ should re-check that list before reusing `stale`. |
| --- |
| +## 2d. Codex round 4, and what happened to it |
| + |
| +Judged at `2f7f63f`. S2-16 closed. S2-15 half-closed through one P2, and |
| +it is the same lesson a third time: the budget was threaded correctly |
| +and then leaked out of the one exit nobody was watching. |
| + |
| +| # | Finding | Disposition | Commit | Test | |
| +|---|---|---|---|---| |
| +| S2-17 (P2) | An aborted probe or a transient refusal that exhausted `waitWithin` returned from `recoverFromNotFound` without releasing the target. The queue survived, the pump's `finally` restarted it, its expired head called `unreachable`, and that opened a reset with a fresh thirty seconds — measured at 31000 for the offer and 61000 for the reset, with zero stale-target calls. Separately `resetLoop` checked the participation before every attempt but never the clock, so one POST could go out past the deadline meant to cover it | **Fixed.** Both loops round back to a single check at the top, which is now the only place a spent budget is answered, and it answers by releasing the target — queue dropped before the pump is released, connection closed, mark set, next roster settles it. `resetLoop` gained the same check. `createPC` clears the mark, because building a connection to a peer means the server has just named it. | `50953f4` | a pure node test — the recovery lifted out of the shipped source, given fifty milliseconds and a probe that never answers, must release its target once and ask for nothing — and `probehang`, which now asserts the offer was the *only* thing A posted, that it is still the only thing ten seconds later, and that A holds the target marked stale | |
| + |
| +Verified failing first: the node test with the exhaustion `return` |
| +restored — "the target must be released exactly once"; `probehang` |
| +against `room.js` at `2f7f63f` — **2** POSTs where the fixed client |
| +makes 1, and no stale mark at all. The second POST is the fingerprint of |
| +the finding: it is the reset that ran on a fresh thirty seconds while |
| +the scenario's earlier assertions were all passing. |
| + |
| +`state()` gained the stale set for this, which is the third thing the |
| +gate has needed the client to say about itself (`boot`, `rosters`, |
| +`stale`). Each is a fact no DOM shows and no HTTP answer reveals: a |
| +client that has *stopped* asking for something looks exactly like one |
| +that never had to. |
| + |
| +--- |
| + |
| ## 3. Carried items, and where they go |
| **Next fix wave** |
diff --git a/test/browser/call.mjs b/test/browser/call.mjs| index cf70f90..8b4e9e4 100644 |
| --- a/test/browser/call.mjs |
| +++ b/test/browser/call.mjs |
| @@ -197,6 +197,7 @@ function state(page) { |
| admitted: s.admitted, |
| terminal: s.terminal, |
| rosters: s.rosters, |
| + stale: s.stale, |
| peers: s.peers, |
| frames: Object.fromEntries(s.frames), |
| signaling: Object.fromEntries(s.signaling), |
| @@ -1188,6 +1189,21 @@ const run = { |
| const took = Date.now() - firstAt; |
| check(took >= 25000 && took <= 35000, |
| 'and it did so on the message’s own clock, not a fresh one (' + took + ' ms; a restarted clock lands near 50000)'); |
| + |
| + // Dropping the target on time is not the claim. The claim is that |
| + // the recovery ended there — and a client that let the queue restart |
| + // would look identical at this instant while a reset ran on a fresh |
| + // thirty seconds behind it (Codex S2-17). So: the one message this |
| + // scenario sent is the only thing A ever posted, and it is still the |
| + // only thing ten seconds later. |
| + const atDrop = signals; |
| + check(atDrop === 1, 'the offer was the only thing A posted (' + atDrop + ')'); |
| + check(dropped && dropped.stale && dropped.stale.includes(pair.b.peer), |
| + 'and A holds the target marked stale, waiting for a roster rather than asking again (' + |
| + JSON.stringify(dropped && dropped.stale) + ')'); |
| + await sleep(10000); |
| + check(signals === atDrop, 'nothing followed it, ten seconds on (' + atDrop + ' → ' + signals + ')'); |
| + |
| const after = await state(pageA); |
| check(after.peer === pair.a.peer && !after.terminal, |
| 'A is still in the call with its own peer id — ' + (await diagnose(pageA, 'member A'))); |