62 lines
4.2 KiB
JavaScript
62 lines
4.2 KiB
JavaScript
// 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 <published-handoff-checkout> <new-output-directory>');
|
|
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));
|