#!/usr/bin/env node // aer1_make.js — ARION harness AER-1 receipt producer (draft-zambo-aer1-14). // Runs a real harness tool invocation, captures stdout, and emits a // schema-0.4 receipt: inner payload carries the version + issuer_claims // binding (kit check 9a/9c); outer envelope carries metadata_hash (§3.1). // // node tools/aer1_make.js -- // node tools/aer1_make.js --out -- // // Brain-side only (node, no deps). Receipt is written to --out or stdout. "use strict"; const { execFileSync } = require("child_process"); const crypto = require("crypto"); const fs = require("fs"); const path = require("path"); const WS = path.resolve(__dirname, ".."); const SYS = "ARION"; // --- canonical JSON (code-point-sorted keys, no whitespace, §7.1 escapes) --- function esc(s) { const q = JSON.stringify(s); let out = ""; for (let i = 0; i < q.length; i++) { const c = q.charCodeAt(i); out += c >= 0x7f ? "\\u" + c.toString(16).padStart(4, "0") : q[i]; } return out; } function canon(v) { if (v === null) return "null"; if (typeof v === "boolean") return v ? "true" : "false"; if (typeof v === "number") return String(v); if (typeof v === "string") return esc(v); if (Array.isArray(v)) return "[" + v.map(canon).join(",") + "]"; const keys = Object.keys(v).sort(); return "{" + keys.map((k) => esc(k) + ":" + canon(v[k])).join(",") + "}"; } const sha = (buf) => crypto.createHash("sha256").update(buf).digest("hex"); const argv = process.argv.slice(2); let outFile = null; const sep = argv.indexOf("--"); if (sep < 0) { console.error("usage: aer1_make.js [--out f] -- "); process.exit(2); } const head = argv.slice(0, sep); for (let i = 0; i < head.length; i++) if (head[i] === "--out") outFile = head[++i]; const cmd = argv.slice(sep + 1); if (!cmd.length) { console.error("no command"); process.exit(2); } let stdout = "", status = 0; try { stdout = execFileSync(cmd[0], cmd.slice(1), { cwd: WS, maxBuffer: 8 << 20 }).toString("utf8"); } catch (e) { stdout = (e.stdout || "").toString("utf8"); status = e.status == null ? 1 : e.status; } const id = crypto.randomUUID(); const created = new Date().toISOString(); const inner = { inputs: { argv: cmd, cwd: "ARION-harness" }, issuer_claims: { provenance_class: "EXECUTED BY " + SYS }, outputs: stdout, exit_status: status, receipt_schema_version: "0.4", tool: cmd[0], }; const canonical = Buffer.from(canon(inner), "utf8"); const receipt = { id, receipt_schema_version: "0.4", created_at: created, tool: { name: "arion-harness", version: "1.0.0", scope: "public" }, provenance_class: "EXECUTED BY " + SYS, canonical_bytes: canonical.toString("base64"), output_hash: "sha256:" + sha(canonical), verification_status: "verified", }; // §3.1 metadata block: exactly these members, canonical serialization. const meta = { created_at: receipt.created_at, id: receipt.id, output_hash: receipt.output_hash, provenance_class: receipt.provenance_class, receipt_schema_version: receipt.receipt_schema_version, tool: { name: receipt.tool.name, scope: receipt.tool.scope, version: receipt.tool.version }, }; receipt.metadata_hash = "sha256:" + sha(Buffer.from(canon(meta), "utf8")); const text = JSON.stringify(receipt, null, 2) + "\n"; if (outFile) { fs.writeFileSync(outFile, text); console.error("wrote " + outFile); } else process.stdout.write(text);