cli, webui: bus, Console and mosaic CLI on one pinned copy (#1579)

Row 85 round 2. Built by Rocko, packet agents/rocko/work/queue-85/r2/,
candidate manifest
bd7ea899c7cacef43b415b8e9ecb164e1f36718ffbe64347881a2c5653170c98.
Filbert approved on #1579, comment 28256; Darkwing approved, comment
28258.

stack-copy.sh builds shared read-only copies under
XDG_DATA_HOME/mosaic-stack/copies; the bus and Console services render
and install against a copy with --copy and --pm. No live step is run
by this commit; those need Mos's go.

Sage's gate rerun on 3a1e11b83 with rows 85, 102 and 103 applied,
Docker up: node suites bus 128, business 67, cli 165, control-board
124, conversation 182, discord 178, harness 73, ledger 78, mosaic 69,
queue 155, runs 43, seat 113, tasks 92, webui 108; scripts auth 15,
conductor 17, config 24, discord 66, extension-package 18,
foundation 44, queue 28, release 14, task 98. All pass, 0 fail.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-13 16:34:47 -05:00
co-authored by Claude Opus 5.5
parent 3a1e11b83e
commit 305a012b13
11 changed files with 532 additions and 133 deletions
+91 -16
View File
@@ -202,7 +202,9 @@ scripts/mosaic talk <role> <text> [--wait SECONDS] [--business <id>]
scripts/mosaic stop <run>
scripts/mosaic launches off | on [--business <id>]
scripts/mosaic launches list [--json]
scripts/bus-service.sh render [--claude PATH] | install [--dir DIR] [--no-reload] [--claude PATH] | uninstall | status <business>
scripts/bus-service.sh render [--copy ID [--copies DIR]] [--pm] [--claude PATH]
scripts/bus-service.sh install [--copy ID [--copies DIR]] [--pm] [--dir DIR] [--no-reload] [--claude PATH]
scripts/bus-service.sh uninstall | status <business>
```
The human commands read and resolve through the broker's human transport
@@ -238,6 +240,86 @@ stdin and stdout it exits 4 with the usage.
Exit codes: `0` ok · `1` failed or outcome unknown · `2` invalid input ·
`3` refused or config problem · `4` usage. Details: `packages/cli/README.md`.
## Stack copies (`scripts/stack-copy.sh`)
```
scripts/stack-copy.sh copy COMMIT [--copies DIR] [--allow-worktree]
scripts/stack-copy.sh wrapper ID [--copies DIR] [--bin DIR]
scripts/stack-copy.sh check [--copies DIR] [--bin DIR] [--business ID]
```
One read-only tree supplies the bus, Console and human CLI (row 85,
#1579). Copies default to `${XDG_DATA_HOME:-$HOME/.local/share}/mosaic-stack/copies/<full id>`.
`copy` requires a canonical checkout (.git directory); --allow-worktree
is a test override. COMMIT must be reachable from both local refactor and
origin/refactor. It archives the runtime/import closure, adapters, roles,
RELEASE, package.json and package-lock.json; refuses missing manifests;
runs npm ci inside the temporary copy; removes write permissions; and
renames beside the target. A published existing copy stays unchanged.
Dependencies use the exact lock and carry Pi's installed lock record.
Failure publishes nothing and removes the temporary tree.
`wrapper` is a live switching step, reserved for Jason or Mos. It accepts
only an existing copy and a regular non-symlink mosaic wrapper with the
Q81 marker on line 2 and its exact lease line to mosaic-old. It preserves
both lines and atomically replaces the wrapper's final exec with the
copy's scripts/mosaic. --bin is a fixture override; the default is
~/.npm-global/bin. Neither the Q81 worktree nor mosaic-old is removed.
`check` changes nothing: it reads effective systemd WorkingDirectory and
ExecStart (including drop-ins) for the bus and Console, plus the wrapper
exec. It prints all three paths and succeeds only on the same existing
copy; absent/failed reads and a mismatched ExecStart fail. Business defaults
to mosaic-stack. Queue work continues on the canonical scripts/mosaic.
Bus render/install accept --copy ID, --copies DIR, --pm and the unchanged
--claude pin. Without --copy the rendered unit stays the checkout unit.
--pm adds the PM startup; a refusal exits 3, and RestartPreventExitStatus
prevents a retry. Install refuses any busy bus instance, even with
--no-reload; a failed state/drop-in read also refuses. Any template or
instance drop-in assigning ExecStart or WorkingDirectory refuses,
naming the file. Status reads effective roots, --pm and DropInPaths,
reports checkout or copy identity and refactor drift, and writes nothing.
### Pinned stack update
This is an operator plan, not an action agents run. Both reviews and the
landing precede it. Mos grants each live step first. A bus restart ends
running sessions; do this only when no PM turn is in flight.
1. Make the pushed target copy with `stack-copy.sh copy COMMIT` and note
its full id. Before the first update, preserve the current Q81 CLI wrapper
(bytes and mode), bus and Console unit files and drop-ins, and the effective
WorkingDirectory/ExecStart output from each status command. Record the bus's
--claude pin and the Console's preview/send flags. Keep the bus pm.conf and
the existing Console copy or checkout: the first rollback needs them.
On later updates, also record the prior shared copy's full id.
2. With Mos's go, stop the Console, then stop the bus.
3. Mos removes overriding bus drop-ins identified by status. The first
update removes `~/.config/systemd/user/[email protected]/pm.conf`.
Reload systemd after removal; keep unrelated environment drop-ins.
4. With Mos's go, install the bus with `bus-service.sh install --copy ID --pm`
and its existing --claude pin, then the Console with
`webui-service.sh install --copy ID` and its existing preview/send flags.
5. With Mos's go, run `stack-copy.sh wrapper ID` to switch the human CLI.
6. Mos starts the bus first, then the Console. Read `stack-copy.sh check`,
both service status commands, Console `/healthz` and the canonical
`scripts/mosaic launches list`; the latter must show the PM.
7. Jason's plain `mosaic talk pm "..."` must print sent and a reply before
#225 closes. Keep the receipts for the three paths and reply.
The first rollback restores the pre-update mixed setup, which has no shared
copy id. Each live step needs Mos's go: stop the Console then the bus;
reinstall the checkout bus unit without --copy or --pm, retaining its saved
--claude pin; restore the saved pm.conf and other bus drop-ins; restore the
saved Console unit and drop-ins pointing to its original checkout or
mosaic-console copy; restore the saved Q81 wrapper bytes and mode; reload
systemd; start the bus then the Console. Check their effective paths against
the saved status output, Console /healthz and the PM launch. The shared-copy
check is expected to refuse this restored mixed setup; #225 remains open.
Later rollback repeats steps 2-6 with the prior shared copy's full id, each
live step again approved by Mos. Nothing prunes copies or removes mosaic-old.
## Console service (`scripts/webui-service.sh`)
```bash
@@ -262,9 +344,9 @@ read-only. The template is
The unit runs a pinned copy, not the working checkout (row 77, #1571).
`copy COMMIT` resolves COMMIT to its full id, refuses one not reachable
from `refs/heads/refactor`, and writes `git archive` of the paths the
Console loads into `DIR/<id>/`, by default
`${XDG_DATA_HOME:-$HOME/.local/share}/mosaic-console/<id>/`. It writes a
from both `refs/heads/refactor` and `refs/remotes/origin/refactor`, and
delegates to stack-copy.sh for the archive and pinned npm install into `DIR/<id>/`, by default
`${XDG_DATA_HOME:-$HOME/.local/share}/mosaic-stack/copies/<id>/`. It writes a
temp directory beside the target, makes every file and directory read-only
(so a writer that replaces by rename can't change it either), then renames
it, so the copy's path is always whole. A copy that exists is left alone
@@ -282,8 +364,8 @@ checks that path against its own, so the bus must run from the same copy
`packages/bus/src/human-cli.mjs`, so a Console on a copy whose bus runs
from the checkout would fail every bus call with `human-required`. With
`--business ID`, install reads `[email protected]`; without it, every
loaded `mosaic-bus@` unit. It reads the effective ExecStart from
`systemctl --user show -p LoadState -p ExecStart`, so a drop-in counts,
loaded `mosaic-bus@` unit. It reads effective WorkingDirectory and ExecStart from
`systemctl --user show -p LoadState -p WorkingDirectory -p ExecStart`, so a drop-in counts,
and every ExecStart path must lie inside the copy. It refuses, naming both
paths, when one doesn't; when the unit is not installed or none is loaded;
when ExecStart has no path; and when the systemctl call fails. The check
@@ -309,16 +391,9 @@ uid, `^[0-9a-f]{64}$` (lead decision 101). The unit sets no `MOSAIC_*`,
`CLAUDE*`, `CODEX*` or `PI_*` variable. Serve's exit 2 (usage, config, or a
port already in use) is not restarted.
The update step: `copy` the commit, stop the unit, `install --copy ID`
with the same flags, start it. Rollback is the same steps with the earlier
id, which must be at or after row 77's landing: an older `cli.mjs` refuses
serve's `--checkout` ("unknown option"). The queue view isn't pinned: it
reads the checkout, so it shows the queue as it is now. Copies are kept
until someone removes them, with `chmod -R u+w DIR && rm -rf DIR` since
they are read-only; nothing prunes them. The step can't be used yet: the
bus still runs from the checkout (`copy` doesn't include
`scripts/mosaic`), so `install --copy ID` refuses until a later row puts
the bus on the copy.
The update/rollback sequence is [Pinned stack update](#pinned-stack-update)
above. The queue view reads the checkout and stays current. Copies remain
until an operator removes them; nothing here prunes or migrates copies.
There is no start, stop, enable or restart verb. The Console reads the bus
through the human transport, so whoever starts it holds the human's