oficina / meet Public

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
Paul Campbell pushed by paul@keymail.dev 088e7c0cb3c6a763b55e31ccca55e0318c78f588 parent 00029e8
2 files changed, +143 −33
  • docs/superpowers/reviews/2026-09-10-slice-4-record.md +72 −0
  • docs/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.
+