#!/usr/bin/env node // Conformance self-test for mizan.verdict/1 verifiers: one command, green or red. // // npm run conformance # this package's verifyToken + verifyTokenAsync (WebCrypto) // npm run conformance -- --verifier ./my-verifier.mjs # YOUR verifier, same published vectors // node conformance.mjs --verifier ./my-verifier.mjs # the same file, standalone (no package needed) // // Vectors: https://mizan.market/verdict/vectors.json (or --vectors PATH|URL), signed with a TEST key from a public seed that is // never in MIZAN's key set. Your module exports verifyToken (or default): (token, {keys, now, payee?, rule?, status?}) -> // {ok, code} (sync or async). Exit 0 = every vector passed; 1 = any failed; 2 = could not run. import { readFile } from "node:fs/promises"; import { pathToFileURL } from "node:url"; import { resolve } from "node:path"; const VECTORS_URL = "https://mizan.market/verdict/vectors.json"; const arg = (n) => { const i = process.argv.indexOf(n); return i > 0 ? process.argv[i + 1] : undefined; }; const src = arg("--vectors") ?? VECTORS_URL; const asJson = process.argv.includes("--json"); const canonical = (o) => Array.isArray(o) ? `[${o.map(canonical).join(",")}]` : o && typeof o === "object" ? `{${Object.keys(o).sort().map((k) => `${JSON.stringify(k)}:${canonical(o[k])}`).join(",")}}` : JSON.stringify(o); async function load() { if (/^https?:\/\//.test(src)) { const r = await fetch(src, { headers: { accept: "application/json", "user-agent": "mizan-conformance/1" } }); if (!r.ok) throw new Error(`vectors fetch failed: HTTP ${r.status}`); return r.json(); } return JSON.parse(await readFile(src, "utf8")); } async function verifiers() { const spec = arg("--verifier"); if (spec) { const m = await import(pathToFileURL(resolve(spec)).href); const fn = m.verifyToken ?? m.default; if (typeof fn !== "function") throw new Error(`${spec} exports no verifyToken or default function`); return [[spec, fn]]; } const m = await import(new URL("../dist/index.js", import.meta.url).href); return [["mizan-agent-tools verifyToken (node:crypto)", m.verifyToken], ["mizan-agent-tools verifyTokenAsync (WebCrypto)", m.verifyTokenAsync]]; } let data, list; try { data = await load(); list = await verifiers(); } catch (e) { console.error(`could not run: ${e?.message ?? e}`); process.exit(2); } let allOk = true; const report = []; for (const [name, fn] of list) { const res = []; const can = data.canonicalisation; if (can) { const got = Buffer.from(canonical(can.claims), "utf8").toString("base64url"); res.push({ name: "canonical_serialisation", expect: "claims_b64", got: got === can.claims_b64 ? "matches" : "differs", ok: got === can.claims_b64 }); } for (const v of data.vectors) { let got; try { const r = await fn(v.token, { keys: data.keys, now: data.now, ...(v.verify ?? {}) }); got = Array.isArray(r) ? [Boolean(r[0]), String(r[1])] : [Boolean(r?.ok), String(r?.code)]; } catch (e) { got = [null, `threw ${e?.name ?? "error"}`]; } const ok = got[0] === v.expect.ok && got[1] === v.expect.code; res.push({ name: v.name, expect: `${v.expect.ok}/${v.expect.code}`, got: `${got[0]}/${got[1]}`, ok }); } const passed = res.filter((r) => r.ok).length; allOk &&= passed === res.length; report.push({ verifier: name, passed, total: res.length, results: res }); if (!asJson) { console.log(`${data.schema} · verifier ${name} · vectors ${src}`); for (const r of res) console.log(` ${r.ok ? "PASS" : "FAIL"} ${r.name.padEnd(38)} expect ${r.expect.padEnd(22)} got ${r.got}`); console.log(`${passed}/${res.length} passed · ${passed === res.length ? "CONFORMANT" : "NOT CONFORMANT"}\n`); } } if (asJson) console.log(JSON.stringify({ schema: data.schema, report }, null, 1)); process.exit(allOk ? 0 : 1);