diff --git a/docs/automation/handoffs/pre-phase10/ENTRY_PROPOSAL_SCHEMA.json b/docs/automation/handoffs/pre-phase10/ENTRY_PROPOSAL_SCHEMA.json index 9a381f6..707a5c0 100644 --- a/docs/automation/handoffs/pre-phase10/ENTRY_PROPOSAL_SCHEMA.json +++ b/docs/automation/handoffs/pre-phase10/ENTRY_PROPOSAL_SCHEMA.json @@ -36,7 +36,7 @@ "unchangedRevisedOracle": { "const": "FAIL" }, "proposedBaselineDiagnostic": { "const": "PASS_OWNER_APPROVED_S01_Y_METRIC" }, "metricDisposition": { "const": "OWNER_APPROVED_S01_METRIC_ONLY" }, - "humanVisual": { "const": "NOT_TESTED" }, + "humanVisual": { "const": "OWNER_APPROVED_FOR_DEVELOPMENT" }, "actualPrinting": { "const": "NOT_TESTED" } } }, diff --git a/docs/automation/handoffs/pre-phase10/PDF_VISUAL_APPROVAL.json b/docs/automation/handoffs/pre-phase10/PDF_VISUAL_APPROVAL.json new file mode 100644 index 0000000..b613af8 --- /dev/null +++ b/docs/automation/handoffs/pre-phase10/PDF_VISUAL_APPROVAL.json @@ -0,0 +1,14 @@ +{ + "kind": "DIRECT_OWNER_VISUAL_APPROVAL_FOR_DEVELOPMENT", + "receivedLocalDate": "2026-10-30", + "timezone": "Europe/Paris", + "reviewedPublishedCheckpoint": "214dbd6c4d173db72cba18959dd8f81c7fc67f64", + "frozenApplicationCommit": "ba04d817fed26d659bfa2a56b089d6cac851dc34", + "ownerStatement": "I have personally reviewed the revised CV PDFs and am satisfied with their visual appearance.", + "scope": "Current UI and CV/PDF visual presentation for proceeding toward Phase10 development; previously approved S01 metric retained", + "pdfVisualPresentation": "APPROVED_FOR_DEVELOPMENT", + "physicalPrinting": "NOT_TESTED", + "physicalDeviceAndAssistiveTechnology": "NOT_TESTED", + "protectedOrchestratorApproval": "NOT_SIGNED_BY_THIS_RECORD", + "production": "NO-GO" +} diff --git a/docs/automation/handoffs/pre-phase10/package.test.mjs b/docs/automation/handoffs/pre-phase10/package.test.mjs index aea8295..bff5488 100644 --- a/docs/automation/handoffs/pre-phase10/package.test.mjs +++ b/docs/automation/handoffs/pre-phase10/package.test.mjs @@ -118,6 +118,10 @@ test('approved S01 cannot conceal a failing revised baseline', () => shadow(base const bytes = zlib.gzipSync(JSON.stringify(value)); fs.writeFileSync(file, bytes); artifact.sha256 = hash(bytes); }); })); + +test('owner visual approval cannot be broadened to unperformed physical printing', () => shadow(base => { + json(base, 'PDF_VISUAL_APPROVAL', value => { value.physicalPrinting = 'PASS'; }); +})); test('a mandatory entry blocker cannot be self-deferred', () => shadow(base => { json(base, 'ACCEPTANCE', value => { value.permissibleDeferrals.push('P10-ENTRY-01'); }); })); diff --git a/docs/automation/handoffs/pre-phase10/verify.mjs b/docs/automation/handoffs/pre-phase10/verify.mjs index 7b609bd..7de4b89 100644 --- a/docs/automation/handoffs/pre-phase10/verify.mjs +++ b/docs/automation/handoffs/pre-phase10/verify.mjs @@ -207,7 +207,7 @@ export function validatePackage(base = directory) { verifyBytes(fs.readFileSync(file), expected); } 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', 'S01_METRIC_APPROVAL.json'].map(name => 'docs/automation/handoffs/pre-phase10/' + name).sort(); + const expectedVerifierFiles = ['verify.mjs', 'verify.test.mjs', 'package.test.mjs', 'verify_git.py', 'HANDOFF_SCHEMA.json', 'S01_METRIC_APPROVAL.json', 'PDF_VISUAL_APPROVAL.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); @@ -244,7 +244,11 @@ export function validatePackage(base = directory) { demand(result.total === 36 && result.passed === 36, 'Approved revised S01 fidelity failed'); revisedS01[platform] = { passed: result.passed, total: result.total }; } - demand(decisions.get('Automated PDF Fidelity') === 'PASS' && decisions.get('PDF Spacing') === 'PARTIAL', 'Scoped PDF or human spacing decision differs'); + const visual = JSON.parse(artifact('pdf-visual-approval').toString('utf8')); + verifyBytes(readPackage('PDF_VISUAL_APPROVAL.json'), digest(readSafe('PDF_VISUAL_APPROVAL.json'))); + verifyBytes(artifact('pdf-visual-approval'), digest(readSafe('PDF_VISUAL_APPROVAL.json'))); + demand(visual.reviewedPublishedCheckpoint === '214dbd6c4d173db72cba18959dd8f81c7fc67f64' && visual.pdfVisualPresentation === 'APPROVED_FOR_DEVELOPMENT' && visual.physicalPrinting === 'NOT_TESTED' && visual.physicalDeviceAndAssistiveTechnology === 'NOT_TESTED' && visual.protectedOrchestratorApproval === 'NOT_SIGNED_BY_THIS_RECORD', 'PDF visual approval scope differs'); + demand(decisions.get('Automated PDF Fidelity') === 'PASS' && decisions.get('PDF Spacing') === 'PASS', 'Scoped PDF or human spacing decision differs'); 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();