Status: written by Sage, lead. It follows `docs/plans/BRIEF-TEMPLATE.md`. It covers the faults from Jason's first PM test that rows 89 to 92 don't: the PM's findings file (`/workspaces/mosaic-stack/pm/findings-2026-10-12.md`) faults 3, 5 and 8, Jason's "blind after each send" report (Mos sage-0103) and his Console report (Mos sage-0105). Fault 9 went into row 89. Jason's ruling through Mos (sage-0104, Q125 yes): these are fixed through Sage's rows, not by the running PM, then the stack is redeployed and his test re-run. Gitea issues #1587, #1588 and #1589. # The fixes from Jason's PM test (2026-10-12) ## A review loop through the bus ### Problem The coder role holds `git.push.working` and `review.request`, and the reviewer holds `review.verdict` (`roles/coder.json:9`, `roles/reviewer.json:9`). The broker lists all three actions (`packages/bus/src/broker.mjs:13-17`) and records `review.*` events against a task (`:254`), but no verb handles them and no typed tool exists (`packages/harness/src/tools.mjs`). In the test the coder couldn't hand its change to the reviewer except by pasting it into a message, the reviewer sent its verdict as a message, and the PM cited that message in `task_close`. P19 #229's review asked for changes only because nothing could be pushed. ### Owner and reviewers Owner: Filbert, after row 91 (a project checkout in each role workspace) and their row 95. Reviewers: Darkwing and Dewey, both required, since the host runs git for a session. ### Files owned - `packages/bus/src/broker.mjs`, the new review verbs only - `packages/cli/src/launcher.mjs` and `packages/seat/src/checkout.mjs`, only the host-side push and fetch - `packages/harness/src/tools.mjs`, `runner.mjs`, the new tools only - `roles/coder.md`, `roles/reviewer.md`, `roles/pm.md`, the review paragraphs only - `packages/bus/README.md`, `packages/harness/README.md`, `packages/seat/README.md` - tests under `packages/bus/tests/`, `packages/harness/tests/`, `packages/seat/tests/` and `packages/cli/tests/` - `agents/filbert/work/queue-/` ### What ships - A working repository per business and project, a bare git repository under the data root that only the host writes. It is not the project's root and not a forge. No credential goes near it. - `push_working` (action `git.push.working`): the host, outside the session's sandbox, pushes one branch of the caller's clone to the working repository as `work/`. It takes a task ref and a branch, refuses anything but a fast-forward of that one ref, and records the commit. - `request_review` (action `review.request`): records a `review.request` event on the task, naming the commit already in the working repository, and messages the reviewer role. A commit not in the working repository refuses. - `fetch_review` for holders of `review.verdict`: the host fetches the requested commit into the caller's clone as a remote-tracking ref, so the reviewer reads and tests it in its own workspace. - `review_verdict` (action `review.verdict`): records `approve` or `changes` with a body on the task, citing the request event. The PM's `task_close` cites the verdict event's id. Whether the broker checks that citation is a choice the packet makes and argues. - The session text from the bundle names the review steps; the role files say the same. - Tests: the whole loop between a fake coder and a fake reviewer session over real clones, each refusal (another task's branch, a non-fast-forward, an unknown commit, a role without the action), and the project root unchanged after every case. Suites: the bus, harness, seat and cli node suites, and every `scripts/test-*.sh`. ### Out of scope Landing an approved commit in the project, which stays Sage's gate under R28. `git.push.protected`. A forge or Gitea remote. Credentials in any session. ### Gate Darkwing and Dewey approve on the row's issue, naming the candidate manifest. The suites above pass on Sage's gate rerun. Live at the next bus restart, which Mos approves first (sage-0104). ## Every message to the human, live ### Problem `mosaic talk` sends, then prints whatever reaches the human until the direct reply comes, and stops there (`packages/cli/src/cli.mjs:204-214`). A message the PM sends later, from a turn another seat or the PM itself started, has no `in_reply_to` and waits unread until the next `talk` prints it. Jason reads that as each answer arriving one message late. `mosaic inbox` lists decisions only (`broker.mjs:846-851`). The Console shows replies to its own send (row 81, the `replies` verb, `broker.mjs:915-925`) and nothing else, so it never shows those messages either. ### Owner and reviewer Owner: Rocko, after row 77, and before their rows 84 and 85. Reviewer: Dewey, who built row 81. ### Files owned - `packages/bus/src/broker.mjs`, a `messages` read verb only - `packages/cli/src/cli.mjs`, `talk` and `inbox` only - `packages/webui/src/serve.mjs` (`BUS_VERBS`), `packages/webui/src/public/bus.js`, `bus.css` if needed - `packages/bus/README.md`, `packages/cli/README.md`, `packages/webui/README.md` - tests under `packages/bus/tests/`, `packages/cli/tests/` and `packages/webui/tests/` - `agents/rocko/work/queue-/` ### What ships - A broker `messages` read verb for the human and the Console's reader: messages to the human, newest first, with `limit` (default 50, at most 200), `after` for paging, and whether each is read. Reading it marks nothing. - `mosaic talk --follow`: after the send, it keeps printing each message to the human as it arrives, marking it read as `talk` does today, until Ctrl-C. `mosaic follow` does the same without sending. Without the flag, `talk` is unchanged. - `mosaic inbox` lists unread messages to the human under the decisions, without marking them read. - A Messages page in the Console, refreshing like the other bus pages, shows every message to the human with its sender, class, time, read state and a trail link. It marks nothing, so `talk` still prints each one. The nav shows the unread count. - Tests: a message with no `in_reply_to` arriving after a `talk` returns is printed by `talk --follow` and by `follow`, listed by `inbox` and shown in the Console; nothing the Console reads is marked read; the verb's paging and refusals. Suites: the bus, cli and webui node suites, and every `scripts/test-*.sh`. ### Out of scope Pushing messages to the browser (polling stays). Marking messages read from the Console. Messages between roles. ### Gate Dewey approves on the row's issue, naming the candidate manifest. The suites above pass on Sage's gate rerun. Sage notifies Mos before the landing; it is live after the bus and Console restart Mos approves. ## The Console's Board shows the stack's seats and a sign-out ### Problem The Console's Board is the machine scan the old board server serves at `127.0.0.1:7331` (`packages/webui/src/serve.mjs:8`, proxied as `/api/board`). It lists tmux and T3 sessions. The stack's own seats (the PM, coder and reviewer) appear only on the Agents page (`packages/webui/src/public/bus.js:410-415`), and a message to them only from the Inbox page's "Message a role" form (`bus.js:270`). In the test Jason saw the fleet's seats and not the stack's (findings fault 8, P19 #231), and he approved hiding the machine sessions. Jason also reports he signs in to mosaic.woltje.com and then can't see updated info or interact (Mos sage-0105). Checked 2026-10-12: - The edge admits the Console. ops-01 read the outpost log: forward-auth 302s only from 19:28:16Z to 19:29:26Z, after a pod replacement at 19:28:07Z, then 225 browser 200s and no 302 to 19:48:25Z (Mos sage-0106). The Console has no websocket; every page polls with `fetch`. - The Board's data is current: the scan at 7331 answered in 1.3 s with `generatedAt` 2026-10-12T19:49:24Z, 44 sessions, projects `mosaic-stack` and `fleet`, all machine sessions. - The running Console started at 19:05:38Z, before row 81 (c9a5650e) landed, so it shows no reply to a web send. Its log has no send line since then. So the Console works as built, and what Jason needs from it isn't there: the stack's seats aren't on the Board, the send is on another page, the replies aren't deployed, and messages the PM starts never show (the row above). During the 19:28 sign-in loss every Console API fetch got a 302 to the sign-in origin (the same answer an unauthenticated `GET https://mosaic.woltje.com/api/board` gets). A fetch that follows a redirect across origins fails with Firefox's "NetworkError when attempting to fetch resource", which the Board showed earlier today. The Console keeps the last data and retries every 10 s, failing the same way until the page is reloaded (`app.js:290-291`, `bus.js:31,42`). A pod replacement will do this again. ### Owner and reviewer Owner: Filbert. Reviewer: Darkwing. ### Files owned - `packages/webui/src/public/app.js`, `bus.js`, `index.html`, `app.css` - `packages/webui/src/serve.mjs`, only if the Board needs a new route - `packages/webui/README.md` - tests under `packages/webui/tests/` - `agents/filbert/work/queue-/` ### What ships - The Board's first section lists the stack's held roles from the bus `agents` read, including claims with no address, each with its state and a Message action that opens the send form addressed to that role. - The machine scan's sessions sit behind a "Show machine sessions" toggle, off by default, kept in the page's settings like the other Board filters. - Every Console fetch uses `redirect: 'manual'`. A redirect answer (`opaqueredirect`) stops the refresh and shows one line: signed out, reload the page to sign in. It doesn't show a NetworkError or stale data as current. - Tests: the Board with held roles and no machine sessions, a claim with no address, the toggle, the Message action's role, and a redirect answer on a Board read, a bus read and a send. Suites: the webui and control-board node suites, and every `scripts/test-*.sh`. ### Out of scope The edge's sign-in lifetime, which is ops-01's through Mos. Removing the machine scan. A Board for other businesses. ### Gate Darkwing approves on the row's issue, naming the candidate manifest. The suites above pass on Sage's gate rerun. Live after the Console restart Mos approves. ## Row 95 follow-up: Board tests fail fast, README lists every redirect ### Problem Darkwing's review of row 95 (comment 27953, approve) left three notes, and row 95 landed in 17ad6f35 without a second round. - N1: `packages/webui/tests/browser-edge.test.mjs` awaits `boardRequested` (line 29), which only the server's `/api/board` handler resolves. If the "Show machine sessions" toggle stops reading the board, the await never settles. The test is cancelled at 120 s, its `finally` never closes the browser and server, and the file never exits. `node --test` has no file timeout, so the webui suite stalls instead of failing. - N2: test 2 in `board-held.test.mjs` claims no further request on the poll, but it waits 600 ms and both polls are 10 s. Mutants 13 and 27 survive for that reason. - N3: the webui README's test paragraph says test 2 redirects three reads. It redirects six: the board, `agents`, `/api/conversations`, `/api/seen`, `/api/queue` and the send. ### Owner and reviewer Owner: assigned on the row. Reviewer: Darkwing. ### Files owned - `packages/webui/tests/browser-edge.test.mjs`, `packages/webui/tests/board-held.test.mjs`, and any test helper they share - `packages/webui/README.md`, the test paragraph only - `agents//work/queue-/` ### What ships - N1: the toggle step in `browser-edge.test.mjs` fails within a few seconds when no `/api/board` request comes, and the browser and server close either way. Prove it with the toggle-on mutant: the file exits nonzero inside 30 s, with no browser or node process left. - N2: either test 2 reaches a poll tick, by a shorter poll in the test or a clock the test controls, and mutants 13 and 27 die; or the test's comment and the README stop claiming the poll. The packet says which, and why. - N3: the README's test paragraph names all six redirected requests. Suites: the webui and control-board node suites, and every `scripts/test-*.sh`. ### Out of scope Any change under `packages/webui/src/`. Row 95's behavior stays as landed. ### Gate Darkwing approves on the row's issue, naming the candidate manifest. The suites above pass on Sage's gate rerun. Tests and README only, so nothing goes live and no restart is involved.