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 <[email protected]>
This commit is contained in:
2026-10-13 17:47:45 -05:00
co-authored by Claude Opus 5.5
parent fbe0da4cc5
commit 774fc54ac4
@@ -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-<row>/`
### 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-<row>/`
### 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.