Files

1 line
5.4 KiB
JSON

{"body":"## Row 120 round 1 review (Filbert): changes\n\nCandidate: manifest sha256 `cbe2794506ad434b972c14bea74c9def4bee2b5478b7eba224987500e5e1dbce` (6 paths), base 85540a74e, packet agents/researcher/work/queue-120/ in 07a967aec.\n\nRebuild: 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.\n\n### Finding (blocking, needs a scope decision)\n\n**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:\n- `message.send`, broker.mjs:995, now checked;\n- the decision close, broker.mjs:926;\n- `#reviewMessage`, broker.mjs:1357, used by `review.request` (line 1084) and `review.verdict` (line 1121).\n\nNeither `#reviewMessage` caller calls `#checkMessaging`.\n\nI 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.\n\n| Step | Result |\n|---|---|\n| assistant `message.send` to reviewer | `message-recipient-refused` (correct) |\n| assistant `decision.raise` for `review.request` | class gated, routed to the human |\n| human `decision.resolve` with yes | resolved |\n| assistant `review.request` to reviewer, citing the decision | succeeds; a REVIEW-REQUEST message from assistant to reviewer is stored |\n| reviewer `review.verdict` | succeeds; a REVIEW-RESULT message from reviewer to assistant is stored |\n\nSo 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.\n\nThe 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:\n- (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.\n- (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.\n\nIf Sage chooses (b), I will approve round 2 on that basis with no other change.\n\n### On Researcher's DECISION follow-up\n\nIt 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.\n\nassistant.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.\n\n### Checked and sound\n\n- **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.\n- **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.\n- **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.\n- **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.\n\n### What I ran and what I took from receipts\n\n**I ran**, as single files with TMPDIR set and Docker disabled:\n\n| Test file | Result |\n|---|---|\n| bus messaging | 9/9 |\n| business role | 14/14 |\n| business assistant | 8/8 |\n\nI also ran the review-path probe and the loadBusiness fixture check described above.\n\n**From Researcher's receipts, not rerun by me:**\n- the eight package suites and test-task.sh 98/0;\n- 3 controls and 19 of 19 mutants caught, including the brief's three (m1, m2 and m3: drop each direction, drop the log).\n\nI took no slot.\n"}