# Row 67 (#1560): decisions read follow-up, round 2 Filbert, 2026-10-12. Brief: `docs/plans/2026-10-11_decisions-read-follow-up.md`, "Decisions read follow-up: seen-then-resolve, list-before-503 and full-row tests" (blob 7674ff40, unchanged). Darkwing and Dewey review. Sage commits after the gate. Nothing in the candidate is committed, staged or pushed. Base f38b7f46, origin/refactor's tip when I rebased. `build.patch` applies there with `git apply --index`, and `candidate-manifest.sha256` pins all 4 files after the patch. Round 1 was manifest `8959e615…7369` on 970ef729, approved by Dewey (27452) and Darkwing (27627). ## Round 2: rebase only Sage set the scope: a rebase onto the current tip, the merged hunks named here, a fresh manifest, and a digest re-approval. Nothing new goes in, so the round 1 description, mutants and questions below still apply. Dewey's N1 (an assertion that test 4's new decisions are listed) is not folded in; it stays a follow-up. ### What landed on the manifest paths Since 970ef729, three rows changed 3 of the 4 files. `packages/bus/tests/decisions.test.mjs` is untouched. - Row 58 (aaf87213): the bus README, `broker.mjs` and webui `bus.test.mjs`. - Row 60 (1771bf98): the bus README, `broker.mjs` and webui `bus.test.mjs`. - Row 59 (a8512007): the bus README and `broker.mjs`. ### Digest check Round 1's patch no longer applies plain (README line 205), as Darkwing found. It applies to f38b7f46 with `git apply --index --3way` with no conflicts. I compared the round 1 and round 2 patches: - The added and removed lines are byte-identical (106 lines each). - The hunk headers moved: README 205 to 235, `broker.mjs` 837 to 861, webui `bus.test.mjs` 163 to 191. The `decisions.test.mjs` hunks are unchanged. - One context line differs, below. `decisions.test.mjs` keeps its digest (`9477cfa7…`). The other three have new digests because their bases changed. ### Merged hunks Line numbers are in the round 2 tree. - `packages/bus/README.md`, lines 235–243: the "Every row is…" sentence is rewritten as in round 1. In round 1 the hunk's last context line was the `## Shared reads and adapter boundary` heading. Row 60 has since put its `credentials` paragraph between the two, so that paragraph's first line is the context now. The sentence and the paragraph share no lines. - `packages/bus/src/broker.mjs`, lines 864–867: the comment above `case 'decisions'` (line 868). Row 59's `agents` block ends above it (line 852 on), and row 60's `case 'credentials'` follows it (line 889). Neither touches the comment or the case. - `packages/webui/tests/bus.test.mjs`, lines 194–198: the three no-bus requests still follow the `/api/bus/agents` not-configured check, inside "bus refusals map to statuses…". Row 58 and row 60's additions sit above. ### Gate (round 2) Run in a worktree of f38b7f46 with the round 2 patch staged and `node_modules` linked, in sequence, with `TMPDIR` in my scratch and `DOCKER_HOST` pointed at a missing socket, 2026-10-12T01:47:57Z to 01:52:03Z. - Packages: webui 62/0, bus 95/0, cli 114/0, control-board 124/0. - All nine `scripts/test-*.sh` exit 0: auth 15, conductor 17, config 24, discord 66, extension-package 18, foundation 44, queue 27, release 4, task 26. `test-queue` skipped `verify` and `render --check`, because the worktree isn't the queue's canonical root. `test-release` skipped its Docker cases without a daemon. - Afterwards `git status` showed only the four staged paths and the `node_modules` link, and the tree checked 4/4 against the manifest. ### Mutants (round 2) I reran round 1's nine mutants (`mut/run.mjs`) on the round 2 tree, 2026-10-12T01:52Z to 01:53Z. All nine are killed, each by the same test as in round 1. The bus suite now has 95 tests and the webui file 9, so the counts read 94/1, 92/3 for `view-blocking-raw` and 8/1 for `web-503-first`, where round 1 read 80/1, 78/3 and 7/1. One edit changed: row 59 added a second `blocking: !!d.blocking,` in the `agents` read (`broker.mjs` line 930), and the runner refuses a target that isn't unique. So `view-blocking-raw` ran from a copy of the runner that anchors on its whole line (line 297, inside the decisions view), and the edit is the same. After each run `git diff` was unchanged, and the tree checked 4/4 against the manifest afterwards. --- The round 1 packet follows unchanged: the description, files, round 1 mutants, round 1 gate and questions. Its base and line numbers are round 1's. # Row 67 (#1560): decisions read follow-up, round 1 Filbert, 2026-10-11. Brief: `docs/plans/2026-10-11_decisions-read-follow-up.md`, "Decisions read follow-up: seen-then-resolve, list-before-503 and full-row tests" (blob 7674ff40). Darkwing and Dewey review. Sage commits after the gate; nothing is committed, staged or pushed yet. Base 970ef729. `build.patch` applies there with `git apply --index`; `candidate-manifest.sha256` pins all 4 files after the patch. No behaviour changes. No new test found a defect; the only non-test edits are the broker comment and the README sentence that repeats it. ## What ships - **Seen, then closed** (`decisions.test.mjs`, new test after `recent`). The human marks the human-routed decision seen and resolves it; `cto` does the same for its decision; `pm` marks its decision seen and the raiser withdraws it. For each, the store holds exactly `seen` then the closing op, and the `recent` row's `closed` has that op, the closing event's `seq` and its `by`. The test also pins the `recent` order by close. Mutant `recent-closed-first-event` fails it. - **List before 503** (`webui/tests/bus.test.mjs`, the no-bus server in "bus refusals map to statuses…"). With no bus configured, `/api/bus/decisions?list=bad` answers 400 `invalid-request`, and `?list=roles` answers 503 `not-configured`, so a good list still reaches the bus check. Trail checks `subject` the same way, so the same loop pins `/api/bus/trail?subject=%3C` at 400. Mutant `web-503-first` fails it. - **The whole row** (test 4, "every row is what trail already gives…"). For every `roles` and `recent` row, for human and reader sessions, the row minus `authorization` and `closed` deep-equals trail's `decisions` row for that id minus `table`, with `blocking` as a boolean. `authorization` deep-equals `{action, target, approvalChoice}` from trail's one `action.allowed` `decision.raise` event for the id. `closed` is present on `recent` rows only, and equals trail's `decision_events` row as before. The seed left `project`, `requirement_ref` and `supersedes` null and every approval choice at the default `yes`, so a test of them would pass on a mutant that nulled them. Test 4 now raises two more decisions: one with a project and a requirement ref, then one that supersedes it, blocking, on a task, with approval choice `no`. The first closes as `withdrawn` by the supersede, so it lands in `recent`; the second is in `roles`. - **The comment** above the `decisions` case in `broker.mjs` now says which part each row equals: inbox's view of the decision, which is trail's `decisions` row with `blocking` as a boolean plus `authorization` from the raise's `action.allowed` event (trail gives that too); a `recent` row adds `closed`, the closing `decision_events` row. - **README** (`packages/bus/README.md`): the sentence "Every row is the decision as `trail` gives…" said the same imprecise thing, so it now says what the comment says. The brief allows this edit only if the comment fix changes what the README says; it does. Out of scope, unchanged: no `LIMIT` on `roles`; `roles` order stays `seq`. ## Files - `packages/bus/src/broker.mjs`: comment only. - `packages/bus/README.md`: one sentence. - `packages/bus/tests/decisions.test.mjs`: one new test; test 4 rewritten. - `packages/webui/tests/bus.test.mjs`: three requests in the no-bus case. ## Mutants `mut/run.mjs` (not shipped): one edit per mutant in the worktree, then the owning suite (`packages/bus/tests/*.test.mjs`, or `packages/webui/tests/bus.test.mjs` for `web-503-first`), then a restore. `git diff` was unchanged after each run. | Mutant | Edit | Base 970ef729 | Candidate | |---|---|---|---| | `recent-closed-first-event` | `closed` is the first event for the id | survives | killed: seen-then-closed | | `recent-join-any-event` | `recent` joins any event, not only closing ops | survives | killed: seen-then-closed | | `web-503-first` | `busRead` answers 503 before checking the request | survives | killed: refusals test | | `view-requirement-null` | row drops `requirement_ref` | survives | killed: test 4 | | `view-supersedes-null` | row drops `supersedes` | survives | killed: test 4 | | `view-project-null` | row drops `project` | survives | killed: test 4 | | `view-task-ref-null` | row drops `task_ref` | survives | killed: test 4 | | `view-approval-fixed` | `approvalChoice` always `yes` | survives | killed: test 4 | | `view-blocking-raw` | `blocking` left as 0/1 | killed: tests 1, 2 | killed: tests 1, 2, 4 | Candidate: 9 of 9 killed, each by the test this row adds or rewrites. Base: 8 of 9 survive. Each mutant fails only the tests named above; no other test moved. ## Gate `gate.sh` (not shipped) on the candidate worktree, sequentially, with `TMPDIR` in scratch and `DOCKER_HOST` pointed at a missing socket: | Suite | Result | |---|---| | `node --test packages/bus/tests/*.test.mjs` | 81 pass, 0 fail | | `node --test packages/control-board/tests/*.test.mjs` | 124 pass, 0 fail | | `node --test packages/webui/tests/*.test.mjs` | 40 pass, 0 fail | | `node --test packages/cli/tests/*.test.mjs` | 112 pass, 0 fail | | `scripts/test-auth.sh` | 15 passed, 0 failed | | `scripts/test-conductor.sh` | 17 passed, 0 failed | | `scripts/test-config.sh` | 24 passed, 0 failed | | `scripts/test-discord.sh` | 66 passed, 0 failed | | `scripts/test-extension-package.sh` | 18 passed, 0 failed | | `scripts/test-foundation.sh` | 44 passed, 0 failed | | `scripts/test-queue.sh` | 27 passed, 0 failed | | `scripts/test-release.sh` | 4 passed, 0 failed | | `scripts/test-task.sh` | 26 passed, 0 failed | Every suite exited 0. `sha256sum -c candidate-manifest.sha256` passes in the worktree, and `git diff 970ef729` there hashes the same as `build.patch`. No commit since 970ef729 touches `packages/`. Rows 59 and 60, both in review, also edit `packages/bus`. On a fresh 970ef729 worktree, `build.patch` applies after row 59's patch with plain `git apply`, and after row 60's (alone, or after 59 then 60) with `--3way` and no conflicts. Whichever lands first, this one re-bases cleanly; the gate above is for this patch alone. ## Questions for reviewers 1. The webui check also pins `trail?subject=` before the 503. The brief names only `list`; the order is the same code path one branch up, and the line is in a file this row owns. Keep it or drop it?