agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
181 lines • 9.33 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.isClaimVerificationRow = exports.readClaimVerificationRow = exports.readClaimIdentity = exports.claimIdentityOf = exports.VERIFICATION_LIMITS = void 0;
/**
* Strict saved claim-verification protocol. Role: validate and detach a recorded
* check, never reconstruct evidence or rerun its checker. Storage authenticity
* and caller authorization are the surrounding checkpoint owner's responsibility.
*/
const boundedJson_js_1 = require("../../../lib/boundedJson.js");
const canonicalJson_js_1 = require("../../../lib/canonicalJson.js");
const encode_js_1 = require("../../../memory/identity/encode.js");
const types_js_1 = require("../../../integrity/assertion/types.js");
const scope_js_1 = require("../../../integrity/claim-binding/scope.js");
const admit_js_1 = require("../../../integrity/claim-binding/admit.js");
const capture_js_1 = require("../../../integrity/claim-binding/capture.js");
const period_js_1 = require("../../../semantics/coverage/period.js");
const types_js_2 = require("./types.js");
const verificationTypes_js_1 = require("./verificationTypes.js");
/** Reuse receipt bounds; declared values have the compact binding-request cap. */
exports.VERIFICATION_LIMITS = Object.freeze({
rowBytes: scope_js_1.BINDING_LIMITS.resultBytes,
valueBytes: scope_js_1.BINDING_LIMITS.scopeBytes,
identityText: scope_js_1.BINDING_LIMITS.resultIdText + 'claim-verification'.length - 'claim-result'.length,
});
/** A runtime-reserved claim occurrence cannot collide with a result occurrence. */
function claimIdentityOf(raw) {
const identity = (0, boundedJson_js_1.ownData)(raw, ['namespace', 'runId', 'ordinal']);
if (!(0, scope_js_1.boundedText)(identity.namespace) ||
!(0, scope_js_1.boundedText)(identity.runId) ||
typeof identity.ordinal !== 'number' ||
!Number.isSafeInteger(identity.ordinal) ||
identity.ordinal < 1)
throw new TypeError('Invalid runtime claim identity.');
return `claim-verification/${(0, encode_js_1.encodeIdentityField)(identity.namespace)}/${(0, encode_js_1.encodeIdentityField)(identity.runId)}/${identity.ordinal}`;
}
exports.claimIdentityOf = claimIdentityOf;
/** Canonical claim tuple reader, shared with checkpoint lineage/counter checks. */
function readClaimIdentity(value) {
if (typeof value !== 'string' || value.length > exports.VERIFICATION_LIMITS.identityText)
throw new TypeError('Invalid saved claim identity.');
const parts = value.split('/');
if (parts.length !== 4 || parts[0] !== 'claim-verification')
throw new TypeError('Invalid saved claim identity.');
const identity = {
namespace: (0, encode_js_1.decodePresentIdentityField)(parts[1]),
runId: (0, encode_js_1.decodePresentIdentityField)(parts[2]),
ordinal: Number(parts[3]),
};
if (value !== claimIdentityOf(identity))
throw new TypeError('Noncanonical saved claim identity.');
return Object.freeze(identity);
}
exports.readClaimIdentity = readClaimIdentity;
function integer(value, minimum) {
return typeof value === 'number' && Number.isSafeInteger(value) && value >= minimum;
}
function eligibleAssertion(raw) {
const assertion = (0, boundedJson_js_1.ownData)(raw, ['subject', 'predicate', 'value', 'stratum', 'provenance']);
const subject = (0, boundedJson_js_1.ownData)(assertion.subject, ['kind', 'id']);
const comparable = (0, types_js_1.comparableValueOf)(assertion.value);
if (subject.kind !== 'entity' ||
!(0, scope_js_1.boundedText)(subject.id) ||
!(0, scope_js_1.boundedText)(assertion.predicate) ||
assertion.stratum !== 'asserted' ||
typeof assertion.provenance !== 'string' ||
!Object.hasOwn(assertion, 'value') ||
!(0, types_js_1.participates)(assertion.value) ||
comparable === null ||
comparable === undefined ||
comparable === 'unknown')
throw new TypeError('Invalid eligible assertion.');
return assertion;
}
function validateResolved(row, claimIdentity) {
const resolved = (0, boundedJson_js_1.ownData)(row.resolved, [
'resultId',
'rule',
'scope',
'coverage',
'period',
'evidence',
'claim',
]);
const rule = (0, boundedJson_js_1.ownData)(resolved.rule, ['id', 'version']);
const scope = (0, boundedJson_js_1.ownData)((0, boundedJson_js_1.copyJson)(resolved.scope, scope_js_1.BINDING_LIMITS.scopeBytes, true));
const resultIdentity = (0, capture_js_1.readResultIdentity)(resolved.resultId);
if (resultIdentity.namespace !== claimIdentity.namespace ||
resolved.resultId !== row.declaredResult ||
!(0, scope_js_1.boundedText)(rule.id) ||
rule.id !== row.declaredRule ||
!(0, scope_js_1.boundedText)(rule.version) ||
Object.keys(scope).length === 0 ||
Object.keys(scope).length > scope_js_1.BINDING_LIMITS.dimensions ||
!Object.keys(scope).every(scope_js_1.boundedText))
throw new TypeError('Invalid verification association.');
(0, admit_js_1.validateCoverage)(resolved.coverage);
if (Object.hasOwn(resolved, 'period') && (0, period_js_1.periodProblem)(resolved.period, 'camel') !== undefined)
throw new TypeError('Invalid verification period.');
const evidence = eligibleAssertion(resolved.evidence);
const claim = eligibleAssertion(resolved.claim);
if ((0, canonicalJson_js_1.canonicalJson)(evidence.subject) !== (0, canonicalJson_js_1.canonicalJson)(claim.subject) ||
evidence.predicate !== claim.predicate ||
evidence.provenance !== `result ${resolved.resultId}` ||
claim.provenance !== `rule ${rule.id}` ||
(0, canonicalJson_js_1.canonicalJson)(claim.value) !== (0, canonicalJson_js_1.canonicalJson)(row.declaredValue))
throw new TypeError('Invalid verification pair association.');
}
/** Validate the recorded protocol without evaluating whether its value agrees. */
function readClaimVerificationRow(raw) {
const detached = (0, boundedJson_js_1.copyJson)(raw, exports.VERIFICATION_LIMITS.rowBytes, true);
const row = (0, boundedJson_js_1.ownData)(detached, [
'kind',
'claimId',
'declaredOn',
'standing',
'iteration',
'turn',
'checker',
'declaredResult',
'declaredRule',
'declaredValue',
'outcome',
'reason',
'resolved',
]);
const checker = (0, boundedJson_js_1.ownData)(row.checker, ['id', 'version']);
const claimIdentity = readClaimIdentity(row.claimId);
if (row.kind !== 'claim-verification' ||
typeof row.standing !== 'string' ||
!types_js_2.STANDING_VALUES.includes(row.standing) ||
!integer(row.iteration, 0) ||
(Object.hasOwn(row, 'turn') && !integer(row.turn, 1)) ||
checker.id !== 'claim-pair' ||
checker.version !== 1 ||
!Object.hasOwn(row, 'declaredValue') ||
(Object.hasOwn(row, 'declaredResult') &&
(typeof row.declaredResult !== 'string' ||
row.declaredResult.length > scope_js_1.BINDING_LIMITS.resultIdText)) ||
(Object.hasOwn(row, 'declaredRule') && !(0, scope_js_1.boundedText)(row.declaredRule)))
throw new TypeError('Invalid claim-verification row.');
(0, boundedJson_js_1.copyJson)(row.declaredValue, exports.VERIFICATION_LIMITS.valueBytes, true);
if (row.declaredOn !== 'answer') {
const at = (0, boundedJson_js_1.ownData)(row.declaredOn, ['toolCallId']);
if (!(0, scope_js_1.boundedText)(at.toolCallId))
throw new TypeError('Invalid claim declaration location.');
}
if (row.outcome === 'unverified') {
if (typeof row.reason !== 'string' ||
!verificationTypes_js_1.VERIFICATION_UNVERIFIED_REASONS.includes(row.reason) ||
Object.hasOwn(row, 'resolved') ||
(row.standing === 'noise' && row.reason !== 'ineligible-noise') ||
(row.reason === 'ineligible-noise' && row.standing !== 'noise') ||
(row.reason === 'missing-check' && Object.hasOwn(row, 'declaredRule')) ||
(!['missing-check', 'malformed-check', 'ineligible-noise'].includes(row.reason) &&
(!Object.hasOwn(row, 'declaredRule') ||
(row.reason !== 'missing-result' && !Object.hasOwn(row, 'declaredResult')))))
throw new TypeError('Invalid unverified claim row.');
}
else {
if (row.standing === 'noise' ||
!((row.outcome === 'supported' &&
(row.reason === 'no-conflict' || row.reason === 'numeric-leniency')) ||
(row.outcome === 'contradicted' && row.reason === 'conflict')))
throw new TypeError('Invalid compared claim row.');
validateResolved(row, claimIdentity);
}
return detached;
}
exports.readClaimVerificationRow = readClaimVerificationRow;
/** Non-throwing checkpoint union guard; admission uses the same strict reader. */
function isClaimVerificationRow(raw) {
try {
readClaimVerificationRow(raw);
return true;
}
catch {
return false;
}
}
exports.isClaimVerificationRow = isClaimVerificationRow;
//# sourceMappingURL=verificationRow.js.map