open-standards/wai/quantum-conformance/browser_verify.mjs
Transaction Science 4ea226d025 wai/quantum-conformance: check the browser leg against the same pinned corpus
The extension claims identical reconstruction native, in the browser, and at the
edge. Native and wasm32-wasip2 were checkable with the existing vectors binary;
the browser was not checkable by anything, so it was the one surface taken on
trust.

browser_verify.mjs drives the deployed wasm-bindgen bundle in headless Chromium
and holds it to expected_hashes.json -- the same corpus pinned 2026-07-30, so all
three surfaces are now checked against one reference rather than against each
other. All four vectors pass, circuit hash and statevector hash both, with no
console errors.

Two things the harness had to learn, now written down: statevector_hash_hex()
returns empty until run() is called, because the circuit is built eagerly and the
state is not -- which reads as a hash mismatch rather than as a missing step; and
the bundle must be served over HTTP rather than file:// for the wasm import to
resolve.

Adds the README the directory never had, covering all three invocations and
stating the corpus's scope honestly: it pins the byte-exact dyadic core, where
hash equality is the right bar. The f64 layers have no WQC circuit to pin and are
held to exact IEEE-754 bits by unit tests instead.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-10 12:30:26 -04:00

106 lines
4.3 KiB
JavaScript

// Browser leg of statevector-equivalence.
//
// The native and `wasm32-wasip2` legs are checked by `wai_quantum_vectors verify`.
// This checks the third surface the extension claims — a browser — against the
// same pinned corpus, so all three are held to one reference rather than to each
// other.
//
// node browser_verify.mjs [path/to/quantum-ml/pkg]
//
// Needs Playwright with its bundled Chromium. `statevector_hash_hex()` is empty
// until `run()` is called: the circuit is built eagerly, the state is not.
import http from "node:http";
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { createRequire } from "node:module";
const HERE = path.dirname(fileURLToPath(import.meta.url));
const PKG = process.argv[2] ||
"/Users/dcharlot/data-share/vibe-coding/wai-transaction-science-web/public/quantum-ml/pkg";
const PLAYWRIGHT_HOST = process.env.PLAYWRIGHT_HOST ||
"/Users/dcharlot/data-share/vibe-coding/joulesperbit/";
const { chromium } = createRequire(PLAYWRIGHT_HOST)("playwright");
const EXPECTED = JSON.parse(fs.readFileSync(path.join(HERE, "expected_hashes.json"), "utf8"));
// vector name -> how the catalog builds that circuit
const CASES = [
["bell.wqc", "bell", 2, 0],
["ghz3.wqc", "ghz", 3, 0],
["qft3.wqc", "qft", 3, 0],
["qft5.wqc", "qft", 5, 0],
];
const MIME = { ".js": "text/javascript", ".wasm": "application/wasm", ".html": "text/html" };
const PORT = Number(process.env.PORT || 8794);
const server = http.createServer((req, res) => {
const url = req.url.split("?")[0];
if (url === "/") {
res.writeHead(200, { "content-type": "text/html" });
return res.end('<!doctype html><meta charset="utf-8"><title>conformance</title><body>ready</body>');
}
const f = path.join(PKG, path.basename(url));
if (!fs.existsSync(f)) { res.writeHead(404); return res.end("not found"); }
res.writeHead(200, { "content-type": MIME[path.extname(f)] || "application/octet-stream" });
fs.createReadStream(f).pipe(res);
});
let browser;
process.on("unhandledRejection", (e) => { console.error("UNHANDLED:", e); process.exitCode = 1; });
try {
if (!fs.existsSync(path.join(PKG, "wai_quantum_ml.js"))) {
console.error(`no bundle at ${PKG} — pass the pkg directory as argv[1]`);
process.exit(2);
}
await new Promise((r) => server.listen(PORT, "127.0.0.1", r));
browser = await chromium.launch();
const page = await browser.newPage();
const errs = [];
page.on("console", (m) => { if (m.type() === "error") errs.push(m.text()); });
page.on("pageerror", (e) => errs.push(String(e)));
// domcontentloaded, never networkidle: the page holds no long-lived connections
// but networkidle still stalls behind them on some hosts.
await page.goto(`http://127.0.0.1:${PORT}/`, { waitUntil: "domcontentloaded", timeout: 30000 });
const got = await page.evaluate(async ({ cases, port }) => {
const mod = await import(`http://127.0.0.1:${port}/wai_quantum_ml.js`);
await mod.default();
const out = {};
for (const [name, id, n, param] of cases) {
const ide = new mod.QuantumIde(n);
const ok = ide.load_algorithm(id, n, param);
if (ok) ide.run(); // the state is not computed until this
out[name] = ok
? { circuit: ide.circuit_hash_hex(), statevector: ide.statevector_hash_hex() }
: { error: "load_algorithm returned false" };
ide.free();
}
return out;
}, { cases: CASES, port: PORT });
let fail = 0;
for (const [name] of CASES) {
const e = EXPECTED[name], g = got[name];
if (g.circuit === e.circuit_hash && g.statevector === e.statevector_hash) {
console.log(`PASS ${name}`);
} else {
fail++;
console.log(`FAIL ${name}`);
console.log(` circuit browser=${g.circuit}\n corpus =${e.circuit_hash}`);
console.log(` statevector browser=${g.statevector}\n corpus =${e.statevector_hash}`);
}
}
console.log(errs.length ? `console errors: ${errs.length}` : "console errors: 0");
if (fail === 0 && errs.length === 0) {
console.log("all vectors pass: statevector-equivalence holds in the browser");
}
process.exitCode = fail || errs.length ? 1 : 0;
} catch (e) {
console.error("HARNESS ERROR:", e);
process.exitCode = 1;
} finally {
if (browser) await browser.close();
server.close();
}