agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
211 lines • 10.5 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.appendEvidenceSnapshot = exports.readEvidenceSnapshot = exports.validateCoverage = exports.bindingRuleOf = void 0;
/** Closed restored-data schemas; shared JSON/coverage/period owners do validation. */
const boundedJson_js_1 = require("../../lib/boundedJson.js");
const items_js_1 = require("../../semantics/coverage/items.js");
const period_js_1 = require("../../semantics/coverage/period.js");
const scope_js_1 = require("./scope.js");
const capture_js_1 = require("./capture.js");
const types_js_1 = require("./types.js");
// Only this owner's detached, deeply frozen outputs carry this ephemeral proof.
// A frozen input, JSON restore or proxy of an output is NOT an admitted identity.
const admittedSnapshots = new WeakMap();
const encoder = new TextEncoder();
const jsonBytes = (value) => encoder.encode(JSON.stringify(value)).byteLength;
function rememberSnapshot(snapshot, bytes = jsonBytes(snapshot)) {
admittedSnapshots.set(snapshot, Object.freeze({ bytes, emptyBytes: jsonBytes({ ...snapshot, results: [] }) }));
return snapshot;
}
function bindingRuleOf(raw) {
const rule = (0, boundedJson_js_1.ownData)(raw, ['id', 'version', 'toolName', 'scopeDeclaration', 'fact', 'coverage']);
const fact = (0, boundedJson_js_1.ownData)(rule.fact, ['entity', 'field']);
if (!(0, scope_js_1.boundedText)(rule.id) ||
!(0, scope_js_1.boundedText)(rule.version) ||
!(0, scope_js_1.boundedText)(rule.toolName) ||
!(0, scope_js_1.boundedText)(fact.field) ||
(Object.hasOwn(rule, 'coverage') && typeof rule.coverage !== 'function'))
throw new Error('Invalid claim binding rule.');
const scopeDeclaration = (0, scope_js_1.scopeDeclarationOf)(rule.scopeDeclaration);
let entity;
if (typeof fact.entity === 'string') {
if (!(0, scope_js_1.boundedText)(fact.entity))
throw new Error('Invalid fact entity.');
entity = fact.entity;
}
else {
const selector = (0, boundedJson_js_1.ownData)(fact.entity, ['scopeDimension']);
if (!(0, scope_js_1.boundedText)(selector.scopeDimension) ||
!scopeDeclaration.dimensions.some((dimension) => dimension.name === selector.scopeDimension))
throw new Error('Invalid entity scope selector.');
entity = Object.freeze({ scopeDimension: selector.scopeDimension });
}
return Object.freeze({
id: rule.id,
version: rule.version,
toolName: rule.toolName,
scopeDeclaration,
fact: Object.freeze({ entity, field: fact.field }),
...(typeof rule.coverage === 'function' && {
coverage: rule.coverage,
}),
});
}
exports.bindingRuleOf = bindingRuleOf;
/** Closed coverage admission shared by receipts and saved verification rows. */
function validateCoverage(raw) {
const coverage = (0, boundedJson_js_1.ownData)(raw, ['checked', 'notChecked', 'cannotCover']);
for (const section of ['checked', 'notChecked', 'cannotCover']) {
const list = coverage[section];
if (!Array.isArray(list) || list.length > scope_js_1.BINDING_LIMITS.results)
throw new Error('Invalid coverage list.');
for (const rawItem of list) {
const item = (0, boundedJson_js_1.ownData)(rawItem, ['what', 'why', 'short', 'kind']);
if (!(0, scope_js_1.boundedText)(item.what) || (Object.hasOwn(item, 'why') && !(0, scope_js_1.boundedText)(item.why)))
throw new Error('Invalid coverage item.');
}
// Ask the one existing rule owner, but never repair the saved shape.
(0, items_js_1.normalizeCoverageList)(section, list, section === 'cannotCover');
}
}
exports.validateCoverage = validateCoverage;
function resultOf(raw) {
raw = (0, boundedJson_js_1.copyJson)(raw, scope_js_1.BINDING_LIMITS.resultBytes, true);
const result = (0, boundedJson_js_1.ownData)(raw, [
'identity',
'id',
'toolName',
'toolCallId',
'iteration',
'scopeDeclaration',
'scope',
'periodDimension',
'unavailable',
'facts',
'coverage',
'period',
]);
const identity = (0, boundedJson_js_1.ownData)(result.identity, ['namespace', 'runId', 'ordinal']);
const declaration = (0, boundedJson_js_1.ownData)(result.scopeDeclaration, ['id', 'version']);
if (!(0, scope_js_1.boundedText)(identity.namespace) ||
!(0, scope_js_1.boundedText)(identity.runId) ||
typeof identity.ordinal !== 'number' ||
!Number.isSafeInteger(identity.ordinal) ||
identity.ordinal < 1 ||
!(0, scope_js_1.boundedResultId)(result.id) ||
result.id !==
(0, capture_js_1.resultIdentityOf)({
namespace: identity.namespace,
runId: identity.runId,
ordinal: identity.ordinal,
}) ||
!(0, scope_js_1.boundedText)(result.toolName) ||
!(0, scope_js_1.boundedText)(result.toolCallId) ||
typeof result.iteration !== 'number' ||
!Number.isSafeInteger(result.iteration) ||
result.iteration < 0 ||
!(0, scope_js_1.boundedText)(declaration.id) ||
!(0, scope_js_1.boundedText)(declaration.version))
throw new Error('Invalid result receipt identity.');
if (Object.hasOwn(result, 'unavailable')) {
if (typeof result.unavailable !== 'string' ||
!types_js_1.BINDING_REASONS.includes(result.unavailable) ||
['scope', 'periodDimension', 'facts', 'coverage', 'period'].some((key) => Object.hasOwn(result, key)))
throw new Error('Invalid unavailable receipt.');
}
else {
const scope = (0, boundedJson_js_1.ownData)((0, boundedJson_js_1.copyJson)(result.scope, scope_js_1.BINDING_LIMITS.scopeBytes, true));
if (Object.keys(scope).length === 0 ||
Object.keys(scope).length > scope_js_1.BINDING_LIMITS.dimensions ||
!Object.keys(scope).every(scope_js_1.boundedText) ||
!Array.isArray(result.facts) ||
result.facts.length > scope_js_1.BINDING_LIMITS.results)
throw new Error('Invalid receipt scope or facts.');
if (Object.hasOwn(result, 'periodDimension') && !(0, scope_js_1.boundedText)(result.periodDimension))
throw new Error('Invalid period dimension.');
for (const rawFact of result.facts) {
const fact = (0, boundedJson_js_1.ownData)(rawFact, [
'entity',
'field',
'value',
'toolName',
'toolCallId',
'iteration',
'measuredAt',
]);
if (!(0, scope_js_1.boundedText)(fact.entity) ||
!(0, scope_js_1.boundedText)(fact.field) ||
!Object.hasOwn(fact, 'value') ||
fact.toolName !== result.toolName ||
fact.toolCallId !== result.toolCallId ||
fact.iteration !== result.iteration ||
(Object.hasOwn(fact, 'measuredAt') &&
!(0, scope_js_1.boundedText)(fact.measuredAt) &&
typeof fact.measuredAt !== 'number'))
throw new Error('Invalid fact receipt.');
}
if (Object.hasOwn(result, 'coverage'))
validateCoverage(result.coverage);
if (Object.hasOwn(result, 'period') && (0, period_js_1.periodProblem)(result.period, 'camel') !== undefined)
throw new Error('Invalid period receipt.');
}
return raw;
}
/** Validate a detached saved snapshot without evaluating or reconstructing evidence. */
function readEvidenceSnapshot(raw) {
if (raw !== null && typeof raw === 'object' && admittedSnapshots.has(raw))
return raw;
const source = (0, boundedJson_js_1.ownData)((0, boundedJson_js_1.copyJson)(raw, scope_js_1.BINDING_LIMITS.snapshotBytes, true), [
'version',
'namespace',
'runIds',
'results',
]);
if (source.version !== 1 ||
!(0, scope_js_1.boundedText)(source.namespace) ||
!Array.isArray(source.runIds) ||
source.runIds.length > scope_js_1.BINDING_LIMITS.results ||
!source.runIds.every(scope_js_1.boundedText) ||
new Set(source.runIds).size !== source.runIds.length ||
!Array.isArray(source.results) ||
source.results.length > scope_js_1.BINDING_LIMITS.results)
throw new Error('Invalid evidence snapshot.');
for (const result of source.results)
resultOf(result);
return rememberSnapshot(Object.freeze(source));
}
exports.readEvidenceSnapshot = readEvidenceSnapshot;
/**
* Append a singleton admitted at the SAME snapshot root depth. Reuse only
* owner-proven immutable history; unknown inputs still pass the strict reader.
* The carrier owner separately checks reservation/lineage/ordinal coherence.
*/
function appendEvidenceSnapshot(snapshot, singleton) {
const previous = readEvidenceSnapshot(snapshot);
const addition = readEvidenceSnapshot(singleton);
if (addition.results.length !== 1 ||
previous.namespace !== addition.namespace ||
previous.runIds.length !== addition.runIds.length ||
previous.runIds.some((runId, index) => runId !== addition.runIds[index]))
throw new Error('Evidence append requires the same snapshot header and one receipt.');
if (previous.results.length >= scope_js_1.BINDING_LIMITS.results)
throw new Error('Invalid evidence snapshot.');
const priorSize = admittedSnapshots.get(previous);
const additionSize = admittedSnapshots.get(addition);
if (priorSize === undefined || additionSize === undefined)
throw new Error('Missing internal evidence admission proof.');
// Exact UTF-8 JSON bytes, including escaping and the new separating comma.
// No old receipt is re-serialized, re-validated or deep-copied here.
const bytes = priorSize.bytes +
additionSize.bytes -
additionSize.emptyBytes +
(previous.results.length ? 1 : 0);
if (bytes > scope_js_1.BINDING_LIMITS.snapshotBytes)
throw new Error('JSON byte limit.');
return rememberSnapshot(Object.freeze({
...previous,
results: Object.freeze([...previous.results, addition.results[0]]),
}), bytes);
}
exports.appendEvidenceSnapshot = appendEvidenceSnapshot;
//# sourceMappingURL=admit.js.map