agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
162 lines • 7.33 kB
JavaScript
;
/**
* runbook/coverage — the spine's ledger: inner folds + chart-declared
* entries + the bridge's own, and the sentence naming the rules.
*
* Pattern: pure fold over three sources, reusing the coverage primitives'
* own recognizer and merge — no second grammar.
* Role: core/runbook.
* Emits: N/A.
*
* The three sources, in the order they land:
* 1. INNER LEDGERS — every recorded inner call's result is read by the
* same `readCoverageResult` funnel the dispatch loop uses, and its
* three lists fold upward. An inner tool's honesty is carried, never
* re-authored.
* 2. THE CHART'S OWN — the procedure states run-specific coverage by
* writing a `coverage` state key (`{ checked?, not_checked?,
* cannot_cover? }`, items as `{what, why?}` bags or bare strings).
* This is the app's vocabulary (which counters mean "not assessed" is
* the chart's to know); the ENVELOPE is the bridge's.
* 3. THE BRIDGE'S OWN — one `checked` entry naming the procedure, its
* recorded step count and the rule set it ran under; and, for a
* verdict-shaped runbook, a `not_checked` entry counting DECLINED rows
* (the three-outcome honesty: a row that reached no classification is
* ground the answer does not cover).
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.carriedProvenanceOf = exports.composeLedger = exports.chartCoverageOf = exports.foldInnerCoverage = void 0;
const read_js_1 = require("../../semantics/coverage/read.js");
const items_js_1 = require("../../semantics/coverage/items.js");
const ledger_js_1 = require("../../semantics/coverage/ledger.js");
const EMPTY = { checked: [], notChecked: [], cannotCover: [] };
/** Fold every recorded inner call's declared coverage upward. */
function foldInnerCoverage(records) {
const checked = [];
const notChecked = [];
const cannotCover = [];
for (const record of records) {
if (record.result === undefined)
continue;
const reading = (0, read_js_1.readCoverageResult)(record.result);
for (const declared of reading?.declared ?? []) {
checked.push(declared.coverage.checked);
notChecked.push(declared.coverage.notChecked);
cannotCover.push(declared.coverage.cannotCover);
}
}
return {
checked: (0, items_js_1.mergeItems)(checked),
notChecked: (0, items_js_1.mergeItems)(notChecked),
cannotCover: (0, items_js_1.mergeItems)(cannotCover),
};
}
exports.foldInnerCoverage = foldInnerCoverage;
/** One list off the chart's `coverage` state key, normalized leniently —
* this is state the chart wrote, not a foreign wire; a malformed entry is
* dropped rather than refused (the run already happened). */
function stateList(value) {
if (!Array.isArray(value))
return [];
const items = [];
for (const entry of value) {
if (typeof entry === 'string' && entry.trim() !== '') {
items.push({ what: entry.trim() });
}
else if (entry !== null &&
typeof entry === 'object' &&
typeof entry.what === 'string') {
const what = entry.what;
const why = entry.why;
items.push({ what, ...(typeof why === 'string' && { why }) });
}
}
return items;
}
/** The chart-declared lists, read off the final state's `coverage` key.
* Accepts both spellings of the two-word keys (state is often snake_case,
* the declaration camelCase) — one reader, no drift. */
function chartCoverageOf(state) {
const bag = state.coverage;
if (bag === null || typeof bag !== 'object' || Array.isArray(bag))
return EMPTY;
const rec = bag;
return {
checked: stateList(rec.checked),
notChecked: stateList(rec.not_checked ?? rec.notChecked),
cannotCover: stateList(rec.cannot_cover ?? rec.cannotCover),
};
}
exports.chartCoverageOf = chartCoverageOf;
/** The rules clause every sentence carries — or the honest silence. */
function rulesClause(rules) {
return rules !== undefined ? ` under ${rules.name} ${rules.version}` : '';
}
/** Compose the whole ledger. Inner folds land first, chart-declared second,
* the bridge's own last — provenance order, so a reader meets the sources
* before the summary. */
function composeLedger(inner, chart, facts) {
const own = [
{
what: `the declared procedure '${facts.chartName}' — ${facts.stepsExecuted} step(s) ` +
`recorded${rulesClause(facts.rules)}`,
...(facts.rowsTotal !== undefined && {
why: `${facts.rowsTotal} row(s) assessed, one decider pass per row`,
}),
},
];
const declinedEntries = facts.declinedRows !== undefined && facts.declinedRows > 0
? [
{
what: `${facts.declinedRows} row(s) that reached NO classification (verdict 'declined')`,
why: 'the declared rules could not be read for them; they are in the rowset with ' +
'everything that IS known, and nothing was guessed to fill the gap',
},
]
: [];
const checked = (0, items_js_1.mergeItems)([inner.checked, chart.checked, own]);
const notChecked = (0, items_js_1.mergeItems)([inner.notChecked, chart.notChecked, declinedEntries]);
const cannotCover = (0, items_js_1.mergeItems)([inner.cannotCover, chart.cannotCover]);
const rowsPart = facts.rowsTotal !== undefined
? `; ${facts.rowsTotal} row(s) assessed` +
(facts.declinedRows !== undefined && facts.declinedRows > 0
? `, ${facts.declinedRows} declined (no classification reached)`
: '')
: '';
const gapsPart = notChecked.length > 0 ? `; ${notChecked.length} not-checked item(s) declared` : '';
const sentence = `Ran '${facts.chartName}'${rulesClause(facts.rules)} — ` +
`${facts.stepsExecuted} step(s) recorded${rowsPart}${gapsPart}.`;
return {
...(checked.length > 0 && { checked }),
...(notChecked.length > 0 && { not_checked: notChecked }),
...(cannotCover.length > 0 && { cannot_cover: cannotCover }),
note: ledger_js_1.COVERAGE_NOTE,
sentence,
};
}
exports.composeLedger = composeLedger;
/**
* The carried provenance stamp: the FIRST inner result that declared an
* `af_provenance` (on its unwrapped payload, or at its top level — both are
* read, in that order). A derived answer is still an answer, and a source's
* confession (`source: 'LOCAL SEED'`) must survive composition.
*/
function carriedProvenanceOf(records) {
const bagOf = (value) => value !== null && typeof value === 'object' && !Array.isArray(value)
? value
: undefined;
for (const record of records) {
const top = bagOf(record.result);
if (top === undefined)
continue;
const payload = bagOf(top.result);
for (const candidate of [payload, top]) {
const stamp = bagOf(candidate?.af_provenance);
if (stamp !== undefined)
return stamp;
}
}
return undefined;
}
exports.carriedProvenanceOf = carriedProvenanceOf;
//# sourceMappingURL=coverage.js.map