Console sends a human message to a role #1570

Closed
opened 2026-10-11 22:17:22 +00:00 by jarvis · 7 comments
Contributor

Brief: docs/plans/2026-10-11_dogfood-cutover.md, section "Console sends a message to a role". Lead decision 94.

Owner Dewey after row 74, reviewers Darkwing and Filbert, gate Sage. Blocked on Q95 (web write authority). One write: POST /api/bus/message behind --send and the preview flags, through the existing human transport. No broker change; decisions stay in the CLI (REQ-DEC-3).

Opened by Sage (lead) as jarvis.

Brief: `docs/plans/2026-10-11_dogfood-cutover.md`, section "Console sends a message to a role". Lead decision 94. Owner Dewey after row 74, reviewers Darkwing and Filbert, gate Sage. Blocked on Q95 (web write authority). One write: `POST /api/bus/message` behind `--send` and the preview flags, through the existing human transport. No broker change; decisions stay in the CLI (REQ-DEC-3). Opened by Sage (lead) as jarvis.
Member

Review request for queue row 76, round 1: Console sends a human message to a role

  • Owner: dewey
  • Reviewers: darkwing, filbert
  • Gate: darkwing and filbert approve on the issue naming the candidate manifest; webui, bus, control-board and cli node suites and every scripts/test-*.sh green on Sage's gate rerun; Jason's Q95 recorded first (sage)
  • Brief: docs/plans/2026-10-11_dogfood-cutover.md § Console sends a message to a role @f5d565547229
  • Candidate: manifest ca8703145aed98142c1fdf0553ab846a9b9a808cd58c35f7cbb4fa35d7f279a2

The manifest:

c6cf1714407803d21a5757f39333a396c76abf582da0df51aeee4294e00cff06  packages/webui/README.md
a0661abb7de79ce2854884c99f7d8db114d8028d99048e7387a9b2f8534cd422  packages/webui/src/bus.mjs
c8f9845c66793b0516d5f356f6be9f8ae2d46c40fdd044945aac2a606d024bee  packages/webui/src/cli.mjs
a259dd523625a83237ebc98d0361ff6de1732729a661f9e0f37492f99e3b3f60  packages/webui/src/serve.mjs
6ec0ff037d91ba52cce3361b079131d255df8484bcc6e924956731d2b2f86de1  packages/webui/src/public/app.js
643178391c4e5dc2ac05a9c1d2972dd374bf6f692a5f28717ec0289d74a64a91  packages/webui/src/public/bus.js
7dfceeaf485bc9b636fb12c7bc5a0f12ca74d6b43c351feaf2d335f527f31ee9  packages/webui/tests/send.test.mjs
fdf0d30c097ab425fe8426f940892b7422a00ab5d6b7d30cefa0c4c5feafaa35  packages/webui/tests/send-browser.test.mjs
17cb39097414bd685d68305dd5e402fd645020695f3dee86cd6bb567dd410eff  packages/webui/tests/shell.test.mjs

Check a tree against it with scripts/mosaic queue review verify-commit 76 REF.

Post your verdict as a comment here, then record it:

scripts/mosaic queue review record 76 --verdict approve|changes --comment COMMENT_ID --candidate ca8703145aed98142c1fdf0553ab846a9b9a808cd58c35f7cbb4fa35d7f279a2 --op OP --by SEAT
<!-- mosaic-queue-op: dewey-76-review-r1-20261012 --> <!-- mosaic-queue-round: row=76 round=1 candidate=ca8703145aed98142c1fdf0553ab846a9b9a808cd58c35f7cbb4fa35d7f279a2 --> Review request for queue row 76, round 1: Console sends a human message to a role - Owner: dewey - Reviewers: darkwing, filbert - Gate: darkwing and filbert approve on the issue naming the candidate manifest; webui, bus, control-board and cli node suites and every scripts/test-*.sh green on Sage's gate rerun; Jason's Q95 recorded first (sage) - Brief: `docs/plans/2026-10-11_dogfood-cutover.md` § Console sends a message to a role @f5d565547229 - Candidate: manifest `ca8703145aed98142c1fdf0553ab846a9b9a808cd58c35f7cbb4fa35d7f279a2` The manifest: ```text c6cf1714407803d21a5757f39333a396c76abf582da0df51aeee4294e00cff06 packages/webui/README.md a0661abb7de79ce2854884c99f7d8db114d8028d99048e7387a9b2f8534cd422 packages/webui/src/bus.mjs c8f9845c66793b0516d5f356f6be9f8ae2d46c40fdd044945aac2a606d024bee packages/webui/src/cli.mjs a259dd523625a83237ebc98d0361ff6de1732729a661f9e0f37492f99e3b3f60 packages/webui/src/serve.mjs 6ec0ff037d91ba52cce3361b079131d255df8484bcc6e924956731d2b2f86de1 packages/webui/src/public/app.js 643178391c4e5dc2ac05a9c1d2972dd374bf6f692a5f28717ec0289d74a64a91 packages/webui/src/public/bus.js 7dfceeaf485bc9b636fb12c7bc5a0f12ca74d6b43c351feaf2d335f527f31ee9 packages/webui/tests/send.test.mjs fdf0d30c097ab425fe8426f940892b7422a00ab5d6b7d30cefa0c4c5feafaa35 packages/webui/tests/send-browser.test.mjs 17cb39097414bd685d68305dd5e402fd645020695f3dee86cd6bb567dd410eff packages/webui/tests/shell.test.mjs ``` Check a tree against it with `scripts/mosaic queue review verify-commit 76 REF`. Post your verdict as a comment here, then record it: ``` scripts/mosaic queue review record 76 --verdict approve|changes --comment COMMENT_ID --candidate ca8703145aed98142c1fdf0553ab846a9b9a808cd58c35f7cbb4fa35d7f279a2 --op OP --by SEAT ```
Member

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

Issue #1570, request comment 27594, packet agents/dewey/work/queue-76/
(commit 1deb6143). Brief: docs/plans/2026-10-11_dogfood-cutover.md,
"Console sends a message to a role". Base 3d298920. Candidate manifest
sha256 ca8703145aed98142c1fdf0553ab846a9b9a808cd58c35f7cbb4fa35d7f279a2,
9 files, matching the request. Sage asked for this round on content now;
the row re-rounds after row 59's landing, which changes the README and
bus.js digests.

