Files
SGF/scripts/audit-module-matrix.mjs
T
2026-11-04 09:37:20 +02:00

15 lines
4.7 KiB
JavaScript

import fs from 'node:fs';import path from 'node:path';import crypto from 'node:crypto';import {fileURLToPath} from 'node:url';
export function validateEvidence(spec,read,loadXml){
const value=read(spec.file);let checks=0;
for(const c of spec.checks??[]){let v=value;for(const key of c.pointer.split('/').slice(1))v=v?.[key];if(v!==c.equals)throw Error('Failed evidence selector '+spec.file+' '+c.pointer);checks++;}
if(spec.selected_junit){const seen=new Set();if(!value.selected_cases?.length||value.selected_cases.length!==value.totals.distinct_tests)throw Error('Incomplete selected case coverage');for(const c of value.selected_cases){if(seen.has(c.name))throw Error('Duplicate selected case');seen.add(c.name);const p=value.proofs.find(p=>p.file===c.proof);if(!p)throw Error('Missing JUnit reference');const xml=loadXml(p.file,p.sha256),cases=[...xml.matchAll(/<testcase\b[^>]*\/>|<testcase\b[^>]*>[\s\S]*?<\/testcase>/g)].map(m=>m[0]),test=cases.find(t=>t.match(/\bname="([^"]+)"/)?.[1]===c.name);if(!test||/<(?:failure|error|skipped)\b/.test(test))throw Error('Selected case did not pass '+c.name);checks++;}}
if(!checks)throw Error('Evidence has no success criterion');return {verified_checks:checks,recorded_proof_verified:true};
}
export function validateOrigins(plan){const ids=new Set(plan.stages.map(s=>s.id));for(const s of plan.stages)for(const branch of s.origin_paths??[]){if(!branch.id||!Array.isArray(branch.requires)||branch.requires.some(x=>!ids.has(x)||x===s.id))throw Error('Invalid alternative origin path');}return true;}
if(process.argv[1]&&path.resolve(process.argv[1])===fileURLToPath(import.meta.url)){
const root=path.resolve('.'),read=file=>JSON.parse(fs.readFileSync(path.join(root,file),'utf8').replace(/^\uFEFF/,'')),hash=file=>crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex'),plan=read('docs/integration/sgf-flow-plan.json'),state=JSON.parse(fs.readFileSync('C:/laragon/data/sgf-tempo/run/modular-regression-current.json','utf8')),roots={sgf:root,compras:path.resolve('../compras')};validateOrigins(plan);
const stages=plan.stages.map(stage=>{if(!stage.evidence?.length)throw Error('Unmapped evidence '+stage.id);const code=stage.source_ids.map(id=>{const s=plan.sources.find(s=>s.id===id);if(!s)throw Error('Unknown source');const base=fs.realpathSync(roots[s.repository]),file=fs.realpathSync(path.join(base,s.path));if(!file.startsWith(base+path.sep))throw Error('Source escape');return {id,file:s.path,repository:s.repository,sha256:hash(file),source_present_only:true};});const evidence=stage.evidence.map(e=>{if(!/^docs\/proofs\/[a-z0-9-]+\.json$/.test(e.file))throw Error('Unsafe evidence path');return {...e,sha256:hash(path.join(root,e.file)),...validateEvidence(e,read,(name,pin)=>{if(!/^workshop-stock-\d+\.xml$/.test(name))throw Error('Unsafe XML reference');const file=path.join(state.root,name);if(hash(file)!==pin)throw Error('JUnit changed');return fs.readFileSync(file,'utf8');})};});for(const source of code){const pins=[];for(const spec of stage.evidence){const p=read(spec.file);for(const field of ['source_files','sources','source_hashes','source_sha256']){const entries=p[field];if(Array.isArray(entries)){for(const e of entries)if((e.file??e.path)?.replaceAll('\\','/').endsWith(source.file)&&e.sha256)pins.push({proof:spec.file,sha256:e.sha256});}else if(entries&&typeof entries==='object'){for(const [file,value]of Object.entries(entries)){const pin=typeof value==='string'?value:value?.sha256;if(file.replaceAll('\\','/').endsWith(source.file)&&/^[a-f0-9]{64}$/.test(pin??''))pins.push({proof:spec.file,sha256:pin});}}}}source.recorded_source_pins=pins;source.current_hash_has_matching_recorded_evidence=pins.length?pins.some(p=>p.sha256===source.sha256):null;}
return {id:stage.id,owner:stage.owner,state:stage.state,code,evidence,remaining_gap:stage.gap,operational_validated:false,current_entire_stage_code_regression_repeated:false};});
const output=process.argv[2];if(!output||!/^docs\/proofs\/[a-z0-9-]+\.json$/.test(output)||fs.existsSync(output))throw Error('Fresh proof output required');fs.writeFileSync(output,JSON.stringify({date:new Date().toISOString(),kind:'matrix_code_recorded_evidence_audit',plan_sha256:hash(path.join(root,'docs/integration/sgf-flow-plan.json')),stages,stages_checked:stages.length,visual_review_pending:true,deployment_capacity_validated:false,limitations:['Historical selected proofs verified; source presence/current hashes are not proof that all later edits were regressed.','No runtime modules, real data, rights, cache or databases altered by this audit.'],database_connections:0,financial_effects:0},null,2));console.log(JSON.stringify({output,stages:stages.length,selected_criteria:stages.reduce((n,s)=>n+s.evidence.reduce((m,e)=>m+e.verified_checks,0),0)}));
}