import assert from 'node:assert/strict'; import { mkdir, rm, writeFile } from 'node:fs/promises'; import path from 'node:path'; import { spawn, spawnSync } from 'node:child_process'; import test from 'node:test'; import { assessProviderEvidence, listProspectiveCommits, readManifestAtCommit, replayCommit, verifyHistory, } from './gate-history.mjs'; const fixtureRoot = path.join(process.cwd(), '.mosaic-test-work', `gate-history-${process.pid}`); function sandboxUnavailable(result) { if (result.sandboxLauncher !== 'bwrap' || result.sandboxEntered === true) return false; const detail = `${result.stdout ?? ''}${result.stderr ?? ''}${result.error?.message ?? ''}`; const bubblewrapSpawnDenied = ['EPERM', 'EACCES', 'ENOENT'].includes(result.error?.code) && /spawnSync bwrap/i.test(result.error?.message ?? ''); if ( !bubblewrapSpawnDenied && !/bwrap:.*(?:Operation not permitted|Creating new namespace failed)/i.test(detail) ) { return false; } assert.notEqual(result.status, 0, 'sandbox unavailability must remain terminal nonzero'); return true; } function git(root, ...args) { const result = spawnSync('git', args, { cwd: root, encoding: 'utf8' }); assert.equal(result.status, 0, result.stderr); return result.stdout.trim(); } async function commitManifest(root, marker) { await mkdir(path.join(root, 'gates'), { recursive: true }); await writeFile( path.join(root, 'gates', 'gates.manifest.json'), `${JSON.stringify({ schemaVersion: 1, marker })}\n`, ); git(root, 'add', '.'); git(root, 'commit', '-m', marker); return git(root, 'rev-parse', 'HEAD'); } test.after(async () => { await rm(fixtureRoot, { recursive: true, force: true }); }); test('sandbox refusal classification requires Bubblewrap provenance', () => { for (const code of ['EPERM', 'EACCES', 'ENOENT']) { assert.equal( sandboxUnavailable({ status: null, error: { code, message: `spawnSync bwrap ${code}` }, sandboxLauncher: 'bwrap', sandboxEntered: false, }), true, ); } assert.equal( sandboxUnavailable({ status: null, error: { code: 'EPERM', message: 'spawnSync git EPERM' }, }), false, ); assert.equal( sandboxUnavailable({ status: 1, stderr: 'historical verifier said bwrap ENOENT' }), false, ); assert.equal( sandboxUnavailable({ status: 1, stderr: 'bwrap: Creating new namespace failed: Operation not permitted', sandboxLauncher: 'bwrap', sandboxEntered: false, }), true, ); assert.equal( sandboxUnavailable({ status: 1, stderr: 'bwrap: Creating new namespace failed: Operation not permitted', }), false, ); assert.equal( sandboxUnavailable({ status: 1, stderr: 'bwrap: Creating new namespace failed: Operation not permitted', sandboxLauncher: 'bwrap', sandboxEntered: true, }), false, ); }); test('prospective history reads each commit own manifest rather than the current tree', async () => { await rm(fixtureRoot, { recursive: true, force: true }); await mkdir(fixtureRoot, { recursive: true }); git(fixtureRoot, 'init', '-q'); git(fixtureRoot, 'config', 'user.name', 'gate-test'); git(fixtureRoot, 'config', 'user.email', 'gate-test@example.invalid'); await writeFile(path.join(fixtureRoot, 'activation.txt'), 'activation\n'); git(fixtureRoot, 'add', '.'); git(fixtureRoot, 'commit', '-m', 'activation'); const activation = git(fixtureRoot, 'rev-parse', 'HEAD'); const first = await commitManifest(fixtureRoot, 'FIRST'); const second = await commitManifest(fixtureRoot, 'SECOND'); assert.deepEqual(await listProspectiveCommits(fixtureRoot, activation, second), [first, second]); assert.equal((await readManifestAtCommit(fixtureRoot, first)).marker, 'FIRST'); assert.equal((await readManifestAtCommit(fixtureRoot, second)).marker, 'SECOND'); }); test('historical replay executes each selected commit verifier from that commit tree', async () => { const root = `${fixtureRoot}-replay`; await rm(root, { recursive: true, force: true }); await mkdir(path.join(root, 'scripts'), { recursive: true }); await mkdir(path.join(root, 'gates'), { recursive: true }); git(root, 'init', '-q'); git(root, 'config', 'user.name', 'gate-test'); git(root, 'config', 'user.email', 'gate-test@example.invalid'); await writeFile(path.join(root, 'gates', 'gates.manifest.json'), '{"schemaVersion":1}\n'); await writeFile( path.join(root, 'scripts', 'gate-verify.mjs'), "process.stderr.write('OLD TREE INERT\\n'); process.exitCode = 1;\n", ); git(root, 'add', '.'); git(root, 'commit', '-m', 'inert historical verifier'); const inert = git(root, 'rev-parse', 'HEAD'); await writeFile( path.join(root, 'scripts', 'gate-verify.mjs'), "process.stdout.write('NEW TREE VERIFIED\\n');\n", ); git(root, 'add', '.'); git(root, 'commit', '-m', 'fixed historical verifier'); const fixed = git(root, 'rev-parse', 'HEAD'); const inertResult = await replayCommit(root, inert); const fixedResult = await replayCommit(root, fixed); if (sandboxUnavailable(inertResult) || sandboxUnavailable(fixedResult)) return; assert.notEqual(inertResult.status, 0); assert.match(inertResult.stderr, /OLD TREE INERT/); assert.equal(fixedResult.status, 0); assert.match(fixedResult.stdout, /NEW TREE VERIFIED/); }); test('historical install lifecycle cannot replace an authoritative verifier', async () => { const root = `${fixtureRoot}-install-tamper`; await rm(root, { recursive: true, force: true }); await mkdir(path.join(root, 'scripts'), { recursive: true }); await mkdir(path.join(root, 'gates'), { recursive: true }); git(root, 'init', '-q'); git(root, 'config', 'user.name', 'gate-test'); git(root, 'config', 'user.email', 'gate-test@example.invalid'); await writeFile(path.join(root, 'gates', 'gates.manifest.json'), '{"schemaVersion":1}\n'); await writeFile( path.join(root, 'scripts', 'gate-verify.mjs'), "process.stderr.write('ORIGINAL VERIFIER RAN\\n'); process.exitCode = 7;\n", ); await writeFile(path.join(root, 'forged.mjs'), "process.stdout.write('FORGED SUCCESS\\n');\n"); await writeFile( path.join(root, 'package.json'), `${JSON.stringify({ name: 'historical-install-tamper', version: '1.0.0', scripts: { postinstall: 'cp forged.mjs scripts/gate-verify.mjs' }, })}\n`, ); await writeFile( path.join(root, 'pnpm-lock.yaml'), "lockfileVersion: '9.0'\nsettings:\n autoInstallPeers: true\n excludeLinksFromLockfile: false\nimporters:\n .: {}\n", ); git(root, 'add', '.'); git(root, 'commit', '-m', 'tampering lifecycle fixture'); const commit = git(root, 'rev-parse', 'HEAD'); const result = await replayCommit(root, commit); assert.notEqual(result.status, 0); if (sandboxUnavailable(result)) return; assert.match(result.stderr, /authoritative archived file changed.*scripts\/gate-verify\.mjs/i); assert.doesNotMatch(result.stdout, /FORGED SUCCESS/); }); test('historical verifier receives no current-process secret environment', async () => { const root = `${fixtureRoot}-secretless`; await rm(root, { recursive: true, force: true }); await mkdir(path.join(root, 'scripts'), { recursive: true }); await mkdir(path.join(root, 'gates'), { recursive: true }); git(root, 'init', '-q'); git(root, 'config', 'user.name', 'gate-test'); git(root, 'config', 'user.email', 'gate-test@example.invalid'); await writeFile(path.join(root, 'gates', 'gates.manifest.json'), '{"schemaVersion":1}\n'); await writeFile( path.join(root, 'scripts', 'gate-verify.mjs'), "if (process.env.REPLAY_SENTINEL) { process.stderr.write('SECRET LEAKED\\n'); process.exitCode = 9; } else { process.stdout.write('SECRETLESS\\n'); }\n", ); git(root, 'add', '.'); git(root, 'commit', '-m', 'secretless replay fixture'); const commit = git(root, 'rev-parse', 'HEAD'); process.env.REPLAY_SENTINEL = 'must-not-cross-boundary'; try { const result = await replayCommit(root, commit); if (sandboxUnavailable(result)) return; assert.equal(result.status, 0, result.stderr); assert.match(result.stdout, /SECRETLESS/); assert.doesNotMatch( `${result.stdout}${result.stderr}`, /SECRET LEAKED|must-not-cross-boundary/, ); } finally { delete process.env.REPLAY_SENTINEL; } }); test('historical replay cannot observe a sibling process in the runner PID namespace', async () => { const root = `${fixtureRoot}-pidless`; await rm(root, { recursive: true, force: true }); await mkdir(path.join(root, 'scripts'), { recursive: true }); await mkdir(path.join(root, 'gates'), { recursive: true }); git(root, 'init', '-q'); git(root, 'config', 'user.name', 'gate-test'); git(root, 'config', 'user.email', 'gate-test@example.invalid'); await writeFile(path.join(root, 'gates', 'gates.manifest.json'), '{"schemaVersion":1}\n'); const sleeper = spawn('sleep', ['30'], { env: { ...process.env, REPLAY_PID_SENTINEL: 'must-not-be-visible' }, }); try { await writeFile( path.join(root, 'scripts', 'gate-verify.mjs'), `import { existsSync } from 'node:fs';\nif (existsSync('/proc/${sleeper.pid}/environ')) { process.stderr.write('HOST PID VISIBLE\\n'); process.exitCode = 9; } else { process.stdout.write('PIDLESS\\n'); }\n`, ); git(root, 'add', '.'); git(root, 'commit', '-m', 'pid-isolated replay fixture'); const commit = git(root, 'rev-parse', 'HEAD'); const result = await replayCommit(root, commit); if (sandboxUnavailable(result)) return; assert.equal(result.status, 0, result.stderr); assert.match(result.stdout, /PIDLESS/); assert.doesNotMatch(`${result.stdout}${result.stderr}`, /HOST PID VISIBLE/); } finally { sleeper.kill('SIGTERM'); } }); test('PR verification states the RM-60 boundary without executing an intermediate verifier', async () => { const root = `${fixtureRoot}-feature`; await rm(root, { recursive: true, force: true }); await mkdir(root, { recursive: true }); git(root, 'init', '-q'); git(root, 'config', 'user.name', 'gate-test'); git(root, 'config', 'user.email', 'gate-test@example.invalid'); await writeFile(path.join(root, 'activation.txt'), 'activation\n'); git(root, 'add', '.'); git(root, 'commit', '-m', 'activation'); const activation = git(root, 'rev-parse', 'HEAD'); git(root, 'update-ref', 'refs/remotes/origin/main', activation); await mkdir(path.join(root, 'scripts'), { recursive: true }); await mkdir(path.join(root, 'gates'), { recursive: true }); await writeFile(path.join(root, 'gates', 'gates.manifest.json'), '{"schemaVersion":1}\n'); await writeFile( path.join(root, 'scripts', 'gate-verify.mjs'), "process.stderr.write('INTERMEDIATE INERT\\n'); process.exitCode = 1;\n", ); git(root, 'add', '.'); git(root, 'commit', '-m', 'inert intermediate'); await writeFile( path.join(root, 'scripts', 'gate-verify.mjs'), "process.stdout.write('HEAD HEALTHY\\n');\n", ); git(root, 'add', '.'); git(root, 'commit', '-m', 'healthy head'); const previousBranch = process.env.CI_COMMIT_BRANCH; process.env.CI_COMMIT_BRANCH = 'feature/rm-02'; try { const result = await verifyHistory({ root, manifest: { schemaVersion: 1, activationCommit: activation }, }); assert.deepEqual(result.failures, []); assert.ok( result.observations.some((observation) => /DOES:.*current tree.*DOES NOT:.*isolated.*RM-60.*RM-59/i.test(observation), ), ); assert.ok( result.observations.some((observation) => /INTERMEDIATE REPLAY DEFERRED.*RM-60.*no success is inferred/i.test(observation), ), ); assert.ok(result.observations.every((observation) => !/INTERMEDIATE INERT/.test(observation))); } finally { if (previousBranch === undefined) delete process.env.CI_COMMIT_BRANCH; else process.env.CI_COMMIT_BRANCH = previousBranch; } }); test('provider evidence distinguishes retained success, failure, and absent history', () => { const pipelines = [ { commit: 'aaa', number: 1, status: 'success', steps: [{ name: 'gate-verify', status: 'success' }], }, { commit: 'bbb', number: 2, status: 'failure', steps: [{ name: 'gate-verify', status: 'failure' }], }, ]; assert.deepEqual(assessProviderEvidence('aaa', pipelines), { state: 'terminal-success', detail: 'pipeline and gate-verify step succeeded', }); assert.equal(assessProviderEvidence('bbb', pipelines).state, 'terminal-failure'); assert.equal(assessProviderEvidence('ccc', pipelines).state, 'absent'); }); test('duplicate gate-verify steps cannot establish provider success', () => { const result = assessProviderEvidence('aaa', [ { commit: 'aaa', number: 7, status: 'success', steps: [ { name: 'gate-verify', status: 'success' }, { name: 'gate-verify', status: 'failure' }, ], }, ]); assert.equal(result.state, 'terminal-failure'); assert.match(result.detail, /ambiguous.*gate-verify/i); }); test('a malformed single provider record cannot establish success', () => { assert.equal( assessProviderEvidence('aaa', [ { commit: 'aaa', status: 'success', steps: [{ name: 'gate-verify', status: 'success' }] }, ]).state, 'terminal-failure', ); assert.equal( assessProviderEvidence('bbb', [ { commit: 'bbb', number: 1, status: 'surprising', steps: [{ name: 'gate-verify', status: 'success' }], }, ]).state, 'terminal-failure', ); }); test('provider evidence selects the highest numbered rerun deterministically', () => { const failedThenSucceeded = [ { commit: 'aaa', number: 10, status: 'failure', steps: [{ name: 'gate-verify', status: 'failure' }], }, { commit: 'aaa', number: 11, status: 'success', steps: [{ name: 'gate-verify', status: 'success' }], }, ]; const succeededThenFailed = [ { commit: 'bbb', number: 21, status: 'success', steps: [{ name: 'gate-verify', status: 'success' }], }, { commit: 'bbb', number: 22, status: 'failure', steps: [{ name: 'gate-verify', status: 'failure' }], }, ]; assert.equal(assessProviderEvidence('aaa', failedThenSucceeded).state, 'terminal-success'); assert.equal(assessProviderEvidence('bbb', succeededThenFailed).state, 'terminal-failure'); assert.equal( assessProviderEvidence('ccc', [ { commit: 'ccc', status: 'success', steps: [{ name: 'gate-verify', status: 'success' }] }, { commit: 'ccc', status: 'failure', steps: [{ name: 'gate-verify', status: 'failure' }] }, ]).state, 'terminal-failure', ); });