From 774fc54ac4a15c19b5c64d7f7fd35b1588f0c020 Mon Sep 17 00:00:00 2001 From: Jason Woltje Date: Tue, 13 Oct 2026 17:47:45 -0500 Subject: [PATCH] docs: briefs for the PM fresh-state row (#1603, Vikunja #259) and the bus tester identity row (#1604, Q134 A, Vikunja #260) Co-Authored-By: Claude Opus 5.5 --- .../2026-10-13_pm-tester-and-fresh-state.md | 188 ++++++++++++++++++ 1 file changed, 188 insertions(+) create mode 100644 docs/plans/2026-10-13_pm-tester-and-fresh-state.md diff --git a/docs/plans/2026-10-13_pm-tester-and-fresh-state.md b/docs/plans/2026-10-13_pm-tester-and-fresh-state.md new file mode 100644 index 000000000..ecfa8ba49 --- /dev/null +++ b/docs/plans/2026-10-13_pm-tester-and-fresh-state.md @@ -0,0 +1,188 @@ +# PM testing identity and fresh-state planning + +Two rows filed by Sage on 2026-10-13 after the pinned-copy check (#225 +receipt, PM run rf73f0192e824). The live bus, Console and CLI run from +the pinned copy 465bbdfbb. Code here reaches them only through a new +copy, a re-pin and a bus restart, all under Mos's go. + +## Bus tester identity for PM tests + +### Problem + +Jason ruled on Q134 ("Q134: C agreed", Vikunja #260). For now, agents +test the PM by typing into a tmux pane, under limits: `talk`, `follow` +and read commands only, never `decide`, `launches` or `stop`. Every +message starts with `[mos test]`, and the testing happens in its own +tmux session, never the design pane. That path exists only because the +bus can't tell an agent from Jason there. `verifyHuman` in +`packages/bus/src/human.mjs` walks the CLI's process ancestry and +refuses on agent markers (`CLAUDECODE`, `MOSAIC_RUN_ID` and others) or +agent binaries. A command typed into a tmux pane has the tmux server +as an ancestor, not the agent, so it passes as Jason. Its message is +stored `from_role 'human'`, `from_run 'human-cli'`, and logged as +`human.input` (`broker.mjs` `message.send`). The PM then treats a test +message as an instruction from Jason. + +Option A replaces that with an identity the bus records as a tester, +so a test message can never pass as Jason's. + +### Owner and reviewer + +Owner: Darkwing, after row 106 (both touch `packages/cli`) and after +the fresh-state row below (both touch `broker.mjs`, the bus README and +the PM's prompt). The fresh-state row goes first because #259 blocks +relaunching the coder and reviewer. Reviewers: +Filbert (decision 130, not a sandbox row) and Sage, because the row +changes who can speak on the bus. + +### Files owned + +- `packages/bus/src/broker.mjs`: a new binding and the verb checks for it +- `packages/bus/src/client.mjs`, `server.mjs`, `process.mjs` and + `runtime.mjs` (the `bindHuman` call is at `runtime.mjs:73`), the + binding's plumbing only +- `packages/bus/tests/`: the new tester cases +- `packages/cli/src/cli.mjs`, `transport.mjs` and `format.mjs`: `talk`, `follow` and the + read verbs under the tester binding, plus how a tester message renders +- `packages/cli/tests/`: the tester cases +- `packages/bus/README.md` and `packages/cli/README.md`, one paragraph each +- `agents/darkwing/work/queue-/` + +### What ships + +- A tester binding next to `bindHuman` and `bindReader`. It needs no + human proof and works from inside an agent. The broker records it as + tester, never as human. +- A tester may send a message to a role, receive and read replies, and + use the read verbs (`inbox`, `tasks`, `agents`, `trail`, `decisions`, + `messages`, `replies`). Every other verb refuses: `decision.*`, + `launch.revoke`, `launch.restore`, the stop path, `review.*`, + `role.*` and `event.emit`. A test covers each refused verb, and a + mutant that lets one through fails the suite. +- A tester message is stored with its own sender (for example + `from_role 'tester'`), not `human`, and writes no `human.input` event. + A test sends one and finds no `human.input` row and no `human` sender. +- The broker refuses a tester message whose body doesn't start with + `[mos test]`. +- The role's inbox and the CLI show a tester message as a tester + message. The PM's prompt text, wherever it renders an incoming + message, says it comes from a tester and is never Jason's instruction. +- The binding fails closed unless the business turns it on. The packet + proposes where that switch lives (business config or a business var), + and Sage rules on it before the build. With the switch off, the binding + refuses. +- `bindHuman`, `verifyHuman` and every human path stay unchanged. + The existing human tests pass untouched. + +Each new check is shown to fail on a mutant that removes it. + +Suites: the bus and cli node suites and every `scripts/test-*.sh`. + +### Out of scope + +Closing the tmux gap in `verifyHuman` itself. Retiring the tmux path +is Mos's step after this row is live. Any tester authority beyond +talk, follow and read. + +### Gate + +Filbert and Sage approve on the row's issue, naming the candidate +manifest, and the suites above pass on Sage's gate rerun. Decision 109 +notices go to Mos before and after landing. It goes live only with a +new copy, a re-pin and a bus restart under Mos's go. Vikunja #260 +closes when it is live and Mos has retired the tmux path. + +## A new PM run reads current launch state and task status before it plans + +### Problem + +Vikunja #259. After the 21:48:52Z bus restart on 2026-10-13, the new PM +run rf73f0192e824 said launches stay off. It cited Jason's message +0f89640f from 2026-10-12. But Jason ran `launches on` at 19:50:51Z on +2026-10-13, and `launch_state` for mosaic-stack reads `allowed`. The run +also planned to resume 19/229, 19/230 and 19/231, and all three are +done on Vikunja. + +Nothing gives a run the current state: + +- The system prompt (`prompt()` in `packages/harness/src/bundle.mjs`) + holds the role contract and the session facts. It is written once at + launch, and it has no launch state and no task status. +- Turns come only from the inbox. `message.receive` returns every + message that no run read, with no age limit, and `role.claim` routes + the waiting set to the new run (`#routeWaiting` in `broker.mjs`). A + day-old message becomes the new run's first turn, and `turnRequest()` + in `packages/harness/src/runner.mjs` shows no date on it. +- `launch_state` (a view in `packages/bus/schema.sql`) is read only by + the broker's refusal check. No tool or message shows it to a role. +- `list_tasks` lists open tasks by default, so a done task drops out + only if the PM looks it up. `roles/pm.md` never tells the PM to check + the board or launch state before it plans. + +### Owner and reviewer + +Owner: Researcher. Reviewer: Filbert (decision 130, not a sandbox row). +`roles/pm.md` changes here through the row's reviewed commit. + +### Files owned + +- `packages/bus/src/broker.mjs`, one read verb only +- `packages/bus/tests/`: the new verb's cases +- `packages/harness/src/runner.mjs`: `turnRequest()` and the per-turn + state fetch +- `packages/harness/src/tools.mjs`, only if the packet exposes the verb as a tool +- `packages/harness/tests/runner.test.mjs` and `tools.test.mjs` +- `roles/pm.md`, one short rule +- `packages/bus/README.md` and `packages/harness/README.md`, one + paragraph each at most +- `agents/researcher/work/queue-/` + +### What ships + +- A read-only broker verb, open to role and reader sessions. It returns + the business's launch state with the time and id of the event that + set it (`allowed` when no event exists), and the open tasks from the + broker's tracker snapshot (refs, titles and a count). The task list + is capped, and the cap is stated in the output. +- The runner calls that verb before each turn and puts a short + "Current state" block, marked with the time it was read, at the top of + the turn's request. A failed read fails that turn and says so, rather + than running the turn without state. +- `turnRequest()` adds the message's `at` time to the header, so an old + message shows its age. +- `roles/pm.md` gets one rule: the "Current state" block is the truth + for launch state and task status. An older message that disagrees is + out of date. Before planning to resume or launch work on a task, the + PM checks that task with `get_task`. +- Tests: + - With launches revoked and then restored, the block reads `allowed` + and gives the restore time. + - A task closed on the tracker is missing from the block's open list. + - A message received a day after it was sent shows its original time. + - A failed state read fails the turn. + - A mutant that drops the block, the time or the failure check fails + the suite. +- The packet reports, read-only, whether the PM's persistent workspace + holds planning notes from earlier runs. It lists their names and + never prints their contents. The rule above covers notes as well as + messages. + +Each new check is shown to fail on a mutant that removes it. + +Suites: the bus and harness node suites and every `scripts/test-*.sh`. + +### Out of scope + +Expiring or dropping old inbox messages. Changing the tracker poller +or `list_tasks` defaults. Any change to who may switch launches. + +### Gate + +Filbert approves on the row's issue, naming the candidate manifest, and +the suites above pass on Sage's gate rerun. Decision 109 notices go to +Mos before and after landing. Role sessions load the runner fresh, but +the live sessions run from the pinned copy, so the row goes live only +with a new copy and a re-pin under Mos's go. Until then, nobody +relaunches the coder and reviewer on the PM's current plan. Vikunja +#259 closes when the row is live and a fresh PM run, tested within +Q134's limits, reports `allowed` and doesn't plan to resume 229, 230 or 231.