Files
stack/agents/darkwing/work/queue-81-review/review-r1.md
T

5.8 KiB

Row 81, the Console shows the reply to a web send, round 1 review (Darkwing)

Issue #1575, request comment 27763, packet agents/dewey/work/queue-81/ (commit 5afa8b0f). Brief: docs/plans/2026-10-11_console-reply-view.md § "Show replies to a message without receiving them" (blob dd42071d). Base ff11aa4b. Candidate manifest sha256 2263ff5f2866ba57d167a060668e819874c893a18b514f62c54fbe4089d6ec2f, 12 files, matching the request.

Verdict: approve. The new replies read is registered everywhere credentials is, refuses what the brief says it refuses, and writes no delivery or read row. The receipt drops a late answer for an earlier send, and a failed replies read stays inside the composer. Nothing blocks. Three small notes follow, then my answers to Dewey's three questions.

Method

  • A detached worktree at ff11aa4b with git apply --index build.patch, then sha256sum -c on the manifest: 12 OK, and again after my mutants. git diff --cached --binary ff11aa4b there hashes the same as build.patch (0da56968…). No commit from ff11aa4b to 4918d410 touches the 12 paths.
  • I read the whole patch against the brief. For the verb, I checked every place credentials is registered (the reader allowlist and the dispatch case in broker.mjs, views.mjs, BUS_VERBS in serve.mjs) and found replies in each. No other verb list in packages/ needs it.
  • For the page, I read the base reader(live) and render's fallback to the kept reader (public/bus.js 49 to 70 and 640 to 650) to see what the composer's get('replies', id) does on a stale render (note 2).
  • The bus, webui and cli suites on the candidate, then 9 mutants of my own (r1/mutants.sh) next to Dewey's 17. Most edit sites theirs don't; human-only overlaps their reader not allowed. Each edits one site with queue-59-review/r1/mut.mjs, runs one test file and restores the file from a backup.

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

Tests and mutants

On the candidate, 18:45:51Z to 18:47:21Z on 2026-10-12 (r1/tests.txt): bus 102, webui 83 and cli 115, all pass, 0 fail. I didn't rerun the scripts suites. Dewey's gate has them green, and Sage reruns it.

Mutants ran 18:47:27Z to 18:48:27Z (r1/mutants.txt):

Mutant Edit Result
exists-any-business the existence check drops business=? killed: the message-not-found test
replies-to-human the query lists messages to the human, not replies killed: two tests
no-identifier no identifier(a.id) killed: the strict arguments test
human-only a reader session refused killed: six tests
id-check-after-bus the id check after the no-bus 503 killed: the refusals test
shows-to-role a reply shows to_role, not from_role killed: the browser test
link-to-send each reply links to the sent message's trail killed: the browser test
failure-keeps-old a failed read keeps the last list killed: the browser test
any-receipt-reads the composer reads replies for any receipt survives, see note 3

exists-any-business covers the defence Dewey mentions for the equivalent mutant they didn't run: the existence check has its own business filter, and the cross-business test catches it.

Notes

1. One comment line in packages/webui/src/bus.mjs isn't wrapped. Line 6 is 103 characters ("reachable as reads: no read writes, not even seen or delivered (Q4). The one write is row 76's human"), where the rest of the comment wraps near 76. Cosmetic; the landing commit or round 2 can reflow it.

2. On a stale render the replies come from the kept answers. When a live read elsewhere on the page fails, render redraws with reader(false). The composer's get('replies', id) then returns the last answer for that id if there is one. If there isn't, it throws not-read, and the receipt says the replies couldn't be read. The cached list doesn't say it's old, but the page's stale banner already does, with the oldest at across every read, including this one. I think that's right and needs no change.

3. any-receipt-reads survives, and I'd leave it. With the guard loosened to any receipt, a refused or unknown receipt has no id, so the read goes to /api/bus/replies with no query, the server answers 400, and the composer sets replies to null. Neither receipt kind renders repliesHtml, so the page looks the same. The only effect is a wasted request per render. A test could pin it by counting replies requests after a refused send, but I don't think it's worth a round.

The overlap with row 61. Row 61 (#1550, mine, in review) also changes serve.mjs, public/bus.js and packages/webui/README.md. The hunks differ, and both patches apply on 4918d410 with git apply --3way in either order. Whichever row lands second needs a new round for its manifest. That's a landing-order matter for Sage, not a finding here.

Dewey's three questions

1. A new verb, not a trail field. Agreed. trail answers [] for an unknown subject, and the brief wants this read to refuse an unknown id. Folding replies into trail would either change trail's answer for every caller or give one field its own refusal rule. A separate verb also keeps the 10 s refresh from reading the whole trail.

2. The bus README verb-table row outside the reads section. Keep it. decisions and credentials have rows in that table, so leaving replies out would make the table wrong. I read the brief's file boundary as naming the README, not one section of it.

3. packages/cli/README.md lines 61 to 65. Agreed, a follow-up. "The reply still lands in the inbox" means the human's message inbox, which is true, but next to this row's timeout wording it reads like mosaic inbox. "The next mosaic talk prints it" would match the CLI's message.