UNPKG

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
"use strict"; /** * 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