Files
stack/docs/plans/2026-10-12_pm-test-fixes.md
T

12 KiB

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 (<dataRoot>/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-<row>/

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/<task ref>. 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-<row>/

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-<row>/

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/<owner>/work/queue-<row>/

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.