#!/usr/bin/env node import { spawn } from "node:child_process"; import { readFile, writeFile, mkdir } from "node:fs/promises"; import { existsSync } from "node:fs"; import { tmpdir } from "node:os"; import { dirname, resolve } from "node:path"; import { sourceFingerprint } from "./source-fingerprint.mjs"; const hostedUrl = normalizeUrl( process.env.BROWSER_SPEAK_HOSTED_URL ?? process.env.BROWSER_SPEAK_URL ?? "https://mike0021-browser-speak.static.hf.space/", ); const rawRealMicPath = resolve( process.env.BROWSER_SPEAK_HOSTED_REAL_MIC_JSON ?? process.env.BROWSER_SPEAK_REAL_MIC_JSON ?? `${tmpdir()}/browser-speak-hosted-real-mic-series.json`, ); const evidencePath = resolve( process.env.BROWSER_SPEAK_HOSTED_EVIDENCE_JSON ?? `${tmpdir()}/browser-speak-hosted-browser-evidence.json`, ); const auditPath = resolve( process.env.BROWSER_SPEAK_HOSTED_EVIDENCE_AUDIT_JSON ?? `${tmpdir()}/browser-speak-hosted-browser-evidence-audit.json`, ); const summaryPath = resolve( process.env.BROWSER_SPEAK_HOSTED_EVIDENCE_SUMMARY_JSON ?? `${tmpdir()}/browser-speak-evidence-summary.json`, ); const reportPath = resolve( process.env.BROWSER_SPEAK_HOSTED_EVIDENCE_REPORT_JSON ?? `${tmpdir()}/browser-speak-hosted-evidence-capture.json`, ); const dryRun = process.env.BROWSER_SPEAK_HOSTED_EVIDENCE_DRY_RUN === "true" || process.env.BROWSER_SPEAK_REAL_MIC_DRY_RUN === "true"; const skipAudit = process.env.BROWSER_SPEAK_HOSTED_EVIDENCE_SKIP_AUDIT === "true" || dryRun; const skipSummary = process.env.BROWSER_SPEAK_HOSTED_EVIDENCE_SKIP_SUMMARY === "true" || dryRun; async function main() { await mkdir(dirname(reportPath), { recursive: true }); await mkdir(dirname(rawRealMicPath), { recursive: true }); await mkdir(dirname(evidencePath), { recursive: true }); await mkdir(dirname(auditPath), { recursive: true }); await mkdir(dirname(summaryPath), { recursive: true }); const steps = []; const realMicEnv = { ...process.env, BROWSER_SPEAK_URL: hostedUrl, BROWSER_SPEAK_DEVICE: process.env.BROWSER_SPEAK_DEVICE ?? "auto", BROWSER_SPEAK_REAL_MIC_JSON: rawRealMicPath, BROWSER_SPEAK_HEADLESS: process.env.BROWSER_SPEAK_HEADLESS ?? "false", }; if (dryRun) realMicEnv.BROWSER_SPEAK_REAL_MIC_DRY_RUN = "true"; console.log(`Hosted evidence URL: ${hostedUrl}`); console.log(`Real-mic artifact: ${rawRealMicPath}`); console.log(`Enriched browser evidence: ${evidencePath}`); if (dryRun) console.log("Dry run enabled; audit and summary steps will be skipped."); steps.push( await runStep("hosted real-mic series", process.execPath, ["tools/run-real-mic-series.mjs"], { env: realMicEnv, inherit: true, }), ); const enrich = await enrichRealMicArtifact(rawRealMicPath, evidencePath).catch((error) => ({ label: "enrich browser evidence", status: "fail", error: error.message, })); steps.push(enrich); if (!skipAudit && enrich.status === "pass") { steps.push( await runStep("browser evidence audit", process.execPath, ["tools/audit-browser-evidence.mjs", evidencePath], { env: { ...process.env, BROWSER_SPEAK_BROWSER_AUDIT_JSON: auditPath, }, inherit: true, }), ); } if (!skipSummary) { steps.push( await runStep("evidence summary", process.execPath, ["tools/summarize-evidence.mjs"], { env: { ...process.env, BROWSER_SPEAK_REAL_MIC_JSON: rawRealMicPath, BROWSER_SPEAK_BROWSER_AUDIT_JSON: auditPath, BROWSER_SPEAK_EVIDENCE_SUMMARY_JSON: summaryPath, }, inherit: true, }), ); } const stepsPassed = steps.every((step) => step.status === "pass"); const passed = !dryRun && stepsPassed; const payload = { generatedAt: new Date().toISOString(), sourceFingerprint: await sourceFingerprint(), passed, stepsPassed, dryRun, hostedUrl, artifacts: { rawRealMicJson: rawRealMicPath, browserEvidenceJson: evidencePath, browserEvidenceAuditJson: skipAudit ? null : auditPath, evidenceSummaryJson: skipSummary ? null : summaryPath, }, steps, nextActions: nextActions(steps, dryRun), }; await writeFile(reportPath, `${JSON.stringify(payload, null, 2)}\n`); console.log(`Wrote hosted evidence capture report: ${reportPath}`); if (!stepsPassed || (!dryRun && !passed)) process.exitCode = 1; } async function enrichRealMicArtifact(inputPath, outputPath) { const startedAt = Date.now(); const payload = JSON.parse(await readFile(inputPath, "utf8")); const rows = Array.isArray(payload.results) ? payload.results : []; const firstEnvironment = rows.map((row) => row.stack?.environment).find((environment) => environment && typeof environment === "object") ?? null; const hostMetadata = payload.hostMetadata ?? payload.browserExport?.hostMetadata ?? payload.runtime?.hostMetadata ?? firstEnvironment?.hostMetadata ?? {}; const runtime = payload.runtime ?? payload.browserExport?.runtime ?? firstEnvironment ?? null; const benchmarkSummary = payload.exportSummary ?? payload.browserExport?.summary ?? payload.summary ?? null; const enriched = { ...payload, hostMetadata, runtime, benchmarkSummary, browserExport: payload.browserExport ?? { generatedAt: payload.generatedAt ?? new Date().toISOString(), hostMetadata, runtime, summary: benchmarkSummary, results: rows, }, }; await writeFile(outputPath, `${JSON.stringify(enriched, null, 2)}\n`); return { label: "enrich browser evidence", status: "pass", durationMs: Date.now() - startedAt, inputPath, outputPath, rowCount: rows.length, host: hostMetadata.host ?? "", hfSpaceCommit: hostMetadata.hfSpaceCommit ?? "", }; } function runStep(label, command, args, { env = process.env, inherit = false } = {}) { console.log(`Running ${label}.`); const startedAt = Date.now(); return new Promise((resolvePromise) => { const child = spawn(command, args, { env, stdio: inherit ? "inherit" : ["ignore", "pipe", "pipe"], }); let stdout = ""; let stderr = ""; if (!inherit) { child.stdout.on("data", (chunk) => { stdout += chunk; }); child.stderr.on("data", (chunk) => { stderr += chunk; }); } child.on("error", (error) => { resolvePromise({ label, command: [command, ...args], status: "fail", error: error.message, durationMs: Date.now() - startedAt, }); }); child.on("exit", (exitCode) => { resolvePromise({ label, command: [command, ...args], status: exitCode === 0 ? "pass" : "fail", exitCode, durationMs: Date.now() - startedAt, stdout: inherit ? undefined : stdout, stderr: inherit ? undefined : stderr, }); }); }); } function nextActions(steps, isDryRun) { if (isDryRun) { return [ "Run node tools/run-hosted-evidence-capture.mjs from a desktop browser session with a real microphone.", "Keep BROWSER_SPEAK_DEVICE=auto on a machine with hardware WebGPU so the mic rows also prove hardware execution.", ]; } const failed = steps.filter((step) => step.status !== "pass").map((step) => step.label); if (failed.length === 0) return ["Use the browser evidence audit JSON in the final validation review."]; const actions = []; if (failed.includes("hosted real-mic series")) { actions.push("Rerun from a desktop session with microphone permission granted and no fake-capture Chrome flags."); } if (failed.includes("browser evidence audit")) { actions.push("Check whether the captured rows include three real-mic passes and at least one hardware WebGPU row."); } if (failed.includes("enrich browser evidence")) { actions.push("Inspect the raw real-mic artifact; enrichment requires a JSON file with benchmark rows."); } return actions; } function normalizeUrl(value) { return value.endsWith("/") ? value : `${value}/`; } main().catch(async (error) => { await mkdir(dirname(reportPath), { recursive: true }).catch(() => {}); const payload = { generatedAt: new Date().toISOString(), sourceFingerprint: await sourceFingerprint().catch(() => null), passed: false, dryRun, hostedUrl, error: error.stack ?? error.message ?? String(error), }; if (!existsSync(reportPath)) await writeFile(reportPath, `${JSON.stringify(payload, null, 2)}\n`).catch(() => {}); console.error(error.stack ?? error.message); process.exitCode = 1; });