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
729da72bwithgit apply --index build.patch, thensha256sum -con the manifest: 9 OK, and again after the mutants.git diff --cached 729da72b --binary -- packages/webuithere hashes the same asbuild.patch(6481006b…). No commit from729da72bto2bbc3097touches the 9 paths. - To split the rebase from the content: a second worktree at
729da72bwith round 1'sbuild.patch(1deb6143) applied bygit apply --3way --index, thendiff -ruofpackages/webuibetween 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.