Files

8.2 KiB

Row 148 round 1 review (Filbert): approve

Candidate: manifest sha256 68bdcecec3b8d92ccfaed7595f415f062270cebf7899f5c0edcdd6cd63e0f5f5, 27 paths, base fcea7d015, packet agents/researcher/work/queue-148/ (request comment 28778). Brief: docs/plans/2026-10-15_q167-rows-r3.md § "A Console editor for the project's spec and docs" at blob 6805904555. Mos's decision 123 go covers public-diff.patch e8b0bc45e8539a9384618dc98d6018264faa4b13f511873188d5dec20c6385fc (sage-0555).

I found no defect. Every test the brief lists is there, and each boundary check matches the Settings write. The notes below don't block.

Rebuild

  • A fresh worktree at fcea7d015. row148.patch is a plain diff, so I applied it with git apply --index.
  • All 27 manifest hashes matched, and git diff --cached --check was clean (27 files, +1421 -46).
  • The added lines have no dash, curly-quote or control characters.
  • No commit from fcea7d015 to refactor HEAD touches a manifest path.
  • The candidate's packages/webui/src/public/ diff has sha256 e8b0bc45..., byte-identical to the public-diff.patch Mos approved.

Brief tests, each found

  • One commit, human author, right content. bus project-files.test "a human lists, opens and saves" checks one commit since the base, author jason, subject Edit spec/north-star.md, the new content at HEAD and a clean tree. project human-file.test covers the same at the package level.
  • An agent capability refuses the save verb. The coder and reviewer sessions refuse all three verbs human-required, and a reader refuses read-only, with HEAD and status unchanged and six refusals recorded.
  • A stale base refuses and writes nothing. After a newer commit, a save on the old base refuses stale-base, HEAD and status are unchanged, and a save on the new base passes. The Console's 409 message says to reload, and the browser test shows it.
  • A path outside the two refuses. project.json, requirements/.gitkeep, spec/other.md, docs/sub/deep.md, ../x.md and docs/a.txt all refuse project-record-path. A docs file that doesn't exist refuses project-read-missing, so the editor can't create files.
  • Each boundary check refuses as Settings does. webui project-file.test covers the wrong Host, the wrong or doubled user header, the wrong or missing Origin, cross-site or missing Fetch Metadata, a non-JSON Content-Type, bad JSON, an extra or missing key, a non-string value and a body over 1 MiB. None reaches projectSave. Without --send the route answers 403 read-only.
  • A <script>, an onerror attribute and a javascript: link run nothing. docs-browser.test loads hostile Markdown into the real page and checks window.injected stays unset, that no script, img, iframe, object, embed, style, form, svg or [onerror]/[onclick] node is built, and that no link has a javascript: or data: href.
  • The preview source has no sink. markdown-preview.test reads the file and refuses innerHTML, outerHTML, insertAdjacentHTML and document.write, and also eval, new Function, DOMParser, createContextualFragment and srcdoc.
  • Each allowed element renders; other schemes stay text. markdown-preview.test renders every element. It covers about 20 scheme evasions (case, tab, newline, leading space, control, bidirectional and zero-width characters, //, backslashes) and checks each stays as written with no link.

What I checked and found correct

  • The 1 MiB limit is on this route only. In webui serve.mjs only /api/project-file POST passes FILE_SAVE_MAX. The Settings and message routes keep their own limits, and a test sends 20000 bytes to each and gets 400. On the bus socket, server.mjs allows the larger envelope only when the save verb appears in the first 1024 bytes, and checks the parsed verb again afterwards. A request that names the save verb first and another verb last, which JSON.parse reads as the second, is refused request-too-large (tested). client.mjs uses the larger limit for the save verb only.
  • The save's checks in the project package. writeHumanFile checks the path (spec/north-star.md or docs/NAME.md with no subdirectory), that the content is nonempty text with no NUL within 1 MiB, that the base is 40 hex, and that the author is the business's human. Under the lock it refuses a missing file, stale-base when the file's last commit at HEAD isn't the base, and an unchanged file. It commits only that path. Two racing saves make one commit (tested).
  • The bus verbs. #requestProjectFile calls #human(s) before anything else, then strict keys per verb, and runs the result through #secretCheck. ProjectError codes are passed on as bus codes, and every refusal is recorded.
  • The preview. It builds nodes with createElement, textContent and text nodes only. linkTarget is the one gate for both the href and whether a link is made: it allows http and https with a host, or a relative target with no whitespace, control, backslash or bidirectional character and no leading //. Everything else stays as source text. An image becomes a span holding its alt text and URL, never an img. Nesting and look-ahead are bounded, and the pathological-input test runs in under 4 s.
  • The editor page. The textarea is filled through esc, so </textarea> in a file stays text. The draft lives in a Map, not the DOM. The 10 s refresh is off on an open file. A save never retries after an unknown outcome. Save shows only when /api/config says edit, which uses the same test as the route.
  • CSP. No change, nothing vendored, no vendor route.

Notes (non-blocking)

  1. Scope: five files are outside the brief's owned list. bus client.mjs, runtime.mjs and server.mjs, webui cli.mjs, and bus tests/spec-fixtures.mjs are changed. The brief owns broker.mjs and human-cli.mjs on the bus side, and serve.mjs, bus.mjs and reads.mjs in webui. The verb can't work without the runtime wiring and the socket's size limit, and cli.mjs is where the Console's bus calls are bound, so each change is needed. BUILD.md lists them but doesn't say why. human-cli.mjs and reads.mjs are untouched. Scope is Sage's call.
  2. The status line is missing a period. serve.mjs's stale-base message has no final period, and the page appends " Draft kept.", so it reads "...make your edit again Draft kept.". serve.mjs's other refusal message, not saved: the bus refused it (CODE), has the same gap. docs-browser.test pins the stale-base string, and BUILD.md already names it. Cosmetic.
  3. A file right at the 1 MiB bound opens but can't be saved. The project accepts content up to 1 MiB, but the route's 1 MiB applies to the JSON body, so the content limit is about 1 MiB minus the envelope and JSON escaping. The test pins this ("over 1 MiB with the envelope"). The refusal is clean: 400, sent: false, nothing written. I read the brief's "the size limit is row 147's 1 MiB file bound, on this route" as met.
  4. After a save the page keeps the old raw. The CRLF note stays until the file is opened again. Cosmetic.
  5. Landing order. Row 139, which is landing now, also changes bus broker.mjs, the bus README and bus tests/spec-fixtures.mjs. If 139 lands first, this candidate's base has drifted on three manifest paths and needs a rebase and a full gate rerun on the new base.
  6. Slot. BUILD.md records that Researcher ran the mutants and the gate without a slot while Darkwing held it. Researcher's receipts below are as reported.

What I ran and what I took from receipts

I ran, as single files on the candidate with Docker disabled and TMPDIR set to scratch, all with 0 fail and 0 skipped, on the slot Sage granted:

Test file Pass
webui project-file 9
webui markdown-preview 10
webui docs-browser 1
webui shell 5
webui views 3
webui browser 1
webui settings-write 3
webui send 13
bus project-files 8
bus broker 16
bus spec 4
bus spec-socket 6
bus project-runtime 3
project human-file 7

I ran no mutants, no suite and no gate.

From Researcher's receipts, not rerun by me (BUILD.md): 23 of 23 mutants killed, each by an assertion failure (Mos's go condition), and the gate from 18:40:43Z to 18:44:29Z with every package suite and every scripts/test-*.sh rc=0, including project 37/37, bus 302/302, cli 215/215 and webui 133/133.