agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
117 lines • 5.48 kB
JavaScript
import { copyJson } from '../lib/boundedJson.js';
import { captureOwn as own } from './causeCapture.js';
import { projectCauseBasis } from './causeProjection.js';
import { bindStoredCause } from './storedCauseBinding.js';
import { storedCauseSources } from './storedCauseSources.js';
function savedNodes(history) {
const nodes = new Map();
const paths = new Map();
const pending = [
{ node: history.executionTree, path: ['executionTree'] },
];
let visited = 0;
while (pending.length) {
const current = pending.pop();
if (!current)
break;
const { node, path } = current;
if (++visited > 4096 || node === null || typeof node !== 'object' || Array.isArray(node))
throw Error('Invalid saved tree.');
for (const field of ['stageReads', 'stageWrites']) {
const view = own(node, field);
if (view !== undefined &&
(view === null ||
typeof view !== 'object' ||
Array.isArray(view) ||
![Object.prototype, null].includes(Object.getPrototypeOf(view))))
throw Error('Invalid saved captured view.');
}
const id = own(node, 'runtimeStageId');
if (id !== undefined) {
if (typeof id !== 'string' || !id.length || id.length > 128 || nodes.has(id))
throw Error('Ambiguous saved tree.');
nodes.set(id, node);
// JSON arrays of literal segments are injective; dotted/slashed business keys stay whole.
paths.set(JSON.stringify(path), id);
}
const next = own(node, 'next');
if (next !== undefined)
pending.push({ node: next, path: [...path, 'next'] });
const children = own(node, 'children');
if (children !== undefined) {
if (!Array.isArray(children) || children.length > 4096)
throw Error('Invalid saved tree.');
for (let index = children.length - 1; index >= 0; index--)
pending.push({ node: children[index], path: [...path, 'children', String(index)] });
}
}
const readKeys = new Map();
// FootPrint already validated the paths and their digest. Only project their recorded meaning.
for (const path of history.absent ?? []) {
const id = paths.get(JSON.stringify(path.slice(0, -2)));
if (id === undefined)
throw Error('Missing saved observation owner.');
if (path[path.length - 2] !== 'stageReads')
continue;
const keys = readKeys.get(id) ?? [];
const key = path[path.length - 1];
if (key === undefined)
throw Error('Missing saved observation key.');
keys.push(key);
readKeys.set(id, keys);
}
return { nodes, readKeys };
}
/** A narrow graph projection over admitted producer history, never a synthetic RuntimeSnapshot. */
export function projectStoredCause(value, runId, maxBytes) {
try {
// Reuse the shared data-only copier. Its detached tree/sidecar/log are retained privately together.
const history = copyJson(value, maxBytes * 4, true, 256);
if (history.runId !== runId || !Array.isArray(history.commitLog))
return { status: 'unknown', reason: 'native-capture' };
if (history.commitLog.length > 4096)
return { status: 'unknown', reason: 'projection-incomplete' };
const version = own(history.capture, 'version');
if ((version !== 1 && version !== 2) ||
(version === 1 && own(history, 'readSources') !== undefined))
return { status: 'unknown', reason: 'native-capture' };
const sources = version === 2 ? storedCauseSources(history) : undefined;
if (typeof sources === 'string')
return { status: 'unknown', reason: sources };
const { nodes, readKeys } = savedNodes(history);
// These are facts from this one history; no runtime-only fields or alternate history are synthesized.
const basis = {
runId: history.runId,
capture: history.capture,
logAddress: own(history.capture, 'logAddress'),
commitValues: own(own(history.capture, 'modes'), 'commitValues'),
writeProvenance: own(own(history.capture, 'modes'), 'writeProvenance'),
commitLog: history.commitLog,
recorders: history.recorders,
};
const projected = projectCauseBasis(basis, nodes, undefined, {
readKeys,
...(history.initialState === undefined ? {} : { initialState: history.initialState }),
...(sources === undefined ? {} : { sources }),
});
if (projected === undefined || projected === 'truncated')
return { status: 'unknown', reason: 'projection-incomplete' };
if ([
'missing-attestation',
'invalid-attestation',
'run-mismatch',
'scope-not-covered',
'incomplete-extent',
'capture-limited',
].includes(projected.capture.reason))
return { status: 'unknown', reason: 'native-capture' };
return {
status: 'ready',
cause: bindStoredCause(projected.graph, projected.capture, history, version),
};
}
catch {
return { status: 'unknown', reason: 'projection-incomplete' };
}
}
//# sourceMappingURL=storedCauseProjection.js.map