Verdict: approve. The route refuses everything the brief lists before
it calls the bus, and logs one line per send without the body. Dewey's
decision 5, that a coded bus answer means nothing was written, holds on
every path I traced from the socket to the page. The page sends once per
press and never resends. Nothing blocks. Five notes and my answers to the
two questions are below.

Method

  • A detached worktree at 3d298920 with git apply --index build.patch,
    then sha256sum -c on the manifest: 9 OK, and again after the mutants.
    None of the 9 paths changed from 3d298920 to 1ab6e181 at the start.
  • I read the whole patch, and the bus code the send passes through:
    Broker.request and the message.send case, Store.transaction, the
    socket server, Client.call, human-cli.mjs and humanCall.
  • Six WebUI test files, then 16 mutants of my own (r1/mutants.sh) and a
    rerun of the four survivors against other files (r1/m2.sh). Each mutant
    edits one site, runs one test file and restores from the worktree index,
    using agents/darkwing/work/queue-59-review/r1/mut.mjs.

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

While I was reviewing, Dewey's working copy of the manifest and
build.patch changed (uncommitted, the row 59 re-round). Everything here
is against the committed ca870314, which still checks 9 OK on my tree.

Is a coded answer really "not written"?

This is what the page's "Not sent" rests on, so I traced each way an error
code can reach sendOutcome (packages/webui/src/bus.mjs:78).

  • The broker runs #dispatch and the result's secret check inside one
    BEGIN IMMEDIATE transaction (broker.mjs request, store.mjs
    transaction). Anything thrown there, a failed COMMIT included, rolls
    back. #refused then writes its action.refused event in a separate
    transaction, or answers storage-unavailable if it can't. Both come
    after the rollback.
  • The socket server (server.mjs) calls broker.request synchronously for
    message.send, so its request-timeout (line 27) can only fire before
    the request is handled. invalid-envelope, incomplete-request and
    request-too-large also come before the broker.
  • Client.call turns every socket error, end or timeout into
    outcome-unknown, and an answer it can't parse into invalid-response.
    It refuses an oversized request before connecting.
  • human-cli.mjs writes invalid-request for an uncoded error (line 37).
    Inside its try, the only steps that can throw without a code run
    before client.call. A failure writing the result to stdout surfaces as
    an error event, not in that catch, and the parent reads that as
    invalid-response.
  • humanCall maps a kill, a timeout and a missing code to
    outcome-unknown or invalid-response.

So the three codes in OPEN are the only ones a committed write can come
back with, and the server shows them as 503 sent: "unknown".

I also checked /api/config, the route order and the log line. The route
sits after the shared Host, Origin and sign-in checks and rechecks the
header itself, so a loopback request gets 401 with no log line. to is
JSON-quoted and cut to 64 characters before it's logged.

Row 59 has landed (a8512007). Its agents read still returns only held
claims, now with state fields, and each row keeps role, so the picker
works the same on top of it.

Tests and mutants

On the candidate, 01:16:27Z to 01:16:44Z on 2026-10-12
(r1/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
preview.test.mjs 11 pass, 0 fail
bus.test.mjs 9 pass, 0 fail
serve.test.mjs 4 pass, 0 fail

I didn't run the full gate. Filbert's and Dewey's runs had the machine,
and Dewey's gate is green with Docker.

Dewey's 21 mutants cover the request checks. I aimed mine at the sites
they don't name (r1/mutants.txt, 01:17:37Z to 01:18:51Z; r1/m2.txt,
01:19:14Z to 01:22:00Z):

Mutant Fails
cli-no-bus-ok: --send starts without the bus config the CLI refusal test
start-send-nonfunction: startServer takes a non-function send the startServer test
open-no-invalid-response the outcome test, the transport test
open-no-too-large the outcome test
nonbus-definite: an uncoded error counts as definite the outcome test
missing-to-passes: no to check the request checks test
route-any-method: no 405 the request checks test
status-mnf-502: message-not-found off the 400 list the outcome test, the real broker test
success-unlogged: no id= line the send test, the transport test
page-never-clears: the text is kept after a send the browser test
page-sentfalse-ignored: a 5xx sent: false shows unknown the browser test
page-no-config-gate: any config shows the box the browser test, on the rerun
page-composer-always: no canSend check the browser test, on the rerun
page-caret-lost: no caret restore survives send-browser and bus-browser
app-reply-4xx-unknown: Reply never says "Not sent." survives 6 files
app-reply-5xx-refused: Reply says "Not sent." for a 5xx survives 3 files

My first run pointed the two config mutants at bus-browser.test.mjs,
which never turns send on. That was my mistake: send-browser kills
both. The last three are notes 1 and 2.

Notes

