Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3f71b359bd | ||
|
|
9af75dc729 | ||
|
|
7e2ead7f3a | ||
|
|
80fd455215 | ||
|
|
c8fe23a371 | ||
|
|
004f133bd6 |
@@ -0,0 +1,292 @@
|
||||
# Row 198 design: retire the control board writer on 7331
|
||||
|
||||
Gitea #1696. Brief `docs/plans/2026-10-20_retire-control-board.md` (blob 11977a5cb, commit a31f1a0f8, Mos's
|
||||
call mos-1019). Owner Bushroot, reviewer Webby, gate owner Sage. Inventory: `agents/bushroot/work/queue-198/INVENTORY.md`
|
||||
(004f133bd, sha256 76b630ce...877).
|
||||
|
||||
Base: brief commit a31f1a0f8 is on origin `refactor` (head aa1bd209e). This branch rebases on `refactor` after
|
||||
row 192 lands (Sage's sequencing, ruling 4).
|
||||
|
||||
## Revision 5 (Webby REVIEW-r4.md, origin/webby/reviews 5a02ee395, on rev 4 9af75dc72; Mos mos-1045)
|
||||
|
||||
- **B1 (r4, the `--tester-port` swap)**: reverted. `parses_flag` is for `--board` only. The `--tester-port` grep at
|
||||
`stack-release.sh:670` stays. The `--tester-port` test row I added in rev 4 is removed.
|
||||
- **N1 (r4, stale mark)**: fixed. A write counter guards `index()` coalescing. `markSeen()` and `reply()` start a fresh
|
||||
scan and never join one. The new `board.test.mjs` case is in the new-tests table, and mutant M15 is in the table.
|
||||
- **Sequencing (sage-1386)**: row 197 lands before 198. The rebase is noted under Open points.
|
||||
|
||||
## Revision 4 (Webby REVIEW-r3.md, origin/webby/reviews c9d65664a, on rev 3 7e2ead7f3)
|
||||
|
||||
- **B1** (blocking child calls): fixed. The board's child calls become async. `createBoard` takes an async `exec`,
|
||||
and `index()`, `markSeen()` and `reply()` return promises. The sync `scan` and `replyToRow` stay for the CLI and
|
||||
their existing tests. See decision 10, the new B1 test and mutant M14.
|
||||
- **N1** (fallback copy): fixed. Decision 8 now says which copy gets the 7331 fallback.
|
||||
- **N2** (flag check matched a refusal): fixed. The parse check matches the parser branch, not the flag name. See
|
||||
decision 8. M12's target now names `--board` only in a refusal.
|
||||
|
||||
## Revision 3 (Sage rulings sage-1372, on rev 2 80fd45521)
|
||||
|
||||
- Ruling 1: `/api/seen` goes under `--send`, the same as `/api/reply`. Mos agrees (mos-1023 item 5). Both POSTs sit
|
||||
above `serve.mjs:558` (decision 5).
|
||||
- Ruling 2: the board context keeps the writer's read-only fleet default (decision 9).
|
||||
- Ruling 3: a rollback to a copy with no recorded `--board` falls back to 7331, which is dark, as it is today (decision 8).
|
||||
- Ruling 5: loopback stays read-only. No new gate in this row. The cross-uid loopback gate is row 196's. Sage took
|
||||
this to Mos (sage-1370). If Mos rules otherwise, Sage says so and this design changes.
|
||||
- Ruling 6, correction: neither the live pointer nor the live unit carries `--board`. Sage checked the live
|
||||
`ExecStart`: `cli.mjs serve --checkout /mnt/storage/src/mosaic-stack --port 7332 --public-host mosaic.woltje.com
|
||||
--auth-header X-authentik-username --allow-user jason.woltje --send --tester-port 7333`. Rev 2 said otherwise, and
|
||||
that was wrong. Decisions 6 and 8 are rewritten. The B2 guard stays. `webui-service.sh` lines 44, 71, 110 and 163
|
||||
still take and emit `--board`, so they are in the retirement.
|
||||
- Ruling 7: base. Brief commit a31f1a0f8 is on origin `refactor` at aa1bd209e.
|
||||
|
||||
## Revision 2 (Webby REVIEW.md, origin/webby/reviews 9afb947c7)
|
||||
|
||||
Rev 1 was c8fe23a37. Rev 2 answered B1-B3 and N1-N5 (80fd45521). Changes are marked where they land.
|
||||
|
||||
- **B1** (pinned copy): fixed. `packages/control-board/src` and `tools/tmux/agent-send.sh` join `COPY_PATHS`. The
|
||||
copy test starts the Console from a fresh copy and expects 200 on `/api/board`. See decision 7.
|
||||
- **B2** (re-pin carries `--board`): fixed. `stack-release.sh` drops `--board` when the target's `cli.mjs` lacks it,
|
||||
as `console_for_target` already does for `--tester-port`. The plan lists the drop. See decision 8.
|
||||
- **B3** (POST placement): fixed. The board POST branches sit above the refusal at `serve.mjs:558`, after the
|
||||
header and origin checks, next to bus message and chat. Loopback cannot reply or mark seen. See decision 5 and mutant M10.
|
||||
- **N1**: the board context uses the writer's default fleet source. See decision 9.
|
||||
- **N2**: the M2 check covers all of `packages/control-board/src`, not one file.
|
||||
- **N3**: the lines that change are listed by file in "Test line changes" below.
|
||||
- **N4**: the README records that loopback board reads stay open to any local process until Mos rules.
|
||||
- **N5**: row 198 lands after row 192. The board route tests rerun on the rebased tree.
|
||||
|
||||
## Decisions
|
||||
|
||||
1. **The Console calls the board's read code in-process.** A new module `packages/control-board/src/board.mjs`
|
||||
holds the route logic that `createServer` runs today: rescan, mark seen, reply and conversation read. It
|
||||
imports no `node:http` and opens no socket. Its methods are async (decision 10).
|
||||
2. **No listener anywhere in the board package.** `createServer` and `startServer` are deleted from
|
||||
`serve.mjs`. `serve.mjs` keeps its pure exports (`replyToRow`, `isLoopbackHost`, `foreignRequest`,
|
||||
`conversationResponse`, `REFUSAL_STATUS`, the reply constants). That keeps `packages/conversation/tests/flows.test.mjs`
|
||||
unchanged. The file name `serve.mjs` now misleads. Renaming it is a separate follow-up and is not in this row.
|
||||
3. **The writer's CLI refuses `serve`.** `packages/control-board/src/cli.mjs` keeps `scan`. `serve` exits 2 with a
|
||||
message that points to the Console. The default port 7331 goes away.
|
||||
4. **The Console gets the board by context, not by URL.** `startServer` in `packages/webui/src/serve.mjs` takes
|
||||
`board` as a context object (or `null` when no board is configured), not a URL. `DEFAULT_BOARD`, `boardURL`
|
||||
and the proxy `fetch` are removed.
|
||||
5. **`/api/reply` and `/api/seen` are gated by `--send`, and sit above the `:558` refusal.** Today the loopback Console
|
||||
forwards both POSTs with no `--send` check. That is a real gap, and this row closes it. Placement matters.
|
||||
In preview mode, `serve.mjs:558` refuses every non-GET before the board block, so a board branch below it never
|
||||
runs. In loopback mode, `--send` cannot be set at all (`:479`). So both POST branches go after the header and
|
||||
origin checks (`:493-516`), beside `/api/bus/message` (`:550`) and chat (`:554`), and require
|
||||
`send !== null && named`. The result: `/api/reply` is the first board write the live Console has ever allowed.
|
||||
Mos ruled it in (mos-1019), and mos-1023 item 5 confirms `/api/seen` too. In loopback mode, reply and mark-seen
|
||||
refuse with 403 `read-only`. Board reads still work there. `/api/seen` writes marks under the data root, and without
|
||||
the gate it would be the only preview-mode POST that passes without `--send`.
|
||||
6. **The retirement removes `--board` from the Console scripts.** No installed unit or live pointer carries `--board`
|
||||
(Sage checked, rev 3). So no live unit needs a flag change to start. The flag goes from `packages/webui/src/cli.mjs`
|
||||
(`:14`, `:18`, `:32`) and `scripts/webui-service.sh` (lines 44, 71, 110, 161-163). An old `--board` argument refuses
|
||||
with the usage error (exit 2). A re-pin with the flag removed is an ordinary re-pin. Cutover needs no unit edit.
|
||||
7. **The pinned copy carries the board code (B1).** The live Console runs from `copies/<sha>`. `COPY_PATHS` in
|
||||
`scripts/stack-copy.sh` (lines 23-33) lacks `packages/control-board/src` and `tools/tmux`. Without them a
|
||||
Console from a copy fails at import, and `reply` cannot find `agent-send.sh`. Both paths join `COPY_PATHS`.
|
||||
`tools/tmux` goes in whole, the same way `packages/plugins` does, because `agent-send.sh` may load its own
|
||||
siblings. The `control-board/src` closure is `conversation/src`, `discord/src` and `seat/src`, and those are already in
|
||||
the list.
|
||||
8. **Re-pin and rollback drop `--board` when the target cannot parse it (B2 guard).** The pointer's console flags
|
||||
(`P_CONSOLE`, `stack-release.sh:128`) pass through `:676` into `RUN_CONSOLE`, and `:459` passes them to
|
||||
`webui-service.sh install`. The live pointer has no `--board` (rev 3), so this is a guard for a pointer that
|
||||
carries one, such as an older pointer or a rollback target. Rule: keep `--board` only when the target's
|
||||
`packages/webui/src/cli.mjs` parses it. Otherwise drop it and report the drop in the plan. That is the same shape
|
||||
as `console_for_target` and `--tester-port` (`:667`, `:687-690`). Rollback to a copy that parses `--board` keeps
|
||||
the recorded flag. A copy that does not parse it (a new copy) gets the flag dropped and starts with no board URL,
|
||||
so its board is in-process or null, never 7331. The 7331 fallback applies only to an older copy that parses
|
||||
`--board` and has no recorded flag. That copy starts with its own default, 7331, which is dark, the same as today
|
||||
(Sage ruling 3).
|
||||
**Parse check (N2, rev 5).** "Parses it" means the target's `cli.mjs` holds the parser branch, `flag === '--board'`,
|
||||
not the flag name. The name also appears in the usage text and in refusal messages, so a plain `grep` would count
|
||||
a copy that refuses the flag as one that accepts it. One helper, `parses_flag FILE FLAG`, greps for
|
||||
`flag === 'FLAG'`. It is used for `--board` only, in `console_for_target` and the rollback path. The existing
|
||||
`--tester-port` grep at `stack-release.sh:670` stays as it is (Webby REVIEW-r4). Changing it would break the
|
||||
`stack-release.test.mjs:265` fixtures, which write a comment, not a parser line. No copy names `--tester-port` in a
|
||||
refusal today, so the gap exists only for `--board`.
|
||||
9. **The board context uses the writer's default fleet source (N1).** `--fleet` defaults to `~/.mosaic/fleet/agents`,
|
||||
as the writer did (`cli.mjs:14`). The Console reads it and never writes it. Whether the fleet is retired
|
||||
and the default becomes `none` is a later decision. This row keeps the board's contents unchanged.
|
||||
10. **The board's child calls are async (B1).** The Console runs the board in its own process. A blocking child call
|
||||
would stall every route, the event stream, `/healthz` and the tester listener, which shares the process
|
||||
(webui `cli.mjs:91`). Rule: no `spawnSync` on any path that `createBoard` reaches.
|
||||
- `scan.mjs` gains `tmuxInspectAsync` (same args, same 5 s timeout) and `scanAsync`. The sync `tmuxInspect` and
|
||||
`scan` stay for the CLI. The parse of the tmux output is one pure function that both paths call, so they cannot
|
||||
drift.
|
||||
- `serve.mjs` gains `replyToRowAsync` (same 15 s timeout, same env, same result shapes). The sync `replyToRow`
|
||||
stays for its existing tests. The reply trailer and status mapping are shared pure code.
|
||||
- The default async `exec` is `spawn` with a timeout that kills the child. A timeout or a spawn failure maps to
|
||||
`{ error }`, the same shape the sync path returns. Tests inject `exec`.
|
||||
- `index()` coalesces concurrent calls, but only onto a scan that started after the last write (rev 5, Webby N1).
|
||||
The board keeps a write counter. `markSeen()` and `reply()` bump it, then start a fresh scan and never join one.
|
||||
`index()` joins an in-flight scan only when that scan started at the current counter value. So an answer never
|
||||
omits a mark that was set before it was asked for. This coalescing is my choice to limit tmux calls under
|
||||
parallel reads; the design has no other constraint on it.
|
||||
- `conversation(url)` spawns nothing. It reads files on the event loop, as the writer did. Not changed in this row.
|
||||
|
||||
## Open points for Sage and Mos
|
||||
|
||||
- **Auth on loopback (Sage ruling 5, taken to Mos as sage-1370).** The Console has two modes. In preview mode, a named
|
||||
request needs the proxy header and proof, and the board routes inherit that. In loopback mode (no `--public-host`),
|
||||
there is no sign-in. Loopback stays read-only with no new gate in this row. Board reads stay open to any local
|
||||
process, as they are today. Reply and mark-seen cannot run there, because `--send` needs preview mode. The
|
||||
cross-uid loopback gate is row 196's. If Mos rules otherwise, Sage says so and this design changes.
|
||||
- **Row 192 overlap (Launchpad, SSO on `webui/src/serve.mjs`).** My edits touch these regions of
|
||||
`packages/webui/src/serve.mjs`:
|
||||
- lines 12-27 (`DEFAULT_BOARD`, `boardURL`)
|
||||
- line 466 (`startServer` signature, `board` option)
|
||||
- lines 473 (`upstream`)
|
||||
- lines 556-580 (board proxy block)
|
||||
- lines 598-610 (`/api/config` `board` field)
|
||||
|
||||
I do not edit the preview and proof gate (lines ~486-505) or the auth-header checks. The board branches go
|
||||
after those checks, in the same order as the bus and chat branches. Sage should sequence 192 and 198 so the
|
||||
second one rebases on the first. I will not touch the SSO code.
|
||||
- **Cutover.** The new Console code reaches the live Console through a copy and re-pin. The restart is a human
|
||||
step (`console-holds-human-authority`). No unit flag changes (decision 6). The live unit stays up until then.
|
||||
- **Sequencing (ruling 4, sage-1386).** Sage sequences 198 after 192 and after row 197 (Herb). I rebase once, onto the
|
||||
tree with 192 and 197 landed, and rerun the board route tests there. 197 replaces closed-endpoint ports with dynamic
|
||||
allocations. Its overlap with this row is `packages/webui/tests/serve.test.mjs:100` and
|
||||
`packages/webui/tests/preview.test.mjs:208`. My rewrites of those lines (the dead-board 503 change at serve.test.mjs:79
|
||||
and :105, and preview.test.mjs:219 in the N3 list) are written against 197's dynamic ports, not the old fixed ones.
|
||||
I do not edit either overlap line before 197 lands. The manifest goes to review only after the rebase.
|
||||
|
||||
## File-by-file
|
||||
|
||||
### Board package
|
||||
|
||||
| File | Change |
|
||||
|---|---|
|
||||
| `packages/control-board/src/board.mjs` | **New.** `createBoard({ specs, boardDir, seatsDir, discordDataRoot, isAlive, now, agentSend, exec, conversationReader })`. Returns `{ index(), markSeen(body), reply(body), conversation(url) }`. `index`, `markSeen` and `reply` return promises and use the async paths (decision 10). Each method is the body of a route that `createServer` runs now. No `node:http`, no listener. |
|
||||
| `packages/control-board/src/scan.mjs` | Adds `tmuxInspectAsync` and `scanAsync`, and one shared pure parse. Sync `tmuxInspect` and `scan` stay for the CLI. |
|
||||
| `packages/control-board/src/serve.mjs` | Delete `createServer` (lines 229-310), `startServer` (312-322), `page` loading (`loadPage`, 224-227), and the `node:http` import. Keep the pure exports and the sync `replyToRow`. Add `replyToRowAsync` (decision 10). Remove the `7331` default. |
|
||||
| `packages/control-board/src/cli.mjs` | `serve` exits 2 with `refused: the control board no longer serves HTTP. Use the Console: node packages/webui/src/cli.mjs serve`. The `--port` and `--host` flags are removed. `scan` is unchanged. Usage text is updated. |
|
||||
| `packages/control-board/src/page.html` | Delete. The Console serves its own `index.html`. |
|
||||
| `packages/control-board/README.md` | Remove the `serve` section (lines ~107, 127), the port default and the start instructions. Keep the reply-convention text (lines 144-151). It names no start command. |
|
||||
| `packages/control-board/package.json` | The `mosaic-board` bin keeps `scan` only. Description text updated. |
|
||||
|
||||
### Console
|
||||
|
||||
| File | Change |
|
||||
|---|---|
|
||||
| `packages/webui/src/serve.mjs` | Remove `DEFAULT_BOARD`, `boardURL` and the proxy `fetch`. `startServer({ board })` takes a context object or `null`. Board routes call `board.index()`, `board.markSeen()`, `board.reply()` or `board.conversation()` directly. If `board` is `null`, the board routes answer 503 with `{ error: 'no board configured' }`. `/api/reply` and `/api/seen` need `send !== null && named`, the same gate as the other writes. Without it they answer 403 `read-only`. `/api/config` returns `board: null` and no URL. The app reads `config.board` only for the footer text, so a `null` value needs a small change in `app.js:468`. |
|
||||
| `packages/webui/src/cli.mjs` | Remove `--board` and its usage text. Build the board context from the config, `--checkout` (or cwd, as the writer did), and the seats and board dirs. Failure to read the system config gives `board: null`, not a refusal. The Console still starts, as it does for the bus today. |
|
||||
| `packages/webui/src/public/app.js` | Line 468: keep the footer, show `Board: in-process` (or hide it). Lines 365, 468 and the 502 message text (`Board unreachable ... Check the board server`) are reworded. The text is not a contract. |
|
||||
| `packages/webui/src/public/index.html` | No change. |
|
||||
| `packages/webui/README.md` | Remove the start line (10), the `--board` line (15), the test command that lists `control-board/tests/` (766, keep only the in-process tests), and the rollback line (881). |
|
||||
| `packages/webui/systemd/mosaic-webui.service.in` | No change in the template. The `@FLAGS@` set loses `--board`. |
|
||||
| `scripts/webui-service.sh` | Remove `--board` from the usage (44, 71), the parser (110), the validation (161-162) and the flag (163). An old `--board` argument refuses with the usage error. |
|
||||
| `scripts/stack-copy.sh` | **B1.** `COPY_PATHS` (23-33) adds `packages/control-board/src` and `tools/tmux`. |
|
||||
| `scripts/stack-release.sh` | **B2.** `console_for_target` (around 664-692) drops `--board` when the target's `cli.mjs` does not parse it. The plan line names the drop. Rollback keeps the recorded flag when the target parses it. |
|
||||
| `packages/webui/README.md` | Also records (N4) that loopback board reads stay open to any local process until Mos rules. |
|
||||
| `packages/webui/src/public/bus.js` | No change. The `#/` board link is the Console's own page, not 7331. |
|
||||
|
||||
### Tests (webui)
|
||||
|
||||
Fixture plan. "Dead board" means a context whose `index()` throws, which replaces `board: 'http://127.0.0.1:9'`.
|
||||
|
||||
| File | Change |
|
||||
|---|---|
|
||||
| `packages/webui/tests/fixture.mjs` | Call `createBoard` directly. Pass the context to `startServer`. No port 0 board server. |
|
||||
| `packages/webui/tests/history-fixture.mjs` | Same as fixture.mjs. |
|
||||
| `packages/webui/tests/discord.test.mjs` | Same. Board server removed. |
|
||||
| `packages/webui/tests/relaunch.test.mjs` | Same. |
|
||||
| `packages/webui/tests/return-flow.test.mjs` | Same. |
|
||||
| `packages/webui/tests/tester-fixture.mjs` | `board: 'http://127.0.0.1:9'` becomes the dead board. |
|
||||
| 16 files with `board: 'http://127.0.0.1:9'` (bus, chat, instances-browser, limits-browser, messages, pinned-copy, preview, proof-cli, proof, routes-browser, send, settings-form, tester-cli, tester-listener, usage-browser, views) | Mechanical: dead board. Assertions that expect a 502 `Board unreachable` string are updated to the new 503 `no board configured` or the dead-board error text. Sage needs to confirm this count before the edit. |
|
||||
| `packages/webui/tests/serve.test.mjs` | Lines 17-19 (`boardURL` checks) are removed. Add the new board route tests (below). |
|
||||
| `packages/webui/tests/reads.test.mjs` | Line 188: the `board` setting becomes `null`. |
|
||||
| `packages/webui/tests/service.test.mjs` | Lines 225-394: unit `ExecStart` text loses `--board`. The `--board` refusal cases (250, 260, 394) become refusal of the old flag. |
|
||||
| Browser tests (`browser-edge`, `bus-browser`, `chat-browser`, `docs-browser`, `editor-browser`) | Fixture context only. No change to the browser assertions. |
|
||||
|
||||
### Tests (control board)
|
||||
|
||||
| File | Change |
|
||||
|---|---|
|
||||
| `packages/control-board/tests/serve.test.mjs` | 32 tests use HTTP. Move the route assertions to `board.test.mjs`, which calls `createBoard` directly. Keep the `replyToRow` tests here. |
|
||||
| `packages/control-board/tests/discord.test.mjs` | Two HTTP uses (lines 123 and the `startServer` call). Call `createBoard`. |
|
||||
| `packages/control-board/tests/scan.test.mjs`, `attention*.test.mjs`, `relaunch.test.mjs` | No change. They test `scan`, not HTTP. |
|
||||
| `packages/control-board/tests/retired.test.mjs` | **New.** Three checks, all in-process, no socket. (1) `cli.mjs serve` exits 2 and prints the Console pointer. (2) The module graph has no `node:http` import and no `listen(` call in `packages/control-board/src`. (3) A grep over the start paths (`scripts/`, `*.service*`, `packages/*/README.md`, `package.json` scripts) finds no `control-board.*serve` or `7331` starter. |
|
||||
|
||||
### New tests for the acceptance items
|
||||
|
||||
| Test | Where | Assertion |
|
||||
|---|---|---|
|
||||
| Board view answers with nothing on 7331 | `packages/webui/tests/board-inprocess.test.mjs` (new) | Console with a context returns 200 on `/api/board` with a stubbed `globalThis.fetch` that throws on any call. Zero fetch calls. |
|
||||
| Every board route refuses without the Console's gate | same file | In preview mode, `/api/board`, `/api/seen`, `/api/reply`, `/api/conversations` each return 401 without the header and 403 with a foreign Host. |
|
||||
| `/api/reply` refuses without `--send` | same file | Preview mode, no `send`. POST `/api/reply` returns 403 `read-only`. `replyToRow` is never called (an injected `exec` counts calls). |
|
||||
| `/api/seen` refuses without `--send` | same file | Same shape. No mark file written. |
|
||||
| `/api/reply` runs with `--send` | same file | Positive control: with `send` and the sign-in header, the injected `exec` is called once with the expected argv. |
|
||||
| No Console proxy left | `packages/webui/tests/board-inprocess.test.mjs` | `DEFAULT_BOARD` is not exported. `boardURL` is not exported. Grep check: no `7331` in `packages/webui/src`. |
|
||||
| markSeen answer includes its own mark (N1, rev 5) | `packages/control-board/tests/board.test.mjs` (new) | Hold a scan in flight with an injected exec that resolves late. Call `index()`, then `markSeen()` before the scan resolves. The `markSeen()` answer shows the mark. A mutant that joins the pre-write scan must fail it. The same check covers `reply()`. |
|
||||
| Board does not block the Console (B1) | same file | Injected async `exec` for the tmux check resolves after 2 s. While `/api/board` is pending, `/healthz` answers within 200 ms. Mutant M14 uses an `exec` that blocks the thread for 2 s with `Atomics.wait`, and this test must kill it. |
|
||||
|
||||
## Mutants
|
||||
|
||||
Each mutant runs on a scratch copy of the tree (not the live checkout), with the new tests. The test that
|
||||
must kill it is listed. A mutant that survives means the test is weak.
|
||||
|
||||
| # | Mutant | Must be killed by |
|
||||
|---|---|---|
|
||||
| M1 | `cli.mjs serve` calls `startServer` again on 7331 | `retired.test.mjs` (1) |
|
||||
| M2 | `serve.mjs` restores `createServer` or `startServer` and exports them | `retired.test.mjs` (2) |
|
||||
| M3 | Console `DEFAULT_BOARD` is restored and the proxy `fetch` returns | `board-inprocess.test.mjs` zero-fetch case |
|
||||
| M4 | `/api/reply` skips the `send` check | `/api/reply refuses without --send` |
|
||||
| M5 | `/api/seen` skips the `send` check | `/api/seen refuses without --send` |
|
||||
| M6 | The board block runs before the preview and header gate | "every board route refuses" (401 case) |
|
||||
| M7 | A board route accepts a foreign Host | "every board route refuses" (403 case) |
|
||||
| M8 | `scripts/webui-service.sh` re-accepts `--board` | `service.test.mjs` refusal case |
|
||||
| M9 | A README or script again tells people to run `control-board serve` | `retired.test.mjs` (3) |
|
||||
| M10 | The `/api/reply` and `/api/seen` branches move below the `:558` refusal | The positive control "`/api/reply` runs with `--send`" (403 from `:558`) |
|
||||
| M11 | `COPY_PATHS` drops `packages/control-board/src` or `tools/tmux` | Fresh-copy test in `pinned-copy.test.mjs`: `/api/board` must answer 200 |
|
||||
| M12 | `console_for_target` keeps `--board` for a target that does not parse it | `stack-release.test.mjs`: re-pin plan lists the drop; install argv has no `--board`. The target `cli.mjs` names `--board` only in a refusal message and has no parser branch (N2). A grep-based check keeps the flag and this test kills it. |
|
||||
| M15 | `index()` joins a scan that started before the last write (N1) | the `markSeen` answer case in `board.test.mjs` |
|
||||
| M13 | Rollback drops a recorded `--board` from a target that parses it | `stack-release.test.mjs`: rollback keeps the flag |
|
||||
| M14 | The board's tmux check or reply runs through a blocking call (`spawnSync`, or an `exec` that blocks the thread) | `board-inprocess.test.mjs` B1 case: `/healthz` must answer within 200 ms while `/api/board` is pending |
|
||||
|
||||
Mutants M11 to M13 run in the cli suite, which needs Sage's slot. M10 and M14 run in one webui test file each.
|
||||
|
||||
## Test additions (B1, B2)
|
||||
|
||||
| Test | File | Assertion |
|
||||
|---|---|---|
|
||||
| Fresh-copy Console answers the board | `packages/webui/tests/pinned-copy.test.mjs` (extend) | `stack-copy.sh` builds a copy at a fixed sha in a temp dir. Starts the Console from it on port 0. `GET /api/board` returns 200 JSON. `agent-send.sh` resolves inside the copy. |
|
||||
| Re-pin drops `--board` | `packages/cli/tests/stack-release.test.mjs` (extend) | Pointer records `--board`. Target `cli.mjs` lacks the parser branch, and names `--board` only in a refusal (the N2 fixture). The plan names the drop. The install argv has no `--board`. |
|
||||
| Rollback keeps `--board` | same | Rollback to a copy with `flag === '--board'` in its `cli.mjs` keeps the recorded flag. |
|
||||
|
||||
## Test line changes (N3)
|
||||
|
||||
Board-related lines that change, by file:
|
||||
|
||||
- `packages/webui/tests/serve.test.mjs:79`: `/api/board` on a dead board, 502 becomes 503.
|
||||
- `packages/webui/tests/serve.test.mjs:105`: 502 with the board URL in the body. Becomes 503 with no URL.
|
||||
- `packages/webui/tests/preview.test.mjs:219`: dead board, 502 becomes 503. `board.body.board` is no longer a URL.
|
||||
- `packages/webui/tests/send-browser.test.mjs:89`: a `502 board unreachable` string. Verify at edit time. Likely a bus status, which stays.
|
||||
- `packages/webui/tests/serve.test.mjs:17-19`: `boardURL` checks, removed.
|
||||
|
||||
Unchanged, not board: `bus.test.mjs:226-236`, `chat.test.mjs:171-179`, `instances.test.mjs:163-168`,
|
||||
`limits-route.test.mjs:35`, `project-file.test.mjs:128`, `routes.test.mjs:170`, `send.test.mjs:230`,
|
||||
`tester-cli.test.mjs:57`, `tester-listener.test.mjs:113`, `usage.test.mjs:24`, `board-held.test.mjs:204` (page text).
|
||||
|
||||
Dead-board fixture files (16 webui plus `tester-fixture.mjs`) match Webby's count.
|
||||
|
||||
## Gate
|
||||
|
||||
Mutant runs use single test files, which run freely. Any suite (control-board, webui, cli, every
|
||||
`scripts/test-*.sh`) needs Sage's slot.
|
||||
|
||||
Webby approves INVENTORY.md (already sent), then this DESIGN.md, then the manifest. Sage's gate runs the
|
||||
control-board and webui suites, the cli suite and every `scripts/test-*.sh`. All must be green, and mutants
|
||||
M1-M15 killed.
|
||||
|
||||
## Stop points
|
||||
|
||||
- If Mos rules that loopback board reads need a gate, this design changes. Sage tells me, and I stop and report.
|
||||
- If the Console's auth model changes under row 192, the board block in section "Console" changes with it.
|
||||
- The Console restart and unit re-render are Jason's or Mos's steps. I do not run them.
|
||||
|
||||
## Boundaries kept
|
||||
|
||||
No change to the live units, `~/.mosaic-dev`, `~/.config/mosaic-dev`, slot files, `~/.mosaic`, or any live port.
|
||||
No request to 7331 or 7332 at any point.
|
||||
@@ -0,0 +1,125 @@
|
||||
# Row 198 inventory: control board writer and port 7331
|
||||
|
||||
Gitea #1696. Brief: `docs/plans/2026-10-20_retire-control-board.md`. Owner Bushroot, reviewer Webby, gate owner Sage.
|
||||
Scope: tracked files at `bushroot/row198` (base `refactor`). Excludes `v1/` and other seats' `agents/*/work/` packets.
|
||||
Method: `git grep -I` for `control-board`, `control_board`, `controlboard` and `7331`, then a read of each code hit.
|
||||
|
||||
## Classes
|
||||
|
||||
- **starts**: runs `packages/control-board` `serve`, or a unit, script or default that does.
|
||||
- **imports**: loads code from `packages/control-board` at run time or test time.
|
||||
- **mentions**: text only (docs, comments, fixture strings, history).
|
||||
|
||||
## Result
|
||||
|
||||
**Live dependency found. Stop before any removal.** The Console (`packages/webui`) proxies its board reads and
|
||||
writes to the writer on 7331. The Console's board view, `/api/board`, `/api/seen`, `/api/reply` and the
|
||||
conversation routes all forward to `DEFAULT_BOARD = http://127.0.0.1:7331`. The Console has no board without
|
||||
a writer on that port. Mos decides before anything is removed.
|
||||
|
||||
Nothing in the repository runs `control-board serve` from a unit. The only starter is the manual command in
|
||||
`packages/control-board/README.md` and `packages/webui/README.md`. `ss -ltnp` showed nothing on 7331 at
|
||||
2026-10-20T19:05:09Z (brief).
|
||||
|
||||
## Hits
|
||||
|
||||
### Starts the writer, or defaults to its port
|
||||
|
||||
| File | Line | Class | What it does |
|
||||
|---|---|---|---|
|
||||
| `packages/control-board/src/cli.mjs` | 4, 14 | starts | `serve` subcommand; default `port: 7331` |
|
||||
| `packages/control-board/src/serve.mjs` | 312 | starts | `startServer` default `port = 7331` (the writer itself) |
|
||||
| `packages/control-board/README.md` | 107, 127 | starts | Tells people to run `serve` and open 7331; documents the default |
|
||||
| `packages/webui/README.md` | 10 | starts | Tells people to run `node packages/control-board/src/cli.mjs serve` |
|
||||
| `packages/webui/README.md` | 15, 881 | depends | Console reads the board at 7331; rollback uses "the unchanged board at 7331" |
|
||||
| `packages/webui/src/cli.mjs` | 14, 18 | depends | `--board` default is `DEFAULT_BOARD` (7331) |
|
||||
| `packages/webui/src/serve.mjs` | 12, 473, 561, 578, 610 | **depends (live)** | `DEFAULT_BOARD`; proxies `GET /api/board`, `GET /api/conversation(s)`, `POST /api/seen`, `POST /api/reply` upstream; returns 502 if the writer is absent |
|
||||
| `packages/webui/src/public/app.js` | 468, 365 | depends | Page reads `/api/config` board, then `/api/board` through the Console proxy |
|
||||
| `packages/webui/src/public/bus.js` | 1037, 1088, 1100 | depends | Routes `#/` to the board page; bus pages link to it |
|
||||
| `scripts/webui-service.sh` | 89-163 | depends | Renders the Console unit with `--board URL`; default comes from `serve` |
|
||||
| `packages/webui/systemd/mosaic-webui.service.in` | 29 | depends | Unit runs the Console (`webui/src/cli.mjs serve`), not the writer; flags carry `--board` |
|
||||
| `scripts/stack-copy.sh` | 88 | mentions | Checks the Console `ExecStart` string; no writer reference |
|
||||
| `scripts/stack-release.sh` | 1372 | mentions | Checks the Console command line; no writer reference |
|
||||
|
||||
### Imports code from `packages/control-board`
|
||||
|
||||
| File | Line | Class | What it imports |
|
||||
|---|---|---|---|
|
||||
| `packages/webui/tests/fixture.mjs` | 6 | imports | `startServer` from `control-board/src/serve.mjs` (test fixture for the Console) |
|
||||
| `packages/webui/tests/history-fixture.mjs` | 8 | imports | same |
|
||||
| `packages/webui/tests/discord.test.mjs` | 6 | imports | same |
|
||||
| `packages/webui/tests/relaunch.test.mjs` | 6 | imports | same |
|
||||
| `packages/webui/tests/return-flow.test.mjs` | 13 | imports | same |
|
||||
| `packages/conversation/tests/flows.test.mjs` | 15 | imports | `replyToRow` from `control-board/src/serve.mjs` |
|
||||
|
||||
The webui tests run the writer's server in-process. They do not start the writer on 7331 (the tests bind
|
||||
their own port), but they need `serve.mjs`. Any removal or refusal in `serve.mjs` breaks these tests unless
|
||||
the writer's read-only code moves first.
|
||||
|
||||
`packages/webui/src/` (non-test code) does not import `control-board`. Only the tests do.
|
||||
|
||||
### Tests that name the writer or its files
|
||||
|
||||
| File | Line | Class | Note |
|
||||
|---|---|---|---|
|
||||
| `packages/control-board/tests/serve.test.mjs` | 37, 744-767 | imports | Tests the writer: reply runner, `REPLY_TRAILER`, `-S <host>:control-board` |
|
||||
| `packages/control-board/tests/scan.test.mjs` | 47, 306, 353 | mentions | Tmp dir names and comments |
|
||||
| `packages/webui/tests/serve.test.mjs` | 18-19, 35 | mentions | `boardURL` checks on port 7331; reply trailer string |
|
||||
| `packages/webui/tests/reads.test.mjs` | 188 | mentions | Expected `board` setting value 7331 |
|
||||
| `packages/webui/tests/service.test.mjs` | 225-394 | mentions | Unit `ExecStart` text includes `--board http://127.0.0.1:7331` |
|
||||
| `packages/plugins/tests/harness.test.mjs` | 173 | mentions | Path string `packages/control-board/src/scan.mjs` |
|
||||
| `packages/ledger/tests/ledger.test.mjs` | 80, 233-327 | mentions | Sender name `control-board` as a test fixture |
|
||||
| `packages/queue/tests/fixtures/genesis-render.md` | 6-11 | mentions | Fixture text |
|
||||
| `packages/conversation/tests/reader.test.mjs` | 2 | mentions | Comment |
|
||||
| `packages/conversation/README.md` | 207 | mentions | Says the control-board suite serves the routes |
|
||||
|
||||
### Code that reads the sender name, not the writer
|
||||
|
||||
| File | Line | Class | Note |
|
||||
|---|---|---|---|
|
||||
| `packages/ledger/src/ledger.mjs` | 120 | mentions | Classifies `control-board` as sender kind `board`. No import. |
|
||||
|
||||
### Package and script metadata
|
||||
|
||||
| File | Line | Class | Note |
|
||||
|---|---|---|---|
|
||||
| `packages/control-board/package.json` | 2, 5, 9 | starts | Declares `bin: mosaic-board -> src/cli.mjs` (a `mosaic-board serve` entry point) |
|
||||
| `package.json` (root) | - | none | No script names the board or 7331 |
|
||||
| `scripts/mosaic` | 3 | mentions | Comment only |
|
||||
| `packages/seat/package.json` | 5 | mentions | Description: "the control board reads" registration |
|
||||
| `packages/seat/README.md` | 139 | mentions | Says the board reads `registration.json` |
|
||||
| `packages/plugins/README.md` | 181 | mentions | Names `scan.mjs` in an exception note |
|
||||
|
||||
### Docs (mention only)
|
||||
|
||||
- `AGENTS.md` lines 144-150: reply convention for control-board attention. Names the package README. Does
|
||||
not tell anyone to start 7331. The brief says to change it only if it does.
|
||||
- `docs/SESSIONS.md` (32), `BUILD-LOG.md` (76), `docs/plans/CURRENT.md` (8), `docs/plans/QUEUE.md` (12),
|
||||
`docs/plans/queue.json` (45), `docs/plans/2026-09-12_control-board-mvp.md` (21),
|
||||
`docs/plans/2026-10-*` and other plan pages, `docs/plans/reviews/*`, `docs/design/*`.
|
||||
- `docs/plans/CURRENT.md:171` says "Start it with `node packages/control-board/src/cli.mjs serve`". That is
|
||||
an instruction to start the writer. Update it when the writer is retired.
|
||||
- `docs/plans/2026-10-18_containerized-install-design.md:927` and `-rows.md:213` cite `serve.mjs` line numbers.
|
||||
- `docs/plans/2026-10-19_seen-click-intent.md:29` cites `serve.mjs:257` (`/api/seen`).
|
||||
- `BUILD-LOG.md` is append-only. Leave it alone.
|
||||
- `agents/bushroot/README.md`, `agents/README.md`, `docs/design/README.md`, `docs/design/IMPLEMENTING.md`:
|
||||
mention only.
|
||||
- `packages/webui/third_party/editor/PROVENANCE.json`: mention only (the vendored editor's clipboard, not the board).
|
||||
- `packages/webui/src/public/*` clipboard, board-view and CSS names: unrelated to the writer.
|
||||
|
||||
### Excluded
|
||||
|
||||
- `v1/*`: archived.
|
||||
- `agents/*/work/*`: other seats' packets (darkwing, filbert, rocko and others). Not read for this inventory.
|
||||
|
||||
## Open items for Mos
|
||||
|
||||
1. The Console's board view and proxy routes need a writer on 7331 or an equivalent read path. Retiring the
|
||||
writer means choosing a replacement first, or accepting that the board view goes dark.
|
||||
2. Four plan pages and `CURRENT.md:171` still tell people to start the writer.
|
||||
3. The webui tests and `conversation/tests/flows.test.mjs` import `serve.mjs`. Any change to it needs those
|
||||
tests moved first.
|
||||
|
||||
## Sha256
|
||||
|
||||
Recorded in the send message after commit. This file is the inventory; the commit carries it.
|
||||
Reference in New Issue
Block a user