// Trusted operator preparation. Validates published native evidence; runs no product suite. import fs from 'node:fs'; import path from 'node:path'; import crypto from 'node:crypto'; import zlib from 'node:zlib'; import { pathToFileURL } from 'node:url'; import { execFileSync } from 'node:child_process'; const [repository, destination] = process.argv.slice(2); if (!repository || !destination) throw new Error('Usage: node prepare_assessment.mjs '); const root = fs.realpathSync(repository), output = path.resolve(destination); if (fs.existsSync(output)) throw new Error('Use a new receipt directory; preserve previous qualification'); const git = (...args) => execFileSync('rtk', ['proxy','git','-C',root,...args], {encoding:'utf8'}).trim(); const handoff = '5cb903b989742b7a96cb22ddf4c0e24993b18980'; const baseline = 'fc5b9d377199ce51f480a920a3772108094929e3'; if (git('rev-parse','HEAD') !== handoff || git('status','--porcelain')) throw new Error('Require the exact clean published handoff checkout'); const remote = git('ls-remote','--exit-code','origin','refs/heads/test/pre-phase10-ux-acceptance'); if (remote.split(/\s/)[0] !== handoff) throw new Error('Handoff publication differs'); if (git('rev-parse',handoff+'^') !== baseline) throw new Error('Baseline relationship differs'); const relative = 'docs/automation/handoffs/pre-phase10'; const directory = path.join(root,relative); const {validatePackage, measureNative} = await import(pathToFileURL(path.join(directory,'verify.mjs')).href); const verified = validatePackage(); if (verified.integrity !== 'PASS' || !verified.observedWorkingTreeClean) throw new Error('Handoff integrity failed'); const evidence = JSON.parse(fs.readFileSync(path.join(directory,'TEST_EVIDENCE.json'))); const artifact = id => { const row=evidence.artifacts.find(item=>item.id===id); if (!row) throw new Error('Missing native artifact'); const raw=fs.readFileSync(path.join(directory,row.path)); return row.path.endsWith('.gz') ? zlib.gunzipSync(raw) : raw; }; const hash = raw => crypto.createHash('sha256').update(raw).digest('hex'); const files = {}; // Freeze all tracked non-envelope sources, including tests/config/security/preservation documentation. for (const name of git('ls-files','-z').split('\0').filter(Boolean)) { if (!name.startsWith(relative+'/')) files[name]=hash(fs.readFileSync(path.join(root,name))); } const nativeReports = evidence.reports.map(report=>{ let observation=JSON.parse(artifact(report.processArtifactId)); const parts=report.processPointer.slice(1).split('/').map(value=>value.replaceAll('~1','/').replaceAll('~0','~')); for (const key of parts.slice(0,-1)) observation=observation[key]; return {...report, measured:report.nativeFormat ? measureNative(report.nativeFormat,artifact(report.artifactId).toString('utf8')) : null, nativeProcess:observation}; }); fs.mkdirSync(output,{recursive:true}); const evidenceFiles={}; for (const name of git('ls-files',relative).split(/\r?\n/).filter(Boolean)) { const local=path.relative(directory,path.join(root,name)); const target=path.join(output,'handoff',local); fs.mkdirSync(path.dirname(target),{recursive:true}); fs.copyFileSync(path.join(root,name),target); evidenceFiles['handoff/'+local.split(path.sep).join('/')]=hash(fs.readFileSync(target)); } const receipt={schemaVersion:1,scope:'P10-00-ASSESSMENT-ONLY',integrity:'PASS', generatedUtc:new Date().toISOString(),baselineCommit:baseline,handoffCommit:handoff, testedCommit:evidence.testedCommit,files,evidenceFiles,nativeReports, retainedDecisions:JSON.parse(fs.readFileSync(path.join(directory,'ACCEPTANCE.json'))).verdicts, revisedS01:verified.revisedS01,verifierObservation:verified, limits:'Source-bound reuse for unchanged read-only assessment. Original failures and conditional/blocked decisions retained. No fresh product, security, model, human or production acceptance.'}; fs.writeFileSync(path.join(output,'receipt.json'),JSON.stringify(receipt,null,2)+'\n'); console.log(JSON.stringify({integrity:'PASS',receipt:path.join(output,'receipt.json'),sha256:hash(fs.readFileSync(path.join(output,'receipt.json'))),frozenSources:Object.keys(files).length,nativeReports:nativeReports.length,productSuitesExecuted:0},null,2));