fix: codex 3, 4, 7, 10 — validate the document not the projection, stable block order, no cycles, real phone reading order
Codex 3: the write gate parsed the JSON view, which drops a formatting attribute it does not know, while y-prosemirror 1.3.7 deletes the XmlText that carries one. New core ABI call slides_core_check (ABI 2) walks every text fragment for anything outside §2.6; appendUpdate refuses such an update 422 before it is stored; fixtures/check.mjs is the same walk and the stage strips unknown attributes before binding. Codex 4: canvas-to-flow (and move_block) cloned every block map, cleared the array and re-inserted copies, so a concurrent text edit addressed a deleted map and was lost on merge. Reading order is now an `order` key on each block map, sorted by the view and never exposed; reorders write the key and the maps stay put. Vectors regenerated (three .yjs moved, no view moved). Codex 7: columns children may name a block once, in one list per slide, and never a block the columns block sits under — refused in both implementations with one message. The renderer and the layout engine append whatever nothing reaches at the end, marked, rather than dropping it (WarnUnreached; data-unreached). Codex 10: rendering for the phone viewport emits the blocks in the phone layer's reading order (regions, then a stable sort by order with unset as 0, columns children included), so assistive technology and the Tab key follow what a sighted reader sees; CSS order is no longer load-bearing. Desktop rendering unchanged; no golden moved. Spec §2.2, §2.3, §2.4, §2.5, §2.6, §3.3 and rulings 18–20 were amended in the tree and were carried by f1c235d.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YYaKcMy5XrMcsfizRXcRSY
core/src/abi.rs+37 −1core/src/check.rs+294 −0core/src/layout.rs+110 −18core/src/lib.rs+17 −12core/src/ops.rs+146 −16core/src/schema.rs+60 −2core/src/view.rs+8 −1core/tests/ops.rs+160 −0fixtures/check.mjs+146 −0fixtures/check.test.mjs+110 −0fixtures/generate.mjs+6 −0fixtures/layout.mjs+15 −11fixtures/ops-apply.mjs+48 −7fixtures/ops-model.mjs+37 −6fixtures/ops.test.mjs+98 −0fixtures/poison.mjs+16 −0fixtures/stage.mjs+13 −4fixtures/vectors/canvas.yjsbinaryfixtures/vectors/columns.yjsbinaryfixtures/vectors/every-block.yjsbinaryfixtures/view.mjs+7 −3fixtures/ydoc.mjs+30 −0internal/slides/core/check_test.go+90 −0internal/slides/core/core.go+41 −2internal/slides/core/core_test.go+2 −2internal/slides/core/testdata/slides_core.wasmbinaryinternal/slides/deck/deck.go+6 −0internal/slides/layout/layout.go+65 −6internal/slides/layout/paint.go+6 −0internal/slides/layout/unreached_test.go+90 −0internal/slides/live.go+23 −5internal/slides/render/order_test.go+140 −0internal/slides/render/render.go+134 −18internal/slides/render/unreached_test.go+93 −0internal/slides/static/{editor.c5e9b4881d5c8a54.js => editor.b15cb47d0e317eee.js}+15 −15internal/slides/static/slides.css+14 −0internal/slidestest/integrity_test.go+120 −0internal/slidestest/testdata/poison/unknownmark.yjsbinary
Intent
<task-notification> <task-id>ac2281ca669d9e1c6</task-id> <tool-use-id>toolu_013TmDDhbeExQX5puph7a4xH</tool-use-id> <output-file>/home/paulca/.cache/tmp/claude-1001/-home-paulca-amadan-net-oficina/c8f62d65-b2d1-4a65-b79e-6a8e82e1e5c6/tasks/ac2281ca669d9e1c6.output</output-file> <status>completed</status> <summary>Agent "Codex wave B: capabilities" finished</summary> <note>A task-notification fires each time this agent stops with no live background children of its own. The user can send it another message and resume it, so the same task-id may notify more than once.</note> <result>Status: done, one commit, not pushed. **f1c235d** — `fix: codex 1, 2, 5, 6, 13, 15 — staged uploads, upload receipts, follow behind its own capability, offboarding, remote ordering and attribution` Per finding (all six citations held; none declined): - **1 fixed** — uploads go to an app-minted staging key (hash of deck+actor+nonce), registration reads and re-hashes (the promised rehash now real) and promotes to an immutable final object; no PUT is ever signed for a verified address; dev `blobs.Dir` path kept. New `asset_uploads` table + migration 0008. - **2 fixed** — registration accepts only a receipt matching this deck *and* actor, unspent, <2h. A ref alone is 404. Cross-deck copy path deliberately not built (spec says why). - **5 fixed** — new `GET /s/{token}/stream`; followers stay on `/s/{token}` for page, fragment, stream and assets; sqid never rendered on a follow page; stream re-resolves the link per event. Permission mismatch settled in spec §3.4 + ruling 15. - **6 fixed** — admission rechecked in `currentUser` (sessions revoked on all devices, cookie cleared, live talks ended, `/signin?err=removed`) and in the bearer gate (401). Offboarding table in spec §6.1; share links deliberately survive. - **13 fixed** — `present.js` tracks requested vs displayed with monotonic request stamps. - **15 fixed** — `remote:<session>` with issuer logged; spec §6.2 + ruling 17. Tests: `make ci` green. New `internal/slidestest/capabilities_test.go` (7 tests) plus updated `assets_test.go`/`present_test.go` — all pass; each new test fails against the old behaviour. Browser drive `go test -tags browser ./internal/slidestest/` **ok (63.9s) only with the Bash sandbox disabled** — sandboxed, Chromium dies on `socket() failed: Operation not permitted` (controller may want `/sandbox`). Full gate deliberately not run (wave A mid-edit on `core/`, `fixtures/`, wasm). Concerns: (1) no `node --test` for `present.js` — no JS harness exists outside `fixtures/`, which was off-limits; finding 13 rests on the browser drive plus attribute assertions. (2) `fixtures/uploads.mjs` has one now-false comment line ("computing it here is what makes the presigned URL possible") — comment only, left untouched as wave A's path. (3) Registration now re-writes ≤20 MiB per picture; documented as the accepted cost. (4) I took spec rulings 14–17; wave A's are 18–19, no collision. Its spec/api edits present in the tree were kept and its spec text rode into this commit (shared file, staged by name).</result> <usage><subagent_tokens>321711</subagent_tokens><tool_uses>171</tool_uses><duration_ms>1897871</duration_ms></usage> </task-notification>
core/src/abi.rsLoading diff…
core/src/check.rsLoading diff…
core/src/layout.rsLoading diff…
core/src/lib.rsLoading diff…
core/src/ops.rsLoading diff…
core/src/schema.rsLoading diff…
core/src/view.rsLoading diff…
core/tests/ops.rsLoading diff…
fixtures/check.mjsLoading diff…
fixtures/check.test.mjsLoading diff…
fixtures/generate.mjsLoading diff…
fixtures/layout.mjsLoading diff…
fixtures/ops-apply.mjsLoading diff…
fixtures/ops-model.mjsLoading diff…
fixtures/ops.test.mjsLoading diff…
fixtures/poison.mjsLoading diff…
fixtures/stage.mjsLoading diff…
fixtures/vectors/canvas.yjsLoading diff…
fixtures/vectors/columns.yjsLoading diff…
fixtures/vectors/every-block.yjsLoading diff…
fixtures/view.mjsLoading diff…
fixtures/ydoc.mjsLoading diff…
internal/slides/core/check_test.goLoading diff…
internal/slides/core/core.goLoading diff…
internal/slides/core/core_test.goLoading diff…
internal/slides/core/testdata/slides_core.wasmLoading diff…
internal/slides/deck/deck.goLoading diff…
internal/slides/layout/layout.goLoading diff…
internal/slides/layout/paint.goLoading diff…
internal/slides/layout/unreached_test.goLoading diff…
internal/slides/live.goLoading diff…
internal/slides/render/order_test.goLoading diff…
internal/slides/render/render.goLoading diff…
internal/slides/render/unreached_test.goLoading diff…
internal/slides/static/{editor.c5e9b4881d5c8a54.js => editor.b15cb47d0e317eee.js}Loading diff…
internal/slides/static/slides.cssLoading diff…
internal/slidestest/integrity_test.goLoading diff…
internal/slidestest/testdata/poison/unknownmark.yjsLoading diff…