38 lines
2.8 KiB
JavaScript
38 lines
2.8 KiB
JavaScript
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};
|
|
});
|
|
}
|