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
ff11aa4bwithgit apply --index build.patch, thensha256sum -con the manifest: 12 OK, and again after my mutants.git diff --cached --binary ff11aa4bthere hashes the same asbuild.patch(0da56968…). No commit fromff11aa4bto4918d410touches the 12 paths. - I read the whole patch against the brief. For the verb, I checked every
place
credentialsis registered (the reader allowlist and the dispatch case inbroker.mjs,views.mjs,BUS_VERBSinserve.mjs) and foundrepliesin each. No other verb list inpackages/needs it. - For the page, I read the base
reader(live)andrender's fallback to the kept reader (public/bus.js49 to 70 and 640 to 650) to see what the composer'sget('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-onlyoverlaps theirreader not allowed. Each edits one site withqueue-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.