Unify halt and fire cancellation by verified run identity with durable first-owner receipts and shared deadlines. Integrate the instance control with the current host, library and tester-socket paths. Focused nine-file evidence is 62 passing tests; broad suites, mutants and review remain pending.
37 lines
2.7 KiB
JavaScript
37 lines
2.7 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { spawnSync } from 'node:child_process';
|
|
import { mkdtempSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import { join } from 'node:path';
|
|
import { initializeControl, transition } from '../src/state.mjs';
|
|
import { sandboxProblem } from '../../seat/src/sandbox.mjs';
|
|
const skip = sandboxProblem() ?? false;
|
|
test('the actual nested read-only control mount refuses unlink, replacement and parent removal', { skip }, t => {
|
|
const root = mkdtempSync(join(tmpdir(), 'processing-mount-'));
|
|
t.after(() => rmSync(root, { recursive: true, force: true }));
|
|
initializeControl(root); transition(root, { business: 'demo', human: 'jason', halted: true });
|
|
const file = join(root, 'control', 'processing.json'), bytes = readFileSync(file), inode = statSync(file).ino;
|
|
writeFileSync(join(root, 'replacement'), 'off replacement');
|
|
const r = spawnSync('bwrap', ['--unshare-user', '--unshare-pid', '--die-with-parent',
|
|
'--ro-bind', '/usr', '/usr', '--symlink', 'usr/bin', '/bin', '--symlink', 'usr/lib', '/lib',
|
|
'--symlink', 'usr/lib64', '/lib64', '--proc', '/proc', '--dev', '/dev',
|
|
'--bind', root, '/fixture', '--ro-bind', join(root, 'control'), '/fixture/control',
|
|
'/bin/sh', '-c', 'rm /fixture/control/processing.json && exit 10; echo unlink-refused; mv /fixture/replacement /fixture/control/processing.json && exit 11; echo replace-refused; rm -rf /fixture/control && exit 12; echo parent-refused'],
|
|
{ encoding: 'utf8', timeout: 5000 });
|
|
assert.equal(r.status, 0, r.stderr);
|
|
assert.match(r.stdout, /unlink-refused/); assert.match(r.stdout, /replace-refused/); assert.match(r.stdout, /parent-refused/);
|
|
assert.deepEqual(readFileSync(file), bytes); assert.equal(statSync(file).ino, inode);
|
|
});
|
|
test('the image entrypoint refuses halted and stale generations using the shared reader', t => {
|
|
const root = mkdtempSync(join(tmpdir(), 'processing-entry-'));
|
|
t.after(() => rmSync(root, { recursive: true, force: true }));
|
|
const initial = initializeControl(root), entry = new URL('../src/entrypoint.mjs', import.meta.url).pathname;
|
|
const run = generation => spawnSync(process.execPath, [entry, root], { env: { ...process.env, MOSAIC_PROCESSING_GENERATION: generation }, encoding: 'utf8' });
|
|
assert.equal(run(initial.generation).status, 0);
|
|
const halt = transition(root, { business: 'demo', human: 'jason', halted: true });
|
|
assert.equal(run(initial.generation).status, 3);
|
|
transition(root, { business: 'demo', human: 'jason', halted: false, generation: halt.generation });
|
|
const stale = run(initial.generation); assert.equal(stale.status, 3); assert.match(stale.stderr, /processing-generation-changed/);
|
|
});
|