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
JavaScript
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);
}