## Filbert review: row 109 round 2 (#1603): changes Candidate manifest sha256 `daa942cd5d03597479c2e9f6bd159b8f7ec5dec135ce1fbc1138e95e5b8ea69d` (8 files). I rebuilt it in my own worktree at BASE 02fe2d9d5 with `row109.patch`. All 8 hashes match, `git diff --check` is clean, and no manifest path has drifted from 02fe2d9d5 to HEAD. The added lines have no dashes, bidi or control characters. ### Blocker **B1. Two tests outside the manifest fail with the patch.** Both still expect the request to begin with the message header, but every request now begins with the state block. - `packages/seat/tests/session.test.mjs:227`: `/^echo: Message .* from pm, class REQUEST:\n\nhello/`. The test "a session runs in its sandbox as pid 1 of its namespace, claims, answers, and stops on mosaic stop" fails, 8/9 pass. - `packages/cli/tests/launcher.test.mjs:240`: `/^echo: Message .* from cto, class REQUEST:\n\nhello/`. The test at line 203 fails, 16/17 pass. The reply body now reads `echo: Current state, read at ...:\nLaunches: allowed (never switched).\nOpen tasks: 0.\n\nMessage from pm, sent , class REQUEST:\n\nhello`. On a clean BASE 02fe2d9d5 worktree the same two files pass, 9/9 and 17/17, so the patch causes both failures. Neither file is under `scripts/test-*.sh`, which may be why the packet's suite claims missed them. Fix: add both test files to the manifest and update the two assertions to the new request shape. Anchoring the block, the header with `sent`, and `hello` is better than loosening the `^`. After that, run the package tests for seat and cli along with bus and harness. ### Sage's five checks (all pass on reading and in tests) 1. **Failed read.** In `runner.mjs` a throw from `client.call("state")` that isn't SESSION_GONE sets `unread`. The turn becomes `{ok:false, reason}` without calling `runTurn`. The existing path then logs `turn N ... failed`, sends RESULT `The turn failed: could not read the current state: ` (it skips this only for an incoming RESULT, as before), and runs `message.read`. SESSION_GONE logs and exits with EXIT.session. The runner test checks the RESULT text, in_reply_to, the read delivery, the stderr log, and that `turns/0001.stderr` doesn't exist, so no adapter ran. 2. **The `state` verb is read-only.** It runs `keys(a, [])` and calls `#agent(s)` only for role sessions, so a non-holder role refuses `not-holder`. Human and reader sessions read without a check, and the reader allowlist gains `state`. It does one `SELECT` on `events`, then `#tasks(business,{done:false})`, which only reads `task_current` and `events`. It has no insert, update or `#event` call. `ORDER BY seq DESC LIMIT 1` over the two launch kinds is the same rule as the `launch_state` view (`max(seq)`), so the block agrees with the `role.launch` check. 3. **Fixture.** The old first-blank-line split would have put the `Message ...:` header into `body`, so `TOOL`, `SLEEP`, `FAIL` and `NOMARKER` would all have dropped through to echo. The regex takes the text after the header. The fallback keeps the old split for a request without a header. If the runner ever lost the header, the fallback could let the TOOL tests pass. The first end-to-end runner test anchors the whole request including the header, so that regression would still fail. Nothing is hidden. 4. **pm.md.** The rule says the block is "the truth for launch state and task status", that a disagreeing message or note "is out of date", and that the PM checks the task with `get_task` before planning to resume or launch work on it. `get_task` is the tool name in `tools.mjs` (unchanged). 5. **Cap and newest event.** `stateBlock` writes `Open tasks: , the first of cap .` whenever the list is shorter than the total. `state` returns `cap: TASK_PAGE` (50), and `state.test.mjs` checks 55 open tasks give 50 shown, total 55 and cap 50. Launch `at` and `event` come from the newest launch event by `seq`. The revoke-then-restore tests (bus and runner) check that the restore's id and time win. ### Notes (not blocking) - **N1.** The broker comment says the verb is "A read for role and reader sessions". The code and the bus README also allow human sessions. Add human to the comment. - **N2.** `state.test.mjs` covers role and reader but not a human cap, and doesn't check that the verb writes nothing. One assertion each would pin both: the human's read equals the role's, and the `events` and `messages` row counts are unchanged across a `state` call. ### What I ran (single files, no slot) `bus/tests/state.test.mjs` 6/6, `harness/tests/runner.test.mjs` 20/20, `bus/tests/broker.test.mjs` 15/15, `harness/tests/tools.test.mjs` 11/11, `seat/tests/session.test.mjs` 8/9 (B1), `cli/tests/launcher.test.mjs` 16/17 (B1). On BASE: session 9/9, launcher 17/17. I didn't rerun the mutants. Worktrees are removed and no process is left in them. Verdict: **changes** (B1).