docs: §15.16 as three separate facts, and Codex round 2 in the record
§15.16 contradicted itself: one paragraph said there was no detection or repair, and one three below it promised recovery and cited a scenario that was skipped. Both sentences are gone, and the section now records the three things separately, because they are three things. The historical observation — Chromium rebuilding every send and receive stream on a rollback of an offer that added an m-line, measured in Task 4 as an encoder loss, three failures in five — stands as evidence of what a rollback costs and is no longer offered as the diagnosis of anything still failing. What the yield does about it is prevent the rollback, which is a duration and not a guarantee. What is repaired when it happens anyway is now two bounded watches on the same one-second walk, the encoder one and the render one, both ending in a reset. And what the gate proves is stated with what it does not: on the head this was written against the encoder loss did not reproduce, and the rendering stall did — with the numbers, the identity reading, and the before-and-after series that show the repair working. The record gains §7: every round-2 finding with its disposition, its commit and its test, and the account of the tile that would not paint — which is the more useful half, because the first candidate explanation was a real defect that turned out not to be the cause.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CzvnuKhLzudvq7jZ7SvVt2
2 files changed,
+143
−33
docs/superpowers/reviews/2026-09-10-slice-4-record.md+72 −0docs/superpowers/specs/2026-09-07-meet-design.md+71 −33
diff --git a/docs/superpowers/reviews/2026-09-10-slice-4-record.md b/docs/superpowers/reviews/2026-09-10-slice-4-record.md| index 5b3fe04..bf48c24 100644 |
| --- a/docs/superpowers/reviews/2026-09-10-slice-4-record.md |
| +++ b/docs/superpowers/reviews/2026-09-10-slice-4-record.md |
| @@ -451,3 +451,75 @@ calling `stopSharing()`, so the `share {on:false}` those paths trigger |
| short-circuits in `broadcast` and no POST goes out after the leave. |
| That is the right order, it is not obvious from reading `stopSharing` |
| alone, and no test would notice if a later edit inverted it. |
| + |
| +--- |
| + |
| +## 7. Codex round 2 |
| + |
| +Judged at `55f5681`. S4-1, S4-2 and S4-3 closed; S4-4, S4-5 and S4-6 |
| +half-closed, each with the half that was missing named; five new |
| +findings. All of them are closed on this branch, and the receiving-side |
| +failure §6 left standing is closed with them. |
| + |
| +| Finding | Disposition | Commit | Test | |
| +|---|---|---|---| |
| +| **S4-9 (P2)** — `adoptTrack` pruned only *ended* tracks, so a camera rebuilt on the known camera mid with its predecessor still `live` left the stream holding [old, new] and the tile showing the old one | Fixed. Each role — camera, screen — keeps a stream of this file's own; `adoptTrack` makes the arriving track *the* track of its kind in it, whatever the predecessor's readyState; `renderTiles` names the track each tile is meant to show and `tile()` binds to that, never to an array position | `560c9d2` | Four, all seen failing against the shipped source first; the same-mid replacement reproduced Codex's `cameraTracks: [old, new]`, `shownTrack: old` exactly | |
| +| **S4-8 (P2)** — exhausting `NEGOTIATE_ROUNDS` cleared `renegotiate` in the `finally`, discarding an outstanding negotiation (waits 4, offers 0) | Fixed. The bound limits how long one attempt goes round, not whether outstanding work matters: the ask stays on the connection and `keepNegotiation` gives it the connection's next return to `stable`, or a timer where nothing else will announce one. One retry at a time; a connection that goes away keeps none | `5546a8d` | Three, two mutation-verified; the first reproduces Codex's trace and asserts the ask survives it | |
| +| **S4-7 (P2)** — Go escapes U+2028/U+2029 unconditionally, `JSON.stringify` leaves them literal, so 2045 a's and one U+2028 counted 2048 in the composer and 2051 at the server | Fixed. `chatBytes` escapes them the way Go does. The pattern is written as escapes because both characters are line terminators in JavaScript source — which is most of why the gap existed | `bb97a15` | Client test seen failing first (6 !== 3); five matching server cases, sent as themselves the way a paste arrives | |
| +| **S4-10 (P3)** — a decreasing `framesEncoded` counter read as "encoding recovered" and replenished the repair allowance | Fixed. The baseline moves either way, because the old number means nothing after a restart; the budget is forgiven only by frames that actually arrived | `716e0f6` | Two; the first seen failing first, the second holds the other side | |
| +| **S4-11 (P3)** — every case in the canonicalisation regression sent an already-canonical payload | Fixed. Two cases now send real JSON escapes and require the decoded letters on the wire — verified by putting the raw bytes back, where those two fail and nothing else does — and `share` has output coverage at last | `716e0f6` | Seven cases, two of them discriminating | |
| +| **The S4-6 gap** — `frameGrowth` returns on the first increase, so its timeout is a deadline and not an observation | Fixed. `sustained` requires growth in two successive intervals, and `checkMedia` asks it of everything a side should be receiving at once. Applied after every settled transition in `share` and `mutualshare`, including after the simultaneous stops and after the asymmetric stop's own five-second wait, which every media check used to precede | `00029e8` | The scenarios themselves; the round-two suppression mutation stays the gate on the baseline half | |
| + |
| +**§15.16 no longer runs three facts together.** It now records |
| +separately: the historical encoder-loss observation from Task 4 (which |
| +stands as evidence of what a rollback costs, and is not the diagnosis |
| +of anything still failing), what the yield does about it (prevents the |
| +rollback; a duration, not a guarantee), and what is repaired when it |
| +happens anyway (two bounded watches, both ending in a reset). The |
| +sentence saying there is no detection or repair, and the one promising |
| +recovery three paragraphs later, are both gone. |
| + |
| +### The tile that would not paint — what the identities showed |
| + |
| +S4-9 was a real defect. It was not this one. With the tile bound to the |
| +role's own track, `laterolls` failed in the same place, and the |
| +identity reading added for the purpose — `meet.media()`, which compares |
| +the objects inside the page because identity does not survive an |
| +`evaluate()` — settled what the failure is not: |
| + |
| +``` |
| +wanted ce46729b-…aef7 |
| +bound ce46729b-…aef7 |
| +element ["ce46729b-…aef7:live"] |
| +receiver mid 1, sendrecv, live, muted:false, isCamera:true |
| +agrees true currentTime 1.452 |
| +``` |
| + |
| +Three answers, one object, on both sides. The element held exactly the |
| +track the connection was receiving the camera on, live and unmuted, and |
| +the decoder went on delivering frames it never painted. Everything |
| +upstream of the element was right and the picture was stopped |
| +downstream of it — which is the branch Codex named for exactly this |
| +result. |
| + |
| +So a second watch, on the same one-second `getStats` walk and in the |
| +same shape as the encoder one: `framesDecoded` per tile, and three |
| +polls of "the decoder is delivering, the track is playable, the clock |
| +has not moved" is a rendering stall. The repair is the stream taken off |
| +the element and given back, and `play()` asked for again; two of those, |
| +then a reset, then it stops asking. A tile receiving nothing is idle |
| +rather than stalled, which is what reading the decode count alongside |
| +the clock is for. |
| + |
| +Measured either side of it, from the scenario's own 500 ms series of |
| +rendered-frame totals: `[25,26,26,26,26,26]` — stopped, and stopped for |
| +the twenty seconds the check waited — before, and `[25,25,25,25,25,26]` |
| +after: frozen for the three polls it takes to notice, then moving |
| +again. **`laterolls` is out of `skipped` and passing, and the |
| +deterministic camera failure §6 recorded as a deploy blocker is |
| +closed.** |
| + |
| +`laterolls` also proves its own premise now, which it did not: it |
| +counts remote offers applied over this side's own outstanding offer — |
| +the rollback, seen from outside — and fails if there was none to |
| +recover from. One was seen on every run. |
diff --git a/docs/superpowers/specs/2026-09-07-meet-design.md b/docs/superpowers/specs/2026-09-07-meet-design.md| index 9e27292..716cd64 100644 |
| --- a/docs/superpowers/specs/2026-09-07-meet-design.md |
| +++ b/docs/superpowers/specs/2026-09-07-meet-design.md |
| @@ -1741,36 +1741,74 @@ after that and the pair is reset instead; after that it stops asking, |
| because a reset that did not take is not a reason to ask again every |
| two seconds. |
| -**Why.** Two peers who each add a screen-share transceiver to the same |
| -connection in the same moment collide, and the polite side's |
| -`setRemoteDescription` rolls its own offer back to accept the impolite |
| -side's. Chromium answers a rollback of an offer that added an m-line by |
| -tearing down and rebuilding every send and receive stream on that |
| -connection, not only the one the new m-line belongs to — so the |
| -camera's own sender comes back on the same SSRC having encoded |
| -nothing, and never encodes another frame, while its m-line goes on |
| -reporting `sendrecv/sendrecv` with a live track and the far side's |
| -matching receiver goes `muted`. Nothing in `room.js` ever touches the |
| -camera; the loss is entirely a side effect of the browser's own |
| -rollback. It reproduced in three of five runs of the browser gate's |
| -`mutualshare` scenario, with both screens perfect and both faces |
| -stopped, and was invisible in every other scenario because none of |
| -them checks the camera again after the call's first few seconds. The |
| -fix does not repair a camera that has already been rebuilt — there is |
| -no detection or repair path — it prevents the rollback from happening |
| -at all: an offer answered from `stable` needs no rollback. |
| - |
| -**The residual.** 300 ms covers the browser gate's own loopback |
| -signalling by two orders of magnitude and a real network hop by a |
| -comfortable margin, but it is a duration, not a guarantee: a pair whose |
| -signalling round trip exceeds it — or whose offer is late for any other |
| -reason, a `429` retry included — will still collide, and on that pair |
| -the polite side still rolls back. What is no longer true is that the |
| -rollback costs it its camera for the rest of the call: the watch above |
| -notices within about two seconds and repairs it. The browser gate's |
| -`laterolls` scenario forces exactly that case, holding one side's own |
| -`offer` requests for four times the yield so the collision is late by |
| -construction, and asserts the recovery. If a deployment reports a |
| -camera stopping after two people share at once, `POLITE_YIELD` is the |
| -first thing to check, the repair path is the second, and whether |
| -Chromium has fixed the underlying rollback behaviour is the third. |
| +**And a tile that decodes and does not paint is handed its media |
| +back.** One link further along, and the one nothing else notices. The |
| +same `getStats` walk reads `framesDecoded` per tile; three polls in a |
| +row where the decoder is delivering, the tile's track is live and |
| +unmuted, and the element's own `currentTime` has not moved is a |
| +rendering stall. The repair is to take the stream off the element and |
| +give it back and ask it to play again; two of those, then a reset, then |
| +it stops asking. A tile receiving nothing is idle rather than stalled — |
| +that is what reading the decode count alongside the clock is for — and |
| +a track that has ended or gone muted is the sender's business. |
| + |
| +**Three separate facts, recorded separately**, because they were run |
| +together once and the section contradicted itself. |
| + |
| +*The historical observation.* Two peers who each add a screen-share |
| +transceiver to the same connection in the same moment collide, and the |
| +polite side's `setRemoteDescription` rolls its own offer back to accept |
| +the impolite side's. Chromium answers a rollback of an offer that added |
| +an m-line by tearing down and rebuilding every send **and receive** |
| +stream on that connection, not only the one the new m-line belongs to. |
| +In slice 4's Task 4 that was measured as an encoder loss: the camera's |
| +sender came back on the same SSRC having encoded nothing and never |
| +encoded another frame, while its m-line went on reporting |
| +`sendrecv/sendrecv` with a live track and the far side's matching |
| +receiver went `muted`. It reproduced in three of five runs of |
| +`mutualshare`, with both screens perfect and both faces stopped. |
| +Nothing in `room.js` touches the camera; the loss was entirely a side |
| +effect of the browser's own rollback. That observation stands as |
| +evidence of what a rollback costs. It is not the diagnosis of anything |
| +still failing. |
| + |
| +*What the yield does about it.* It prevents the rollback rather than |
| +undoing one: an offer answered from `stable` needs no rollback. It is a |
| +duration and not a guarantee — a pair whose signalling round trip |
| +exceeds 300 ms, or whose offer is late for any other reason, a `429` |
| +retry included, still collides, and on that pair the polite side still |
| +rolls back. |
| + |
| +*What is repaired when it happens anyway.* Two watches, both on the |
| +one-second `getStats` walk, both bounded, both ending in a reset: the |
| +encoder watch above (outbound `framesEncoded` static on a sending |
| +m-line with a live track) and the render watch (frames decoded, clock |
| +stopped). Neither is a proof that the rollback is harmless; each is a |
| +bounded recovery from one way it has been seen to hurt. |
| + |
| +**What the gate proves, and what it does not.** `laterolls` holds one |
| +side's own `offer` requests for four times the yield, so the remote |
| +offer arrives after it and the collision is late by construction, and |
| +it asserts the rollback separately from the recovery: the scenario |
| +counts remote offers applied over this side's own outstanding offer, |
| +and fails if there was no rollback to recover from. The recovery it |
| +then requires is frames arriving across two successive intervals, for |
| +both cameras and every screen. On the head this was written against, |
| +the encoder loss did **not** reproduce — the far side's outbound counts |
| +kept rising throughout — and what did was the rendering stall: the tile |
| +bound to exactly the receiver's own live, unmuted track (`meet.media()` |
| +reports `agrees: true`), the decoder delivering, and the picture |
| +stopped at `currentTime` 1.452 for as long as anything watched it. |
| +Rendered frames every 500 ms read `[25,26,26,26,26,26]` before the |
| +render watch and `[25,25,25,25,25,26]` after it: frozen for the three |
| +polls it takes to notice, then moving again. |
| + |
| +**If a deployment reports a face freezing after two people share at |
| +once**, the order to check is: `POLITE_YIELD` against the pair's actual |
| +signalling round trip; then which watch fired, if either — the encoder |
| +one says the camera stopped being produced, the render one says it |
| +stopped being painted; then `meet.media()`'s `agrees`, which separates |
| +a binding fault from a pipeline one; and then whether Chromium still |
| +rebuilds every stream on the connection when it rolls back an offer |
| +that added an m-line. |
| + |