plans: one-copy brief for bus, Console and mosaic CLI (#225, Mos sage-0094/0095)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,182 @@
|
|||||||
|
# Bus, Console and mosaic CLI on one pinned copy (2026-10-12)
|
||||||
|
|
||||||
|
Status: written by Sage, lead. It follows `docs/plans/BRIEF-TEMPLATE.md`.
|
||||||
|
It is the fix plan in my comment 52 on Vikunja #225, which Mos asked for
|
||||||
|
in sage-0094 ("Tell me the plan before anything touches the wrapper or
|
||||||
|
the unit") and sage-0095 ("Keep the one-copy row brief going"). It
|
||||||
|
follows queue row 77 (#1571), whose round 2 makes `webui-service.sh
|
||||||
|
install --copy` refuse unless the bus runs from the same copy.
|
||||||
|
|
||||||
|
## Bus, Console and mosaic CLI on one pinned copy (#225)
|
||||||
|
|
||||||
|
### Problem
|
||||||
|
|
||||||
|
On 2026-10-12 Jason ran `mosaic talk pm "Can you hear me now?"` and got
|
||||||
|
`mosaic: human-required` (#225). The bus accepts a human proof only from
|
||||||
|
its own `packages/bus/src/human-cli.mjs`: `verifyHuman` compares the
|
||||||
|
proof process's `argv[1]` with `resolve(cliPath)`
|
||||||
|
(`packages/bus/src/human.mjs:52-102`), and `cliPath` comes from the bus's
|
||||||
|
own tree (`packages/bus/src/runtime.mjs:68`). The CLI spawns the
|
||||||
|
human-cli of its own tree (`packages/cli/src/transport.mjs:12`), and so
|
||||||
|
does the Console (`packages/webui/src/bus.mjs`). Today three things run
|
||||||
|
from three trees:
|
||||||
|
|
||||||
|
- the bus unit, `ExecStart=/mnt/storage/src/mosaic-stack/scripts/mosaic
|
||||||
|
bus start %i`, rendered by `scripts/bus-service.sh` from
|
||||||
|
`packages/cli/systemd/mosaic-bus.service.in` with REPO set to the
|
||||||
|
script's own checkout;
|
||||||
|
- the `mosaic` wrapper (Q81, `~/.npm-global/bin/mosaic`), which runs a
|
||||||
|
linked worktree at `~/.local/share/mosaic-cli/stack`, pinned at
|
||||||
|
d3ca2c22, six bus and CLI commits behind;
|
||||||
|
- the Console, which runs from the checkout today and from a row 77 copy
|
||||||
|
under `~/.local/share/mosaic-console/<id>/` once row 77 lands and is
|
||||||
|
installed.
|
||||||
|
|
||||||
|
So the proof works only from one tree at a time. The stopgap is the full
|
||||||
|
path, `/mnt/storage/src/mosaic-stack/scripts/mosaic talk pm ...`, which
|
||||||
|
Jason confirmed works (6b5b7aef). The bus also runs code from the moving
|
||||||
|
checkout, so every landing in `packages/bus` or `packages/cli` is live at
|
||||||
|
its next restart, the same problem row 77 fixes for the Console (lead
|
||||||
|
decision 109). And the unit starts the host without `--pm`
|
||||||
|
(`packages/cli/src/cli.mjs:265-279`), so no PM session runs unless
|
||||||
|
someone starts the host by hand (the S7 run log, 2026-10-12T18:49Z).
|
||||||
|
|
||||||
|
Accepting a second path in the proof (#225 option B) is rejected: it adds
|
||||||
|
a trusted path and keeps the three trees on different code.
|
||||||
|
|
||||||
|
### Owner and reviewer
|
||||||
|
|
||||||
|
Owner: Rocko. Reviewers: Darkwing and Filbert. Rocko has no Gitea token;
|
||||||
|
Sage runs the review request as jarvis. This row starts when row 77 is
|
||||||
|
done, and it goes ahead of row 84, since S7 comes before M1 (Q97).
|
||||||
|
|
||||||
|
### Files owned
|
||||||
|
|
||||||
|
- `scripts/stack-copy.sh`, new
|
||||||
|
- `scripts/webui-service.sh`, only the `copy` verb, the copies directory
|
||||||
|
and the same-copy check row 77 round 2 adds
|
||||||
|
- `scripts/bus-service.sh`
|
||||||
|
- `packages/cli/systemd/mosaic-bus.service.in`
|
||||||
|
- tests for the above, under `packages/cli/tests/` and
|
||||||
|
`packages/webui/tests/`, or a new `scripts/test-stack-copy.sh`
|
||||||
|
- `packages/cli/README.md`, the "Bus host" section
|
||||||
|
- `packages/webui/README.md`, the service section
|
||||||
|
- `docs/TOOLS.md`, the entries for the three scripts only
|
||||||
|
- `agents/rocko/work/queue-<row>/`
|
||||||
|
|
||||||
|
### What ships
|
||||||
|
|
||||||
|
One copy for all three:
|
||||||
|
|
||||||
|
- `scripts/stack-copy.sh copy COMMIT [--copies DIR]` makes the copy. It
|
||||||
|
takes over row 77's copy code and rules: canonical checkout only (the
|
||||||
|
`--allow-worktree` test override stays), the full 40-character id, a
|
||||||
|
temp directory beside the target and a rename, write permission
|
||||||
|
removed, and an existing copy left as it is (`exists:`). COMMIT must be
|
||||||
|
reachable from both `refs/heads/refactor` and
|
||||||
|
`refs/remotes/origin/refactor`, since the wrapper runs pushed code only
|
||||||
|
(Q81).
|
||||||
|
- The default directory is
|
||||||
|
`${XDG_DATA_HOME:-$HOME/.local/share}/mosaic-stack/copies/<full id>/`.
|
||||||
|
`webui-service.sh copy` stays as a call into `stack-copy.sh copy`, so
|
||||||
|
row 77's documented steps still work, and both scripts read the same
|
||||||
|
directory.
|
||||||
|
- The path list grows to what the bus host, the CLI and the Console load:
|
||||||
|
row 77's list, plus `scripts/mosaic`, `adapters/`, `packages/queue/src`
|
||||||
|
if any copied path imports it, `package.json` and `package-lock.json`.
|
||||||
|
The script runs `npm ci` in the temp directory before the rename, so
|
||||||
|
the copy carries the pi pin's `node_modules` and `pi-pin.mjs`'s lock
|
||||||
|
check passes there (`packages/conversation/src/pi-pin.mjs:52-57`). The
|
||||||
|
script refuses if `package.json` is missing from the temp directory, so
|
||||||
|
`npm ci` can never walk up. Rocko derives the final list from the
|
||||||
|
imports and records how in the packet.
|
||||||
|
- Tests build a copy from a fixture repository, then from that copy:
|
||||||
|
`bus start` reaches `bus host up` with a stand-in config, `cli.mjs
|
||||||
|
serve` answers `/healthz` 200, and `scripts/mosaic talk` gets past the
|
||||||
|
human proof against a broker started from the same copy. A path missing
|
||||||
|
from the list fails the suite.
|
||||||
|
|
||||||
|
The proof across copies, as tests (with the agent-environment check
|
||||||
|
stubbed as `packages/bus/tests/human.test.mjs` already does, never
|
||||||
|
bypassed in the code):
|
||||||
|
|
||||||
|
- a human-cli from the same copy as the broker is accepted;
|
||||||
|
- a human-cli from another copy, or from the checkout, is refused with
|
||||||
|
`human-required`.
|
||||||
|
|
||||||
|
The bus unit:
|
||||||
|
|
||||||
|
- `bus-service.sh render` and `install` take `--copy ID` and `--pm`.
|
||||||
|
`--copy ID` names an existing copy by its full id, and the template's
|
||||||
|
`@REPO@` becomes the copy's path, so `WorkingDirectory`, `ExecStart`
|
||||||
|
and `Documentation` point at it. Without `--copy` the unit renders as
|
||||||
|
today, so a fresh install from the checkout still works, and `status`
|
||||||
|
says the unit runs the checkout.
|
||||||
|
- `--pm` renders `ExecStart=<copy>/scripts/mosaic bus start %i --pm`. A
|
||||||
|
PM refusal at start exits 3, which the unit doesn't retry
|
||||||
|
(`RestartPreventExitStatus=2 3 4`), so the host stays down and its
|
||||||
|
journal names the refusal. The README says so.
|
||||||
|
- `install` refuses while any `mosaic-bus@` instance is active,
|
||||||
|
activating or deactivating, with row 77's N1 wording (Jason or Mos stops
|
||||||
|
it first), and with row 77's N4 rule for a failed `systemctl` read.
|
||||||
|
- `status` prints the copy id the unit runs, read from its
|
||||||
|
`WorkingDirectory`, whether `--pm` is in its `ExecStart`, and whether
|
||||||
|
`refactor` points at another commit. It changes nothing.
|
||||||
|
- The `--claude PATH` pin (lead decision 91) is unchanged and is rendered
|
||||||
|
next to `--copy` and `--pm`.
|
||||||
|
|
||||||
|
The check and the wrapper:
|
||||||
|
|
||||||
|
- `stack-copy.sh check` prints the tree each of the three runs: the bus
|
||||||
|
unit's `WorkingDirectory`, the Console unit's `WorkingDirectory`, and
|
||||||
|
the copy the `mosaic` wrapper execs. It exits 0 only when all three
|
||||||
|
name the same copy, and 1 otherwise, naming each path. It changes
|
||||||
|
nothing. A missing unit or wrapper counts as a mismatch, named as
|
||||||
|
absent.
|
||||||
|
- `stack-copy.sh wrapper ID` rewrites `~/.npm-global/bin/mosaic` to exec
|
||||||
|
`<copy>/scripts/mosaic`. It keeps the Q81 lease line that sends
|
||||||
|
`__lease-capability` to `mosaic-old`, and keeps the Q81 marker on line
|
||||||
|
2. It refuses unless the current file carries that marker and isn't a
|
||||||
|
symlink, and it writes a temp file, then renames it. `--bin DIR` points
|
||||||
|
it at a test directory. Tests cover the refusal on a foreign file and
|
||||||
|
the lease line.
|
||||||
|
|
||||||
|
The update step, documented once in `docs/TOOLS.md` and pointed to from
|
||||||
|
both READMEs: `stack-copy.sh copy COMMIT`; stop the Console, then the
|
||||||
|
bus; `bus-service.sh install --copy ID --pm` with its flags;
|
||||||
|
`webui-service.sh install --copy ID` with its flags; `stack-copy.sh
|
||||||
|
wrapper ID`; start the bus, then the Console; `stack-copy.sh check`,
|
||||||
|
`webui-service.sh status`, `/healthz` and `scripts/mosaic launches list`
|
||||||
|
showing the PM. Rollback is the same steps with the earlier id. A bus
|
||||||
|
restart ends the running sessions (`packages/cli/src/launcher.mjs:304-320`),
|
||||||
|
so the docs say to do it when no PM turn is in flight. The stops, starts,
|
||||||
|
installs and the wrapper rewrite are Jason's or Mos's (lead decisions 94,
|
||||||
|
102 and 103, and Mos's sage-0094: nothing touches the wrapper or the
|
||||||
|
units before the row lands and Mos says go).
|
||||||
|
|
||||||
|
Suites: the cli, bus and webui node suites, and every
|
||||||
|
`scripts/test-*.sh`.
|
||||||
|
|
||||||
|
### Out of scope
|
||||||
|
|
||||||
|
Running any step of the update on the live system: stopping, starting,
|
||||||
|
installing or enabling either unit, and rewriting the real wrapper.
|
||||||
|
Removing the Q81 worktree at `~/.local/share/mosaic-cli/stack` or
|
||||||
|
`mosaic-old`; that's Mos's or Jason's after the first update. Pruning old
|
||||||
|
copies. A health-gated activate and rollback like `scripts/release.sh`,
|
||||||
|
which is D1. Any change to the human proof itself
|
||||||
|
(`packages/bus/src/human.mjs`). Any change to `packages/queue`; `mosaic
|
||||||
|
queue` from a copy refuses as it does from the Q81 worktree today, and
|
||||||
|
queue work stays on the checkout's `scripts/mosaic`. Relaunching a PM
|
||||||
|
whose session ended while the host runs.
|
||||||
|
|
||||||
|
### Gate
|
||||||
|
|
||||||
|
Darkwing and Filbert approve on the row's issue, each naming the
|
||||||
|
candidate manifest. The cli, bus and webui node suites and every
|
||||||
|
`scripts/test-*.sh` pass on Sage's gate rerun. Before the landing Sage
|
||||||
|
notifies Mos (decision 109); after it, Sage sends Mos the commit and
|
||||||
|
`scripts/webui-service.sh status`, `scripts/bus-service.sh status
|
||||||
|
mosaic-stack` and `/healthz`. #225 closes when Mos's first update
|
||||||
|
step at the landed commit ends with `stack-copy.sh check` exiting 0, and
|
||||||
|
Jason's plain `mosaic talk pm "..."` printing `sent` and then a reply.
|
||||||
Reference in New Issue
Block a user