UNPKG

noa-receipt

Version:

NOA Agent Action Receipt — open, offline-verifiable provenance for AI-agent actions. The governance/receipt organ only; the NOA brain is separate and proprietary.

103 lines (102 loc) • 3.2 kB
import { isFiniteNumber, isInteger, isSafeInteger, objectIs, numToString, isArray, objectKeys, arraySort, strIsWellFormed, strCodePointAt, strSlice, strPadStart, } from "./intrinsics.js"; export class JcsError extends Error { constructor(message) { super(message); this.name = "JcsError"; } } export function canonicalize(value) { return serialize(value, 0); } export const MAX_DEPTH = 64; function serialize(v, depth) { if (depth > MAX_DEPTH) throw new JcsError("max nesting depth exceeded"); if (v === null) return "null"; const t = typeof v; if (t === "boolean") return v ? "true" : "false"; if (t === "number") { const n = v; if (!isFiniteNumber(n)) throw new JcsError("non-finite number not allowed"); if (!isInteger(n)) throw new JcsError("non-integer (float) not allowed in receipts"); if (!isSafeInteger(n)) throw new JcsError("integer outside safe range not allowed"); if (objectIs(n, -0)) return "0"; return numToString(n); } if (t === "bigint") throw new JcsError("bigint not allowed"); if (t === "string") return serializeString(v); if (isArray(v)) { let out = "["; for (let i = 0; i < v.length; i++) { if (i > 0) out += ","; out += serialize(v[i], depth + 1); } return out + "]"; } if (t === "object") { const obj = v; const keys = arraySort(objectKeys(obj)); let out = "{"; let first = true; for (let ki = 0; ki < keys.length; ki++) { const k = keys[ki]; const val = obj[k]; if (val === undefined) throw new JcsError(`undefined value at key "${k}" not allowed`); if (!first) out += ","; first = false; out += serializeString(k) + ":" + serialize(val, depth + 1); } return out + "}"; } throw new JcsError(`unsupported value type: ${t}`); } function serializeString(s) { if (!strIsWellFormed(s)) throw new JcsError("unpaired surrogate in string not allowed"); let out = '"'; for (let i = 0; i < s.length;) { const code = strCodePointAt(s, i); const width = code > 0xffff ? 2 : 1; switch (code) { case 0x22: out += '\\"'; break; case 0x5c: out += "\\\\"; break; case 0x08: out += "\\b"; break; case 0x0c: out += "\\f"; break; case 0x0a: out += "\\n"; break; case 0x0d: out += "\\r"; break; case 0x09: out += "\\t"; break; default: if (code < 0x20) out += "\\u" + strPadStart(numToString(code, 16), 4, "0"); else out += strSlice(s, i, i + width); } i += width; } return out + '"'; }