researcher: row 80 BUILD.md, Console follow-up and fixes (#1574)

Filbert's R1 (comment 28188): the brief requires the packet to name what
the Console would need to show the stranded list. Added as a Console
follow-up section by the subagent, read-only, with unchecked items named.
Also N1 (M12 note) and N2 (round 1 title). Candidate unchanged, manifest
52c41526. Sage corrected one count: M12 failed 6 of 11 tests, not 13 (the
13 was the count of x-marker lines).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-13 10:26:12 -05:00
co-authored by Claude Opus 5.5
parent c98344e30a
commit 11bf4cb00a
+89 -89
View File
@@ -1,12 +1,12 @@
# Row 80 (#1574) build, round 2 # Row 80 (#1574) build, round 1
Status: Phase B complete on the full gate. Every suite, the nine scripts and the 15 mutants exited 0 (see "Sage's narrowed ruling and the full rerun" below). Earlier round 2 attempts stopped at cli for the missing `node_modules` and are recorded in the sections that follow. Not committed, not pushed, queue row not moved. Status: Phase B ran three times on this packet. Run 1 failed in `bus` (119/120). Run 2 passed `bus` (120/120) and `business` (60/60) after the fix, then stopped at `cli` (exit 1) because the worktree had no `node_modules`. Run 3 was the full gate with a `node_modules` symlink in place: all 14 node suites and the nine `scripts/test-*.sh` exited 0, and `mutants.py` (15 mutants) exited 0 with all 15 killed. Not committed, not pushed, queue row not moved.
## Identity ## Identity
- Row 80: bus, "stranded delivery" display. Brief: `docs/plans/2026-10-11_bus-stranded-delivery.md` (not edited). - Row 80: bus, "stranded delivery" display. Brief: `docs/plans/2026-10-11_bus-stranded-delivery.md` (not edited).
- Owner: researcher (decisions 122, 129). Reviewer: Filbert. Dispatch: Sage (lead decision 129). - Owner: researcher (decisions 122, 129). Reviewer: Filbert (approved, comment 28188). Dispatch: Sage (lead decision 129).
- BASE: `57a2ddc5978b05b780dc0e69fb4e6c2a60ab5877` (see `base.txt`). Round 1 used `2b2a9e14eec4ff81cdada5dc42bbf1b8063b29b4`; the rebase to 57a2ddc59 changed none of the three paths. - BASE: `57a2ddc5978b05b780dc0e69fb4e6c2a60ab5877` (see `base.txt`). Phase A used `2b2a9e14eec4ff81cdada5dc42bbf1b8063b29b4`; the rebase to 57a2ddc59 changed none of the three paths.
- Worktree: `.claude/worktrees/agent-abe72e00ca2bcdf7e`, detached at BASE, changes uncommitted. No commit, no push, no queue move. - Worktree: `.claude/worktrees/agent-abe72e00ca2bcdf7e`, detached at BASE, changes uncommitted. No commit, no push, no queue move.
- Candidate manifest: `candidate-manifest.sha256`, checked with `sha256sum -c` from the worktree root: all three paths OK. - Candidate manifest: `candidate-manifest.sha256`, checked with `sha256sum -c` from the worktree root: all three paths OK.
@@ -16,105 +16,105 @@ Status: Phase B complete on the full gate. Every suite, the nine scripts and the
| `packages/bus/src/broker.mjs` | `f685af1c5c496f0f1907995919017b9b098f7d84d9aa1047d126f3f4cc4991ef` | | `packages/bus/src/broker.mjs` | `f685af1c5c496f0f1907995919017b9b098f7d84d9aa1047d126f3f4cc4991ef` |
| `packages/bus/tests/agents.test.mjs` | `076137bca90b6594cc0888fb0daa2c57903fff5f1035122c00dd5540de65f6eb` | | `packages/bus/tests/agents.test.mjs` | `076137bca90b6594cc0888fb0daa2c57903fff5f1035122c00dd5540de65f6eb` |
`build.patch` is `git diff` against BASE, limited to these three paths (regenerated this round). `build.patch` is `git diff` against BASE, limited to these three paths.
## Round 2 changes ## Changes
- Round 1 Phase B failure: `bus` exit 1, 119/120. `agents.test.mjs:164` failed at L174. Cause: `node:sqlite` returns rows with a null prototype, so `assert.deepStrictEqual` against plain objects fails although the values match. - Fix (Sage's direction, in `broker.mjs`, not the test): the stranded query at L1047 to L1052 ends with `).map((m) => ({ id: m.id, from: m.from, run: m.run, at: m.at }));`. The `.map` is on L1052. Cause: `node:sqlite` returns rows with a null prototype, so `assert.deepStrictEqual` against plain objects fails although the values match. The test expectation is unchanged.
- Fix (Sage's direction, in `broker.mjs`, not the test): the stranded query at L1048 to L1052 ends with `).map((m) => ({ id: m.id, from: m.from, run: m.run, at: m.at }));` (the `.map` is at L1052). The test expectation is unchanged. - `node --test tests/agents.test.mjs` passes 11/11 after the fix.
- `agents.test.mjs` file: `node --test tests/agents.test.mjs` passes 11/11 after the fix. - Mutant M15 added (`.map` removed, expected killer `agents.test.mjs:174 prototype check`). The table has 15 entries.
- Mutant M15 added (`.map` removed, expected killer `agents.test.mjs:174 prototype check`). The table now has 15 entries. - `phase-b.sh` stops on any nonzero step exit. A stop writes `STOP: <step> exited <rc>`, skips the rest, runs the cwd scan and ends `PHASE-B-STOPPED`. A full pass ends `PHASE-B-DONE`.
- `phase-b.sh` now stops on any nonzero step exit. A stop writes `STOP: <step> exited <rc>`, skips the rest, runs the cwd scan and ends `PHASE-B-STOPPED`. A full pass ends `PHASE-B-DONE`.
- Stale round 1 logs in `out/` were deleted before the rerun.
## Phase B round 2 results ## Phase B runs
| Step | Exit | Tests | Notes | ### Run 1
| --- | --- | --- | --- |
| `bus` | 0 | 120 pass, 0 fail | Round 1 was 119/120. The L174 failure is gone. |
| `business` | 0 | 60 pass, 0 fail | |
| `cli` | 1 | 131 total: 121 pass, 8 fail, 2 cancelled | Stop. See below. |
| remaining 11 suites | not run | | Stopped |
| nine `scripts/test-*.sh` | not run | | Stopped |
| `mutants.py` (15) | not run | | Stopped |
### The cli stop - `bus` exit 1, 119/120. `agents.test.mjs:164` failed at L174 (null prototype, as above).
- Stopped on TERM (exit 143) on purpose, before the fix was applied. A `/proc` check found no leftover test process.
Cause: the worktree has no `node_modules`. The seat sandbox (`packages/seat/src/sandbox.mjs:95`, `binds`) calls `realpathSync` on `<worktree>/node_modules`, which fails with ENOENT. The failing trace runs through `packages/seat/src/session.mjs:122` and `packages/cli/src/launcher.mjs:237`. Two launcher tests then time out at 30 s (`tests/launcher.test.mjs:471`, the host that died hard). The rest of the failures are the same ENOENT. ### Run 2
Scope: the candidate touches only `packages/bus`, and no package imports another workspace package (this was checked in Phase A). The cli failure is therefore a property of the worktree layout, not of row 80. I have not proven it on BASE: I did not run cli on `57a2ddc59` in a clean tree, and I did not link `node_modules` into the worktree, because that would change the candidate tree and is a decision for Sage. - Run after the `.map` fix.
- `bus` exit 0, 120/120. `business` exit 0, 60/60.
- `cli` exit 1: 131 total, 121 pass, 8 fail, 2 cancelled. The stop rule fired. The remaining 11 suites, the nine scripts and the mutants did not run.
- Cause: no `node_modules` in the worktree. The seat sandbox (`packages/seat/src/sandbox.mjs:95`, `binds`) calls `realpathSync` on `<worktree>/node_modules`, which fails with ENOENT. The failing trace runs through `packages/seat/src/session.mjs:122` and `packages/cli/src/launcher.mjs:237`. Two launcher tests then time out at 30 s (`tests/launcher.test.mjs:471`). The rest of the failures are the same ENOENT.
- Scope: the candidate touches only `packages/bus`, and no package imports another workspace package (checked in Phase A). The cli failure is a property of the worktree layout, not of row 80. I did not run cli on `57a2ddc59` in a clean tree to prove this on BASE.
- The cwd scan reported three processes with a cwd in the worktree at the stop (pids 1353827, 1359345, 1362919). A re-check a few seconds later found none.
The cwd scan reported three processes with a cwd in the worktree when the stop fired (pids 1353827, 1359345, 1362919). A re-check a few seconds later found none. I did not identify them. They were most likely children of the failed cli run that exited on their own. ### Run 3
## Mutants (15), not run this round - `node_modules` symlinked from the canonical root into the worktree. `git status --porcelain` showed the three candidate paths, `?? node_modules` (expected: `.gitignore` line 2 is `node_modules/`, which matches directories only, so a symlink shows as untracked), and `?? agents/researcher/work/queue-80/`. Nothing else. Sage's ruling on this status is recorded in the coordinator message, not decided here.
- Full phase B from the top. Summary in `out/phase-b-summary.txt`, end marker `PHASE-B-DONE`.
- Node suites, all exit 0: bus (120/120), business (60/60), cli (passes with the link; count in `out/`), control-board, conversation, discord, harness, ledger, mosaic, queue, runs, seat, tasks, webui.
- Scripts, all exit 0: `test-auth`, `test-conductor`, `test-config`, `test-discord`, `test-extension-package`, `test-foundation`, `test-queue`, `test-release`, `test-task`.
- The cli pass confirms the run 2 cli failure was the missing `node_modules`.
- Two attempts did not run phase B. A symlink attempt in the earlier round was stopped by the untracked-link rule. A later chained command was refused by the worktree guard. Neither changed the candidate.
Each mutant is one exact-once replacement in `broker.mjs`. Line numbers are in the candidate. ### Cleanup after run 3
| ID | File:line | Expected killer | - `rm <worktree>/node_modules` (plain rm, the link only).
| --- | --- | --- |
| M01 | broker.mjs:47 | `agents.test.mjs:203` length 50 |
| M02 | broker.mjs:1048 | `agents.test.mjs:204` oldest-first ids |
| M03 | broker.mjs:1048 | `agents.test.mjs:203` length 50 |
| M04 | broker.mjs:1048 | `agents.test.mjs:189` current holder keeps unread |
| M05 | broker.mjs:1048 | `agents.test.mjs:174` released-run entry |
| M06 | broker.mjs:1048 | `agents.test.mjs:192` read message not listed |
| M07 | broker.mjs:1048 | `agents.test.mjs:230` per business |
| M08 | broker.mjs:1048 | `agents.test.mjs:221` per role |
| M09 | broker.mjs:1048 | `agents.test.mjs:174` entry shape (`run` renamed) |
| M10 | broker.mjs:1048 | `agents.test.mjs:174` entry shape (`from` renamed) |
| M11 | broker.mjs:1048 | `agents.test.mjs:174` entry shape (`at` dropped) |
| M12 | broker.mjs:1053 | `agents.test.mjs:58` key list |
| M13 | broker.mjs:932 | `agents.test.mjs:62` role session keys |
| M14 | broker.mjs:1048 | `agents.test.mjs:239` routed, not yet received |
| M15 | broker.mjs:1052 | `agents.test.mjs:174` prototype check |
These expectations are reasoned, not observed. M09, M11 and M15 are the ones Sage asked about. They have not run against the fixed broker yet.
## Gate rerun after Sage's environment ruling (2026-10-13T15:13Z)
Sage ruled the cli stop an environment gap and asked for a symlink of the canonical root `node_modules` into the worktree, then a full rerun. Steps taken:
1. Cwd scan run from `/`: zero processes with a cwd in the worktree. An earlier count of one was my own scan subshell, which had inherited the worktree cwd. That process was gone on the re-check.
2. `ln -s /mnt/storage/src/mosaic-stack/node_modules <worktree>/node_modules`. `git status --porcelain` listed `?? node_modules`. Sage's rule for that case is stop and report, so the rerun did not start. The symlink was removed with `rm` (link only). Status is back to the three candidate paths and the packet; the manifest still verifies. `build.patch` covers the three paths only, so the link never entered it.
3. No rerun. Round 2 phase B results above stand as the last recorded run (bus 120/120, business 60/60, cli stopped at 121/131 with 8 fail and 2 cancelled, the rest not run).
Open for Sage: whether an untracked `node_modules` link is acceptable for the gate, or whether the gate should link it outside the worktree or exclude it from `git status`. The cli failures are not yet re-run with the link in place.
## Sage's narrowed ruling and the full rerun (2026-10-13)
Sage ruled that `?? node_modules` is expected: `.gitignore` line 2 is `node_modules/`, and a trailing slash matches only directories, so a symlink shows as untracked. Nothing is committed from the worktree, and `build.patch` and the manifest cover only the three paths. Stopping at the first symlink check was right under the earlier rule. The rerun was ordered.
Steps taken:
1. Symlink linked again. `git status --porcelain` showed the three candidate paths, `?? node_modules`, and `?? agents/researcher/work/queue-80/`. Nothing else.
2. First attempt did not start. A chained command was refused by the worktree guard, so `phase-b.sh` never ran. The earlier monitor read the previous round's summary. That monitor was stopped, the old out/ logs were removed, and the run was restarted with a plain command.
3. Full phase B from the top, with the symlink in place.
Results (`out/phase-b-summary.txt`, end marker `PHASE-B-DONE`):
| Step | Exit |
| --- | --- |
| bus, business, cli, control-board, conversation, discord, harness, ledger, mosaic, queue, runs, seat, tasks, webui | 0 each |
| `script-test-auth`, `-conductor`, `-config`, `-discord`, `-extension-package`, `-foundation`, `-queue`, `-release`, `-task` | 0 each |
| `mutants.py` (15) | 0 |
Per-suite counts for bus, business and cli are in their logs under `out/`. The cli suite passes with the link in place, which confirms the round 2 cli failure was the missing `node_modules`. The bus suite (120/120) includes the `.map` fix.
Mutants: baseline green, manifest clean after restore, all 15 KILLED. Two notes on the verdicts:
- M09, M10, M11 and M15 were all killed at `agents.test.mjs:174`, as expected.
- M12 is killed, but not by the expected killer. Removing the `stranded` key makes the test read `undefined`, which throws a TypeError at `agents.test.mjs:203` and `:221` before the L58 key-list assertion runs. The verdict line says "error marker in log". The kill is real. The expected-killer note in the table is wrong about the mechanism.
Cleanup:
- `rm <worktree>/node_modules` (plain rm, the link only) was run after the summary.
- `git status --porcelain` shows only the three candidate paths and the packet. - `git status --porcelain` shows only the three candidate paths and the packet.
- `build.patch` covers the three paths only. - `build.patch` covers the three paths only.
- `sha256sum -c` verifies all three hashes. - `sha256sum -c` verifies all three candidate hashes.
- A scan from `/` finds zero processes with a cwd in the worktree. Three transient processes flagged at the end of the run belonged to my own monitor's shell and its `sleep`, and they exited when the monitor stopped. - A scan from `/` finds zero processes with a cwd in the worktree.
## Mutants (15), phase B run 3
Each mutant is one exact-once replacement in `broker.mjs`. The "Observed" column lists the `agents.test.mjs` line numbers that appear in the stack frames of `out/M<nn>.log`. The expected killer line appears in every log.
| ID | Site | Expected killer | Observed assertion frames |
| --- | --- | --- | --- |
| M01 | broker.mjs:47 (`STRANDED` cap) | `:203` length 50 | 203 |
| M02 | broker.mjs:1048 (sort order) | `:204` oldest-first ids | 204 |
| M03 | broker.mjs:1048 (cap) | `:203` length 50 | 203 |
| M04 | broker.mjs:1048 (current holder) | `:189` current holder keeps unread | 189 |
| M05 | broker.mjs:1048 (released run) | `:174` released-run entry | 174, 189, 203, 221 |
| M06 | broker.mjs:1048 (read filter) | `:192` read message not listed | 192 |
| M07 | broker.mjs:1048 (business scope) | `:230` per business | 230 |
| M08 | broker.mjs:1048 (role scope) | `:221` per role | 221 |
| M09 | broker.mjs:1048 (`run` renamed) | `:174` entry shape | 174, 205 |
| M10 | broker.mjs:1048 (`from` renamed) | `:174` entry shape | 174, 205 |
| M11 | broker.mjs:1048 (`at` dropped) | `:174` entry shape | 174 |
| M12 | broker.mjs:1053 (`stranded` key removed) | `:58` key list | 58, 174, 189, 203, 221, 239 |
| M13 | broker.mjs:932 (role session rows) | `:62` role session keys | 62 |
| M14 | broker.mjs:1048 (routed, not received) | `:239` routed, not yet received | 239 |
| M15 | broker.mjs:1052 (`.map` removed) | `:174` prototype check | 174 |
Verdicts: baseline green, manifest clean after restore, all 15 KILLED.
M12 note: the L58 key-list assertion does catch M12, at `agents.test.mjs:58:32`. The run also shows TypeErrors at `:203` and `:221`, because the test reads `stranded` as `undefined` in those assertions. The first failing test header in that log is `:49`, with 6 of 11 tests failing (`ℹ fail 6`; the 13 `✖` lines are each failure twice plus the "failing tests:" header). The kill is real and the expected killer is correct. The mechanism is partly TypeErrors, not only the L58 check.
## Console follow-up
Scope: read-only research of `packages/webui` and `packages/cli`. No code changed. Line numbers are from the current worktree.
Not checked, listed once here: a live read of `/api/bus/agents` (no run was made); the `bus-browser` test run; the contents of the "Not shown here yet" gap list at the end of `agentsView`; whether `packages/webui` references `stranded` anywhere (I did not grep for it, and the render functions below do not use it); whether `#/messages/<id>` exists as a detail route (grep found only list pages); the text of decision 123 in the repo; which session type (`human` or `reader`) the Console's `humanCall` opens; and whether the human-cli `agents` verb passes broker rows through unchanged.
### (a) Which read carries `stranded` to the Console
- Route: `packages/webui/src/serve.mjs:52` (`BUS_VERBS` includes `agents`); `busRead` at L54; the GET-only `/api/bus/` route at L223-227 returns 404 for unknown verbs.
- Transport: `packages/webui/src/bus.mjs:27` `humanCall` runs the human transport `packages/bus/src/human-cli.mjs` (header L1-2; lead decision 70).
- Broker: `packages/bus/src/broker.mjs:932` returns `#agentState(c)` rows when `s.human || s.reader`, and claim rows otherwise. `#agentState` at L1053 returns `{ ...c, state, working, inputNeeded, stranded }`. The stranded query is at L1047-1052.
- Result: for a human or reader session, the broker already puts `stranded` on the `agents` read. No read-path change is needed for the Console to receive it, subject to the unchecked items above. `bus.test.mjs:77` already asserts that `inputNeeded` comes through the reader view. `stranded` sits on the same object, so the same path applies, but no test covers it yet.
- CLI: `packages/cli/src/cli.mjs:188` prints `agents` through `formatAgents`. `packages/cli/src/format.mjs:52-55` prints role, holder run, harness and `since`. `packages/cli/src/app.mjs:433` prints the detail lines (held by, harness, since). None of these read `inputNeeded` or `stranded` (grep of `packages/cli/src` for both names: no matches). A cli change is optional and not needed for the Console.
### (b) Where a list or count would render, and what `inputNeeded` does
- Render path: `packages/webui/src/public/bus.js`. `fetchRead(verb, subject)` at L34, and the `get` helper at L59-62.
- `attention(c)` at L421-428 is the existing `inputNeeded` renderer. It links the first ask to `#/inbox/<id>`, shows a blocking chip, and shows a "+N" link to `#/inbox`.
- `heldTable(claims)` at L435 builds the table. `paintHeld(err)` at L440-460 draws the Board held count, the refusal banner and the table. `agentsView(get)` at L462-470 renders the Held roles table with columns Role, State, Attention, Holder, Harness and Held since.
- The Board refresh at L706-717 reads inbox, agents and messages, then calls `paintHeld`.
- Natural fit for a follow-up: a stranded count in the Attention cell, built the same way as `attention(c)`, with a link per message. The link target would be `#/messages/<id>`, which is not confirmed to exist (see unchecked list). A separate "Stranded" column is the alternative.
### (c) What the follow-up row would test, and `public/`
- Existing test patterns to extend: `packages/webui/tests/bus.test.mjs:77` (reader-view `inputNeeded` assertion) and `packages/webui/tests/bus-browser.test.mjs:248-259` (browser check of the Agents table: headers, state mark, "Input needed:" text, `#/inbox/` link).
- A follow-up would need, at minimum: stranded entries present in the reader rows; rendered count or links; the link target; no change to the read when nothing is stranded; and a check that listing writes nothing (no delivery row changes).
- Public path: any render change touches `packages/webui/src/public/bus.js`, and any stylesheet under `public/` would too. Per decision 123 that needs Mos's go. The read-path files (`serve.mjs`, `bus.mjs`) and the cli files are not under `public/`.
## Open items ## Open items
1. Not committed, not pushed, queue row not moved. 1. Not committed, not pushed, queue row not moved.
2. Sage to review M12's killer note (the mechanism differs from the table). The kill itself is sound. 2. Sage to review the corrected M12 note above.
3. Console follow-up is research only. It needs a separate queue row, and Mos's go for any `public/` change.
## Round 1 (superseded) 4. Phase A (superseded): base `2b2a9e14`, 14 mutants, manifest sha256 `4c91e42d71ff9a95e336db57f6c5821f3ef94f6e3dbf07ef860d71145d664c8a`, broker hash `cc949cea…`. Superseded by the broker hash above.
Phase A packet, base `2b2a9e14`, 14 mutants. Its manifest sha256 was `4c91e42d71ff9a95e336db57f6c5821f3ef94f6e3dbf07ef860d71145d664c8a`, and its broker hash `cc949cea…` is superseded. Phase B round 1 failed at bus (119/120) as described above.