Unclaimed files left in the canonical checkout. Inventory: canonical-cleanup-inventory.md (commit-as-evidence groups). detect-secrets scan clean on 2026-11-02: hits are sha256 pins, git object ids, test sentinels already in tracked tests and unit-file lines.
5.3 KiB
Row 120 round 1 review (Filbert): changes
Candidate: manifest sha256 cbe2794506ad434b972c14bea74c9def4bee2b5478b7eba224987500e5e1dbce (6 paths), base 85540a74e, packet agents/researcher/work/queue-120/ in 07a967aec.
Rebuild: a fresh worktree at 85540a74e with row120.patch applied. Every manifest hash matched and git diff --check was clean. Added lines have no dash, curly-quote or control characters, and no commit from 85540a74e to HEAD touches a candidate path.
Finding (blocking, needs a scope decision)
F1. Review messages bypass both lists. REQ-ASSIST-2 says "the broker limits the assistant's messages": the assistant may send only to the user and the chief of staff, and only those two may send to it. The broker inserts messages in three places:
message.send, broker.mjs:995, now checked;- the decision close, broker.mjs:926;
#reviewMessage, broker.mjs:1357, used byreview.request(line 1084) andreview.verdict(line 1121).
Neither #reviewMessage caller calls #checkMessaging.
I reproduced this with a broker probe on the candidate, using the shipped assistant.json lists and a reviewer instance. The task's push was a single git.push.working action.allowed event inserted into the store directly; #latestPush (line 524) looks up the task, not the pusher.
| Step | Result |
|---|---|
assistant message.send to reviewer |
message-recipient-refused (correct) |
assistant decision.raise for review.request |
class gated, routed to the human |
human decision.resolve with yes |
resolved |
assistant review.request to reviewer, citing the decision |
succeeds; a REVIEW-REQUEST message from assistant to reviewer is stored |
reviewer review.verdict |
succeeds; a REVIEW-RESULT message from reviewer to assistant is stored |
So the assistant reaches a role outside sendTo, and a role outside receiveFrom reaches the assistant. It needs a human approval, but REQ-ASSIST-2 has no approval exception. REQ-ASSIST-1 also says the assistant doesn't direct other agents.
The brief limits broker.mjs to "message.send only", so the candidate did what the brief asked. The fix needs Sage to choose one of these:
- (a) Widen the row to the review paths. Apply the same check in
review.requestbefore the request is recorded, and test that both review directions are refused and logged. - (b) Accept a partial REQ-ASSIST-2 for this row, and file a follow-up row that closes the review paths before anything relies on the limit.
If Sage chooses (b), I will approve round 2 on that basis with no other change.
On Researcher's DECISION follow-up
It holds today, and the reason is narrower than "holds no decision authority". A decision the assistant raises routes to the human when gated, to an arbiter when cross-role, and to itself when within its role (broker.mjs, decision.raise route). Only the route (or the human) can close it (decision.resolve, around line 893). The assistant's crossRole is [], so nothing it raises routes to an arbiter. Its DECISION messages therefore come only from the human, which receiveFrom allows. My probe stored exactly one DECISION message to the assistant, and it was from the human.
assistant.test.mjs:15, 72, 75 and 97 pin crossRole: [], so a widening fails a test. Recording it as a follow-up is reasonable. If F1 is fixed under (a), the same choke point is a natural place to cover the decision close.
Checked and sound
- Field and validation. The
messagingfield is optional. role.mjs'scheckMessaginguses strict keys and distinct id lists, and refusestester. A v1 role and a v2 role without the field load asnull. The broker refuses a malformed shape at boot (invalid-messaging). That check is looser than role.mjs (it doesn't refuse unknown keys or duplicates), which is fine because role.mjs is the loader. - Sender and target checks. The sender check runs first (
message-recipient-refused), then the target check (message-sender-refused). The human and the tester are checked on the target side only. A tester send to the assistant is refused; a send to the tester from a limited role is refused. A role without the field is unchanged on both sides. A refusal throws inside the transaction, so nothing is stored or consumed andaction.refusedis logged. - Data path. I loaded a fixture of the post-119 example (placeholders filled) with
loadBusinessagainst the candidate's roles/.definitions.assistant.messagingis the shipped pair and chief-of-staff's isnull.busBusinesspassesdefinitionsthrough viastructuredClone(packages/bus/src/business.mjs:6), so no resolve, digest or launcher change is needed, as build.md says. - Definition names, not instance names. This is the right call, and the caveat (two instances of one definition are treated alike) is stated in build.md.
What I ran and what I took from receipts
I ran, as single files with TMPDIR set and Docker disabled:
| Test file | Result |
|---|---|
| bus messaging | 9/9 |
| business role | 14/14 |
| business assistant | 8/8 |
I also ran the review-path probe and the loadBusiness fixture check described above.
From Researcher's receipts, not rerun by me:
- the eight package suites and test-task.sh 98/0;
- 3 controls and 19 of 19 mutants caught, including the brief's three (m1, m2 and m3: drop each direction, drop the log).
I took no slot.