import assert from 'node:assert/strict'; import { copyFile, mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'; import os from 'node:os'; import path from 'node:path'; import { spawnSync } from 'node:child_process'; import test from 'node:test'; const root = process.cwd(); const verifierPath = path.join(root, 'scripts', 'gate-verify.mjs'); const manifestPath = path.join(root, 'gates', 'gates.manifest.json'); const requiredGateId = 'hook-pre-push'; function output(result) { return `${result.stdout ?? ''}\n${result.stderr ?? ''}`; } function shrinkManifest(manifest, removedGateId) { const removedGate = manifest.gates.find((gate) => gate.id === removedGateId); assert.ok(removedGate, `fixture gate ${removedGateId} must exist`); const removedCaseRefs = new Set( removedGate.cases.map((gateCase) => `${removedGateId}/${gateCase.id}`), ); const removedCriterionIds = new Set( manifest.criteria .filter( (criterion) => criterion.caseRefs.length > 0 && criterion.caseRefs.every((caseRef) => removedCaseRefs.has(caseRef)), ) .map((criterion) => criterion.id), ); manifest.gates = manifest.gates.filter((gate) => gate.id !== removedGateId); manifest.criteria = manifest.criteria .filter((criterion) => !removedCriterionIds.has(criterion.id)) .map((criterion) => ({ ...criterion, caseRefs: criterion.caseRefs.filter((caseRef) => !removedCaseRefs.has(caseRef)), ...(criterion.gateRefs ? { gateRefs: criterion.gateRefs.filter((gateId) => gateId !== removedGateId) } : {}), })); manifest.proseClaims = manifest.proseClaims.filter( (claim) => !removedCriterionIds.has(claim.criterionId) && !removedCaseRefs.has(claim.caseRef), ); manifest.compatibilityScenarios = manifest.compatibilityScenarios .map((scenario) => ({ ...scenario, caseRefs: scenario.caseRefs.filter((caseRef) => !removedCaseRefs.has(caseRef)), })) .filter((scenario) => scenario.caseRefs.length > 0); for (const gate of manifest.gates) { for (const gateCase of gate.cases) { gateCase.criterionIds = gateCase.criterionIds.filter( (criterionId) => !removedCriterionIds.has(criterionId), ); } } } test('shrinking the verifier inventory and manifest together is rejected by a same-checkout baseline', async () => { const fixture = await mkdtemp(path.join(os.tmpdir(), 'rm02-shrink-both-')); try { await mkdir(path.join(fixture, 'scripts'), { recursive: true }); await mkdir(path.join(fixture, 'gates'), { recursive: true }); const verifier = await readFile(verifierPath, 'utf8'); const inventoryEntry = " ['hook-pre-push', '.husky/pre-push'],\n"; assert.equal(verifier.split(inventoryEntry).length - 1, 1, 'source inventory fixture drifted'); await writeFile( path.join(fixture, 'scripts', 'gate-verify.mjs'), verifier.replace(inventoryEntry, ''), ); await copyFile( path.join(root, 'gates', 'required-gates.baseline.json'), path.join(fixture, 'gates', 'required-gates.baseline.json'), ); const manifest = JSON.parse(await readFile(manifestPath, 'utf8')); shrinkManifest(manifest, requiredGateId); await writeFile( path.join(fixture, 'gates', 'gates.manifest.json'), `${JSON.stringify(manifest)}\n`, ); const result = spawnSync( process.execPath, [ path.join(fixture, 'scripts', 'gate-verify.mjs'), '--root', fixture, '--manifest', 'gates/gates.manifest.json', '--structure-only', ], { cwd: fixture, encoding: 'utf8' }, ); assert.notEqual(result.status, 0, 'shrinking source anchor and manifest together must go red'); assert.match(output(result), /same-checkout required-gate baseline.*hook-pre-push/i); } finally { await rm(fixture, { recursive: true, force: true }); } }); test('shrinking the same-checkout baseline and manifest together is rejected by verifier inventory', async () => { const fixture = await mkdtemp(path.join(os.tmpdir(), 'rm02-shrink-baseline-manifest-')); try { await mkdir(path.join(fixture, 'scripts'), { recursive: true }); await mkdir(path.join(fixture, 'gates'), { recursive: true }); await copyFile(verifierPath, path.join(fixture, 'scripts', 'gate-verify.mjs')); const baseline = JSON.parse( await readFile(path.join(root, 'gates', 'required-gates.baseline.json'), 'utf8'), ); baseline.gates = baseline.gates.filter((gate) => gate.id !== requiredGateId); await writeFile( path.join(fixture, 'gates', 'required-gates.baseline.json'), `${JSON.stringify(baseline)}\n`, ); const manifest = JSON.parse(await readFile(manifestPath, 'utf8')); shrinkManifest(manifest, requiredGateId); await writeFile( path.join(fixture, 'gates', 'gates.manifest.json'), `${JSON.stringify(manifest)}\n`, ); const result = spawnSync( process.execPath, [ path.join(fixture, 'scripts', 'gate-verify.mjs'), '--root', fixture, '--manifest', 'gates/gates.manifest.json', '--structure-only', ], { cwd: fixture, encoding: 'utf8' }, ); assert.notEqual(result.status, 0, 'shrinking baseline and manifest together must go red'); assert.match(output(result), /verifier inventory.*hook-pre-push.*baseline/i); } finally { await rm(fixture, { recursive: true, force: true }); } }); test('every gate carries an evidence-side subject distinct from its definition', async () => { const manifest = JSON.parse(await readFile(manifestPath, 'utf8')); for (const gate of manifest.gates) { assert.ok(gate.cases.length > 0, `${gate.id} must have consumable evidence`); for (const gateCase of gate.cases) { assert.equal( gateCase.evidence?.subject, gate.id, `${gate.id}/${gateCase.id} must source its subject from the evidence record`, ); } } }); test('inventory claim control rejects qualified independence wording', async () => { const fixture = await mkdtemp(path.join(os.tmpdir(), 'rm02-inventory-overclaim-')); const artifacts = [ 'gates/required-gates.baseline.json', 'gates/gates.manifest.json', 'docs/PRD.md', 'docs/ADMIN-GUIDE/quality-gate-registry.md', 'docs/DEVELOPER-GUIDE/quality-gate-registry.md', 'docs/remediation/GATE-CLAIMS.md', 'docs/plans/2026-08-01-rm-02-gate-registry.md', 'docs/SITEMAP.md', ]; try { await mkdir(path.join(fixture, 'scripts'), { recursive: true }); await copyFile( path.join(root, 'scripts', 'gate-inventory-claim-control.mjs'), path.join(fixture, 'scripts', 'gate-inventory-claim-control.mjs'), ); for (const relativePath of artifacts) { const target = path.join(fixture, relativePath); await mkdir(path.dirname(target), { recursive: true }); await copyFile(path.join(root, relativePath), target); } const prd = path.join(fixture, 'docs', 'PRD.md'); await writeFile( prd, `${await readFile(prd, 'utf8')}\nThis provides an independent seven-gate baseline comparison.\n`, ); const result = spawnSync( process.execPath, [path.join(fixture, 'scripts', 'gate-inventory-claim-control.mjs')], { cwd: fixture, encoding: 'utf8' }, ); assert.equal(result.status, 84, output(result)); assert.match(output(result), /INVENTORY_CLAIM_OVERSTATED.*docs\/PRD\.md/i); } finally { await rm(fixture, { recursive: true, force: true }); } }); test('evidence population control depends on production result consumption wiring', async () => { const fixture = await mkdtemp(path.join(os.tmpdir(), 'rm02-evidence-consumer-inert-')); try { await mkdir(path.join(fixture, 'scripts'), { recursive: true }); await mkdir(path.join(fixture, 'gates'), { recursive: true }); const verifier = await readFile(verifierPath, 'utf8'); const consumer = ` const subjectFailure = consumeEvidenceSubject(gate, result.evidence);\n if (subjectFailure) failures.push(subjectFailure);\n`; assert.equal(verifier.split(consumer).length - 1, 1, 'consumer fixture drifted'); await writeFile( path.join(fixture, 'scripts', 'gate-verify.mjs'), verifier.replace(consumer, ''), ); await copyFile( path.join(root, 'scripts', 'gate-population-control.mjs'), path.join(fixture, 'scripts', 'gate-population-control.mjs'), ); await copyFile(manifestPath, path.join(fixture, 'gates', 'gates.manifest.json')); const result = spawnSync( process.execPath, [path.join(fixture, 'scripts', 'gate-population-control.mjs'), 'evidence-subject'], { cwd: fixture, encoding: 'utf8' }, ); assert.equal(result.status, 0, output(result)); assert.match(output(result), /did not reject every registered gate/i); } finally { await rm(fixture, { recursive: true, force: true }); } }); test('production verification has a closed current-tree observation renderer', async () => { const verifier = await readFile(verifierPath, 'utf8'); assert.doesNotMatch(verifier, /from ['"]\.\/gate-history\.mjs['"]/); assert.doesNotMatch(verifier, /\bverifyHistory\s*\(/); assert.doesNotMatch(verifier, /history[-_ ]?provenance/i); const attacks = [ [ 'history wording', ' /^META-NEGATIVE-CONTROL /,', ' /^HISTORY PROVENANCE VERIFIED /,\n /^META-NEGATIVE-CONTROL /,', ], [ 'renamed ancestry wording', ' /^META-NEGATIVE-CONTROL /,', ' /^COMMIT ANCESTRY VERIFIED /,\n /^META-NEGATIVE-CONTROL /,', ], [ 'provider lineage wording', ' /^META-NEGATIVE-CONTROL /,', ' /^PROVIDER LINEAGE SUCCESS /,\n /^META-NEGATIVE-CONTROL /,', ], ['writer assertion bypass', ' assertCurrentTreeObservation(output);\n', ''], [ 'final success stdout bypass', ' writeCurrentTreeOutput(\n `REGISTRY SUMMARY open behavior deltas: ${defects}; required-behavior conformance is not asserted while deltas remain`,\n );', " process.stdout.write('COMMIT ANCESTRY VERIFIED\\n');", ], [ 'direct success stderr bypass', " const defects = observations.filter((line) => line.startsWith('DEFECT ')).length;", " process.stderr.write('PROVIDER LINEAGE SUCCESS\\n');\n const defects = observations.filter((line) => line.startsWith('DEFECT ')).length;", ], ]; for (const [name, find, replace] of attacks) { const fixture = await mkdtemp(path.join(os.tmpdir(), 'rm02-history-renderer-')); try { await mkdir(path.join(fixture, 'scripts'), { recursive: true }); assert.equal(verifier.split(find).length - 1, 1, `${name}: fixture drifted`); await writeFile( path.join(fixture, 'scripts', 'gate-verify.mjs'), verifier.replace(find, replace), ); await copyFile( path.join(root, 'scripts', 'gate-history-exclusion-control.mjs'), path.join(fixture, 'scripts', 'gate-history-exclusion-control.mjs'), ); const result = spawnSync( process.execPath, [path.join(fixture, 'scripts', 'gate-history-exclusion-control.mjs')], { cwd: fixture, encoding: 'utf8' }, ); assert.equal(result.status, 79, `${name}: ${output(result)}`); assert.match(output(result), /HISTORY_PROVENANCE_FORBIDDEN/); } finally { await rm(fixture, { recursive: true, force: true }); } } });