Files
stack/agents/darkwing/work/queue-76-review/review-r2.md
T

8.4 KiB

Row 76, Console sends a message to a role, round 2 review (Darkwing)

Issue #1570, request comment 27672, packet agents/dewey/work/queue-76/ (commit 86821889). Base 729da72b. Candidate manifest sha256 04ecf1b57c594a35d34d53277b2df915a571f4fa9f7cc29146dbedfa612f77d4, 9 files, matching the request. Scope, per lead decisions 99 and 100: the rebase on row 59, my round 1 notes N1 to N5 and the Q1 proxy line, and Filbert's round 1 notes, each read as content.

Verdict: approve. The rebase changes no line I approved in round 1. Each of my notes is fixed as I asked, and N1 and N2 are now held by tests: the three mutants that survived my round 1 run all fail here. Filbert's 408 change is right, and conservative in the direction that matters. Nothing blocks. One cosmetic note.

Method

  • A detached worktree at 729da72b with git apply --index build.patch, then sha256sum -c on the manifest: 9 OK, and again after the mutants. git diff --cached 729da72b --binary -- packages/webui there hashes the same as build.patch (6481006b…). No commit from 729da72b to 2bbc3097 touches the 9 paths.
  • To split the rebase from the content: a second worktree at 729da72b with round 1's build.patch (1deb6143) applied by git apply --3way --index, then diff -ru of packages/webui between the two trees.
  • I read the content diff against my round 1 review (comment 27612), the bus code behind N3 and N4, and the row 59 lines near the merged hunks.
  • Eight WebUI test files, then 22 mutants (r2/mutants.sh): round 1's 16 with their anchors moved to round 2's lines, and 6 aimed at round 2's changes. Each edits one site, runs one test file and restores from the worktree index.

Node v26.8.1, TMPDIR=~/darkwing-scratch/rv76r2/tmp.

The rebase

Round 1's patch merges onto 729da72b by --3way without a conflict. On that tree, round 1's manifest (ca870314) checks 7 OK, and fails only README.md and public/bus.js, the two files row 59 also changed. That matches build.md.

None of the patch's removed lines in those two files are row 59's. The README's "Agent state" section and bus.js's stateMark, attention and agentsView lines are untouched, and bus-browser.test.mjs, which holds row 59's agents view, passes on the candidate.

The content changes

Between the rebased round 1 tree and the candidate, five files differ (291 lines of diff -ru). serve.mjs, bus.mjs, cli.mjs and shell.test.mjs are byte for byte round 1's. Every change is one build.md lists under decisions 99 or 100.

My N1, the board Reply. send-browser now opens an inspection, types a reply, and answers /api/reply with 403, 502 and 408 from the stub. It asserts the exact receipt each time, that the reply text is kept, and that the fake board captured nothing. app-reply-4xx-unknown and app-reply-5xx-refused survived six files in round 1 and fail here.

