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
+15 -11
View File
@@ -33,15 +33,18 @@ function tmp(t, prefix) {
function fixtureRepo(t) {
const dir = tmp(t, 'webui-copy-');
const repo = join(dir, 'repo');
const files = execFileSync('git', ['-C', REPO, 'ls-files', '-z', '-co', '--exclude-standard', '--', 'packages', 'scripts', 'roles', 'RELEASE', ':!packages/*/tests', ':!scripts/foundation'], { encoding: 'utf8' }).split('\0').filter(Boolean);
const files = execFileSync('git', ['-C', REPO, 'ls-files', '-z', '-co', '--exclude-standard', '--', 'packages', 'scripts', 'roles', 'adapters', 'RELEASE', ':!packages/*/tests', ':!scripts/foundation'], { encoding: 'utf8' }).split('\0').filter(Boolean);
for (const f of files) if (existsSync(join(REPO, f))) cpSync(join(REPO, f), join(repo, f));
writeFileSync(join(repo, 'RELEASE'), '0.0.0-fixture\n');
writeFileSync(join(repo,'package.json'),JSON.stringify({name:'copy-fixture',version:'1.0.0'}));
writeFileSync(join(repo,'package-lock.json'),JSON.stringify({name:'copy-fixture',version:'1.0.0',lockfileVersion:3,packages:{'':{name:'copy-fixture',version:'1.0.0'}}}));
git(dir, 'init', '-q', '-b', 'refactor', repo);
// The commit leaves thousands of loose objects; a detached gc --auto
// would still be deleting them when the cleanup's chmod walks .git.
git(repo, 'config', 'gc.auto', '0');
git(repo, 'add', '-A');
git(repo, 'commit', '-q', '-m', 'fixture');
git(repo,'update-ref','refs/remotes/origin/refactor',git(repo,'rev-parse','HEAD'));
return { dir, repo, script: join(repo, 'scripts', 'webui-service.sh'), head: git(repo, 'rev-parse', 'HEAD') };
}
@@ -69,12 +72,12 @@ test('copy writes the Console paths at the full id, read-only, and leaves an exi
assert.equal(r.status, 0, r.stderr);
assert.match(r.stdout, new RegExp(`^copied: ${target}$`, 'm'));
assert.deepEqual(readdirSync(copies), [head], 'no temp directory left behind');
assert.deepEqual(readdirSync(target).sort(), ['RELEASE', 'packages', 'roles', 'scripts']);
assert.deepEqual(readdirSync(join(target, 'scripts')), ['mosaic-config.mjs']);
assert.deepEqual(readdirSync(target).sort(), ['RELEASE', 'adapters', 'package-lock.json', 'package.json', 'packages', 'roles', 'scripts']);
assert.deepEqual(readdirSync(join(target, 'scripts')), ['mosaic', 'mosaic-config.mjs']);
assert.ok(existsSync(join(target, 'packages', 'webui', 'README.md')), "the unit's Documentation=");
assert.equal(statSync(join(target, 'packages', 'webui', 'README.md')).mode & 0o222, 0, 'README.md is read-only');
assert.ok(existsSync(join(target, 'packages', 'tasks', 'fields.json')), 'the task field table');
assert.ok(!existsSync(join(target, 'packages', 'queue')), 'the queue read runs from the checkout');
assert.ok(existsSync(join(target, 'packages', 'queue', 'src')), 'wrapper queue branch imports queue code; mutations still refuse off checkout');
assert.equal(readFileSync(join(target, 'RELEASE'), 'utf8'), '0.0.0-fixture\n');
const files = filesUnder(target);
assert.ok(files.length > 50, String(files.length));
@@ -99,7 +102,7 @@ test('copy writes the Console paths at the full id, read-only, and leaves an exi
// The default directory is under XDG_DATA_HOME, else ~/.local/share.
const byDefault = await run(dir, script, ['copy', 'refactor']);
assert.equal(byDefault.status, 0, byDefault.stderr);
assert.ok(existsSync(join(dir, '.local', 'share', 'mosaic-console', head, 'packages', 'webui', 'src', 'cli.mjs')));
assert.ok(existsSync(join(dir, '.local', 'share', 'mosaic-stack', 'copies', head, 'packages', 'webui', 'src', 'cli.mjs')));
assert.equal(git(repo, 'status', '--porcelain'), '', 'the checkout is untouched');
});
@@ -112,7 +115,7 @@ test('copy refuses a commit off refactor, an unknown one, a worktree and a bad c
git(repo, 'commit', '-q', '-m', 'side');
const side = git(repo, 'rev-parse', 'HEAD');
git(repo, 'checkout', '-q', 'refactor');
for (const [commit, why] of [[side, new RegExp(`^webui-service: refusing: ${side} is not reachable from refs/heads/refactor$`, 'm')], ['side', /is not reachable from refs\/heads\/refactor/], ['nope', /^webui-service: not a commit: nope$/m], ['a b', /commit must be a commit id or branch name/], ['HEAD~1', /commit must be a commit id or branch name/], ['.refactor', /^webui-service: commit must be a commit id or branch name: \.refactor$/m]]) {
for (const [commit, why] of [[side, new RegExp(`^stack-copy: refusing: ${side} is not reachable from refs/heads/refactor$`, 'm')], ['side', /is not reachable from refs\/heads\/refactor/], ['nope', /^stack-copy: not a commit: nope$/m], ['a b', /commit must be a commit id or branch name/], ['HEAD~1', /commit must be a commit id or branch name/], ['.refactor', /^stack-copy: commit must be a commit id or branch name: \.refactor$/m]]) {
const r = await run(dir, script, ['copy', commit, '--copies', copies]);
assert.equal(r.status, 1, commit);
assert.match(r.stderr, why, commit);
@@ -132,7 +135,7 @@ test('copy refuses a commit off refactor, an unknown one, a worktree and a bad c
git(repo, 'worktree', 'add', '-q', '--detach', tree, 'refactor');
const fromTree = await run(dir, join(tree, 'scripts', 'webui-service.sh'), ['copy', 'refactor', '--copies', copies]);
assert.equal(fromTree.status, 1);
assert.match(fromTree.stderr, /^webui-service: refusing: \S+ is not the canonical checkout \(its \.git is not a directory\)/m);
assert.match(fromTree.stderr, /^stack-copy: refusing: \S+ is not the canonical checkout \(its \.git is not a directory\)/m);
assert.ok(!existsSync(copies));
const allowed = await run(dir, join(tree, 'scripts', 'webui-service.sh'), ['copy', 'refactor', '--copies', copies, '--allow-worktree']);
assert.equal(allowed.status, 0, allowed.stderr);
@@ -145,9 +148,10 @@ test('copy writes nothing when an archived path is missing at the commit', async
const copies = join(dir, 'copies');
git(repo, 'rm', '-q', 'scripts/mosaic-config.mjs');
git(repo, 'commit', '-q', '-m', 'drop the config check');
git(repo,'update-ref','refs/remotes/origin/refactor',git(repo,'rev-parse','HEAD'));
const r = await run(dir, script, ['copy', 'refactor', '--copies', copies]);
assert.equal(r.status, 1);
assert.match(r.stderr, /^webui-service: git archive of [0-9a-f]{40} failed; no copy written$/m);
assert.match(r.stderr, /^stack-copy: git archive failed; no copy written$/m);
assert.deepEqual(readdirSync(copies), [], 'the temp directory is removed');
});
@@ -194,7 +198,7 @@ async function copyWithMv(t, mvBody) {
test('a copy whose rename fails with no target refuses, writes no copy and leaves no temp directory', async (t) => {
const r = await copyWithMv(t, 'exit 1');
assert.equal(r.status, 1);
assert.equal(r.stderr, `webui-service: could not rename the copy into ${r.target}\n`);
assert.ok(r.stderr.includes(`stack-copy: could not rename copy into ${r.target}\n`));
assert.equal(r.stdout, '');
assert.deepEqual(readdirSync(r.copies), [], 'no target and no temp directory');
});
@@ -211,7 +215,7 @@ test('a copy that meets a target holding files says exists: and moves nothing in
test('a copy that meets an empty target directory replaces it by rename, not by moving into it', async (t) => {
const r = await copyWithMv(t, 'for a; do t="$a"; done\nmkdir "$t"\nexec /usr/bin/mv "$@"');
assert.equal(r.status, 0, r.stderr);
assert.equal(r.stdout, `copied: ${r.target}\ninstall it with: scripts/webui-service.sh install --copy ${r.head}, with the unit's flags\n`);
assert.equal(r.stdout, `copied: ${r.target}\n`);
assert.deepEqual(readdirSync(r.copies), [r.head], 'the temp directory is gone');
assert.ok(existsSync(join(r.target, 'packages', 'webui', 'README.md')), 'the copy is the target itself');
});
@@ -266,7 +270,7 @@ test('serve runs from a copy: /healthz 200, the config check, roles and RELEASE
assert.equal((await plain.get('/healthz')).status, 200);
const none = await plain.get('/api/queue');
assert.equal(none.status, 503);
assert.equal(none.body.error, 'read-failed');
assert.equal(none.body.error, 'queue-refused');
});
test('serve refuses a --checkout that is not an absolute directory path', async (t) => {
+12 -10
View File
@@ -24,7 +24,7 @@ const PREVIEW_FLAGS = `--public-host console.example.com --auth-header X-authent
// A copy as `copy` leaves it, reduced to the file install looks for.
const COPY_ID = '0123456789abcdef0123456789abcdef01234567';
const INSTALL = ['--copy', COPY_ID, '--allow-worktree'];
const copyRoot = (dir) => join(dir, '.local', 'share', 'mosaic-console', COPY_ID);
const copyRoot = (dir) => join(dir, '.local', 'share', 'mosaic-stack', 'copies', COPY_ID);
function fakeCopy(dir) {
mkdirSync(join(copyRoot(dir), 'packages', 'webui', 'src'), { recursive: true });
writeFileSync(join(copyRoot(dir), 'packages', 'webui', 'src', 'cli.mjs'), '');
@@ -61,9 +61,9 @@ function tmp(t) {
return dir;
}
// The bus unit as `systemctl show -p LoadState -p ExecStart` prints it,
// The bus unit as `systemctl show -p LoadState -p WorkingDirectory -p ExecStart` prints it,
// started from `root`.
const busShow = (root) => `LoadState=loaded\nExecStart={ path=${root}/scripts/mosaic ; argv[]=${root}/scripts/mosaic bus start acme ; ignore_errors=no ; start_time=[n/a] ; stop_time=[n/a] ; pid=0 ; code=(null) ; status=0/0 }\n`;
const busShow = (root) => `LoadState=loaded\nWorkingDirectory=${root}\nExecStart={ path=${root}/scripts/mosaic ; argv[]=${root}/scripts/mosaic bus start acme ; ignore_errors=no ; start_time=[n/a] ; stop_time=[n/a] ; pid=0 ; code=(null) ; status=0/0 }\n`;
const BUS_UNITS = '[email protected] loaded active running Mosaic bus host (acme)\n';
// A stand-in systemctl that prints `lines` for the Console's list-units and
@@ -81,7 +81,7 @@ function standIn(dir, lines = '', { listFails = false, bus = null, busUnits = BU
if (bus !== null) writeFileSync(join(dir, 'bus-show.txt'), bus);
if (busOther !== null) writeFileSync(join(dir, 'bus-show-other.txt'), busOther);
const fail = 'echo "Failed to connect to bus" >&2; exit 1';
const show = bus !== null ? `cat "${dir}/bus-show.txt"` : `r="$HOME/.local/share/mosaic-console/${COPY_ID}"; printf '%s' "${busShow('$r')}"`;
const show = bus !== null ? `cat "${dir}/bus-show.txt"` : `r="$HOME/.local/share/mosaic-stack/copies/${COPY_ID}"; printf '%s' "${busShow('$r')}"`;
writeFileSync(join(bin, 'systemctl'), `#!/bin/sh
echo "$*" >> "${dir}/calls.txt"
case "$*" in
@@ -106,7 +106,7 @@ function quiet(dir) {
return join(home, 'bin');
}
// Every call but the reads: the Console's state and install's bus check.
const READS = /^--user (list-units --no-legend --plain mosaic-webui\.service|list-units --all --no-legend --plain mosaic-bus@\*\.service|show mosaic-bus@\S+ -p LoadState -p ExecStart)$/;
const READS = /^--user (list-units --no-legend --plain mosaic-webui\.service|list-units --all --no-legend --plain mosaic-bus@\*\.service|show mosaic-bus@\S+ -p LoadState -p WorkingDirectory -p ExecStart)$/;
const otherCalls = (file) => readFileSync(file, 'utf8').split('\n').filter(Boolean).filter((c) => !READS.test(c));
// Async, so an in-process server can answer the script's /healthz request.
@@ -227,7 +227,7 @@ test('install without --no-reload runs daemon-reload and nothing that starts the
const r = await run(dir, ['install', ...INSTALL, '--dir', join(dir, 'units')], bin);
assert.equal(r.status, 0, r.stderr);
assert.match(r.stdout, /^daemon-reload done$/m);
assert.equal(readFileSync(join(dir, 'calls.txt'), 'utf8'), '--user list-units --all --no-legend --plain mosaic-bus@*.service\n--user show [email protected] -p LoadState -p ExecStart\n--user list-units --no-legend --plain mosaic-webui.service\n--user daemon-reload\n');
assert.equal(readFileSync(join(dir, 'calls.txt'), 'utf8'), '--user list-units --all --no-legend --plain mosaic-bus@*.service\n--user show [email protected] -p LoadState -p WorkingDirectory -p ExecStart\n--user list-units --no-legend --plain mosaic-webui.service\n--user daemon-reload\n');
});
test('uninstall refuses while the unit is running, with or without --no-reload, and removes it once it is not', async (t) => {
@@ -478,7 +478,7 @@ test('install needs --copy naming a copy that exists, and refuses outside the ca
[['--copy', COPY_ID.slice(0, 12)], /^webui-service: --copy must be a full 40-character commit id: 0123456789ab$/m],
[['--copy', COPY_ID.toUpperCase()], /^webui-service: --copy must be a full 40-character commit id: /m],
[['--copy', `${COPY_ID}0`], /^webui-service: --copy must be a full 40-character commit id: /m],
[['--copy', 'f'.repeat(40)], new RegExp(`^webui-service: no copy ${'f'.repeat(40)} in ${esc(join(dir, '.local', 'share', 'mosaic-console'))}; make it with: scripts/webui-service\\.sh copy ${'f'.repeat(40)}$`, 'm')],
[['--copy', 'f'.repeat(40)], new RegExp(`^webui-service: no copy ${'f'.repeat(40)} in ${esc(join(dir, '.local', 'share', 'mosaic-stack', 'copies'))}; make it with: scripts/webui-service\\.sh copy ${'f'.repeat(40)}$`, 'm')],
[['--copy', COPY_ID, '--copies', 'relative'], /^webui-service: copies directory must be an absolute path: relative$/m],
[['--copy', COPY_ID, '--copies', join(dir, 'no copies')], /^webui-service: copies directory contains a character the unit cannot carry: /m],
];
@@ -532,20 +532,22 @@ test('install refuses unless the bus unit runs from the same copy, naming both p
// The same with --business, which names the one unit to read.
s = sys({ bus: busShow(REPO) });
refused(await install(s.bin, ['--business', 'acme']), `[email protected] runs from ${REPO}/scripts/mosaic, 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`);
assert.equal(readFileSync(join(s.d, 'calls.txt'), 'utf8'), '--user show [email protected] -p LoadState -p ExecStart\n');
assert.equal(readFileSync(join(s.d, 'calls.txt'), 'utf8'), '--user show [email protected] -p LoadState -p WorkingDirectory -p ExecStart\n');
// A path that only starts with the copy's name is not inside it.
s = sys({ bus: busShow(`${copy}-old`) });
refused(await install(s.bin), `[email protected] runs from ${copy}-old/scripts/mosaic, 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`);
s = sys({bus:busShow(copy).replace(`WorkingDirectory=${copy}`, 'WorkingDirectory=/different')});
refused(await install(s.bin), `[email protected] WorkingDirectory is /different, not the copy ${copy}; nothing was changed`);
// Two loaded bus units: each must run from the copy.
s = sys({ busUnits: `${BUS_UNITS}[email protected] loaded active running Mosaic bus host (other)\n` });
assert.equal((await install(s.bin)).status, 0);
assert.match(readFileSync(join(s.d, 'calls.txt'), 'utf8'), /show mosaic-bus@other\.service -p LoadState -p ExecStart/);
assert.match(readFileSync(join(s.d, 'calls.txt'), 'utf8'), /show mosaic-bus@other\.service -p LoadState -p WorkingDirectory -p ExecStart/);
rmSync(unitDir, { recursive: true });
// The second loaded unit is off the copy: refused, naming the second unit and its path.
const two = `${BUS_UNITS}[email protected] loaded active running Mosaic bus host (other)\n`;
s = sys({ busUnits: two, bus: busShow(copy), busOther: busShow(REPO) });
refused(await install(s.bin), `[email protected] runs from ${REPO}/scripts/mosaic, 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`);
assert.match(readFileSync(join(s.d, 'calls.txt'), 'utf8'), /show mosaic-bus@acme\.service -p LoadState -p ExecStart\n.*show mosaic-bus@other\.service -p LoadState -p ExecStart/s);
assert.match(readFileSync(join(s.d, 'calls.txt'), 'utf8'), /show mosaic-bus@acme\.service -p LoadState -p WorkingDirectory -p ExecStart\n.*show mosaic-bus@other\.service -p LoadState -p WorkingDirectory -p ExecStart/s);
// No bus unit installed for the business: refused.
s = sys({ bus: 'LoadState=not-found\nExecStart=\n' });