// #1873 (redact6): a cli refusal names the file, the instance or the field, never a host path, // and never relays mosaic-config's stderr or a Node system error's message. The sentinel is a // component of every scratch path, so a refusal that quotes a path quotes the sentinel too; each // test also checks the scratch root and the label. The launcher cases need no bus: a bus socket // under a sentinel path would pass the Unix socket length limit, so they use a stub host. import test from 'node:test'; import assert from 'node:assert/strict'; import { mkdirSync, readFileSync, symlinkSync, writeFileSync } from 'node:fs'; import { spawnSync } from 'node:child_process'; import { join } from 'node:path'; import { createLauncher, relay } from '../src/launcher.mjs'; import { CliError } from '../src/errors.mjs'; import { loadSystem } from '../src/config.mjs'; import { main } from '../src/cli.mjs'; import { initializeControl } from '../../processing-control/src/state.mjs'; import { launchLogFile } from '../../seat/src/session.mjs'; import { fakeHarnesses, fixture, io, tmp, withoutHostCredentials } from './helpers.mjs'; const secret = 'sk-test-NOT-A-SECRET-1234'; function scratch(t) { const root = tmp(t, 'r6-'); const dir = join(root, secret); mkdirSync(dir, { mode: 0o700 }); return { root, dir }; } function clean(text, root) { assert.ok(!text.includes(root), text); assert.doesNotMatch(text, /sk-test|NOT-A-SECRET/); } const PI = JSON.stringify(new URL('../../plugins/harness/pi/index.mjs', import.meta.url).href); function stubLauncher(t, { op = async () => ({}) } = {}) { const { root, dir } = scratch(t); const f = fixture(dir, 'acme'); initializeControl(f.dataRoot); withoutHostCredentials(f.env, { piKey: true }); const system = JSON.parse(readFileSync(f.config, 'utf8')); const host = { business: 'acme', beforeClose: () => {}, op }; return { root, f, make: () => createLauncher({ host, system, env: f.env, bwrap: '/usr/bin/true', log: () => {} }) }; } test('an unreadable sessions file refuses exit 3 naming bus-host/sessions.json under the data root, never its path', t => { const s = stubLauncher(t); mkdirSync(join(s.f.dataRoot, 'bus-host'), { recursive: true, mode: 0o700 }); writeFileSync(join(s.f.dataRoot, 'bus-host', 'sessions.json'), '{not json'); assert.throws(() => s.make(), e => { assert.ok(e instanceof CliError, e.stack); assert.equal(e.exitCode, 3); assert.equal(e.message, 'sessions file is unreadable: bus-host/sessions.json under the data root; check which sessions still run, then move the file aside'); clean(e.message, s.root); return true; }); writeFileSync(join(s.f.dataRoot, 'bus-host', 'sessions.json'), JSON.stringify({ sessionsVersion: 999, sessions: [] })); assert.throws(() => s.make(), e => e.exitCode === 3 && e.message.startsWith('sessions file is malformed: bus-host/sessions.json under the data root;') && !e.message.includes(s.root)); }); test('a launch socket path that is not a socket refuses exit 3 by label, never its path', async t => { const s = stubLauncher(t); mkdirSync(join(s.f.dataRoot, 'bus-host'), { recursive: true, mode: 0o700 }); writeFileSync(join(s.f.dataRoot, 'bus-host', 'launch.sock'), 'in the way\n'); const launcher = s.make(); t.after(() => launcher.close?.()); await assert.rejects(launcher.listen(), e => { assert.ok(e instanceof CliError, e.stack); assert.equal(e.exitCode, 3); assert.equal(e.message, "launch socket path exists and isn't a socket"); clean(e.message, s.root); return true; }); }); // The launch-routes pattern: a stub host stops each launch at authorizeLaunch, and the launch // log's refused event carries the reason. async function routed(t, harness) { const { root, dir } = scratch(t); const f = fixture(dir, 'acme', doc => { doc.roles.coder.vars = { harness, model: 'claude-sonnet-5-5', thinking: 'high' }; doc.plugins = { ...doc.plugins, harness: ['pi', 'claude-code', harness] }; return doc; }); initializeControl(f.dataRoot); withoutHostCredentials(f.env, { piKey: true }); const host = { business: 'acme', beforeClose: () => {}, op: async x => { if (x.op === 'identity') return { business: 'acme', role: 'pm' }; if (x.op === 'mayLaunch') return { allow: true }; if (x.op === 'authorizeLaunch') throw Object.assign(Error('fixture authorization stop'), { code: 'fixture-policy' }); return {}; } }; const absent = join(dir, 'absent.json'); const makeLoader = await fakeHarnesses(t, { pi: null, 'claude-code': null }, write => { // fsthrows: its preflight reads a missing file, so the error's message quotes the path. write('harness', 'fsthrows', `export * from ${PI};\nimport { readFileSync } from "node:fs";\nexport function preflight() { readFileSync(${JSON.stringify(absent)}); }\n`); // plainthrows: a plain Error whose message quotes a host path, as a module's own text. write('harness', 'plainthrows', `export * from ${PI};\nexport function preflight() { throw new Error(${JSON.stringify(`cannot read ${absent}`)}); }\n`); // pathfails: a preflight that returns a failure whose message quotes a host path. write('harness', 'pathfails', `export * from ${PI};\nexport function preflight() { return { code: "harness-binary-missing", message: ${JSON.stringify(`no binary at ${absent}; see file://${absent}`)} }; }\n`); }); const system = JSON.parse(readFileSync(f.config, 'utf8')); const launcher = createLauncher({ host, system, env: f.env, makeLoader, bwrap: '/usr/bin/true', log: () => {} }); const last = () => readFileSync(launchLogFile(f.dataRoot, 'acme'), 'utf8').trim().split('\n').map(l => JSON.parse(l)).at(-1); return { root, f, launch: () => launcher.fromSession('fixture-cap', 'coder'), last }; } test('a harness whose preflight throws a plain Error naming a path refuses harness-failed by its class, never its message', async t => { const s = await routed(t, 'plainthrows'); assert.deepEqual(await s.launch(), { ok: false, error: 'harness-failed' }); assert.equal(s.last().why, 'harness plainthrows preflight: threw Error without a system code'); clean(JSON.stringify(s.last()), s.root); }); test('a preflight failure whose message quotes a path keeps its code and relays the text with the path replaced', async t => { const s = await routed(t, 'pathfails'); assert.deepEqual(await s.launch(), { ok: false, error: 'harness-binary-missing' }); assert.equal(s.last().why, 'no binary at a host path; see a host path'); clean(JSON.stringify(s.last()), s.root); }); test('the claude-code preflight names MOSAIC_CLAUDE_BIN, never its value', async t => { const { root, dir } = scratch(t); const f = fixture(dir, 'acme', doc => { doc.roles.coder.vars = { harness: 'claude-code', model: 'claude-sonnet-5-5', thinking: 'high' }; return doc; }); initializeControl(f.dataRoot); withoutHostCredentials(f.env); f.env.CLAUDE_CODE_OAUTH_TOKEN = 'fixture-only'; f.env.MOSAIC_CLAUDE_BIN = join(dir, 'absent-claude'); const host = { business: 'acme', beforeClose: () => {}, op: async x => x.op === 'identity' ? { business: 'acme', role: 'pm' } : x.op === 'mayLaunch' ? { allow: true } : {} }; const makeLoader = await fakeHarnesses(t, { pi: null, 'claude-code': null }); const launcher = createLauncher({ host, system: JSON.parse(readFileSync(f.config, 'utf8')), env: f.env, makeLoader, bwrap: '/usr/bin/true', log: () => {} }); assert.deepEqual(await launcher.fromSession('fixture-cap', 'coder'), { ok: false, error: 'harness-binary-missing' }); const last = JSON.parse(readFileSync(launchLogFile(f.dataRoot, 'acme'), 'utf8').trim().split('\n').at(-1)); assert.equal(last.why, "MOSAIC_CLAUDE_BIN isn't an executable file at an absolute path"); clean(JSON.stringify(last), root); }); test('relay replaces absolute, home and file: paths and keeps the rest of the text', () => { assert.equal(relay(`bad ${'/srv/x/' + secret} and ~/y, (file:///z) here`), 'bad a host path and a host path, (a host path) here'); assert.equal(relay('fatal: not a git repository: .git; see https://example.test/a'), 'fatal: not a git repository: .git; see https://example.test/a'); }); test('a harness whose preflight throws a system error refuses harness-failed naming the call and code, never the path', async t => { const s = await routed(t, 'fsthrows'); assert.deepEqual(await s.launch(), { ok: false, error: 'harness-failed' }); assert.equal(s.last().why, 'harness fsthrows preflight: cannot open a host file (ENOENT)'); clean(JSON.stringify(s.last()), s.root); }); test('a harness the loader refuses names its code only, never the loader message or a path', async t => { const s = await routed(t, 'absent'); assert.deepEqual(await s.launch(), { ok: false, error: 'harness-refused' }); assert.match(s.last().why, /^harness absent: [a-z][a-z0-9-]*$/); clean(JSON.stringify(s.last()), s.root); }); test('the halt gate refusal names the instance, never its data root, and keeps exit 1 and the count', async t => { const { root, dir } = scratch(t); const result = { version: 1, generation: '11111111-1111-4111-8111-111111111111', operation: '11111111-1111-4111-8111-111111111111', business: 'demo', human: 'jason', at: '2026-10-20T00:00:00.000Z', recovered: false, halted: true, quiescent: false, remaining: 2, stopped: 0 }; const x = io(); await assert.rejects(main(['halt', '--business', 'demo'], x, { system: { dataRoot: join(dir, 'data') }, transport: () => async () => result }), e => { assert.equal(e.exitCode, 1); assert.equal(e.message, 'halt gate is on for this instance; 2 stops unconfirmed. Use mosaic processing status'); clean(e.message, root); return true; }); const odd = io(); await assert.rejects(main(['halt', '--business', 'demo'], odd, { system: { dataRoot: join(dir, 'data') }, transport: () => async () => ({ ...result, remaining: join(dir, 'x') }) }), e => { assert.equal(e.message, 'halt gate is on for this instance; unknown stops unconfirmed. Use mosaic processing status'); clean(e.message, root); return true; }); }); test('a system config problem names the config by label and never relays mosaic-config stderr', t => { const { root, dir } = scratch(t); // Missing: the label for where it came from, with the bootstrap hint. assert.throws(() => loadSystem({ env: { PATH: process.env.PATH, HOME: dir, MOSAIC_CONFIG: join(dir, 'absent.json') } }), e => { assert.ok(e instanceof CliError, e.stack); assert.equal(e.exitCode, 3); assert.equal(e.message, 'system config problem (mosaic-config exit 3): configuration not found: the system config MOSAIC_CONFIG names (run scripts/bootstrap.sh to create it)'); clean(e.message, root); return true; }); assert.throws(() => loadSystem({ env: { PATH: process.env.PATH, HOME: dir } }), e => { assert.match(e.message, /^system config problem \(mosaic-config exit 3\): configuration not found: the default system config \(run scripts\/bootstrap\.sh to create it\)$/); clean(e.message, root); return true; }); // A field whose value quotes the sentinel: the field and its constraint, never the value. const f = fixture(dir, 'acme'); const doc = JSON.parse(readFileSync(f.config, 'utf8')); writeFileSync(f.config, JSON.stringify({ ...doc, dataRoot: `relative/${secret}` })); assert.throws(() => loadSystem({ env: f.env }), e => { assert.equal(e.exitCode, 3); assert.match(e.message, /^system config problem \(mosaic-config exit \d+\): dataRoot: expected a canonical absolute instance data path: the system config MOSAIC_CONFIG names$/); clean(e.message, root); return true; }); }); // scripts/mosaic-config.mjs run standalone: every refusal names the config by where it came from, // the data root and its marker by their roles, and a key only when it is a plain name. Exit codes // are unchanged: 2 invalid, 3 missing or unclaimed, 4 usage. const CONFIG_SCRIPT = new URL('../../../scripts/mosaic-config.mjs', import.meta.url).pathname; function mosaicConfig(home, env, ...args) { return spawnSync(process.execPath, [CONFIG_SCRIPT, ...args], { encoding: 'utf8', env: { PATH: process.env.PATH, HOME: home, ...env } }); } function refusedBy(r, status, text, root) { assert.equal(r.status, status, r.stderr); assert.equal(r.stdout, ''); assert.ok(r.stderr.includes(text), r.stderr); clean(r.stderr, root); } test('mosaic-config refusals name the config, data root and keys by label, never a host path or value', t => { const { root, dir } = scratch(t); const file = join(dir, 'config.json'); const env = { MOSAIC_CONFIG: file }; writeFileSync(file, 'not json'); refusedBy(mosaicConfig(dir, env, 'validate'), 2, 'mosaic-config: configuration is not valid JSON (the system config MOSAIC_CONFIG names)', root); const good = { configVersion: 1, environment: 'development', dataRoot: join(dir, 'data'), execution: { backend: 'docker', provider: 'zai', model: 'glm-5.3-flash' } }; writeFileSync(file, JSON.stringify({ ...good, [join(dir, 'k')]: 1 })); refusedBy(mosaicConfig(dir, env, 'validate'), 2, 'unsupported configuration key: that is not a plain name', root); writeFileSync(file, JSON.stringify({ ...good, console: { [join(dir, 'k')]: 1 } })); refusedBy(mosaicConfig(dir, env, 'validate'), 2, 'console has a key that is not a plain name', root); writeFileSync(file, JSON.stringify({ ...good, dataRoot: dir })); refusedBy(mosaicConfig(dir, env, 'validate'), 2, 'execution "dataRoot" must not be or contain the home directory', root); writeFileSync(join(dir, 'real.json'), JSON.stringify(good)); const link = join(dir, 'link.json'); symlinkSync(join(dir, 'real.json'), link); refusedBy(mosaicConfig(dir, { MOSAIC_CONFIG: link }, 'validate'), 2, 'configuration path must be a regular, non-symbolic-link file: the system config MOSAIC_CONFIG names', root); // A named instance: MOSAIC_CONFIG naming another file is usage (4); an unmarked root is 3. refusedBy(mosaicConfig(dir, env, 'validate', '--instance', 'alpha'), 4, 'instance-config-mismatch: --instance alpha names its own config, but MOSAIC_CONFIG names another file', root); refusedBy(mosaicConfig(dir, {}, 'validate', '--instance', join(dir, 'x')), 4, 'instance-name-invalid: that name must match', root); const named = join(dir, '.config', 'mosaic-alpha'); mkdirSync(named, { recursive: true, mode: 0o700 }); mkdirSync(join(dir, '.mosaic-alpha'), { mode: 0o700 }); writeFileSync(join(named, 'config.json'), JSON.stringify({ ...good, dataRoot: join(dir, '.mosaic-alpha') })); refusedBy(mosaicConfig(dir, {}, 'validate', '--instance', 'alpha'), 3, 'instance-root-unclaimed: the data root holds no instance.json naming alpha', root); mkdirSync(join(dir, '.mosaic-alpha', 'instance.json')); refusedBy(mosaicConfig(dir, {}, 'validate', '--instance', 'alpha'), 3, "instance-root-unclaimed: the data root's instance marker must be a regular, non-symbolic-link file", root); });