Files
stack/agents/researcher/work/queue-98/build.patch
T
jason.woltjeandClaude Sonnet 5.5 452ea3cade researcher: row 98 build packet (#1592)
Packet commit, agents/researcher/work/queue-98 only. Secrets scan clean. Not pushed.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-12 18:15:44 -05:00

125 lines
8.3 KiB
Diff

diff --git a/packages/webui/tests/pinned-copy.test.mjs b/packages/webui/tests/pinned-copy.test.mjs
index 4103f533..07589efc 100644
--- a/packages/webui/tests/pinned-copy.test.mjs
+++ b/packages/webui/tests/pinned-copy.test.mjs
@@ -169,6 +169,53 @@ test('a copy that loses the rename race leaves no read-only temp directory behin
assert.deepEqual(readdirSync(copies), [head], 'the temp directory is removed');
});
+// Row 98 (#1592). The rename's two other outcomes: it fails and no target
+// appears (a copy that was never written must not print exists:), it
+// meets a target that holds files (mv fails and nothing lands in the
+// target), and it meets an empty target directory. Only the last tells mv -T
+// from plain mv: the temp copy's directories are 0555, so plain mv cannot move
+// it into a target at all (Permission denied) and fails exactly as -T does on a
+// full one, but -T may replace an empty directory by rename.
+async function copyWithMv(t, mvBody) {
+ const { dir, script, head } = fixtureRepo(t);
+ const copies = join(dir, 'copies');
+ const bin = join(dir, 'bin');
+ mkdirSync(bin);
+ writeFileSync(join(bin, 'mv'), `#!/bin/sh\n${mvBody}\n`, { mode: 0o755 });
+ const env = { PATH: `${bin}:${NODE_DIR}:/usr/bin:/bin`, HOME: dir, XDG_CONFIG_HOME: join(dir, 'config') };
+ const child = spawn(script, ['copy', 'refactor', '--copies', copies], { env });
+ let stdout = '', stderr = '';
+ child.stdout.on('data', (d) => { stdout += d; });
+ child.stderr.on('data', (d) => { stderr += d; });
+ const [status] = await once(child, 'close');
+ return { copies, target: join(copies, head), head, status, stdout, stderr };
+}
+
+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.equal(r.stdout, '');
+ assert.deepEqual(readdirSync(r.copies), [], 'no target and no temp directory');
+});
+
+test('a copy that meets a target holding files says exists: and moves nothing into it', async (t) => {
+ // The stand-in makes the target appear with a file in it, then runs the real mv with the script's flags.
+ const r = await copyWithMv(t, 'for a; do t="$a"; done\nmkdir "$t"\n: > "$t/other"\nexec /usr/bin/mv "$@"');
+ assert.equal(r.status, 0, r.stderr);
+ assert.equal(r.stdout, `exists: ${r.target}\n`);
+ assert.deepEqual(readdirSync(r.copies), [r.head], 'the temp directory is removed');
+ assert.deepEqual(readdirSync(r.target), ['other'], 'nothing was moved into the target');
+});
+
+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.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');
+});
+
// serve from a copy: /healthz, the config check, roles and RELEASE from the
// copy, and the queue read from --checkout or, without it, from the copy.
async function serve(t, cli, args, env) {
diff --git a/packages/webui/tests/service.test.mjs b/packages/webui/tests/service.test.mjs
index d1226b19..d5d55a1b 100644
--- a/packages/webui/tests/service.test.mjs
+++ b/packages/webui/tests/service.test.mjs
@@ -71,19 +71,22 @@ const BUS_UNITS = '[email protected] loaded active running Mosaic bus host
// to stderr. The bus calls install makes are answered apart: `busUnits` for
// the mosaic-bus@ list-units, and `bus` for show, by default a bus started
// from the run's own copy ($HOME, as run() sets it). `busFails` makes both
-// bus calls fail.
-function standIn(dir, lines = '', { listFails = false, bus = null, busUnits = BUS_UNITS, busFails = false } = {}) {
+// bus calls fail. `busOther` answers show for [email protected]
+// apart from `bus`, so a second loaded unit can differ from the first.
+function standIn(dir, lines = '', { listFails = false, bus = null, busUnits = BUS_UNITS, busFails = false, busOther = null } = {}) {
const bin = join(dir, 'bin');
mkdirSync(bin);
writeFileSync(join(dir, 'list-units.txt'), lines);
writeFileSync(join(dir, 'bus-units.txt'), busUnits);
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')}"`;
writeFileSync(join(bin, 'systemctl'), `#!/bin/sh
echo "$*" >> "${dir}/calls.txt"
case "$*" in
*list-units*mosaic-bus@*) ${busFails ? fail : `cat "${dir}/bus-units.txt"`} ;;
+ *show*[email protected]*) ${busFails ? fail : busOther !== null ? `cat "${dir}/bus-show-other.txt"` : show} ;;
*show*mosaic-bus@*) ${busFails ? fail : show} ;;
*list-units*) ${listFails ? fail : `cat "${dir}/list-units.txt"`} ;;
*is-enabled*) echo enabled ;;
@@ -538,10 +541,21 @@ test('install refuses unless the bus unit runs from the same copy, naming both p
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/);
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);
// No bus unit installed for the business: refused.
s = sys({ bus: 'LoadState=not-found\nExecStart=\n' });
refused(await install(s.bin, ['--business', 'acme']), `no bus unit [email protected] is installed (LoadState not-found); the Console on the copy ${copy} needs its bus on that copy`);
+ // Only loaded counts: a unit systemd shows at any other LoadState is not the bus, even with an
+ // ExecStart on the copy.
+ for (const state of ['bad-setting', 'error', 'masked']) {
+ s = sys({ bus: busShow(copy).replace('LoadState=loaded', `LoadState=${state}`) });
+ refused(await install(s.bin, ['--business', 'acme']), `no bus unit [email protected] is installed (LoadState ${state}); the Console on the copy ${copy} needs its bus on that copy`);
+ }
// No --business and no mosaic-bus@ unit loaded: refused.
s = sys({ busUnits: '' });
refused(await install(s.bin), `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`);
@@ -644,6 +658,14 @@ test('status names the copy and whether refactor has moved past it, and says whe
r = await run(dir, ['status', '--dir', unitDir], bin);
assert.match(r.stdout, new RegExp(`^copy: none; the unit runs from ${esc(REPO)}$`, 'm'));
assert.ok(!/^refactor:/m.test(r.stdout));
+ // Only a whole 40-hex last part is a copy: 40 hex plus a character, or 41 hex, is not.
+ for (const last of [`${COPY_ID}x`, `x${COPY_ID}`, `${COPY_ID}0`]) {
+ unit(join(dir, 'copies', last));
+ r = await run(dir, ['status', '--dir', unitDir], bin);
+ assert.equal(r.status, 0, r.stderr);
+ assert.match(r.stdout, new RegExp(`^copy: none; the unit runs from ${esc(join(dir, 'copies', last))}$`, 'm'), last);
+ assert.ok(!/^refactor:/m.test(r.stdout), last);
+ }
// status reads; it calls nothing that changes the unit.
assert.deepEqual(readFileSync(join(dir, readdirSync(dir).find((d) => d.startsWith('si-')), 'calls.txt'), 'utf8').split('\n').filter(Boolean).filter((c) => !/^--user (is-enabled|is-active|show) mosaic-webui\.service( |$)/.test(c)), []);
});