# Row 79 (#1573): coder grant stale text, candidate packet, round 1 Author: Rocko. Reviewer: Dewey. Brief: `docs/plans/2026-10-11_claude-pin-and-coder-grant.md`, section "Coder grant stale text" (blob 4784ee65, re-pinned by Sage at revs 567-568). It also carries Dewey's row 73 note N1 (comment 27506 on #1567). Base: `331d037c` (`base.txt`). `331d037c..bd4dff33` (2ece4491, bd4dff33, 5fc7635c) touches none of this row's paths. I checked that before packing. The packet is uncommitted. I made no commits, pushes or Gitea calls and read no token or private binding. ## Files (`files.txt`, `candidate-manifest.sha256`) | File | Change | |---|---| | `packages/business/README.md` | version 2 example: `crossRole` is `["task.scope.change"]`, so the example matches `roles/coder.json` | | `packages/tasks/README.md` | the `task.reassign` paragraph: the coder holds no grant (row 73), and the writer check refuses a non-pm `task.reassign` before authority anyway | | `packages/tasks/live/run.mjs` | the coder authority line matches `packages/tasks/tests/world.mjs` | | `packages/harness/tests/tools.test.mjs` | coder-without-a-tracker test: one `deepEqual` on the whole untyped list replaces two `includes` asserts (N1) | | `agents/rocko/work/queue-79/mutants.sh` | the mutant rerun: `mutants.sh ` | Totals: 4 tracked files, +7 -9, and the new `mutants.sh`. I changed no role file, `broker.mjs`, field table or any other test. ### run.mjs, the coder line The old line was `withinRole: ['task.update.assigned', 'message.send'], crossRole: ['task.reassign', 'task.scope.change']`. It was stale in two ways: the reassign grant, and the missing `git.push.working` and `review.request`. The new line is word for word the coder entry in `world.mjs` and has the same authority as `roles/coder.json`. Two other lines in the same `AUTHORITY` object differ from `world.mjs`, and I left them alone because the brief owns only the coder line: - pm: `run.mjs` has no `role.launch`. `world.mjs` and `roles/pm.json` have it. - reviewer: `run.mjs` has no `review.verdict`. `world.mjs` and `roles/reviewer.json` have it. The live run uses neither action, so this doesn't change its result. If Sage wants the whole object to match, that's a follow-up row. ## N1: the untyped list (`out/mutants-candidate.txt`, `out/mutants-base.txt`) `untypedActions` (`packages/harness/src/tools.mjs:122-125`) returns `[...withinRole, ...crossRole]` minus the actions that have a typed tool. For the shipped coder without a tracker, that is `task.update.assigned`, `git.push.working`, `review.request` and `task.scope.change`. `message.send` is typed (`send_message`). The test now pins that list, including its order, with one `deepEqual`. | Mutant | Edit in `packages/harness/src/tools.mjs` | Base `331d037c` | Candidate | |---|---|---|---| | `untyped-within-only` | `return [...withinRole].filter(…)` (Dewey's mutant) | **survived** | killed | | `untyped-cross-first` | `return [...crossRole, ...withinRole].filter(…)` | **survived** | killed | | `untyped-keeps-typed` | `.filter((a) => a === "message.send" \|\| !typed.has(a))` | killed | killed | Every kill is in "a coder gets no launch, no resolve_decision, and no task tools without a tracker". On the base, the old `!includes("message.send")` assert already kills `untyped-keeps-typed`. I kept that mutant to show the `deepEqual` doesn't lose that check. The script saves the file, applies the sed and checks it changed something. It then runs `packages/harness/tests/tools.test.mjs` and restores the file from the saved copy. The base run used a clean detached worktree at `331d037c`, which I removed afterwards. `out/mutants-*/` holds each diff and log. The script exits 1 on the base, as it should when a mutant survives. A correction to my own first run: I wrote `untyped-keeps-typed` with `a !== "message.send"`. That inverts it into a mutant that keeps every typed action except `message.send`, and the coder has no other typed action in `withinRole` or `crossRole`, so it survived. That run says nothing about the test. I fixed the expression to `===` and reran. The outputs in `out/` are from the fixed script only. ## Repository grep for `task.reassign` `git grep -n 'task\.reassign'` on the candidate tree, outside `agents/`: | Path | Hits | Why it stays | |---|---|---| | `roles/pm.json:9` | 1 | the pm grant (decision 90: assignee is pm-only) | | `packages/business/src/vocabulary.mjs:17` | 1 | the action vocabulary | | `packages/bus/src/broker.mjs:10` | 1 | the broker's action list. The brief puts `broker.mjs` out of scope | | `packages/harness/src/tools.mjs:46` | 1 | the `task_reassign` typed tool. A pm with a tracker gets it | | `packages/tasks/src/verbs.mjs:13,191,200,201` | 4 | `PM_VERBS` and the verb itself | | `packages/tasks/README.md:107,150,176,193-194` | 5 | the writer-check list, the verb table, the `task-unassigned` refusal, and this row's rewritten paragraph | | `packages/tasks/live/README.md:65` | 1 | the pm's live reassign step | | `packages/tasks/live/run.mjs:29,154` | 2 | the pm grant and the pm's reassign step. The coder line no longer has it | | `packages/tasks/tests/world.mjs:34` | 1 | the pm grant in the test world | | `packages/tasks/tests/verbs.test.mjs:96-110` | 6 | a coder's `task.reassign` refused with `field-writer`, no coder holds it, and the pm's reassign paths | | `packages/tasks/tests/shapes.test.mjs:127` | 1 | a pm reassign | | `packages/bus/tests/broker.test.mjs:361,373,381` | 3 | the shipped coder raises `task.reassign` as gated to the human. That holds because the coder has no grant for it | | `packages/business/tests/resolve.test.mjs:141` | 1 | `classify(coder, "task.reassign")` is `gated`, the same fact | | `packages/business/tests/role.test.mjs:64,117` | 2 | 64 is row 73's comment on the coder file. 117 is a synthetic role that holds the action in both lists, to test that refusal | | `scripts/test-task.sh:279` | 1 | `v2ok`, a synthetic version 2 role with the action in `crossRole`, used only to check that `resolve-role` accepts a minimal role. It isn't the coder | | `docs/design/mosaic-stack-design.html:729,732,975` | 3 | the action list, the pm grant and the verb list. There's no coder grant here | | `docs/plans/2026-10-04_slice-1.md:73` | 1 | the field table: assignee is pm, via `task.assign` and `task.reassign` | | `docs/plans/2026-09-26_lead-decisions.md:1914` | 1 | decision 90's record of the old coder grant, which is history | | `docs/plans/2026-10-11_claude-pin-and-coder-grant.md` | 14 | the briefs for rows 73 and 79 | | `docs/plans/queue.json`, `docs/plans/QUEUE.md` | 5, 1 | row titles and notes | | `BUILD-LOG.md`, `docs/SESSIONS.md` | 7, 2 | append-only history | `v1/` has no hits. No hit outside history and briefs shows the coder holding `task.reassign`. ## Dewey's correction to the row 73 packet Dewey's approval of row 73 (comment 27506) corrects a line in that packet. The header of `agents/rocko/work/queue-73/mutants.sh` says "the tasks test kills nothing". That's true only for my five mutants. Dewey's `verbs-reassign-not-pm` mutant is killed only by the verbs line row 73 added. The committed row 73 file stays as it is, on Sage's instruction, and this note is the record. ## Gate (`out/gate/`, sequential, scratch worktree at 331d037c with the candidate, `node_modules` linked) Run 00:11:53Z-00:17:41Z (2026-10-12). The brief's gate names the tasks, business and harness suites and every script. I also ran bus, cli, webui and seat, the package set row 73's gate used. | Suite | Result | |---|---| | tasks | 71 pass, 0 fail, 0 skipped | | bus | 84 pass, 0 fail, 0 skipped | | business | 60 pass, 0 fail, 0 skipped | | harness | 59 pass, 0 fail, 0 skipped | | cli | 113 pass, 0 fail, 0 skipped (the base predates row 78's added test) | | webui | 61 pass, 0 fail, 0 skipped | | seat | 27 pass, 0 fail, 0 skipped | | test-auth | 15 passed, 0 failed | | test-conductor | 17 passed, 0 failed | | test-config | 24 passed, 0 failed | | test-discord | 66 passed, 0 failed | | test-extension-package | 18 passed, 0 failed | | test-foundation | 44 passed, 0 failed | | test-queue | 27 passed, 0 failed | | test-release | 14 passed, 0 failed | | test-task | 98 passed, 0 failed (Docker) | The harness count is unchanged, because the row edits one existing test. After the gate I removed the `rocko-r79_default` network.