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
JavaScript
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 + '"';
}