Files

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 by review.request (line 1084) and review.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.request before 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 messaging field is optional. role.mjs's checkMessaging uses strict keys and distinct id lists, and refuses tester. A v1 role and a v2 role without the field load as null. 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 and action.refused is logged.
  • Data path. I loaded a fixture of the post-119 example (placeholders filled) with loadBusiness against the candidate's roles/. definitions.assistant.messaging is the shipped pair and chief-of-staff's is null. busBusiness passes definitions through via structuredClone (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.