1. The Reply change in app.js has no test. Line 378 now says "Not
sent." for a 4xx. No WebUI test asserts the Reply error text, so making it
always or never "Not sent." passes browser, browser-edge,
return-flow, history-return-flow, preview and shell. The change
looks right to me (the board's replyToRow returns its 4xx before it runs
agent-send, and the Console's proxy errors are 502), but nothing holds
it. A browser case that answers /api/reply with 403 and then 502 would
cover it.

2. The caret restore isn't tested. build.md says focus and the caret
survive the refresh. Focus is checked; the caret line (bus.js:594) can
go and send-browser still passes. browser.test.mjs:55 does this for
the board's Reply box and could be copied.

3. "Recorded exactly like one sent from the CLI" isn't quite right
(README line 114). mosaic talk sends class REQUEST, and the Console
sends the broker's default, INFO, as the brief asks. The runner handles
both the same way, with a turn and a RESULT reply, but the role's model
reads class INFO in the message head. I'd say "the same way as one sent
from the CLI, with class INFO where mosaic talk uses REQUEST". Sage may
want REQUEST here later; that's a brief change, not this row.

4. The talk sentence (README line 185). The broker records each
message delivered when message.receive returns it; talk then marks it
read. "talk prints each one and records it delivered" credits talk with
the broker's step. The effect it states is right.

5. The refusal receipt repeats the code: "Not sent to coder: not sent:
the bus refused it (unknown-role) (unknown-role)." The server's message
already names the code, and the page adds it again. Cosmetic.

Notes 1 to 5 can go into the row 59 re-round or a follow-up, as Sage
prefers.

Questions

1. Is it safe to show a proxy 4xx as "not sent"? Yes, under the
README's proxy contract, with one line added to it. Every 4xx the Console
returns on this route comes before send is called. An edge proxy's own
4xx (sign-in, 413, 400, 431, 429) comes before it forwards. A proxy that
has forwarded and lost the upstream answer gives 502 or 504, which the
page shows as unknown. The case that breaks it is a proxy set to replace
upstream errors with its own page, nginx's proxy_intercept_errors with
an error_page redirect for example. That could turn the Console's 503
outcome-unknown into a 4xx or a redirect, and the page would say "not
sent" about a message that may exist. I'd add to the contract: the proxy
passes the Console's status and body through and doesn't rewrite upstream
errors.

2. Should --send refuse to start without the bus config? Yes. The
operator asked for sending. A Console that starts and shows a box that
can only fail hides the problem until someone types a message, and
refusing at start is the repository's fail-closed rule. A read-only
Console still starts without the config, as before.

Round 2

When Dewey re-rounds on row 59's landing, I'll check the new digest, the
README and bus.js merges against row 59's lines, and rerun
send-browser, bus-browser and my mutants on the new tree.

# Row 76, Console sends a message to a role, round 1 review (Darkwing) Issue #1570, request comment 27594, packet `agents/dewey/work/queue-76/` (commit `1deb6143`). Brief: `docs/plans/2026-10-11_dogfood-cutover.md`, "Console sends a message to a role". Base `3d298920`. Candidate manifest sha256 `ca8703145aed98142c1fdf0553ab846a9b9a808cd58c35f7cbb4fa35d7f279a2`, 9 files, matching the request. Sage asked for this round on content now; the row re-rounds after row 59's landing, which changes the README and `bus.js` digests. Verdict: **approve**. The route refuses everything the brief lists before it calls the bus, and logs one line per send without the body. Dewey's decision 5, that a coded bus answer means nothing was written, holds on every path I traced from the socket to the page. The page sends once per press and never resends. Nothing blocks. Five notes and my answers to the two questions are below. ## Method - A detached worktree at `3d298920` with `git apply --index build.patch`, then `sha256sum -c` on the manifest: 9 OK, and again after the mutants. None of the 9 paths changed from `3d298920` to `1ab6e181` at the start. - I read the whole patch, and the bus code the send passes through: `Broker.request` and the `message.send` case, `Store.transaction`, the socket server, `Client.call`, `human-cli.mjs` and `humanCall`. - Six WebUI test files, then 16 mutants of my own (`r1/mutants.sh`) and a rerun of the four survivors against other files (`r1/m2.sh`). Each mutant edits one site, runs one test file and restores from the worktree index, using `agents/darkwing/work/queue-59-review/r1/mut.mjs`. Node v26.8.1, `TMPDIR=~/darkwing-scratch/rv76/tmp`. While I was reviewing, Dewey's working copy of the manifest and `build.patch` changed (uncommitted, the row 59 re-round). Everything here is against the committed `ca870314`, which still checks 9 OK on my tree. ## Is a coded answer really "not written"? This is what the page's "Not sent" rests on, so I traced each way an error code can reach `sendOutcome` (`packages/webui/src/bus.mjs:78`). - The broker runs `#dispatch` and the result's secret check inside one `BEGIN IMMEDIATE` transaction (`broker.mjs` `request`, `store.mjs` `transaction`). Anything thrown there, a failed `COMMIT` included, rolls back. `#refused` then writes its `action.refused` event in a separate transaction, or answers `storage-unavailable` if it can't. Both come after the rollback. - The socket server (`server.mjs`) calls `broker.request` synchronously for `message.send`, so its `request-timeout` (line 27) can only fire before the request is handled. `invalid-envelope`, `incomplete-request` and `request-too-large` also come before the broker. - `Client.call` turns every socket error, end or timeout into `outcome-unknown`, and an answer it can't parse into `invalid-response`. It refuses an oversized request before connecting. - `human-cli.mjs` writes `invalid-request` for an uncoded error (line 37). Inside its `try`, the only steps that can throw without a code run before `client.call`. A failure writing the result to stdout surfaces as an `error` event, not in that `catch`, and the parent reads that as `invalid-response`. - `humanCall` maps a kill, a timeout and a missing code to `outcome-unknown` or `invalid-response`. So the three codes in `OPEN` are the only ones a committed write can come back with, and the server shows them as 503 `sent: "unknown"`. I also checked `/api/config`, the route order and the log line. The route sits after the shared Host, Origin and sign-in checks and rechecks the header itself, so a loopback request gets 401 with no log line. `to` is JSON-quoted and cut to 64 characters before it's logged. Row 59 has landed (`a8512007`). Its `agents` read still returns only held claims, now with state fields, and each row keeps `role`, so the picker works the same on top of it. ## Tests and mutants On the candidate, 01:16:27Z to 01:16:44Z on 2026-10-12 (`r1/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 | | `preview.test.mjs` | 11 pass, 0 fail | | `bus.test.mjs` | 9 pass, 0 fail | | `serve.test.mjs` | 4 pass, 0 fail | I didn't run the full gate. Filbert's and Dewey's runs had the machine, and Dewey's gate is green with Docker. Dewey's 21 mutants cover the request checks. I aimed mine at the sites they don't name (`r1/mutants.txt`, 01:17:37Z to 01:18:51Z; `r1/m2.txt`, 01:19:14Z to 01:22:00Z): | Mutant | Fails | |---|---| | `cli-no-bus-ok`: `--send` starts without the bus config | the CLI refusal test | | `start-send-nonfunction`: `startServer` takes a non-function `send` | the `startServer` test | | `open-no-invalid-response` | the outcome test, the transport test | | `open-no-too-large` | the outcome test | | `nonbus-definite`: an uncoded error counts as definite | the outcome test | | `missing-to-passes`: no `to` check | the request checks test | | `route-any-method`: no 405 | the request checks test | | `status-mnf-502`: `message-not-found` off the 400 list | the outcome test, the real broker test | | `success-unlogged`: no `id=` line | the send test, the transport test | | `page-never-clears`: the text is kept after a send | the browser test | | `page-sentfalse-ignored`: a 5xx `sent: false` shows unknown | the browser test | | `page-no-config-gate`: any config shows the box | the browser test, on the rerun | | `page-composer-always`: no `canSend` check | the browser test, on the rerun | | `page-caret-lost`: no caret restore | survives `send-browser` and `bus-browser` | | `app-reply-4xx-unknown`: Reply never says "Not sent." | survives 6 files | | `app-reply-5xx-refused`: Reply says "Not sent." for a 5xx | survives 3 files | My first run pointed the two config mutants at `bus-browser.test.mjs`, which never turns `send` on. That was my mistake: `send-browser` kills both. The last three are notes 1 and 2. ## Notes **1. The Reply change in `app.js` has no test.** Line 378 now says "Not sent." for a 4xx. No WebUI test asserts the Reply error text, so making it always or never "Not sent." passes `browser`, `browser-edge`, `return-flow`, `history-return-flow`, `preview` and `shell`. The change looks right to me (the board's `replyToRow` returns its 4xx before it runs `agent-send`, and the Console's proxy errors are 502), but nothing holds it. A browser case that answers `/api/reply` with 403 and then 502 would cover it. **2. The caret restore isn't tested.** `build.md` says focus and the caret survive the refresh. Focus is checked; the caret line (`bus.js:594`) can go and `send-browser` still passes. `browser.test.mjs:55` does this for the board's Reply box and could be copied. **3. "Recorded exactly like one sent from the CLI" isn't quite right** (README line 114). `mosaic talk` sends class `REQUEST`, and the Console sends the broker's default, `INFO`, as the brief asks. The runner handles both the same way, with a turn and a `RESULT` reply, but the role's model reads `class INFO` in the message head. I'd say "the same way as one sent from the CLI, with class INFO where `mosaic talk` uses REQUEST". Sage may want `REQUEST` here later; that's a brief change, not this row. **4. The talk sentence** (README line 185). The broker records each message delivered when `message.receive` returns it; talk then marks it read. "talk prints each one and records it delivered" credits talk with the broker's step. The effect it states is right. **5. The refusal receipt repeats the code**: "Not sent to coder: not sent: the bus refused it (unknown-role) (unknown-role)." The server's message already names the code, and the page adds it again. Cosmetic. Notes 1 to 5 can go into the row 59 re-round or a follow-up, as Sage prefers. ## Questions **1. Is it safe to show a proxy 4xx as "not sent"?** Yes, under the README's proxy contract, with one line added to it. Every 4xx the Console returns on this route comes before `send` is called. An edge proxy's own 4xx (sign-in, 413, 400, 431, 429) comes before it forwards. A proxy that has forwarded and lost the upstream answer gives 502 or 504, which the page shows as unknown. The case that breaks it is a proxy set to replace upstream errors with its own page, nginx's `proxy_intercept_errors` with an `error_page` redirect for example. That could turn the Console's 503 `outcome-unknown` into a 4xx or a redirect, and the page would say "not sent" about a message that may exist. I'd add to the contract: the proxy passes the Console's status and body through and doesn't rewrite upstream errors. **2. Should `--send` refuse to start without the bus config?** Yes. The operator asked for sending. A Console that starts and shows a box that can only fail hides the problem until someone types a message, and refusing at start is the repository's fail-closed rule. A read-only Console still starts without the config, as before. ## Round 2 When Dewey re-rounds on row 59's landing, I'll check the new digest, the README and `bus.js` merges against row 59's lines, and rerun `send-browser`, `bus-browser` and my mutants on the new tree.
Member

Row 76 round 1 review (Filbert): approve

Candidate: agents/dewey/work/queue-76/candidate-manifest.sha256, sha256 ca8703145aed98142c1fdf0553ab846a9b9a808cd58c35f7cbb4fa35d7f279a2, base 3d298920, request comment 27594, packet 1deb6143.

This is the round 1 content review Sage asked for. Row 59 has since landed (a8512007) and changes two manifest paths, packages/webui/README.md and packages/webui/src/public/bus.js, so round 2 needs a re-digest. Nothing else on the manifest paths moved between 3d298920 and 729da72b.

What I checked

  • build.patch applies with git apply --index to a detached worktree of 3d298920, and all 9 manifest entries check OK.
  • I read the server route, the CLI wiring, the page composer, the app.js Reply change and the three test files, and traced the send path down through human-cli.mjs, the bus Client and the broker's message.send.
  • Gate, run in sequence in that worktree with TMPDIR in my scratch and DOCKER_HOST pointed at a missing socket: webui 76/0, bus 88/0, cli 114/0, control-board 124/0, and all nine scripts/test-*.sh exit 0 (auth 15, conductor 17, config 24, discord 66, extension-package 18, foundation 44, queue 27, release 4, task 26). test-release and test-task skipped their Docker cases without a daemon; Dewey's gate ran them. Afterwards the worktree still checked 9/9 against the manifest.
  • Mutants: 27 of my own, one edit each in a second worktree, then the webui suite, then a restore (2026-10-12T01:18Z to 01:45Z, after the gate). 17 killed, 10 survived. One survivor is equivalent and nine are test gaps, all in N2 and N3 below. Afterwards git diff was unchanged and the tree checked 9/9 against the round 1 manifest.

The send path

The checks run in the right order: the shared row 74 checks, then the header again (401, so a loopback request can't send), exact Origin (403), exact Sec-Fetch-Site: same-origin (403), a JSON content type, at most 16 KiB and only the keys to, body, in_reply_to (400), then message.send. The JSON content type makes the request non-simple and there is no CORS, so a cross-site form or fetch can't reach the broker. The log line can't be forged: to is JSON-quoted and cut to 64, the user is JSON-quoted, and error codes pass the /^[a-z-]{1,64}$/ filter in human-cli.mjs. The body is never logged.

sendOutcome is right about what was written. The broker runs dispatch and its secret check inside one store transaction, so a coded broker error means nothing was stored. The transport codes that leave the outcome open (outcome-unknown, invalid-response, response-too-large), an uncoded error, a killed child (no code line, so invalid-response) and an answer without an id all give 503 with sent: 'unknown'. request-too-large from the Client is unreachable, since 16 KiB of input can't build a 64 KiB request.

Dewey's questions

Q1, is a proxy 4xx safe to show as "not sent"? For the proxies the README names, yes. nginx, Caddy and Traefik answer 5xx (502, 504) when something fails after they've forwarded the request, and their 4xx answers (400, 401, 403, 413, 431) come before forwarding. One exception is worth handling, see N1.

Q2, should --send refuse to start without the bus config? Yes. That fails closed, and it's the right place to catch a missing config. A configured bus with no host running still serves, and each send answers no-bus-host with sent: false, which is also right: nothing was written.

Notes (non-blocking, for round 2)

N1. 408. Envoy's stream_idle_timeout (default 5 minutes) answers 408 Request Timeout when no upstream response header has arrived, and that can happen after the request was forwarded. Istio, Contour and Envoy Gateway inherit it. Behind one of those, a slow broker could have stored the message while the page says "Not sent". I'd treat 408 as unknown in bus.js line 750 (and the app.js Reply check, line 378), with a test, and add one README line saying a 408 counts as unknown. A cheap fix, and it costs nothing for the named proxies.

N2. The app.js Reply change (lines 378–379, ". Not sent." on a 4xx) has no test. No webui or control-board test pins either string, and both of my mutants survive: refused always false and refused always true. One assertion for a 4xx and one for a 5xx would hold it.

N3. Surviving mutants. Each one marks new row 76 behaviour that no test holds. By reading, the code does what the README says in each case:

Mutant Edit What a test would need
p-no-field-focus after a refresh, focus never returns to the send field that had it type in #send-body, refresh by timer or by a call that leaves focus where it is, then check document.activeElement. The existing refresh check clicks #refresh, which moves focus off the field, so the restore path never runs
p-no-caret the textarea's caret and scroll are not restored the same test, checking selectionStart and selectionEnd
p-not-held-dropped a draft's role that is no longer held drops out of the To list a draft to a role, then a refresh where that role isn't held: the option stays and stays selected
p-inbox-not-current Inbox isn't marked current on #/trail/message/… the nav's current item on a message trail
s-reply-truthy the server forwards in_reply_to only when it is truthy {to, body, in_reply_to: ""} reaches send with the key, so the broker judges it. With the mutant it goes out as a plain message
s-plain-error-code sendOutcome takes err.code from any error, not only a BusReadError the plain-error case in send.test.mjs (line 230) has no code. Real Node errors carry one (ENOENT). Give it one, and the 503 unknown assertion holds the branch
s-log-cut-65 the log cuts to at 65 a 65-character to in the log assertion

s-array-ok (dropping Array.isArray(value)) is equivalent: a non-empty array fails the key check on its index keys, an empty one has no to, and JSON can't give an array named properties.

N4. Nit. The refused receipt repeats itself: send-browser.test.mjs line 103 asserts Not sent to coder: not sent: the bus refused it (unknown-role) (unknown-role). The draft is kept. The server message already carries "not sent" and the code, and the page adds both again. Dropping one of each would read better. Optional.

I approve. Round 2 can carry N1 to N4 with the rebase and Darkwing's notes; I'll read them as content then.

## Row 76 round 1 review (Filbert): approve Candidate: `agents/dewey/work/queue-76/candidate-manifest.sha256`, sha256 `ca8703145aed98142c1fdf0553ab846a9b9a808cd58c35f7cbb4fa35d7f279a2`, base `3d298920`, request comment 27594, packet `1deb6143`. This is the round 1 content review Sage asked for. Row 59 has since landed (`a8512007`) and changes two manifest paths, `packages/webui/README.md` and `packages/webui/src/public/bus.js`, so round 2 needs a re-digest. Nothing else on the manifest paths moved between `3d298920` and `729da72b`. ### What I checked - `build.patch` applies with `git apply --index` to a detached worktree of `3d298920`, and all 9 manifest entries check OK. - I read the server route, the CLI wiring, the page composer, the app.js Reply change and the three test files, and traced the send path down through `human-cli.mjs`, the bus `Client` and the broker's `message.send`. - Gate, run in sequence in that worktree with `TMPDIR` in my scratch and `DOCKER_HOST` pointed at a missing socket: webui 76/0, bus 88/0, cli 114/0, control-board 124/0, and all nine `scripts/test-*.sh` exit 0 (auth 15, conductor 17, config 24, discord 66, extension-package 18, foundation 44, queue 27, release 4, task 26). `test-release` and `test-task` skipped their Docker cases without a daemon; Dewey's gate ran them. Afterwards the worktree still checked 9/9 against the manifest. - Mutants: 27 of my own, one edit each in a second worktree, then the webui suite, then a restore (2026-10-12T01:18Z to 01:45Z, after the gate). 17 killed, 10 survived. One survivor is equivalent and nine are test gaps, all in N2 and N3 below. Afterwards `git diff` was unchanged and the tree checked 9/9 against the round 1 manifest. ### The send path The checks run in the right order: the shared row 74 checks, then the header again (401, so a loopback request can't send), exact Origin (403), exact `Sec-Fetch-Site: same-origin` (403), a JSON content type, at most 16 KiB and only the keys `to`, `body`, `in_reply_to` (400), then `message.send`. The JSON content type makes the request non-simple and there is no CORS, so a cross-site form or fetch can't reach the broker. The log line can't be forged: `to` is JSON-quoted and cut to 64, the user is JSON-quoted, and error codes pass the `/^[a-z-]{1,64}$/` filter in `human-cli.mjs`. The body is never logged. `sendOutcome` is right about what was written. The broker runs dispatch and its secret check inside one store transaction, so a coded broker error means nothing was stored. The transport codes that leave the outcome open (`outcome-unknown`, `invalid-response`, `response-too-large`), an uncoded error, a killed child (no code line, so `invalid-response`) and an answer without an id all give 503 with `sent: 'unknown'`. `request-too-large` from the `Client` is unreachable, since 16 KiB of input can't build a 64 KiB request. ### Dewey's questions Q1, is a proxy 4xx safe to show as "not sent"? For the proxies the README names, yes. nginx, Caddy and Traefik answer 5xx (502, 504) when something fails after they've forwarded the request, and their 4xx answers (400, 401, 403, 413, 431) come before forwarding. One exception is worth handling, see N1. Q2, should `--send` refuse to start without the bus config? Yes. That fails closed, and it's the right place to catch a missing config. A configured bus with no host running still serves, and each send answers `no-bus-host` with `sent: false`, which is also right: nothing was written. ### Notes (non-blocking, for round 2) N1. 408. Envoy's `stream_idle_timeout` (default 5 minutes) answers `408 Request Timeout` when no upstream response header has arrived, and that can happen after the request was forwarded. Istio, Contour and Envoy Gateway inherit it. Behind one of those, a slow broker could have stored the message while the page says "Not sent". I'd treat 408 as unknown in `bus.js` line 750 (and the app.js Reply check, line 378), with a test, and add one README line saying a 408 counts as unknown. A cheap fix, and it costs nothing for the named proxies. N2. The app.js Reply change (lines 378–379, ". Not sent." on a 4xx) has no test. No webui or control-board test pins either string, and both of my mutants survive: `refused` always false and `refused` always true. One assertion for a 4xx and one for a 5xx would hold it. N3. Surviving mutants. Each one marks new row 76 behaviour that no test holds. By reading, the code does what the README says in each case: | Mutant | Edit | What a test would need | |---|---|---| | `p-no-field-focus` | after a refresh, focus never returns to the send field that had it | type in `#send-body`, refresh by timer or by a call that leaves focus where it is, then check `document.activeElement`. The existing refresh check clicks `#refresh`, which moves focus off the field, so the restore path never runs | | `p-no-caret` | the textarea's caret and scroll are not restored | the same test, checking `selectionStart` and `selectionEnd` | | `p-not-held-dropped` | a draft's role that is no longer held drops out of the `To` list | a draft to a role, then a refresh where that role isn't held: the option stays and stays selected | | `p-inbox-not-current` | Inbox isn't marked current on `#/trail/message/…` | the nav's current item on a message trail | | `s-reply-truthy` | the server forwards `in_reply_to` only when it is truthy | `{to, body, in_reply_to: ""}` reaches `send` with the key, so the broker judges it. With the mutant it goes out as a plain message | | `s-plain-error-code` | `sendOutcome` takes `err.code` from any error, not only a `BusReadError` | the plain-error case in `send.test.mjs` (line 230) has no `code`. Real Node errors carry one (`ENOENT`). Give it one, and the 503 `unknown` assertion holds the branch | | `s-log-cut-65` | the log cuts `to` at 65 | a 65-character `to` in the log assertion | `s-array-ok` (dropping `Array.isArray(value)`) is equivalent: a non-empty array fails the key check on its index keys, an empty one has no `to`, and JSON can't give an array named properties. N4. Nit. The refused receipt repeats itself: `send-browser.test.mjs` line 103 asserts `Not sent to coder: not sent: the bus refused it (unknown-role) (unknown-role). The draft is kept.` The server message already carries "not sent" and the code, and the page adds both again. Dropping one of each would read better. Optional. I approve. Round 2 can carry N1 to N4 with the rebase and Darkwing's notes; I'll read them as content then.
Member

Review request for queue row 76, round 2: Console sends a human message to a role

  • Owner: dewey
  • Reviewers: darkwing, filbert
  • Gate: darkwing and filbert approve on the issue naming the candidate manifest; webui, bus, control-board and cli node suites and every scripts/test-*.sh green on Sage's gate rerun; Jason's Q95 recorded first (sage)
  • Brief: docs/plans/2026-10-11_dogfood-cutover.md § Console sends a message to a role @f5d565547229
  • Candidate: manifest 04ecf1b57c594a35d34d53277b2df915a571f4fa9f7cc29146dbedfa612f77d4

The manifest:

911feb19e9b16fa4f163d77ee10c33270049ea60d16cabcd4197cd8a8847eaf7  packages/webui/README.md
a0661abb7de79ce2854884c99f7d8db114d8028d99048e7387a9b2f8534cd422  packages/webui/src/bus.mjs
c8f9845c66793b0516d5f356f6be9f8ae2d46c40fdd044945aac2a606d024bee  packages/webui/src/cli.mjs
a259dd523625a83237ebc98d0361ff6de1732729a661f9e0f37492f99e3b3f60  packages/webui/src/serve.mjs
8f627ccde5775281aa7676b8495d55bcb65a5f32f6a2bb53f1a313afb28ccd8f  packages/webui/src/public/app.js
78d75b88a89a2bbd83a264e8b8a5da739edb0b19dd7a8cfbbc0f18fd4895fceb  packages/webui/src/public/bus.js
8401bd8f650dadf07fadf4f1304d8f509f706441763d870e48fd14215c9a7a22  packages/webui/tests/send.test.mjs
97304808bab8eb27fde756081863ff56e932f7021fc151165db72ac2a7a83c81  packages/webui/tests/send-browser.test.mjs
17cb39097414bd685d68305dd5e402fd645020695f3dee86cd6bb567dd410eff  packages/webui/tests/shell.test.mjs

Check a tree against it with scripts/mosaic queue review verify-commit 76 REF.

Post your verdict as a comment here, then record it:

scripts/mosaic queue review record 76 --verdict approve|changes --comment COMMENT_ID --candidate 04ecf1b57c594a35d34d53277b2df915a571f4fa9f7cc29146dbedfa612f77d4 --op OP --by SEAT
<!-- mosaic-queue-op: dewey-76-review-r2-20261012 --> <!-- mosaic-queue-round: row=76 round=2 candidate=04ecf1b57c594a35d34d53277b2df915a571f4fa9f7cc29146dbedfa612f77d4 --> Review request for queue row 76, round 2: Console sends a human message to a role - Owner: dewey - Reviewers: darkwing, filbert - Gate: darkwing and filbert approve on the issue naming the candidate manifest; webui, bus, control-board and cli node suites and every scripts/test-*.sh green on Sage's gate rerun; Jason's Q95 recorded first (sage) - Brief: `docs/plans/2026-10-11_dogfood-cutover.md` § Console sends a message to a role @f5d565547229 - Candidate: manifest `04ecf1b57c594a35d34d53277b2df915a571f4fa9f7cc29146dbedfa612f77d4` The manifest: ```text 911feb19e9b16fa4f163d77ee10c33270049ea60d16cabcd4197cd8a8847eaf7 packages/webui/README.md a0661abb7de79ce2854884c99f7d8db114d8028d99048e7387a9b2f8534cd422 packages/webui/src/bus.mjs c8f9845c66793b0516d5f356f6be9f8ae2d46c40fdd044945aac2a606d024bee packages/webui/src/cli.mjs a259dd523625a83237ebc98d0361ff6de1732729a661f9e0f37492f99e3b3f60 packages/webui/src/serve.mjs 8f627ccde5775281aa7676b8495d55bcb65a5f32f6a2bb53f1a313afb28ccd8f packages/webui/src/public/app.js 78d75b88a89a2bbd83a264e8b8a5da739edb0b19dd7a8cfbbc0f18fd4895fceb packages/webui/src/public/bus.js 8401bd8f650dadf07fadf4f1304d8f509f706441763d870e48fd14215c9a7a22 packages/webui/tests/send.test.mjs 97304808bab8eb27fde756081863ff56e932f7021fc151165db72ac2a7a83c81 packages/webui/tests/send-browser.test.mjs 17cb39097414bd685d68305dd5e402fd645020695f3dee86cd6bb567dd410eff packages/webui/tests/shell.test.mjs ``` Check a tree against it with `scripts/mosaic queue review verify-commit 76 REF`. Post your verdict as a comment here, then record it: ``` scripts/mosaic queue review record 76 --verdict approve|changes --comment COMMENT_ID --candidate 04ecf1b57c594a35d34d53277b2df915a571f4fa9f7cc29146dbedfa612f77d4 --op OP --by SEAT ```
Member

Row 76 round 2 review (Filbert): approve

Candidate: agents/dewey/work/queue-76/candidate-manifest.sha256, sha256 04ecf1b57c594a35d34d53277b2df915a571f4fa9f7cc29146dbedfa612f77d4, base 729da72b, request comment 27672, packet 86821889. Per lead decision 100, I re-reviewed the round 2 changes as content.

What I checked

  • build.patch applies with git apply --index to a detached worktree of 729da72b, and all 9 manifest entries check OK. Since 729da72b, the only webui change on refactor is row 67's tests/bus.test.mjs (189410ed). That isn't a manifest path, so there's no drift.
  • I compared round 1 with round 2 as trees: 729da72b with round 1's patch applied 3-way, against 729da72b with round 2's. bus.mjs, cli.mjs, serve.mjs and shell.test.mjs are identical. The differences are in the README (+19/−5 lines), app.js (one condition and its comment), bus.js (the refused branch, the receipt and the box's note), send-browser.test.mjs and send.test.mjs. Each difference maps to one of the notes in build.md's round 2 sections. I found nothing else.
  • Gate, in that worktree, run in sequence, with TMPDIR in my scratch and DOCKER_HOST pointed at a missing socket. Package suites: webui 76/0, bus 94/0, cli 114/0, control-board 124/0. All nine scripts/test-*.sh exit 0: auth 15, conductor 17, config 24, discord 66, extension-package 18, foundation 44, queue 27, release 4, task 26. test-queue skipped verify and render --check outside the canonical root. test-release skipped its Docker cases without a daemon. The manifest still checked 9/9 afterwards.
  • My own mutant runner, 33 mutants: round 1's 27, with targets updated for the new conditions, plus six new ones. Each mutant ran the webui suite and was then restored. 32 are killed, and the tree's diff is unchanged after the run. The one survivor is s-array-ok, which is equivalent, as in round 1. I didn't run Dewey's mutants.py.

My round 1 notes

  • N1, 408 is unknown. Both checks now leave 408 out of the 4xx they read as not sent: bus.js:762, and the Reply's refused in app.js. The send box's unknown loop gains a 408, and the Reply case a third answer, 408. The README says so in the "Outcome unknown" bullet, and the proxy bullet says "a 4xx other than 408". My mutants p-408-refused and a-408-refused (408 put back into the refused range) are killed. A 408 that carries sent: false still reads as not sent, but the Console never answers 408, so I agree with leaving it.
  • N2, the Reply's 4xx/5xx text. The stub answers /api/reply from its own queue. The 403, 502 and 408 receipts are asserted as exact strings, the draft is kept each time, and nothing reaches the board's capture list. a-4xx-unknown and a-all-refused both survived round 1. Both are killed now.
  • N3, the survivors. p-no-caret, p-no-field-focus, p-not-held-dropped, p-inbox-not-current, s-reply-truthy, s-plain-error-code and s-log-cut-65 are each killed by the assertion build.md names for it. My new p-caret-not-saved (the caret not saved before the refresh) is killed too. The endLaunch test of a role no longer held asserts the full option list, so it also checks that pm stays selected and the placeholder doesn't.
  • N4, the code shown once. The receipt drops the server's leading not sent: and appends (code) only when the message lacks it. The 400 and 503 receipts are exact strings now. The sign-in receipt still gets (sign-in), because its message has no code. p-code-twice, p-code-never and p-keep-prefix are all killed. Dropping the prefix goes past the note's letter, but without it the receipt read "Not sent to coder: not sent: …", so the drop is right.

Darkwing's notes, read as content

  • N2 (the caret) is covered above.
  • N3: the README and the box's note now say "the same way as one sent from the CLI, with class INFO". That matches the source. serve.mjs passes no class, the broker defaults to INFO (broker.mjs:758), and mosaic talk sends REQUEST (cli.mjs:199).
  • N4: the reply-path sentence matches the source. message.receive records each row delivered as it returns it (broker.mjs:771-787), and talk then calls message.read (cli.mjs:208).
  • Q1: the proxy bullet is clear and names a real way to break it (proxy_intercept_errors with error_page).

Notes (non-blocking)

N1. In send-browser.test.mjs, line 4 of the header comment is now 157 characters, and the comment's other lines wrap near 95. The added sentence ("A role's run is ended in the broker…") was put into the middle of an existing line.

N2. That test's name still ends "the board Reply on a 4xx and a 5xx", although it now also covers a 408.

Neither changes behaviour, and I'm not asking for another round over them.

I approve.

## Row 76 round 2 review (Filbert): approve Candidate: `agents/dewey/work/queue-76/candidate-manifest.sha256`, sha256 `04ecf1b57c594a35d34d53277b2df915a571f4fa9f7cc29146dbedfa612f77d4`, base `729da72b`, request comment 27672, packet `86821889`. Per lead decision 100, I re-reviewed the round 2 changes as content. ### What I checked - `build.patch` applies with `git apply --index` to a detached worktree of `729da72b`, and all 9 manifest entries check OK. Since `729da72b`, the only webui change on `refactor` is row 67's `tests/bus.test.mjs` (`189410ed`). That isn't a manifest path, so there's no drift. - I compared round 1 with round 2 as trees: `729da72b` with round 1's patch applied 3-way, against `729da72b` with round 2's. `bus.mjs`, `cli.mjs`, `serve.mjs` and `shell.test.mjs` are identical. The differences are in the README (+19/−5 lines), `app.js` (one condition and its comment), `bus.js` (the refused branch, the receipt and the box's note), `send-browser.test.mjs` and `send.test.mjs`. Each difference maps to one of the notes in build.md's round 2 sections. I found nothing else. - Gate, in that worktree, run in sequence, with `TMPDIR` in my scratch and `DOCKER_HOST` pointed at a missing socket. Package suites: webui 76/0, bus 94/0, cli 114/0, control-board 124/0. All nine `scripts/test-*.sh` exit 0: auth 15, conductor 17, config 24, discord 66, extension-package 18, foundation 44, queue 27, release 4, task 26. test-queue skipped `verify` and `render --check` outside the canonical root. test-release skipped its Docker cases without a daemon. The manifest still checked 9/9 afterwards. - My own mutant runner, 33 mutants: round 1's 27, with targets updated for the new conditions, plus six new ones. Each mutant ran the webui suite and was then restored. 32 are killed, and the tree's diff is unchanged after the run. The one survivor is `s-array-ok`, which is equivalent, as in round 1. I didn't run Dewey's `mutants.py`. ### My round 1 notes - **N1, 408 is unknown.** Both checks now leave 408 out of the 4xx they read as not sent: `bus.js:762`, and the Reply's `refused` in `app.js`. The send box's unknown loop gains a 408, and the Reply case a third answer, 408. The README says so in the "Outcome unknown" bullet, and the proxy bullet says "a 4xx other than 408". My mutants `p-408-refused` and `a-408-refused` (408 put back into the refused range) are killed. A 408 that carries `sent: false` still reads as not sent, but the Console never answers 408, so I agree with leaving it. - **N2, the Reply's 4xx/5xx text.** The stub answers `/api/reply` from its own queue. The 403, 502 and 408 receipts are asserted as exact strings, the draft is kept each time, and nothing reaches the board's capture list. `a-4xx-unknown` and `a-all-refused` both survived round 1. Both are killed now. - **N3, the survivors.** `p-no-caret`, `p-no-field-focus`, `p-not-held-dropped`, `p-inbox-not-current`, `s-reply-truthy`, `s-plain-error-code` and `s-log-cut-65` are each killed by the assertion build.md names for it. My new `p-caret-not-saved` (the caret not saved before the refresh) is killed too. The `endLaunch` test of a role no longer held asserts the full option list, so it also checks that `pm` stays selected and the placeholder doesn't. - **N4, the code shown once.** The receipt drops the server's leading `not sent: ` and appends `(code)` only when the message lacks it. The 400 and 503 receipts are exact strings now. The sign-in receipt still gets `(sign-in)`, because its message has no code. `p-code-twice`, `p-code-never` and `p-keep-prefix` are all killed. Dropping the prefix goes past the note's letter, but without it the receipt read "Not sent to coder: not sent: …", so the drop is right. ### Darkwing's notes, read as content - N2 (the caret) is covered above. - N3: the README and the box's note now say "the same way as one sent from the CLI, with class INFO". That matches the source. `serve.mjs` passes no class, the broker defaults to `INFO` (`broker.mjs:758`), and `mosaic talk` sends `REQUEST` (`cli.mjs:199`). - N4: the reply-path sentence matches the source. `message.receive` records each row delivered as it returns it (`broker.mjs:771-787`), and talk then calls `message.read` (`cli.mjs:208`). - Q1: the proxy bullet is clear and names a real way to break it (`proxy_intercept_errors` with `error_page`). ### Notes (non-blocking) N1. In `send-browser.test.mjs`, line 4 of the header comment is now 157 characters, and the comment's other lines wrap near 95. The added sentence ("A role's run is ended in the broker…") was put into the middle of an existing line. N2. That test's name still ends "the board Reply on a 4xx and a 5xx", although it now also covers a 408. Neither changes behaviour, and I'm not asking for another round over them. I approve.
Member

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.

# 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.
Author
Contributor

Landed in c45b948d on refactor. Row 76 is done at rev 626 (c60839b2).

  • Dewey's round 2, candidate manifest 04ecf1b5, packet agents/dewey/work/queue-76/, base 729da72b. The Console sends a message to a role from the web page through a Console route; nothing new goes on the broker row. Round 2 carries Darkwing's and Filbert's round 1 notes (decisions 99 and 100), including a 408 counted as unknown in bus.js and the Reply check, and the README line on the proxy contract.
  • Round 2 approved by Filbert (27685) and Darkwing (27688). Round 1 approved by Darkwing (27612) and Filbert (27637).
  • My gate ran on be6a913b plus the patch, 03:25:26Z to 03:29:09Z on 2026-10-12, keeping each suite's full output. bus 95/0, webui 76/0, cli 114/0, control-board 124/0, and all nine scripts/test-*.sh passed: auth 15, conductor 17, config 24, discord 66, ext-pkg 18, foundation 44, queue 27, release 14, task 98.
  • The drift check from 729da72b to HEAD on the 9 manifest paths was empty. queue review verify-commit 76 c45b948d matches the manifest on all 9 paths.
  • Cosmetic notes left for a follow-up: Darkwing's spliced header-comment sentence at send-browser.test.mjs line 4, and Filbert's two round 2 notes.
Landed in c45b948d on `refactor`. Row 76 is done at rev 626 (c60839b2). - Dewey's round 2, candidate manifest `04ecf1b5`, packet `agents/dewey/work/queue-76/`, base 729da72b. The Console sends a message to a role from the web page through a Console route; nothing new goes on the broker row. Round 2 carries Darkwing's and Filbert's round 1 notes (decisions 99 and 100), including a 408 counted as unknown in `bus.js` and the Reply check, and the README line on the proxy contract. - Round 2 approved by Filbert (27685) and Darkwing (27688). Round 1 approved by Darkwing (27612) and Filbert (27637). - My gate ran on be6a913b plus the patch, 03:25:26Z to 03:29:09Z on 2026-10-12, keeping each suite's full output. bus 95/0, webui 76/0, cli 114/0, control-board 124/0, and all nine `scripts/test-*.sh` passed: auth 15, conductor 17, config 24, discord 66, ext-pkg 18, foundation 44, queue 27, release 14, task 98. - The drift check from 729da72b to HEAD on the 9 manifest paths was empty. `queue review verify-commit 76 c45b948d` matches the manifest on all 9 paths. - Cosmetic notes left for a follow-up: Darkwing's spliced header-comment sentence at `send-browser.test.mjs` line 4, and Filbert's two round 2 notes.
Sign in to join this conversation.
4 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: mosaicstack/stack#1570