import { readdirSync, lstatSync, readlinkSync, existsSync } from 'node:fs'; import { join } from 'node:path'; import { within, refuse } from './lane-state.mjs'; export function attributeEntries(metadata,run){ const result={owned:[],unrelated:[],unknown:[]}; for(const e of metadata){const owns=e.path.includes(run.nonce)||e.link&&within(run.tempRoot,e.link)||(run.fixturePaths??[]).some(path=>within(path,e.path)||e.link&&within(path,e.link));result[owns?'owned':'unrelated'].push(e);} return result; } export function homeMetadata(home){ const out=[]; for(const name of ['.claude','.pi','.cache']){ const root=join(home,name);if(!existsSync(root))continue; // Two levels are evidence only. Never open or hash file contents. const walk=(dir,depth)=>{for(const d of readdirSync(dir,{withFileTypes:true})){const path=join(dir,d.name);try{const st=lstatSync(path);out.push({path,size:st.size,mtime:st.mtimeMs,mode:st.mode,link:st.isSymbolicLink()?readlinkSync(path):null});if(d.isDirectory()&&depth)walk(path,depth-1);}catch{}}}; walk(root,1); } return out; } export function socketInventory(root){const out=[];const walk=dir=>{for(const d of readdirSync(dir,{withFileTypes:true})){const p=join(dir,d.name);if(d.isDirectory())walk(p);else if(d.isSocket()){const bytes=Buffer.byteLength(p);out.push({path:p,bytes});if(bytes>107)refuse('socket path exceeds 107 bytes');}}};walk(root);return out;} export function summarizePairs(solos,pairs){ const byCandidate=new Map(solos.map(r=>[r.candidate,r])); if(solos.length!==2||byCandidate.size!==2||solos.some(r=>!Number.isFinite(r.wallMs)||r.wallMs<=0))refuse('solo timing invalid'); if(pairs.length!==3)refuse('proof needs three pairs'); let first; return pairs.map((pair,index)=>{ if(pair.length!==2||pair[0].candidate===pair[1].candidate||new Set(pair.map(r=>r.lane)).size!==2||pair.some(r=>!['a','b'].includes(r.lane)))refuse('pair needs different candidates and lanes'); for(const run of pair){ if(!run.clean||run.rc!==0||!byCandidate.has(run.candidate))refuse('proof gate failed'); if(!Number.isFinite(run.startMs)||!Number.isFinite(run.endMs)||run.endMs<=run.startMs)refuse('paired timing invalid'); } if(index===0)first=new Map(pair.map(r=>[r.candidate,r.lane])); if(index===1&&pair.some(r=>first.get(r.candidate)===r.lane))refuse('second pair must swap lanes'); if(Math.max(...pair.map(r=>r.startMs))>=Math.min(...pair.map(r=>r.endMs)))refuse('paired gates did not overlap'); const makespan=Math.max(...pair.map(r=>r.endMs))-Math.min(...pair.map(r=>r.startMs)); return {makespanMs:makespan,runs:pair.map(r=>({candidate:r.candidate,wallMs:r.endMs-r.startMs,slowdown:(r.endMs-r.startMs)/byCandidate.get(r.candidate).wallMs})),throughput:pair.reduce((n,r)=>n+byCandidate.get(r.candidate).wallMs,0)/makespan}; }); }