import test from 'node:test'; import assert from 'node:assert/strict'; import { spawnSync } from 'node:child_process'; import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs'; import { join } from 'node:path'; import { fixture, tmp } from '../../cli/tests/helpers.mjs'; import { initializeControl, transition } from '../src/state.mjs'; const TASK = new URL('../../../scripts/mosaic-task.mjs', import.meta.url).pathname; function setup(t, stop = false) { const root = tmp(t), f = fixture(root), bin = join(root, 'bin'); mkdirSync(bin); const source = new URL('../src/state.mjs', import.meta.url).href; const runtimes = new URL('../src/runtimes.mjs', import.meta.url).href; writeFileSync(join(bin, 'docker'), `#!${process.execPath} (async()=>{const args=process.argv.slice(2);require('node:fs').writeFileSync(${JSON.stringify(join(root, 'called'))},JSON.stringify(args)); ${stop ? `const {transition}=await import(${JSON.stringify(source)});const {updateRuntime}=await import(${JSON.stringify(runtimes)});const s=transition(process.env.MOSAIC_DATA_ROOT,{business:'acme',human:'jason',halted:true});const tag=args.find(x=>x.startsWith('mosaic.runtime='));if(tag)updateRuntime(process.env.MOSAIC_DATA_ROOT,tag.slice(15),{haltGeneration:s.generation});process.exit(137);` : `console.log('normal response');`} })().catch(e=>{console.error(e.message);process.exit(1)});\n`, { mode: 0o755 }); f.env.PATH = bin + ':' + f.env.PATH; const task = join(root, 'task.json'); writeFileSync(task, JSON.stringify({ taskVersion: 1, id: 'one', prompt: 'request' })); const run = args => spawnSync(process.execPath, [TASK, ...args], { env: f.env, encoding: 'utf8', timeout: 10000 }); return { root, f, task, run }; } test('task halt keeps the true Docker exit status, writes kill-switch once and leaves snapshots unchanged', t => { const f = setup(t, true), result = f.run(['run', f.task]); assert.equal(result.status, 1, result.stderr); const runs = join(f.f.dataRoot, 'runs'), [id] = readdirSync(runs); const file = join(runs, id, 'result.json'), bytes = readFileSync(file), record = JSON.parse(bytes); assert.equal(record.reason, 'kill-switch'); assert.match(record.haltGeneration, /^[a-f0-9-]{36}$/); assert.equal(record.exitCode, 137); assert.equal(record.signal, null); assert.deepEqual(readFileSync(join(runs, id, 'task.json')), readFileSync(f.task)); const retry = f.run(['retry', id]); assert.equal(retry.status, 3, retry.stderr); assert.deepEqual(readFileSync(file), bytes); assert.deepEqual(readdirSync(runs), [id]); }); test('halt refuses direct task launch before Docker or a run record; clear allows a fresh normal result', t => { const f = setup(t), actor = { business: 'acme', human: 'jason' }; initializeControl(f.f.dataRoot); const halted = transition(f.f.dataRoot, { ...actor, halted: true }); const result = f.run(['run', f.task]); assert.equal(result.status, 3, result.stderr); assert.equal(existsSync(join(f.root, 'called')), false); assert.equal(existsSync(join(f.f.dataRoot, 'runs')), false); transition(f.f.dataRoot, { ...actor, halted: false, generation: halted.generation }); const normal = f.run(['run', f.task]); assert.equal(normal.status, 0, normal.stderr); const [id] = readdirSync(join(f.f.dataRoot, 'runs')); const record = JSON.parse(readFileSync(join(f.f.dataRoot, 'runs', id, 'result.json'))); assert.equal(record.reason, null); assert.equal(record.status, 'succeeded'); assert.equal(record.response, 'normal response'); assert.equal('haltGeneration' in record, false); }); for (const [script, args] of [['hello.sh', []], ['verify.sh', []], ['agent.sh', ['fixture']]]) { test(`halt refuses ${script} before Docker, credentials or release alignment`, t => { const f = setup(t); initializeControl(f.f.dataRoot); transition(f.f.dataRoot, { business: 'acme', human: 'jason', halted: true }); const r = spawnSync('bash', [new URL('../../../scripts/' + script, import.meta.url).pathname, ...args], { env: f.f.env, encoding: 'utf8', timeout: 5000 }); assert.equal(r.status, 3, r.stderr); assert.match(r.stderr, /processing-halted/); assert.equal(existsSync(join(f.root, 'called')), false); }); } for (const starter of ['task', 'container-cli']) test(`a nonzero Docker client exit retains its unresolved runtime receipt (${starter})`, t => { const f = setup(t); writeFileSync(join(f.root, 'bin', 'docker'), `#!${process.execPath}\nprocess.exit(137);\n`, { mode: 0o755 }); const result = starter === 'task' ? f.run(['run', f.task]) : spawnSync(process.execPath, [new URL('../src/container-cli.mjs', import.meta.url).pathname, 'request'], { env: f.f.env, encoding: 'utf8', timeout: 10000 }); assert.equal(result.status, starter === 'task' ? 1 : 137, result.stderr); const files = readdirSync(join(f.f.dataRoot, 'control', 'runtimes')); assert.equal(files.length, 1, 'a failed Docker client cannot prove the container stopped'); const runtime = JSON.parse(readFileSync(join(f.f.dataRoot, 'control', 'runtimes', files[0]))); assert.notEqual(runtime.phase, 'ended'); });