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.

184 lines (183 loc) • 7.57 kB
import { ARRAY_PROTOTYPE, INERT_ARRAY_PROTOTYPE, OBJECT_PROTOTYPE, arrayIndexOf, arrayPush, getOwnPropertyDescriptor, getPrototypeOf, hasOwn, isArray, objectCreateNull, objectDefineProperty, objectFreeze, objectGetOwnPropertyNames, collectionBrand, objectIsFrozen, objectSetPrototypeOf, newWeakSet, weakSetHas, weakSetAdd, publishArray, } from "./intrinsics.js"; export { INERT_ARRAY_PROTOTYPE } from "./intrinsics.js"; export function makeInertArray(values) { objectSetPrototypeOf(values, INERT_ARRAY_PROTOTYPE); return objectFreeze(values); } export function inertArray(values) { objectSetPrototypeOf(values, INERT_ARRAY_PROTOTYPE); return values; } export function isInertArray(v) { return isArray(v) && getPrototypeOf(v) === INERT_ARRAY_PROTOTYPE; } const FROZEN_SET_BRAND = Symbol("noa.frozen-set"); export function frozenSet(values) { const members = []; for (let i = 0; i < values.length; i++) { const v = values[i]; if (arrayIndexOf(members, v) === -1) arrayPush(members, v); } const frozenMembers = makeInertArray(members); const out = objectCreateNull(); objectDefineProperty(out, FROZEN_SET_BRAND, { value: true, enumerable: false, writable: false, configurable: false }); objectDefineProperty(out, "has", { value: (value) => arrayIndexOf(frozenMembers, value) !== -1, enumerable: false, writable: false, configurable: false, }); objectDefineProperty(out, "values", { value: frozenMembers, enumerable: true, writable: false, configurable: false }); objectDefineProperty(out, "size", { value: frozenMembers.length, enumerable: true, writable: false, configurable: false }); return objectFreeze(out); } export function isFrozenSet(v) { return typeof v === "object" && v !== null && hasOwn(v, FROZEN_SET_BRAND); } export function membership(values) { const table = objectCreateNull(); for (let i = 0; i < values.length; i++) { objectDefineProperty(table, values[i], { value: true, enumerable: true, writable: false, configurable: false, }); } objectFreeze(table); return (v) => typeof v === "string" && hasOwn(table, v); } export class MutablePolicyTableError extends Error { path; constructor(message, path) { super(message); this.path = path; this.name = "MutablePolicyTableError"; } } function describe(v) { if (v === null) return "null"; const t = typeof v; if (t !== "object" && t !== "function") return t; if (t === "function") return "a function"; const proto = getPrototypeOf(v); if (proto === null) return "a null-prototype object"; const ctorDesc = getOwnPropertyDescriptor(proto, "constructor"); const ctor = ctorDesc === undefined ? undefined : ctorDesc.value; const nameDesc = ctor === undefined || ctor === null ? undefined : getOwnPropertyDescriptor(ctor, "name"); const ctorName = nameDesc === undefined ? undefined : nameDesc.value; return typeof ctorName === "string" ? `a ${ctorName}` : "an exotic object"; } export function frozenTable(table, path = "<table>") { return freezeInert(table, path); } function freezeInert(v, path) { if (v === null) return v; const t = typeof v; if (t === "string" || t === "number" || t === "boolean" || t === "bigint" || t === "undefined" || t === "symbol") return v; if (t === "function") { throw new MutablePolicyTableError(`policy table ${path} contains a function; a policy table must be inert data only`, path); } if (isFrozenSet(v)) return v; if (isArray(v)) { const arr = v; for (let i = 0; i < arr.length; i++) freezeInert(arr[i], `${path}[${i}]`); return makeInertArray(arr); } const proto = getPrototypeOf(v); if (proto !== OBJECT_PROTOTYPE && proto !== null) { throw new MutablePolicyTableError(`policy table ${path} contains ${describe(v)}; only plain objects, arrays, FrozenSets and primitives are inert ` + `(a Set/Map keeps its mutators through Object.freeze — that is the exact defect this refusal exists to prevent)`, path); } const names = objectGetOwnPropertyNames(v); for (let i = 0; i < names.length; i++) { const k = names[i]; const d = getOwnPropertyDescriptor(v, k); if (d === undefined) continue; if (d.get !== undefined || d.set !== undefined) { throw new MutablePolicyTableError(`policy table ${path}.${k} is an ACCESSOR; a policy table must be plain data (an accessor can answer differently on two reads)`, `${path}.${k}`); } freezeInert(d.value, `${path}.${k}`); } if (proto === OBJECT_PROTOTYPE) objectSetPrototypeOf(v, null); return objectFreeze(v); } export function inertViolations(v, path, seen = newWeakSet()) { const out = []; const walk = (value, at) => { if (value === null) return; const t = typeof value; if (t !== "object" && t !== "function") return; if (t === "function") return; if (weakSetHas(seen, value)) return; weakSetAdd(seen, value); if (isFrozenSet(value)) return; if (collectionBrand(value) !== null) { arrayPush(out, `${at} is ${describe(value)} — its mutators survive Object.freeze, so it is runtime-mutable policy state`); return; } if (!objectIsFrozenSafe(value)) arrayPush(out, `${at} is not frozen`); if (!isArray(value) && getPrototypeOf(value) === OBJECT_PROTOTYPE) { arrayPush(out, `${at} is rooted on the LIVE Object.prototype — one Object.prototype pollution forges a member of this table (C-03; use frozenTable, which re-roots onto null)`); } if (isArray(value)) { const arr = value; if (getPrototypeOf(value) === ARRAY_PROTOTYPE) { arrayPush(out, `${at} is an array rooted on the LIVE Array.prototype — poisoning Array.prototype.includes/find redirects every lookup through it`); } for (let i = 0; i < arr.length; i++) walk(arr[i], `${at}[${i}]`); return; } const names = objectGetOwnPropertyNames(value); for (let i = 0; i < names.length; i++) { const k = names[i]; const d = getOwnPropertyDescriptor(value, k); if (d === undefined) continue; if (d.get !== undefined || d.set !== undefined) { arrayPush(out, `${at}.${k} is an accessor`); continue; } walk(d.value, `${at}.${k}`); } }; walk(v, path); return publishArray(out); } function objectIsFrozenSafe(v) { try { return objectIsFrozen(v); } catch { return false; } } export function deepFreeze(o) { if (o === null || (typeof o !== "object" && typeof o !== "function")) return o; const names = objectGetOwnPropertyNames(o); for (let i = 0; i < names.length; i++) { const key = names[i]; const d = getOwnPropertyDescriptor(o, key); if (d === undefined || d.get !== undefined || d.set !== undefined) continue; const v = d.value; if (v !== null && (typeof v === "object" || typeof v === "function") && !objectIsFrozen(v)) { deepFreeze(v); } } return objectFreeze(o); }