agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
115 lines • 4.49 kB
JavaScript
;
/**
* isJsonFaithful — does JSON write this value down faithfully?
*
* Role: Fold. A leaf predicate: it reads its argument and nothing else.
* Reads: its argument.
* Emits: N/A.
*
* True when `JSON.parse(JSON.stringify(value))` gives back the same value —
* the question a binding has to ask before it fingerprints a value with
* `canonicalJson`, which (by design, for the audit chain) writes a `Date` as a
* string, a `Map` as `{}` and `NaN` as `null`. Two different values that JSON
* writes the same way would fingerprint the same, so a binding over them would
* match what it never saw. This predicate is how such a value is refused by
* its DOMAIN instead.
*
* Faithful: `null`, booleans, strings, finite numbers, dense arrays of
* faithful values, and plain objects (prototype `Object.prototype` or `null`,
* string keys, data properties) whose values are faithful.
*
* JSON's own two equivalences are accepted, because JSON is what a person is
* shown and what a stored checkpoint holds: a property whose value is
* `undefined` reads back as absent, and `-0` reads back as `0`.
*
* Not faithful: `undefined` at the top or in an array, `NaN`, `±Infinity`,
* `bigint`, symbols, functions, a `Date`, `Map`, `Set`, `RegExp`, `Error`,
* typed array or any class instance, an array with holes or a named property,
* an object with a symbol key or an accessor property, a cycle, or nesting deeper than
* {@link MAX_JSON_DEPTH}. A shared (non-cyclic) reference is fine.
*
* Never throws: a value it cannot read is not faithful.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.isJsonFaithful = exports.MAX_JSON_DEPTH = void 0;
/** Nesting deeper than this is refused — a depth a tool's arguments never reach. */
exports.MAX_JSON_DEPTH = 100;
function isJsonFaithful(value) {
try {
return faithful(value, 0, new Set());
}
catch {
return false;
}
}
exports.isJsonFaithful = isJsonFaithful;
function faithful(value, depth, path) {
if (value === null)
return true;
switch (typeof value) {
case 'string':
case 'boolean':
return true;
case 'number':
return Number.isFinite(value);
case 'object':
break;
default:
return false;
}
if (depth >= exports.MAX_JSON_DEPTH)
return false;
const object = value;
// A cycle is refused rather than followed; a reference shared by two
// siblings is not a cycle, so the set holds only the current path.
if (path.has(object))
return false;
path.add(object);
try {
return Array.isArray(object)
? faithfulArray(object, depth, path)
: faithfulObject(object, depth, path);
}
finally {
path.delete(object);
}
}
function faithfulArray(array, depth, path) {
if (Object.getPrototypeOf(array) !== Array.prototype)
return false;
// JSON writes the indices only: a named property on an array (`arr.note`)
// would be dropped. Indices plus `length` is every own property it may have.
if (Object.getOwnPropertySymbols(array).length > 0)
return false;
if (Object.getOwnPropertyNames(array).length !== array.length + 1)
return false;
for (let i = 0; i < array.length; i++) {
// A hole and an `undefined` element both read back as `null`.
if (!Object.prototype.hasOwnProperty.call(array, i))
return false;
if (!faithful(array[i], depth + 1, path))
return false;
}
return true;
}
function faithfulObject(object, depth, path) {
const proto = Object.getPrototypeOf(object);
if (proto !== Object.prototype && proto !== null)
return false;
if (Object.getOwnPropertySymbols(object).length > 0)
return false;
for (const key of Object.getOwnPropertyNames(object)) {
const descriptor = Object.getOwnPropertyDescriptor(object, key);
// A getter can answer differently on every read; JSON skips a
// non-enumerable property, so the value it holds would not be shown.
if (descriptor === undefined || !('value' in descriptor) || !descriptor.enumerable) {
return false;
}
if (descriptor.value === undefined)
continue;
if (!faithful(descriptor.value, depth + 1, path))
return false;
}
return true;
}
//# sourceMappingURL=jsonFaithful.js.map