const $=id=>document.getElementById(id); const esc=v=>String(v??'').replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); let bundle; function structure(b){ if(b?.schema_version!=='clw-reliability-lab/1'||!Array.isArray(b.experiments)||b.experiments.length!==4||typeof b.source_sha256!=='string')throw Error('Not a four-fixture Reliability Lab result.'); for(const e of b.experiments)for(const p of ['before','after'])if(!e[p]?.trace?.messages||!Array.isArray(e[p].attempts))throw Error('Incomplete evidence.'); } function phase(p,label){return `
${label}

${esc(p.outcome.replaceAll('_',' '))}

${p.attempts.length} attempt${p.attempts.length===1?'':'s'} · ${esc(p.policy.timeout_ms)} ms deadline

Clinic findings (${p.clinic.findings.filter(f=>f.severity!=='info').length})${p.clinic.findings.filter(f=>f.severity!=='info').map(f=>`

${esc(f.id)}: ${esc(f.explain)}

`).join('')||'

No heuristic findings.

'}
Trace and integrity digest

${esc(p.trace_sha256)}

${esc(JSON.stringify(p.trace,null,2))}
`;} function render(){const e=bundle.experiments.find(x=>x.id===$('fixture').value);$('experiment').innerHTML=`

${esc(e.title)}

${esc(e.change)}

${phase(e.before,'BEFORE')}${phase(e.after,'AFTER')}

Fixture checks ${e.verification.pass?'pass':'fail'}

Tradeoff: ${esc(e.tradeoff)}

`;} function download(data){const a=document.createElement('a'),url=URL.createObjectURL(new Blob([JSON.stringify(data,null,2)],{type:'application/json'}));a.href=url;a.download='reliability-evidence.json';a.click();setTimeout(()=>URL.revokeObjectURL(url),1000);} async function integrity(b){for(const e of b.experiments)for(const p of ['before','after']){const bytes=new TextEncoder().encode(JSON.stringify(e[p].trace));const digest=Array.from(new Uint8Array(await crypto.subtle.digest('SHA-256',bytes)),x=>x.toString(16).padStart(2,'0')).join('');if(digest!==e[p].trace_sha256)throw Error('Trace integrity mismatch.');}} $('upload').onchange=async()=>{try{const file=$('upload').files[0];if(!file)return;if(file.size>2097152)throw Error('Maximum comparison file size is 2 MiB.');const other=JSON.parse(await file.text());structure(other);await integrity(other);if(other.source_sha256!==bundle.source_sha256||other.fixture_version!==bundle.fixture_version)throw Error('Source or fixture version differs. These runs are not directly comparable.');const rows=bundle.experiments.map(e=>{const o=other.experiments.find(x=>x.id===e.id);if(!o)throw Error('Missing fixture.');const agrees=['before','after'].every(p=>e[p].outcome===o[p].outcome&&e[p].attempts.length===o[p].attempts.length&&JSON.stringify(e[p].policy)===JSON.stringify(o[p].policy));return `

${esc(e.id)}: ${agrees?'same outcome and policy':'DIFFERENT outcome or policy'}

`;});$('comparison').hidden=false;$('comparison').innerHTML=`

Result comparison

${esc(bundle.runtime.platform)} → ${esc(other.runtime.platform)} (declared runtimes)

${rows.join('')}

Trace hashes checked. Timings are observations, not a cloud ranking.

`;$('status').textContent='Comparison stays in this browser.';}catch(e){$('comparison').hidden=true;$('comparison').replaceChildren();$('status').textContent=e.message;}}; $('fixture').onchange=render;$('download').onclick=()=>bundle&&download(bundle); try{const response=await fetch('sample.json');if(!response.ok)throw Error('Recorded sample unavailable.');bundle=await response.json();structure(bundle);await integrity(bundle);$('fixture').innerHTML=bundle.experiments.map(e=>``).join('');$('status').textContent=`Recorded ${bundle.generated_at} · ${bundle.runtime.platform} / ${bundle.runtime.os} ${bundle.runtime.arch}. This page replays evidence; it does not launch cloud jobs.`;$('provenance').textContent='Source SHA-256 '+bundle.source_sha256;render();}catch(e){$('status').textContent=e.message;$('download').disabled=true;}