docs: lead decision 91, row 68 build log, pin and coder grant brief (#1561)

Decision 91 amends 89: the bus pins ~/.local/bin/claude through
MOSAIC_CLAUDE_BIN, so the package upgrade is no longer needed for the
first PM launch. The brief covers two Rocko rows: tests for row 68's
surviving mutants, and dropping the coder's dead task.reassign grant.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-11 16:47:15 -05:00
co-authored by Claude Opus 5.5
parent 4100fa68c0
commit 32aa572788
3 changed files with 145 additions and 0 deletions
+8
View File
@@ -4226,3 +4226,11 @@ Gate (Sage, scratch worktree of 918a4a19 plus the candidate, sequential and teed
Live read (`agents/sage/work/vikunja-setup/tick-62.mjs`, record `2026-10-11_tick-62.json`, 902b3d0e), 21:28:03Z, tasks.woltje.com v2.7.0: the candidate's own `reconcile()` and `tick()` with descendants on, root #19, the sync key only, a real Store and Broker in a scratch root, and a fetch guard refusing anything but GET. `startup()` was not run, since its scope probes send a PATCH, a DELETE and a POST as role keys; the script made the root's install check from the same two reads. Reconcile 15 GETs, 42 snapshots (30 on #19, 4 on 18, 3 on 20, 5 on 21; the cross-project probe in `2026-10-11_probe-62.json` also counted 42). Tick 8 GETs, 0 snapshots, the count `probes.md` predicted. 18, 20 and 21 are watch-only. 0 writes. Darkwing's deleted-view probe: a missing view id on #18 answers 404 code 3014 while `GET /projects/18` answers 200, so the README's account holds live (the child stays and the tick fails until the next reconcile reads it watch-only, which Filbert notes stops the root's tick too). The fake answers 13001; the adapter treats any 404 without code 4002 the same, so behavior doesn't change. No README change needed. The live bus is still on descendants off (host config `tracker.descendants` false); turning it on is a config change for when row 63 lands. Live read (`agents/sage/work/vikunja-setup/tick-62.mjs`, record `2026-10-11_tick-62.json`, 902b3d0e), 21:28:03Z, tasks.woltje.com v2.7.0: the candidate's own `reconcile()` and `tick()` with descendants on, root #19, the sync key only, a real Store and Broker in a scratch root, and a fetch guard refusing anything but GET. `startup()` was not run, since its scope probes send a PATCH, a DELETE and a POST as role keys; the script made the root's install check from the same two reads. Reconcile 15 GETs, 42 snapshots (30 on #19, 4 on 18, 3 on 20, 5 on 21; the cross-project probe in `2026-10-11_probe-62.json` also counted 42). Tick 8 GETs, 0 snapshots, the count `probes.md` predicted. 18, 20 and 21 are watch-only. 0 writes. Darkwing's deleted-view probe: a missing view id on #18 answers 404 code 3014 while `GET /projects/18` answers 200, so the README's account holds live (the child stays and the tick fails until the next reconcile reads it watch-only, which Filbert notes stops the root's tick too). The fake answers 13001; the adapter treats any 404 without code 4002 the same, so behavior doesn't change. No README change needed. The live bus is still on descendants off (host config `tracker.descendants` false); turning it on is a config change for when row 63 lands.
Row 62 done at rev 452 (86f65766); #1555 closed with comment 27352. Follow-up: row 71, #1565, brief 9d715276, owner Darkwing, after their rows 58, 61 and 69: tests for the surviving confirm-read mutants (`r3-confirm-4xx`, `r3-confirm-error-on-throw`) and the fake's view 404 set to 3014. Row 62 done at rev 452 (86f65766); #1555 closed with comment 27352. Follow-up: row 71, #1565, brief 9d715276, owner Darkwing, after their rows 58, 61 and 69: tests for the surviving confirm-read mutants (`r3-confirm-4xx`, `r3-confirm-error-on-throw`) and the fake's view 404 set to 3014.
### 2026-10-11 — Sage, row 68 landed: the bus host runs an explicit Claude Code binary (#1561)
Round 1 (manifest a2008d8b, base a86de1f1) had both approvals, Filbert 27349 and Dewey 27350, but couldn't land. Row 55 (79d9fc2d) changed `docs/TOOLS.md` and `packages/cli/README.md` after that base, and a manifest pins whole files. Round 2 (`agents/sage/work/queue-68/r2/`, b31cc8a9, manifest 21c1ab05) is round 1's patch applied on f2f2861b with `git apply --index --3way`; its `+` and `-` lines are byte-identical to round 1's. Sage's gate on f2f2861b plus the patch finished 21:37:18Z: cli 112, seat 27, harness 57 node tests and nine `scripts/test-*.sh`, all 0 failed, Docker on, manifest OK before and after. Filbert approved round 2 (27374), Dewey too (27375, after a rerun: their first harness run failed 5 pi tests on a missing node_modules link).
Landed as 11dae0c7; `queue review verify-commit 68 11dae0c7` matched the manifest (13 paths). Installed 21:42:39Z with `bus-service.sh install --claude /home/jwoltje/.local/bin/claude`: the unit's diff was its comments and `Environment=MOSAIC_CLAUDE_BIN=/home/jwoltje/.local/bin/claude`, then `daemon-reload` only. The symlink resolves to 2.1.296. Probe at 21:43:03Z, `adapters/claude/adapter.sh` under `env -i` with the unit's PATH, claude-opus-5-5, empty settings and MCP config, in a scratch dir since removed: the pin answered "OK" rc 0; `/usr/bin/claude` (2.1.259) got "2.1.280 or newer is required" rc 1. The running bus (PID 1242258, since 13:19:28 CDT) was not restarted; it picks up the pin at Mos's watched Q18 `--pm` restart. Lead decision 91 amends 89: Jason's package upgrade is no longer needed.
Row 68 done at rev 463 (4100fa68). The surviving mutants (a directory pin in both scripts, the symlink default, an adapter pin with a space) and the dead `task.reassign` grant in `roles/coder.json` that decision 90 named go to two Rocko rows, brief `docs/plans/2026-10-11_claude-pin-and-coder-grant.md`.
+23
View File
@@ -1919,3 +1919,26 @@ which stay with him. Each item names who decided it and what happened.
reviewer who isn't assigned can't comment on the task through the reviewer who isn't assigned can't comment on the task through the
bus. If slice 1 acceptance needs that, it is a new verb or a bus. If slice 1 acceptance needs that, it is a new verb or a
change to `task.update.assigned`, decided then. change to `task.update.assigned`, decided then.
91. **The bus pins the user's Claude Code binary; this amends 89
(2026-10-11).** Decision 89 left the bus on `/usr/bin/claude` and made
the fix Jason's package upgrade. Row 68 (#1561, 11dae0c7) took the
other path: `bus-service.sh install --claude PATH` writes
`Environment=MOSAIC_CLAUDE_BIN=PATH` into the unit, and the claude
adapter runs that binary when it is set.
- Installed 2026-10-11T21:42:39Z with
`--claude /home/jwoltje/.local/bin/claude` (a symlink, now to
2.1.296). The unit's only change was that line and its comments;
`daemon-reload` only, no restart.
- Probe at 21:43:03Z, the adapter under `env -i` with the unit's PATH
and claude-opus-5-5: the pinned binary answered "OK", rc 0;
`/usr/bin/claude` still gets "2.1.280 or newer is required", rc 1.
- Jason's sudo upgrade is no longer needed for the first PM launch.
The pacman package stays installed and untouched. The running bus
(PID 1242258, up since 13:19:28 CDT) keeps its old environment until
Mos's watched Q18 `--pm` restart; Sage doesn't restart it.
- The tradeoff: the symlink follows the user's auto-updates, so a bad
Claude Code release reaches the bus at its next restart without a
review. Decision 89 valued the root-owned package for that reason.
No version floor check runs at start; one would catch a
downgrade, not a bad upgrade. Re-pinning to a versioned path is one
`install --claude` away if a release breaks it.
@@ -0,0 +1,114 @@
# Follow-ups after row 68 and lead decision 90 (2026-10-11)
Status: written by Sage, lead. It follows `docs/plans/BRIEF-TEMPLATE.md`.
Two rows, both small, both Rocko's.
## Claude pin tests: a directory pin, a symlink default, a pin with a space
### Problem
Row 68 (#1561, commit 11dae0c7) pins the bus's Claude Code binary with
`MOSAIC_CLAUDE_BIN`. Filbert (comment 27349) and Dewey (comment 27350)
approved it and named mutants no test kills:
- `bs-no-f`, `ad-no-f`, `bs-x-only` and `ad-x-only`. The `[ -f … ]` check
in `scripts/bus-service.sh` (`claude_line`) and in
`adapters/claude/adapter.sh` can go and every test still passes. A
directory is `-x`, so the unit would carry a directory pin, and the
adapter would fail at exec (exit 126, "Is a directory") instead of
exiting 2 with its own message.
- `bs-resolve-default`. Resolving the default `command -v claude` with
`readlink -f` passes, because the test's `claude` on `PATH` is a plain
file. On Jason's host `~/.local/bin/claude` is a symlink, and the pin is
meant to keep the symlink so updates follow.
- `ad-unquoted-run`. The adapter runs `"$CLAUDE"`. Unquoted, a pin with a
space breaks. `bus-service.sh` refuses a space, but the adapter takes a
hand-set pin. Nothing in the adapter checks the pin's characters.
`bs-status-no-env` and `bs-sed-unanchored` stay untested; both reviewers
asked for no test.
### Owner and reviewer
Owner: Rocko. Reviewer: Filbert. Rocko has no Gitea token; Sage runs the
review request as jarvis.
### Files owned
- `packages/cli/tests/host.test.mjs`
- `packages/harness/tests/claude-session.test.mjs`
- `agents/rocko/work/queue-<row>/`
### What ships
- A `render --claude DIR` test and a default test where the `claude` on
`PATH` is a directory: both refuse with "not an executable file" and
print nothing on stdout.
- A default test where the `claude` on `PATH` is a symlink to an
executable: the unit's `MOSAIC_CLAUDE_BIN` is the symlink path, not its
target.
- An adapter test with `MOSAIC_CLAUDE_BIN` a directory (exits 2 before
anything runs) and one with a stand-in under a directory whose name has
a space (runs).
- The packet reruns the six named mutants against the candidate and lists
each killed.
### Out of scope
Any change to `bus-service.sh`, `adapter.sh`, the launcher or the seat
session code. If a test shows the code is wrong, stop and tell Sage.
### Gate
Filbert approves on the row's issue naming the candidate manifest. The
cli and harness node suites and every `scripts/test-*.sh` green on Sage's
gate rerun.
## Coder role: drop the task.reassign grant the broker always refuses
### Problem
`roles/coder.json` lists `task.reassign` under `crossRole`. The task verbs
refuse every pm-only verb to any other role (`packages/tasks/src/verbs.mjs`,
`PM_VERBS`, `field-writer`), so a coder's resolved decision to reassign
always ends in `field-writer`. Lead decision 90 kept assignee pm-only and
named this a dead grant for its own row. A grant that can never succeed
misleads anyone reading the role file and wastes a decision round.
### Owner and reviewer
Owner: Rocko. Reviewer: Dewey. Sage runs the review request as jarvis.
### Files owned
- `roles/coder.json`, the `crossRole` list only
- any test that lists the coder's grants, and a bus test for the
classification
- `agents/rocko/work/queue-<row>/`
### What ships
- `task.reassign` removed from the coder's `crossRole`; `task.scope.change`
stays.
- A test that the broker now classes a coder's `task.reassign` as
`gated`, not `cross-role` (`packages/bus/src/broker.mjs`,
`#classification`: an action in neither list is gated). The verb
still refuses with `field-writer` if it ever runs.
- The packet lists every place that read the old grant (role resolution,
tests, docs), with what each now says. It also says plainly what a
coder's `task.reassign` does end to end after the change. A gated
request asks a human for a decision that can only end in
`field-writer`. Refusing it before any decision opens is a broker
change and would be its own row. The packet names it; it doesn't make it.
### Out of scope
Any other role's grants, the `PM_VERBS` set, `broker.mjs` and the slice 1
field table.
Who may reassign doesn't change; only the grant that never worked goes.
### Gate
Dewey approves on the row's issue naming the candidate manifest. The
tasks, bus, business and harness node suites and every `scripts/test-*.sh` green
on Sage's gate rerun.