agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
148 lines • 7.22 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.bindClaim = void 0;
/** Exact evidence selection and eligibility, before the shared pair evaluator. */
const boundedJson_js_1 = require("../../lib/boundedJson.js");
const types_js_1 = require("../assertion/types.js");
const period_js_1 = require("../../semantics/coverage/period.js");
const capture_js_1 = require("./capture.js");
const scope_js_1 = require("./scope.js");
const admit_js_1 = require("./admit.js");
const unverified = (reason) => ({ kind: 'unverified', reason });
const isDoubt = (value) => value === null || value === 'unknown' || value === undefined;
/**
* The model supplies only result/value/rule. Snapshot lineage, rule registry
* and intendedScope are runtime/app inputs. A bound pair is not a verdict.
* JSON-restored snapshots must originate from the caller's verified recording;
* this module validates shape and associations, not signatures or ownership.
*/
function bindClaim(rawClaim, snapshot, rules, intendedScope) {
let claim;
try {
claim = (0, boundedJson_js_1.ownData)((0, boundedJson_js_1.copyJson)(rawClaim, scope_js_1.BINDING_LIMITS.scopeBytes, true), [
'result',
'value',
'rule',
]);
if (!(0, scope_js_1.boundedResultId)(claim.result) ||
!(0, scope_js_1.boundedText)(claim.rule) ||
!Object.hasOwn(claim, 'value'))
return unverified('invalid-claim');
}
catch {
return unverified('invalid-claim');
}
let matchingRules;
try {
matchingRules = (0, scope_js_1.ownConfigArray)(rules, scope_js_1.BINDING_LIMITS.results)
.map(admit_js_1.bindingRuleOf)
.filter((rule) => rule.id === claim.rule);
}
catch {
return unverified('invalid-rule');
}
if (matchingRules.length === 0)
return unverified('unknown-rule');
if (matchingRules.length !== 1)
return unverified('invalid-rule');
const rule = matchingRules[0];
if (rule === undefined)
return unverified('invalid-rule');
try {
const saved = (0, admit_js_1.readEvidenceSnapshot)(snapshot);
const matches = saved.results.filter((result) => result.id === claim.result);
if (matches.length === 0)
return unverified('missing-result');
if (matches.length !== 1)
return unverified('ambiguous-result');
const result = matches[0];
if (result === undefined || result.id !== (0, capture_js_1.resultIdentityOf)(result.identity))
return unverified('invalid-evidence');
if (result.identity.namespace !== saved.namespace ||
!saved.runIds.includes(result.identity.runId))
return unverified('foreign-result');
if (result.unavailable !== undefined)
return unverified(result.unavailable);
if (result.toolName !== rule.toolName)
return unverified('invalid-rule');
if (result.scopeDeclaration.id !== rule.scopeDeclaration.id ||
result.scopeDeclaration.version !== rule.scopeDeclaration.version)
return unverified('scope-version-mismatch');
if (result.scope === undefined)
return unverified('missing-scope');
const dimensions = rule.scopeDeclaration.dimensions.map((dimension) => dimension.name);
if (!dimensions.every(scope_js_1.boundedText) ||
new Set(dimensions).size !== dimensions.length ||
dimensions.some((name) => !Object.hasOwn(result.scope, name)) ||
Object.keys(result.scope).some((name) => !dimensions.includes(name)))
return unverified('missing-scope');
if (result.periodDimension !== rule.scopeDeclaration.periodDimension)
return unverified('scope-version-mismatch');
if (intendedScope !== undefined && !(0, scope_js_1.scopesMatch)(result.scope, intendedScope))
return unverified('scope-mismatch');
const entity = typeof rule.fact.entity === 'string'
? rule.fact.entity
: result.scope[rule.fact.entity.scopeDimension];
if (!(0, scope_js_1.boundedText)(entity) || !(0, scope_js_1.boundedText)(rule.fact.field))
return unverified('invalid-rule');
if (!Array.isArray(result.facts))
return unverified('invalid-evidence');
const facts = result.facts.filter((fact) => fact.entity === entity && fact.field === rule.fact.field);
if (facts.length === 0)
return unverified('missing-fact');
if (facts.length !== 1)
return unverified('ambiguous-fact');
const fact = facts[0];
if (fact === undefined || !(0, types_js_1.participates)(fact.value) || !(0, types_js_1.participates)(claim.value))
return unverified('unasserted-value');
// Absence/doubt needs a separately authored absence rule in a later slice.
if (isDoubt((0, types_js_1.comparableValueOf)(fact.value)) || isDoubt((0, types_js_1.comparableValueOf)(claim.value)))
return unverified('unasserted-value');
if (result.period !== undefined) {
const verdict = (0, period_js_1.periodVerdict)(result.period);
if (verdict === 'unknown')
return unverified('coverage-unknown');
if (verdict !== 'covered')
return unverified('coverage-partial');
}
if (result.periodDimension !== undefined) {
if (result.period === undefined)
return unverified('coverage-undeclared');
if (!(0, scope_js_1.queriedPeriodMatches)(result.scope[result.periodDimension], result.period))
return unverified('scope-mismatch');
}
if (rule.coverage === undefined || result.coverage === undefined)
return unverified('coverage-undeclared');
const completeness = rule.coverage({
scope: result.scope,
coverage: result.coverage,
...(result.period !== undefined && { period: result.period }),
});
if (completeness === 'partial')
return unverified('coverage-partial');
if (completeness === 'stale')
return unverified('coverage-stale');
if (completeness !== 'complete')
return unverified('coverage-unknown');
const common = {
subject: { kind: 'entity', id: entity },
predicate: rule.fact.field,
stratum: 'asserted',
};
return (0, boundedJson_js_1.copyJson)({
kind: 'bound',
resultId: result.id,
rule: { id: rule.id, version: rule.version },
scope: result.scope,
coverage: result.coverage,
...(result.period !== undefined && { period: result.period }),
evidence: { ...common, value: fact.value, provenance: `result ${result.id}` },
claim: { ...common, value: claim.value, provenance: `rule ${rule.id}` },
}, scope_js_1.BINDING_LIMITS.resultBytes, true);
}
catch {
return unverified('invalid-evidence');
}
}
exports.bindClaim = bindClaim;
//# sourceMappingURL=bindEvidence.js.map