UNPKG

agentfootprint

Version:

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

176 lines • 8.1 kB
"use strict"; /** * unsupported-claim — the answer's typed claims against the run's settled * facts, at the CLAIM seam. * * Pattern: pure function over (declared contract, ledger rows, parsed * answer); same algebra, last seam. * Role: the check the evidence gate states it cannot do — "a FALSE * CLAIM ASSEMBLED FROM REAL VALUES" passes token grounding. Typed * facts (semantics envelopes) make the contradiction * representable; the DECLARED contract (`.claims()`, the * `argumentsFrom` precedent: the author names the edge, the * checker joins and never infers) makes it decidable. * * THE FENCES, because they are the check's honesty: * - TYPED STRATUM ONLY. The check reads the schema-validated answer * object; prose files nothing, ever. * - LATEST SETTLES, EARLIER QUOTES. Only the last ledger row for a fact * asserts; earlier rows ride as `quoted` witnesses — history is * quotation, so claiming a superseded value is a contradiction WITH the * history on the record. * - UNCOLLECTED IS UNREACHABLE. A declared claim whose fact no envelope * ever carried is incomparable — never an accusation. Same for a ledger * value that is itself an unknown/not-applicable Claim: honest absence * propagates (`participates`). * - DOUBT IS AN ADVISORY, NOT A DEFECT. An answer of `null` or the literal * string 'unknown' where the ledger holds a verified value files a * finding marked `advisory: true` and counted APART — the model * declining to claim is a softer fact than the model claiming wrong. * - ONE STATED LENIENCY: a numeric fact answered as its exact string * ('30' for 30) is not a contradiction — models stringify numbers, and * on an accusation boundary a representational difference is not a * difference. One direction only, string-of-number toward number. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.unsupportedClaimsOf = exports.compareClaim = void 0; const types_js_1 = require("../assertion/types.js"); const evaluate_js_1 = require("./evaluate.js"); /** Minimal dot-path read; `undefined` for any miss along the way. */ function readPath(answer, path) { let cursor = answer; for (const key of path.split('.')) { if (cursor === null || typeof cursor !== 'object') return undefined; cursor = cursor[key]; } return cursor; } /** * Compare ONE declared claim of the parsed answer against the ledger. Pure. * Throws exactly what the pair evaluator throws (a value JSON cannot render). */ function compareClaim(claim, ledger, answer, epoch) { const claimed = readPath(answer, claim.answerField); if (claimed === undefined) return { kind: 'omitted' }; const rows = ledger.filter((r) => r.entity === claim.entity && r.field === claim.field); const settledRow = rows[rows.length - 1]; if (settledRow === undefined) return { kind: 'uncollected' }; const subject = { kind: 'entity', id: claim.entity }; const ledgerProvenance = `${settledRow.toolName} (call ${settledRow.toolCallId}, iteration ` + `${String(settledRow.iteration)})` + (settledRow.measuredAt !== undefined ? `, measured_at ${String(settledRow.measuredAt)}` : ''); const evidence = { subject, predicate: claim.field, value: settledRow.value, epoch, stratum: 'asserted', provenance: ledgerProvenance, }; const assertion = { subject, predicate: claim.field, value: claimed, epoch, stratum: 'asserted', provenance: `answer field '${claim.answerField}'`, }; return { kind: 'compared', evaluation: (0, evaluate_js_1.evaluateClaimPair)(evidence, assertion), claimed, rows, settledRow, subject, ledgerProvenance, evidence, assertion, }; } exports.compareClaim = compareClaim; /** * Compare the parsed answer's declared claims against the ledger. * Pure; the caller files findings and dispositions. */ function unsupportedClaimsOf(declared, ledger, answer, epoch) { const findings = []; const dispositions = []; for (const claim of declared) { const comparison = compareClaim(claim, ledger, answer, epoch); if (comparison.kind === 'omitted') { // The schema allowed the absence: the model claimed nothing here. dispositions.push({ claim: claim.answerField, disposition: 'not-applicable' }); continue; } if (comparison.kind === 'uncollected') { // Never collected — incomparable, never an accusation. dispositions.push({ claim: claim.answerField, disposition: 'unreachable' }); continue; } const { evaluation, claimed, rows, settledRow, subject, ledgerProvenance, evidence, assertion, } = comparison; if (evaluation === 'evidence-unavailable') { dispositions.push({ claim: claim.answerField, disposition: 'unreachable' }); continue; } const settledValue = (0, types_js_1.comparableValueOf)(settledRow.value); if (evaluation === 'doubt-against-value') { // The model declined to claim a fact the run verified — an advisory. findings.push({ kind: 'unsupported-claim', seam: 'claim', advisory: true, subjects: [subject], // Two fields of one entity are two disagreements: without the // predicate their identities collide and the second is deduplicated // away, leaving the event channel disagreeing with the ledger. predicate: claim.field, witnesses: [evidence], epoch, message: `The answer's '${claim.answerField}' says ${JSON.stringify(claimed)} while the run ` + `verified ${claim.field}(${claim.entity}) = ${JSON.stringify(settledValue)} ` + `(${ledgerProvenance}). Declining to claim a verified fact is doubt, not ` + `contradiction — filed as an advisory, counted apart.`, }); dispositions.push({ claim: claim.answerField, disposition: 'checked-pass' }); continue; } if (evaluation !== 'conflict') { // Preserve the existing diagnostic mapping, including both-doubt and // claim-nonparticipating. checked-pass here is NOT a support certificate. dispositions.push({ claim: claim.answerField, disposition: 'checked-pass' }); continue; } const witnesses = [ evidence, assertion, // Earlier values ride as QUOTED history: legitimately superseded, on // the record so a reader sees the fact's whole life, never firing. ...rows.slice(0, -1).map((r) => ({ subject, predicate: claim.field, value: r.value, stratum: 'quoted', provenance: `${r.toolName} (call ${r.toolCallId}, iteration ${String(r.iteration)})`, })), ]; findings.push({ kind: 'unsupported-claim', seam: 'claim', subjects: [subject], predicate: claim.field, witnesses, epoch, message: `The answer's '${claim.answerField}' claims ${JSON.stringify(claimed)} but the run's ` + `settled fact is ${claim.field}(${claim.entity}) = ${JSON.stringify(settledValue)} ` + `(${ledgerProvenance}). The record supports the fact, not the claim — nothing here ` + `rewrites the answer; it makes the disagreement attributable.`, }); dispositions.push({ claim: claim.answerField, disposition: 'checked-fail' }); } return { findings, dispositions }; } exports.unsupportedClaimsOf = unsupportedClaimsOf; //# sourceMappingURL=check.js.map