test(handoff): bind verifier source and reject acceptance promotion

This commit is contained in:
cesnimda
2026-10-29 18:44:08 +01:00
parent 78e5296881
commit 657db9d294
2 changed files with 25 additions and 1 deletions
@@ -83,3 +83,9 @@ test('artifact paths cannot escape the imported package', () => shadow(base => {
test('an imported schema cannot weaken the trusted schema', () => shadow(base => {
fs.writeFileSync(path.join(base, 'HANDOFF_SCHEMA.json'), '{}');
}));
test('model quality cannot become PASS from the rejected development probes', () => shadow(base => {
json(base, 'ACCEPTANCE', value => { value.verdicts.find(item => item.criterion === 'CV AI Accuracy').status = 'PASS'; });
}));
test('a mandatory entry blocker cannot be self-deferred', () => shadow(base => {
json(base, 'ACCEPTANCE', value => { value.permissibleDeferrals.push('P10-ENTRY-01'); });
}));
@@ -162,6 +162,7 @@ export function validatePackage(base = directory) {
const prefixes = ['JobTrackerApi/', 'JobTrackerApi.Tests/', 'JobTrackerApi.MariaDb.Tests/', 'job-tracker-ui/', 'scripts/', 'tools/summarizer/', 'docs/pre-phase10-ux/', 'docs/redesign/phase-9/'];
const tracked = execFileSync('rtk', ['proxy', 'git', 'ls-files', '-z'], { cwd: root, encoding: 'utf8' }).split('\0').filter(name => prefixes.some(prefix => name.startsWith(prefix))).sort();
demand(Object.keys(source).length <= 5000 && JSON.stringify(Object.keys(source).sort()) === JSON.stringify(tracked), 'Frozen source membership does not match the tracked qualification scope');
execFileSync('rtk', ['proxy', 'git', 'diff', '--quiet', evidence.testedCommit, '--', ...prefixes], { cwd: root });
for (const [name, expected] of Object.entries(source)) {
demand(!path.isAbsolute(name) && !name.split(/[\\/]/).includes('..'), 'Unsafe source path');
const file = path.resolve(root, name);
@@ -171,7 +172,10 @@ export function validatePackage(base = directory) {
const verifierFiles = JSON.parse(artifact(evidence.verifierSourceArtifactId).toString('utf8'));
const expectedVerifierFiles = ['verify.mjs', 'verify.test.mjs', 'package.test.mjs', 'verify_git.py', 'HANDOFF_SCHEMA.json'].map(name => 'docs/automation/handoffs/pre-phase10/' + name).sort();
demand(JSON.stringify(Object.keys(verifierFiles).sort()) === JSON.stringify(expectedVerifierFiles), 'Missing verifier source membership');
for (const [name, expected] of Object.entries(verifierFiles)) verifyBytes(fs.readFileSync(path.join(root, name)), expected);
for (const [name, expected] of Object.entries(verifierFiles)) {
verifyBytes(fs.readFileSync(path.join(root, name)), expected);
verifyBytes(execFileSync('rtk', ['proxy', 'git', 'show', `${evidence.verifierCommit}:${name}`], { cwd: root }), expected);
}
execFileSync('rtk', ['proxy', 'git', 'merge-base', '--is-ancestor', evidence.verifierCommit, 'HEAD'], { cwd: root });
execFileSync('rtk', ['proxy', 'git', 'merge-base', '--is-ancestor', evidence.testedCommit, 'HEAD'], { cwd: root });
const delta = execFileSync('rtk', ['proxy', 'git', 'diff', '--name-only', evidence.testedCommit, 'HEAD'], { cwd: root, encoding: 'utf8' }).trim().split(/\r?\n/).filter(Boolean);
@@ -181,6 +185,20 @@ export function validatePackage(base = directory) {
const ids = new Set(documents.BLOCKERS.items.map(item => item.id));
demand(ids.size === documents.BLOCKERS.items.length, 'Duplicate blocker IDs');
demand(declared.unresolvedBlockerIds.every(id => ids.has(id)), 'Unresolved blocker is not registered');
const open = documents.BLOCKERS.items.filter(item => item.status !== 'CLOSED').map(item => item.id).sort();
demand(JSON.stringify([...declared.unresolvedBlockerIds].sort()) === JSON.stringify(open), 'Unresolved findings were omitted');
demand(declared.permissibleDeferrals.every(id => documents.BLOCKERS.items.some(item => item.id === id && item.classification === 'CONDITIONAL_DEVELOPMENT' && item.deferralAuthority)), 'A mandatory or unsupported condition was relabelled as deferrable');
const decisions = new Map(declared.verdicts.map(item => [item.criterion, item.status]));
demand(decisions.size === declared.verdicts.length, 'Duplicate acceptance criteria');
for (const [criterion, status] of [['CV AI Accuracy', 'BLOCKED'], ['Human/Device Acceptance', 'NOT TESTED'], ['Hosted CI', 'BLOCKED'], ['Production', 'NO-GO']])
demand(decisions.get(criterion) === status, 'Unqualified model/human/hosted/release acceptance was promoted');
const probe = JSON.parse(artifact('model-probe-results').toString('utf8'));
demand(probe.cases?.length === 3 && probe.cases.every(item => item.status === 'REJECTED_OR_FAILED'), 'This snapshot lacks its recorded negative CV-model evidence');
const oracles = evidence.reports.filter(item => ['windows-template-oracle', 'linux-template-oracle'].includes(item.id));
demand(oracles.length === 2 && oracles.every(item => item.status === 'PARTIAL' && item.exitCode !== 0), 'Original PDF failures were concealed');
demand(decisions.get('Automated PDF Fidelity') === 'FAIL' && decisions.get('PDF Spacing') === 'PARTIAL', 'Revised PDF failures were promoted');
const stages = documents.GATES.stages;
demand(stages.map(item => item.sequence).join(',') === '5,6,7,8,9,10,11' && stages[0].manualApprovals.includes('phase10-development-entry') && stages[0].manualApprovals.includes('candidate-specific-cleanup-scope') && stages.every(item => item.permissibleConditionalDeferrals.length === 0), 'Source-derived stage/approval conditions changed');
const observedLocalCommit = execFileSync('rtk', ['proxy', 'git', 'rev-parse', 'HEAD'], { cwd: root, encoding: 'utf8' }).trim();
const observedWorkingTreeClean = !execFileSync('rtk', ['proxy', 'git', 'status', '--porcelain'], { cwd: root, encoding: 'utf8' }).trim();
const branch = execFileSync('rtk', ['proxy', 'git', 'branch', '--show-current'], { cwd: root, encoding: 'utf8' }).trim();