#!/usr/bin/env node /** Decode only locally retained Visual files in an offline disposable browser; emit counts only. */ import { readFileSync, lstatSync, realpathSync } from 'node:fs'; import { resolve, join } from 'node:path'; import { createHash } from 'node:crypto'; import { chromium } from 'playwright'; let browser; try { const root = resolve(process.argv[2]); const reportBytes = readFileSync(join(root, 'reconciliation.json')); const report = JSON.parse(reportBytes); if (report.status !== 'completed_private_visual_preparation') throw new Error('unfinished_bundle'); const entries = Object.entries(report.file_sha256).filter(([name]) => name.startsWith('captured-visuals/')); browser = await chromium.launch({ headless: true }); const context = await browser.newContext({ serviceWorkers: 'block', offline: true }); await context.route('**/*', route => route.abort()); const page = await context.newPage(); let decoded = 0; for (const [name, hash] of entries) { if (!/^captured-visuals\/[a-f0-9]{64}\.(png|jpg|webp)$/.test(name)) throw new Error('invalid_asset_path'); const path = join(root, name), info = lstatSync(path); if (!info.isFile() || info.isSymbolicLink() || info.size > 16 * 1024 * 1024 || (info.mode & 0o077) || info.uid !== process.getuid() || realpathSync(path) !== path) throw new Error('unsafe_asset'); const data = readFileSync(path); if (createHash('sha256').update(data).digest('hex') !== hash) throw new Error('changed_asset'); const mime = name.endsWith('.jpg') ? 'image/jpeg' : name.endsWith('.webp') ? 'image/webp' : 'image/png'; const ok = await page.evaluate(async ({data, mime}) => { const bytes = Uint8Array.from(atob(data), x => x.charCodeAt(0)); const url = URL.createObjectURL(new Blob([bytes], {type: mime})); try { const img = new Image(); img.src = url; await Promise.race([img.decode(), new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), 10000))]); return img.naturalWidth > 0 && img.naturalHeight > 0; } finally { URL.revokeObjectURL(url); } }, {data: data.toString('base64'), mime}); if (!ok) throw new Error('image_decode_failed'); decoded++; } console.log(JSON.stringify({schema: 'coulomb.private-visual-decode.v1', status: 'all_captured_visuals_decoded', decoded_images: decoded, bundle_sha256: createHash('sha256').update(reportBytes).digest('hex'), browser_network_disabled: true, productive_fidelity_accepted: false})); } catch { console.error('Private visual decode verification failed; no acceptance inferred.'); process.exitCode = 1; } finally { if (browser) await browser.close(); }