UNPKG

agentfootprint

Version:

The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.

204 lines • 9.34 kB
/** Closed restored-data schemas; shared JSON/coverage/period owners do validation. */ import { copyJson, ownData } from '../../lib/boundedJson.js'; import { normalizeCoverageList } from '../../semantics/coverage/items.js'; import { periodProblem } from '../../semantics/coverage/period.js'; import { BINDING_LIMITS, boundedText, boundedResultId, scopeDeclarationOf } from './scope.js'; import { resultIdentityOf } from './capture.js'; import { BINDING_REASONS } from './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; } export function bindingRuleOf(raw) { const rule = ownData(raw, ['id', 'version', 'toolName', 'scopeDeclaration', 'fact', 'coverage']); const fact = ownData(rule.fact, ['entity', 'field']); if (!boundedText(rule.id) || !boundedText(rule.version) || !boundedText(rule.toolName) || !boundedText(fact.field) || (Object.hasOwn(rule, 'coverage') && typeof rule.coverage !== 'function')) throw new Error('Invalid claim binding rule.'); const scopeDeclaration = scopeDeclarationOf(rule.scopeDeclaration); let entity; if (typeof fact.entity === 'string') { if (!boundedText(fact.entity)) throw new Error('Invalid fact entity.'); entity = fact.entity; } else { const selector = ownData(fact.entity, ['scopeDimension']); if (!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, }), }); } /** Closed coverage admission shared by receipts and saved verification rows. */ export function validateCoverage(raw) { const coverage = ownData(raw, ['checked', 'notChecked', 'cannotCover']); for (const section of ['checked', 'notChecked', 'cannotCover']) { const list = coverage[section]; if (!Array.isArray(list) || list.length > BINDING_LIMITS.results) throw new Error('Invalid coverage list.'); for (const rawItem of list) { const item = ownData(rawItem, ['what', 'why', 'short', 'kind']); if (!boundedText(item.what) || (Object.hasOwn(item, 'why') && !boundedText(item.why))) throw new Error('Invalid coverage item.'); } // Ask the one existing rule owner, but never repair the saved shape. normalizeCoverageList(section, list, section === 'cannotCover'); } } function resultOf(raw) { raw = copyJson(raw, BINDING_LIMITS.resultBytes, true); const result = ownData(raw, [ 'identity', 'id', 'toolName', 'toolCallId', 'iteration', 'scopeDeclaration', 'scope', 'periodDimension', 'unavailable', 'facts', 'coverage', 'period', ]); const identity = ownData(result.identity, ['namespace', 'runId', 'ordinal']); const declaration = ownData(result.scopeDeclaration, ['id', 'version']); if (!boundedText(identity.namespace) || !boundedText(identity.runId) || typeof identity.ordinal !== 'number' || !Number.isSafeInteger(identity.ordinal) || identity.ordinal < 1 || !boundedResultId(result.id) || result.id !== resultIdentityOf({ namespace: identity.namespace, runId: identity.runId, ordinal: identity.ordinal, }) || !boundedText(result.toolName) || !boundedText(result.toolCallId) || typeof result.iteration !== 'number' || !Number.isSafeInteger(result.iteration) || result.iteration < 0 || !boundedText(declaration.id) || !boundedText(declaration.version)) throw new Error('Invalid result receipt identity.'); if (Object.hasOwn(result, 'unavailable')) { if (typeof result.unavailable !== 'string' || !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 = ownData(copyJson(result.scope, BINDING_LIMITS.scopeBytes, true)); if (Object.keys(scope).length === 0 || Object.keys(scope).length > BINDING_LIMITS.dimensions || !Object.keys(scope).every(boundedText) || !Array.isArray(result.facts) || result.facts.length > BINDING_LIMITS.results) throw new Error('Invalid receipt scope or facts.'); if (Object.hasOwn(result, 'periodDimension') && !boundedText(result.periodDimension)) throw new Error('Invalid period dimension.'); for (const rawFact of result.facts) { const fact = ownData(rawFact, [ 'entity', 'field', 'value', 'toolName', 'toolCallId', 'iteration', 'measuredAt', ]); if (!boundedText(fact.entity) || !boundedText(fact.field) || !Object.hasOwn(fact, 'value') || fact.toolName !== result.toolName || fact.toolCallId !== result.toolCallId || fact.iteration !== result.iteration || (Object.hasOwn(fact, 'measuredAt') && !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') && periodProblem(result.period, 'camel') !== undefined) throw new Error('Invalid period receipt.'); } return raw; } /** Validate a detached saved snapshot without evaluating or reconstructing evidence. */ export function readEvidenceSnapshot(raw) { if (raw !== null && typeof raw === 'object' && admittedSnapshots.has(raw)) return raw; const source = ownData(copyJson(raw, BINDING_LIMITS.snapshotBytes, true), [ 'version', 'namespace', 'runIds', 'results', ]); if (source.version !== 1 || !boundedText(source.namespace) || !Array.isArray(source.runIds) || source.runIds.length > BINDING_LIMITS.results || !source.runIds.every(boundedText) || new Set(source.runIds).size !== source.runIds.length || !Array.isArray(source.results) || source.results.length > BINDING_LIMITS.results) throw new Error('Invalid evidence snapshot.'); for (const result of source.results) resultOf(result); return rememberSnapshot(Object.freeze(source)); } /** * 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. */ export 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 >= 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 > BINDING_LIMITS.snapshotBytes) throw new Error('JSON byte limit.'); return rememberSnapshot(Object.freeze({ ...previous, results: Object.freeze([...previous.results, addition.results[0]]), }), bytes); } //# sourceMappingURL=admit.js.map