My N2, the caret. The refresh case focuses the text box, selects 3 to 7, clicks Refresh from script (which doesn't move focus) and asserts focus and {start: 3, end: 7} on the redrawn box. page-caret-lost fails here.

My N3. The README and the box's note now say the message goes the same way as one from the CLI, with class INFO. mosaic talk sends class: "REQUEST" (packages/cli/src/cli.mjs:199), so the README's "where mosaic talk uses REQUEST" is accurate.

My N4. message.receive selects messages with no delivered or read row and writes delivered for each (broker.mjs 777 to 784); talk calls message.read for each one it prints (cli.mjs:208). The new sentence says exactly that.

My N5. The receipt drops the server's leading not sent: and adds the code only when the message doesn't already contain (code). Both browser receipts are now asserted as exact strings. A refusal before the bus (Content-Type must be application/json) still gets its code, and the sign-in receipt keeps (sign-in), which the test asserts. Dropping the prefix goes past what I asked; I agree with it, since without it the receipt read "Not sent to coder: not sent: ...".

Q1. The new bullet in "What the proxy must do" is the line I proposed, with "a 4xx other than 408" after Filbert's N1.

Filbert's N1, 408. Both checks now leave 408 out of "not sent". I agree. Reading a 408 as unknown can only cost a "check before resending" where "not sent" would have been true; the other direction risks a duplicate message. Neither the Console's handlers (serve.mjs answers 200, 400, 401, 403, 404, 405, 500, 502 and 503, plus the board's own status on the Reply proxy) nor the board send a 408. Node's request timeout can, but only while the request is still arriving, so before anything is sent; unknown is cautious there, not wrong. A 408 carrying the Console's sent: false would still read as not sent, and build.md says so. The send box's unknown loop and the Reply case each gain a 408.

Filbert's N3. The six tests are where build.md says: the caret and focus case, pm dropped by endLaunch and still selected as pm (not held now), Inbox current on a message trail, in_reply_to: "" passed through, the ENOENT error now carrying code, and the log's to cut at exactly 64.

Tests and mutants

On the candidate, 02:43:32Z to 02:43:53Z on 2026-10-12 (r2/tests-light.txt):

File Result
send.test.mjs 13 pass, 0 fail
send-browser.test.mjs 1 pass, 0 fail
shell.test.mjs 5 pass, 0 fail
bus-browser.test.mjs 1 pass, 0 fail
bus.test.mjs 9 pass, 0 fail
browser.test.mjs 1 pass, 0 fail
serve.test.mjs 4 pass, 0 fail
preview.test.mjs 11 pass, 0 fail

I didn't run the full gate. Dewey's is green with Docker, Filbert was running theirs while I reviewed, and Sage reruns it.

I started my mutants after Filbert's own mutant run on this row had ended (03:21:03Z), so the two didn't share the machine. 03:21:14Z to 03:22:09Z (r2/mutants.txt), the manifest 9 OK afterwards:

Mutant Round 1 Round 2 fails
cli-no-bus-ok killed the CLI refusal test
start-send-nonfunction killed the startServer test
open-no-invalid-response killed the outcome test, the transport test
open-no-too-large killed the outcome test
nonbus-definite killed the outcome test
missing-to-passes killed the request checks test
route-any-method killed the request checks test
status-mnf-502 killed the outcome test, the real broker test
success-unlogged killed the send test, the transport test
page-never-clears killed send-browser
page-sentfalse-ignored killed send-browser
page-no-config-gate killed send-browser
page-composer-always killed send-browser
page-caret-lost survived send-browser
app-reply-4xx-unknown survived send-browser
app-reply-5xx-refused survived send-browser
page-code-never: no code appended, ever new send-browser
page-408-only-with-body: a 408 with an error reads as not sent new send-browser
app-reply-4xx-all: the Reply reads a 408 as not sent new send-browser
send-log-uncut: the log's to not cut new the forged-line test
page-code-any-paren: any ( in the message drops the code new survives
page-prefix-anywhere: not sent: removed anywhere, not only at the start new survives

All three of round 1's survivors fail now. Round 1's seven page and Reply mutants ran against send-browser alone this time, and that one file catches all seven.

The two survivors are equivalent for every message the Console sends. The only one with a parenthesis or not sent: in it is the bus refusal, not sent: the bus refused it (${code}) (serve.mjs:98), where both edits give the same receipt. They could differ only for a proxy's JSON body with a message field, which is outside the contract. No test needed.

Note

1. The new sentence in send-browser.test.mjs's header ("A role's run is ended in the broker to show a role no longer held") went into the middle of line 4 without a reflow. The line is 157 characters, and the sentence sits between "recording what the page sent" and "Server-side checks are send.test.mjs's", where it reads as part of the stub's description. It belongs with the broker sentence after it. Cosmetic; a follow-up or the landing commit can move it.

Round 1 questions

Q1 is in the README as above. Q2 stays as built.