Files
SGF/scripts/inspect-module-flow-plan.mjs
T
2026-11-04 09:37:20 +02:00

52 lines
4.1 KiB
JavaScript

import fs from 'node:fs';
import {validateOrigins} from './audit-module-matrix.mjs';
import path from 'node:path';
import crypto from 'node:crypto';
import {fileURLToPath} from 'node:url';
// Preparation inventory only: never loads Laravel, executes source or connects to DBs.
const root=path.resolve(path.dirname(fileURLToPath(import.meta.url)),'..');
const roots={sgf:root,compras:path.resolve(root,'../compras')};
const planPath=path.join(root,'docs/integration/sgf-flow-plan.json');
const digest=b=>crypto.createHash('sha256').update(b).digest('hex');
const planBytes=fs.readFileSync(planPath), plan=JSON.parse(planBytes);validateOrigins(plan);
const requireValue=(condition,message)=>{if(!condition)throw Error(message);};
requireValue(plan.schema_version===1&&plan.status==='preparation_only','Unsupported plan');
const modules=new Set(plan.modules), sourceIds=new Set(), stages=new Map();
requireValue(modules.size===6,'Expected the six existing modules');
const sources=plan.sources.map(s=>{
requireValue(!sourceIds.has(s.id),'Duplicate source');sourceIds.add(s.id);
requireValue(roots[s.repository]&&/^(app|packages|resources|docs|integrations|public)\//.test(s.path)&&/\.(php|js|jsx|mjs|md)$/.test(s.path),'Unexpected source path');
const base=fs.realpathSync(roots[s.repository]), file=fs.realpathSync(path.resolve(base,s.path));
requireValue(file.startsWith(base+path.sep)&&!s.path.split('/').includes('..'),'Source escapes repository');
return {...s,sha256:digest(fs.readFileSync(file)),meaning:'source_present_only_not_operational_proof'};
});
for(const s of plan.stages){
requireValue(!stages.has(s.id)&&modules.has(s.owner),'Duplicate stage or unknown owner');
requireValue(['planned','source_not_integrated','partial_synthetic'].includes(s.state),'Preparation cannot claim operational delivery');
requireValue(s.effect&&s.gap&&s.exit_evidence.length&&s.source_ids.every(id=>sourceIds.has(id)),'Stage missing evidence/gap/source');
stages.set(s.id,s);
}
const visited=new Set(), visiting=new Set();
function visit(id){
requireValue(stages.has(id),'Unknown dependency');
requireValue(!visiting.has(id),'Cyclic mandatory dependency');
if(visited.has(id))return;
visiting.add(id);for(const parent of stages.get(id).requires)visit(parent);visiting.delete(id);visited.add(id);
}
for(const id of stages.keys())visit(id);
for(const c of plan.event_consumers)requireValue(stages.has(c.source)&&stages.has(c.target)&&c.status==='planned'&&c.rule,'Invalid planned consumer');
requireValue(plan.evidence_contract.runtime_implemented===false&&plan.evidence_contract.unknown_is_zero===false,'Evidence contract must remain a proposal');
const report={id:plan.id,date:plan.date,kind:'static_preparation_inventory',plan_sha256:digest(planBytes),sources,
stages:plan.stages.map(s=>({id:s.id,owner:s.owner,effect:s.effect,state:s.state,operational_integration_validated:false,gap:s.gap})),
checks:{source_paths_and_hashes:true,single_owner_per_stage:true,mandatory_dependency_graph_acyclic:true,planned_consumers_explicit:true},
limitations:['Source existence is not execution, parity, acceptance or deployment evidence.','Dependencies represent the selected synthetic path, not universal product eligibility rules.','No stage transition engine, inventory adapter or journal engine implemented by this inventory.'],
preservation:{database_connections:0,source_execution:false,application_routes_changed:false,rights_changed:false,protected_six_records_untouched:true,blocked_cache_untouched:true,
demos_changed:false,purchasing_activated:false,publication:false,checkpoint:false},visual_review_pending:true,deployment_capacity_validated:false};
const outputName=process.argv[2]??'module-flow-preparation-20261102.json';
requireValue(/^[a-z0-9-]+\.json$/.test(outputName),'Proof filename required');
const output=path.join(root,'docs/proofs',outputName);
requireValue(!fs.existsSync(output),'Completed proof preserved; choose a new proof filename');
fs.writeFileSync(output,JSON.stringify(report,null,2)+'\n');
console.log(JSON.stringify({modules:modules.size,stages:stages.size,sources:sources.length,mandatory_cycles:0,kind:report.kind,database_connections:0,operational_integration_validated:false},null,2));