Files
stack/agents/rocko/work/queue-73/BUILD.md
T
jason.woltjeandClaude Opus 5.5 2137dd7f5d review(rocko): row 73 round 1 packet, coder role drops task.reassign (#1567)
Rocko's packet agents/rocko/work/queue-73/ (candidate manifest bf4e5ed6,
packet manifest 41ffcd5c, 53 files), their BUILD-LOG entry and SESSIONS
line. Sage's SESSIONS line for the review request (comment 27467) and
row 79 (#1573) for the stale text the packet names.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-11 18:20:34 -05:00

141 lines
8.6 KiB
Markdown

# Row 73 (#1567): drop the coder's task.reassign grant, candidate packet, round 1
Author: Rocko. Reviewer: Dewey. Brief:
`docs/plans/2026-10-11_claude-pin-and-coder-grant.md`, section "Coder role:
drop the task.reassign grant the broker always refuses". Base: `324c064b`
(`base.txt`). At 23:16Z the checkout HEAD `f6b9ea4e` has no change since the
base to any candidate path or to `packages/{bus,business,tasks}/src` or
`packages/harness/src/tools.mjs`. `build.patch` applies to it cleanly. 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 |
|---|---|
| `roles/coder.json` | `crossRole`: `["task.reassign", "task.scope.change"]` → `["task.scope.change"]`. Nothing else in the file changes. |
| `packages/business/tests/role.test.mjs` | "shipped authority follows the note's table": asserts that the coder's `crossRole` is exactly `["task.scope.change"]` |
| `packages/business/tests/resolve.test.mjs` | "classify": the coder's `task.reassign` is now `gated` and its `task.scope.change` is `cross`. "limits.authority narrows cross-role actions too" moves from the coder to the pm (see below). |
| `packages/bus/tests/broker.test.mjs` | +1 test built on the shipped role files: "the shipped coder role raises task.reassign as gated to the human; task.scope.change stays cross-role" |
| `packages/harness/tests/tools.test.mjs` | +1 test: "a coder with a tracker gets its own task tools and no task_reassign" |
| `packages/tasks/tests/world.mjs` | the fixture's coder `crossRole` now matches the shipped role |
| `packages/tasks/tests/verbs.test.mjs` | in "assign and reassign …": a coder's `task.reassign` refuses `field-writer` and records no `action.allowed` |
| `agents/rocko/work/queue-73/mutants.sh` | the mutant rerun: `mutants.sh <tree>` |
No change to `broker.mjs`, `PM_VERBS`, the slice 1 field table or any other
role's grants.
### Why the narrowing test moved to the pm
Before this change, the test limited the coder to `task.scope.change` and
watched `task.reassign` drop out of `crossRole`. With one cross-role action
left, that limit drops nothing from `crossRole`. I checked this: with the new
role file and the old test, the mutant `resolve-cross-unnarrowed` (crossRole
ignores `limits.authority`) passes the test (`out/old-narrow-test-under-mutant.txt`).
The pm still holds two cross-role actions. The test now limits the pm to
`task.create`, `task.close` and `task.priority.change`. It checks that
`crossRole` keeps `task.priority.change` and drops `task.scope.change`, and
that `withinRole` narrows too.
## Every place that read the old grant, and what it says now
Probe (`out/probe-base.txt`, `out/probe-candidate.txt`): the shipped roles
through `validateBusinessDocument`, `resolveInstance`, `busBusiness` and a
real `Broker`, as `bootConfig` assembles them.
| Reader | Before (base) | After (candidate) |
|---|---|---|
| `business/src/resolve.mjs` `resolveInstance` | coder `crossRole` `[task.reassign, task.scope.change]` | `[task.scope.change]`. The resolved digest of every coder instance changes too. Only the bundle manifest records it, and nothing compares it. |
| `business/src/resolve.mjs` `classify` | `cross` | `gated` |
| `bus/src/broker.mjs` `#classification` (via `cli/src/config.mjs` `bootConfig` → `busBusiness`) | `cross-role`; `decision.raise` routes to the domain arbiter, `pm` by default (`cto` for technical) | `gated`; routes to `human` |
| `harness/src/tools.mjs` `typedTools`, with a tracker | the coder gets a `task_reassign` tool | no `task_reassign` tool; `task_scope_change` and `task_update_assigned` remain |
| `harness/src/tools.mjs` `untypedActions` → `bundle.mjs` prompt, without a tracker | the prompt lists `task.reassign` among "Actions you hold that have no bus tool yet" | not listed |
| `tasks/src/verbs.mjs` `PM_VERBS` | `field-writer` before authority | unchanged: `field-writer` before authority |
| `tasks/tests/world.mjs` fixture | copied the old grant | matches the shipped role |
| business, bus, harness and tasks tests | see Files | see Files |
Readers I did not change (outside the brief's files; Sage's call):
- `packages/business/README.md`, lines 30-43: the version 2 example is the
shipped coder file and still shows `"crossRole": ["task.reassign",
"task.scope.change"]`. It goes stale with this change. Suggested fix: drop
`"task.reassign", ` from the example.
- `packages/tasks/README.md`, lines 193-196: "A coder role's `crossRole`
grant of `task.reassign` can't be used. The writer check refuses it before
authority is looked at. …" This will be stale too. Suggested text: "The
coder role holds no `task.reassign` grant (row 73). The writer check
refuses a non-pm `task.reassign` before authority is looked at anyway. If
a coder should hand work on, that's a change to the field table, not to
the role file."
- `packages/tasks/live/run.mjs`, line 32: the live run's own copy of the
slice 1 authority still gives the coder `task.reassign`. Nothing runs it in
the suites. It never has the coder call `task.reassign`, so its result
doesn't change. It should match `world.mjs`.
- Unaffected: `scripts/test-task.sh` line 279 and
`business/tests/role.test.mjs` line 115 are role-validation fixtures, not
the coder's grant. Historical notes are left as they are: the slice 1
plan, the Darkwing and Filbert work notes, and lead decision 90.
## What a coder's task.reassign does end to end after the change
1. A pi coder session has no `task_reassign` tool, so it can't make the
call through the harness.
2. It can still `raise_decision` with `action: task.reassign`, because the
tool's action enum is every action. The broker classes it `gated` and
routes it to the human, where before it went to the pm as cross-role. The
human is then asked a question that can only end in `field-writer`.
3. If the human approves and the coder calls the verb directly on the bus,
the broker's `authorize` passes as `gated`; the bus test shows that. The
task verbs refuse first with `field-writer`, before `authorize`, so nothing
is consumed and nothing is written (tasks test).
The brief names the follow-up but doesn't make it, and nor does this row. To
refuse a non-pm `task.reassign` (or any `PM_VERBS` action) at
`decision.raise`, before a decision opens, is a broker change and would be
its own row.
A bus that is already running keeps the authority it loaded at boot. The
change reaches it on its next restart.
## Mutants (`out/mutants-candidate.txt`, `out/mutants-base.txt`)
| Mutant | Edit | Base `324c064b` | Candidate |
|---|---|---|---|
| `role-keeps-reassign` | `roles/coder.json` puts `task.reassign` back | n/a (the base is this mutant) | killed by role, classify, bus and harness tests |
| `broker-unlisted-cross` | `#classification`: an action in neither list → `cross-role` | **survived** | killed by the new bus test |
| `resolve-cross-unnarrowed` | `resolveInstance`: `crossRole` ignores `limits.authority` | killed | killed (by the narrowing test and the harness test "an action outside the instance's authority has no tool") |
| `classify-unlisted-cross` | `classify`: an unlisted action → `cross` | killed | killed |
| `tools-all-task-verbs` | `typedTools`: a task tool for every verb, held or not | killed | killed (now also by the new harness test) |
The script saves each file, applies the sed and checks it changed something.
It then runs the five test files and restores the file from the saved copy,
so it works on an uncommitted tree. `out/mutants-*/` holds each diff and log.
I ran the candidate twice. The run recorded here uses the final script; the
first run used the same script before a comment-only reflow, with the same
results.
## Gate (`out/gate/`, sequential, scratch worktree at 324c064b with the candidate, `node_modules` linked, 23:09:50Z-23:14:34Z)
| Suite | Result |
|---|---|
| tasks `node --test 'packages/tasks/tests/*.test.mjs'` | 71 pass, 0 fail |
| bus | 81 pass, 0 fail |
| business | 60 pass, 0 fail |
| harness | 58 pass, 0 fail, 0 skipped |
| cli | 112 pass, 0 fail |
| webui | 50 pass, 0 fail |
| seat | 27 pass, 0 fail |
| 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) |
cli, webui and seat go beyond the gate the brief names. I ran them because
they read the shipped roles. After the gate I removed the `rocko-r73_default`
network.