#!/usr/bin/env node import { chmod, mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'; import os from 'node:os'; import path from 'node:path'; import { verifyRegistry } from './gate-verify.mjs'; const mode = process.argv[2]; const root = process.cwd(); const source = JSON.parse(await readFile(path.join(root, 'gates/gates.manifest.json'), 'utf8')); const expectedGateIds = source.gates.map((gate) => gate.id); if (expectedGateIds.length === 0) { process.stderr.write('gate population control requires a non-empty anchored inventory\n'); process.exit(2); } async function rejectedForEveryGate(mutate, diagnostic) { for (const gateId of expectedGateIds) { const manifest = structuredClone(source); const gate = manifest.gates.find((candidate) => candidate.id === gateId); mutate(gate); const directory = await mkdtemp(path.join(os.tmpdir(), 'gate-population-control-')); const manifestPath = path.join(directory, 'manifest.json'); try { await writeFile(manifestPath, `${JSON.stringify(manifest)}\n`); const result = await verifyRegistry({ root, manifest: manifestPath, structureOnly: true, fixtureProfile: false, }); if (!result.failures.some((failure) => diagnostic(failure, gateId))) return false; } finally { await rm(directory, { recursive: true, force: true }); } } return true; } let rejected; if (mode === 'evidence-subject') { rejected = true; for (const gateId of expectedGateIds) { const directory = await mkdtemp(path.join(os.tmpdir(), 'gate-evidence-consumption-')); try { await mkdir(path.join(directory, 'gates'), { recursive: true }); const probe = path.join(directory, 'gates', 'probe.sh'); await writeFile(probe, '#!/bin/sh\necho EVIDENCE_PROBE >&2\nexit 7\n'); await chmod(probe, 0o755); const manifest = { schemaVersion: 1, gateRoots: ['gates'], governingClaimFiles: [], coverageBoundary: { included: ['evidence fixture'], excluded: [], trackedBy: 'RM-02' }, criteria: [ { id: 'EVIDENCE-CONSUMPTION', originalText: 'Consumed evidence stays bound to its gate.', currentText: 'Consumed evidence stays bound to its gate.', claimType: 'integrity', source: 'gate-population-control', meaningChanges: [], caseRefs: [`${gateId}/probe`], }, ], proseClaims: [], compatibilityScenarios: [], gates: [ { id: gateId, source: 'gates/probe.sh', invocation: ['gates/probe.sh'], deployment: { kind: 'none', reason: 'population fixture' }, inertMutation: { file: 'gates/probe.sh', find: 'exit 7', replace: 'exit 0', caseId: 'probe', expected: { exitCode: 0 }, }, cases: [ { id: 'probe', criterionIds: ['EVIDENCE-CONSUMPTION'], mustFail: true, required: { exitCode: 7 }, actual: { exitCode: 7 }, evidence: { subject: 'different-gate-subject' }, reasonPattern: 'EVIDENCE_PROBE', }, ], }, ], }; const manifestPath = path.join(directory, 'gates', 'gates.manifest.json'); await writeFile(manifestPath, `${JSON.stringify(manifest)}\n`); const result = await verifyRegistry({ root: directory, manifest: manifestPath, structureOnly: false, fixtureProfile: true, }); if ( !result.failures.some( (failure) => failure.includes(`gate ${gateId}: consumed evidence subject`) && failure.includes('does not match gate definition'), ) ) { rejected = false; break; } } finally { await rm(directory, { recursive: true, force: true }); } } } else if (mode === 'type-strict') { rejected = await rejectedForEveryGate( (gate) => { gate.cases[0].actual.exitCode = '0'; }, (failure, gateId) => failure.includes( `${gateId}/${source.gates.find((gate) => gate.id === gateId).cases[0].id}.actual.exitCode`, ) && failure.includes('expected an integer'), ); } else { process.stderr.write(`unknown gate population control ${String(mode)}\n`); process.exit(2); } if (!rejected) { process.stdout.write(`${mode} population control did not reject every registered gate\n`); process.exit(0); } process.stderr.write(`${mode} population control rejected every registered gate\n`); process.exit(1);