UNPKG

agentfootprint

Version:

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

143 lines • 6.8 kB
/** * Internal dispatch/return/delivery receipt boundary, not mounted in the Agent. * The eventual runtime adapter reserves a namespace/run/ordinal before await, * captures at actual execute, then finalizes against the authorized served value. */ import { decodePresentIdentityField, encodeIdentityField } from '../../memory/identity/encode.js'; import { copyJson } from '../../lib/boundedJson.js'; import { canonicalJson } from '../../lib/canonicalJson.js'; import { readSemantics, semanticsForModel, coverageOfSemantics, periodOfSemantics, } from '../../semantics/envelope.js'; import { claimRowsOf } from '../unsupported-claim/ledger.js'; import { BINDING_LIMITS, boundedText, executedScope, scopeDeclarationOf } from './scope.js'; export function resultIdentityOf(identity) { if (!boundedText(identity.namespace) || !boundedText(identity.runId) || !Number.isSafeInteger(identity.ordinal) || identity.ordinal < 1) throw new Error('Invalid runtime result identity.'); return `claim-result/${encodeIdentityField(identity.namespace)}/${encodeIdentityField(identity.runId)}/${identity.ordinal}`; } /** Decode only canonical runtime result occurrences; no lookup or authority is inferred. */ export function readResultIdentity(raw) { if (typeof raw !== 'string' || raw.length > BINDING_LIMITS.resultIdText) throw new TypeError('Invalid saved result identity.'); const parts = raw.split('/'); if (parts.length !== 4 || parts[0] !== 'claim-result') throw new TypeError('Invalid saved result identity.'); const identity = Object.freeze({ namespace: decodePresentIdentityField(parts[1]), runId: decodePresentIdentityField(parts[2]), ordinal: Number(parts[3]), }); if (resultIdentityOf(identity) !== raw) throw new TypeError('Noncanonical saved result identity.'); return identity; } /** Capture selected scope now; a later mutation of args cannot change it. */ export function captureDispatch(identity, tool, executedArgs, declaration) { declaration = scopeDeclarationOf(declaration); const id = resultIdentityOf(identity); if (!boundedText(tool.toolName) || !boundedText(tool.toolCallId) || !Number.isSafeInteger(tool.iteration) || tool.iteration < 0) throw new Error('Invalid tool identity.'); const common = { identity: { ...identity }, id, toolName: tool.toolName, toolCallId: tool.toolCallId, iteration: tool.iteration, scopeDeclaration: { id: declaration.id, version: declaration.version }, ...(declaration.periodDimension !== undefined && { periodDimension: declaration.periodDimension, }), }; try { return copyJson({ ...common, scope: executedScope(executedArgs, declaration) }, BINDING_LIMITS.scopeBytes * 2, true); } catch { return copyJson({ ...common, unavailable: 'missing-scope' }, BINDING_LIMITS.scopeBytes * 2, true); } } /** Preserve the returned reading before any async result rule can mutate it. */ export function captureReturnedEvidence(dispatch, original) { try { const semantics = readSemantics(copyJson(original, BINDING_LIMITS.resultBytes, true)); if (semantics === undefined) throw new Error('No recognized evidence.'); return Object.freeze({ resultId: dispatch.id, semantics }); } catch { return Object.freeze({ resultId: dispatch.id, unavailable: 'invalid-evidence' }); } } /** * returned is the return-time copy from that invocation. served is * the final model-facing payload AFTER all governance, placement and caps. * Visibility is a trusted adapter decision; equality alone grants no access. * Rewrites need a future explicit association, and are unavailable here. */ export function finalizeEvidence(dispatch, returned, served, access) { const unavailable = (reason) => copyJson({ identity: dispatch.identity, id: dispatch.id, toolName: dispatch.toolName, toolCallId: dispatch.toolCallId, iteration: dispatch.iteration, scopeDeclaration: dispatch.scopeDeclaration, unavailable: reason, }, BINDING_LIMITS.scopeBytes * 2, true); if (dispatch.id !== resultIdentityOf(dispatch.identity)) return unavailable('invalid-evidence'); if (access.completed !== true) return unavailable('incomplete-result'); if (access.resultVisible !== true || access.scopeVisible !== true) return unavailable('withheld-evidence'); if (dispatch.unavailable !== undefined) return unavailable(dispatch.unavailable); if (returned.resultId !== dispatch.id) return unavailable('unassociated-result'); if (returned.unavailable !== undefined) return unavailable(returned.unavailable); try { const raw = copyJson(returned.semantics, BINDING_LIMITS.resultBytes, true); const semantics = readSemantics(raw); if (semantics === undefined) return unavailable('invalid-evidence'); const model = typeof served === 'string' ? JSON.parse(served.length <= BINDING_LIMITS.resultBytes ? served : '') : served; const allowed = copyJson(model, BINDING_LIMITS.resultBytes, true); const projection = copyJson(semanticsForModel(semantics), BINDING_LIMITS.resultBytes, true); if (canonicalJson(allowed) !== canonicalJson(projection)) return unavailable('unassociated-result'); const period = periodOfSemantics(semantics); return copyJson({ ...dispatch, facts: claimRowsOf(semantics, { toolName: dispatch.toolName, toolCallId: dispatch.toolCallId, iteration: dispatch.iteration, }), ...((semantics.coverage !== undefined || period !== undefined) && { coverage: coverageOfSemantics(semantics), }), ...(period !== undefined && { period }), }, BINDING_LIMITS.resultBytes, true); } catch { return unavailable('invalid-evidence'); } } /** Freeze the caller's completed evidence universe at the declaration moment. */ export function evidenceSnapshot(namespace, verifiedRunIds, results) { if (!boundedText(namespace) || verifiedRunIds.length > BINDING_LIMITS.results || !verifiedRunIds.every(boundedText) || new Set(verifiedRunIds).size !== verifiedRunIds.length || results.length > BINDING_LIMITS.results) throw new Error('Invalid evidence snapshot boundary.'); return copyJson({ version: 1, namespace, runIds: verifiedRunIds, results }, BINDING_LIMITS.snapshotBytes, true); } //# sourceMappingURL=capture.js.map