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
+10 -64
View File
@@ -10,7 +10,7 @@
# write `git archive` of the paths the Console
# loads at COMMIT into DIR/<full id>/, read-only;
# DIR defaults to
# ${XDG_DATA_HOME:-~/.local/share}/mosaic-console
# ${XDG_DATA_HOME:-~/.local/share}/mosaic-stack/copies
# render [--copy ID [--copies DIR]] [FLAGS]
# print the rendered unit on stdout; without
# --copy it runs from this checkout
@@ -37,8 +37,7 @@
# --copy ID` with the same flags; Jason or Mos starts it. Rollback is the
# same with the earlier id. Copies stay until someone removes them. install
# refuses unless the bus unit runs from the same copy (bus_on_copy below),
# and nothing puts the bus on a copy yet, so the step can't be used until a
# later row does.
# row 85 supplies the shared copy and bus unit flags; Mos owns switching.
#
# FLAGS are serve's: --port N (default 7332; 7330 is the operator's own
# Console), --business ID, --board URL, and the preview set --public-host
@@ -72,7 +71,7 @@ die() { echo "webui-service: $*" >&2; exit 1; }
usage() { echo "$USAGE" >&2; exit 4; }
UNIT_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/systemd/user"
COPIES="${XDG_DATA_HOME:-$HOME/.local/share}/mosaic-console"
COPIES="${XDG_DATA_HOME:-$HOME/.local/share}/mosaic-stack/copies"
COPY_ID=""
COPY_FLAGS=0
ALLOW_WORKTREE=0
@@ -152,33 +151,8 @@ serve_flags() {
echo "$flags"
}
# The paths the Console loads, the only ones a copy holds: serve's import
# closure (packages/webui/src/cli.mjs and the human transport child,
# packages/bus/src/human-cli.mjs) reaches these nine src directories
# (packages/tasks/src since row 61: reads.mjs imports its field table
# loader, which reads packages/tasks/fields.json);
# serve runs scripts/mosaic-config.mjs as its config check and reads roles/
# and RELEASE; the unit's Documentation= names the README. The queue read
# runs from the checkout (serve --checkout), so packages/queue is not here.
# The pinned-copy test serves from a copy: a missing import fails it at
# load, and a missing file read at request time (fields.json, RELEASE,
# roles/) fails the route that reads it, which the test also GETs.
COPY_PATHS=(
packages/bus/src
packages/business/src
packages/cli/src
packages/conversation/src
packages/discord/src
packages/harness/src
packages/seat/src
packages/webui/src
packages/webui/README.md
packages/tasks/src
packages/tasks/fields.json
scripts/mosaic-config.mjs
roles
RELEASE
)
# Row 85: stack-copy.sh owns the pushed runtime closure and npm install.
# This script delegates copy and shares its copies directory.
# N2 (row 75 review): copy and install read and point at this checkout, so
# they run from the canonical one, never a worktree someone may remove.
@@ -243,7 +217,7 @@ running() {
# has loaded, and none loaded refuses. Like the state read, a failed call
# refuses with one line.
bus_on_copy() {
local copy="$1" units unit out load paths p
local copy="$1" units unit out load paths p working
if [ -n "$BUSINESS" ]; then
units="mosaic-bus@$BUSINESS.service"
else
@@ -252,7 +226,7 @@ bus_on_copy() {
[ -n "$units" ] || die "refusing: no mosaic-bus@ unit is loaded, and the Console on the copy $copy needs its bus on that copy; name the business with --business ID"
fi
for unit in $units; do
out="$(systemctl --user show "$unit" -p LoadState -p ExecStart 2>/dev/null)" || die "refusing: systemctl --user show $unit failed, so the bus unit is unknown; nothing was changed"
out="$(systemctl --user show "$unit" -p LoadState -p WorkingDirectory -p ExecStart 2>/dev/null)" || die "refusing: systemctl --user show $unit failed, so the bus unit is unknown; nothing was changed"
load="$(printf '%s\n' "$out" | sed -n 's/^LoadState=//p')"
[ "$load" = loaded ] || die "refusing: no bus unit $unit is installed (LoadState ${load:-unknown}); the Console on the copy $copy needs its bus on that copy"
paths="$(printf '%s\n' "$out" | sed -n 's/^ExecStart=//p' | grep -o 'path=[^ ;]*' | sed 's/^path=//' || true)"
@@ -260,6 +234,8 @@ bus_on_copy() {
for p in $paths; do
case "$p" in "$copy"/*) ;; *) die "refusing: $unit runs from $p, not the copy $copy; the bus accepts only its own human-cli.mjs, so every Console bus call would fail human-required until the bus runs from the same copy" ;; esac
done
working="$(printf '%s\n' "$out" | sed -n 's/^WorkingDirectory=//p')"
[ "$working" = "$copy" ] || die "refusing: $unit WorkingDirectory is ${working:-unknown}, not the copy $copy; nothing was changed"
done
}
@@ -376,37 +352,7 @@ fetch(process.argv[1], { signal: AbortSignal.timeout(5000) })
# permission off every file, then renames it into place, so a copy at its
# final path is always whole and read-only. An existing copy is left alone.
copy_commit() {
[ $# -ge 1 ] || usage
local commit="$1"; shift
case "$commit" in -*) usage ;; esac
parse_flags "$@"
[ "$SERVE_SET" = 0 ] && [ "$DIR_FLAGS" = 0 ] && [ -z "$COPY_ID" ] || usage
canonical
copies_ok
case "$commit" in [A-Za-z0-9]*) ;; *) die "commit must be a commit id or branch name: $commit" ;; esac
case "$commit" in *[!A-Za-z0-9._/-]*) die "commit must be a commit id or branch name: $commit" ;; esac
git -C "$REPO" rev-parse --verify --quiet refs/heads/refactor >/dev/null || die "no refs/heads/refactor in $REPO"
local id
id="$(git -C "$REPO" rev-parse --verify --quiet "$commit^{commit}")" || die "not a commit: $commit"
git -C "$REPO" merge-base --is-ancestor "$id" refs/heads/refactor || die "refusing: $id is not reachable from refs/heads/refactor"
local target="$COPIES/$id"
if [ -e "$target" ]; then echo "exists: $target"; return 0; fi
mkdir -p "$COPIES"
COPY_TMP="$(mktemp -d "$COPIES/.$id.XXXXXX")"
trap 'chmod -R u+w "$COPY_TMP" 2>/dev/null; rm -rf "$COPY_TMP"' EXIT
git -C "$REPO" archive --format=tar "$id" -- "${COPY_PATHS[@]}" | tar -x -f - -C "$COPY_TMP" || die "git archive of $id failed; no copy written"
# Read-only files and directories: a writer that replaces by rename
# (git, editors, sed -i) can't change a copy either.
find "$COPY_TMP" -type f -exec chmod a-w {} +
find "$COPY_TMP" -type d -exec chmod 0555 {} +
if ! mv -T "$COPY_TMP" "$target" 2>/dev/null; then
[ -e "$target" ] || die "could not rename the copy into $target"
echo "exists: $target"
return 0
fi
trap - EXIT
echo "copied: $target"
echo "install it with: scripts/webui-service.sh install --copy $id, with the unit's flags"
exec "$REPO/scripts/stack-copy.sh" copy "$@"
}
[ $# -ge 1 ] || usage