agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
130 lines • 6.25 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.populationsMayOverlap = exports.sameRecordedSubject = exports.aboutContainsSubjects = exports.derivedSubjects = exports.deriveCitationKind = void 0;
/** Evidence-owned scope and absence spelling. The model selects a claim; native
* reads determine whether it holds. Several reads never become a broader union. */
const normalize_js_1 = require("../../lib/normalize.js");
const readings_js_1 = require("./readings.js");
const matcher_js_1 = require("./matcher.js");
/** Only normalize the absence family, after every cited read explicitly declares
* absence. Never turn a negative into complete, or downgrade requested completeness. */
function deriveCitationKind(requested, reads) {
if (requested === 'complete' ||
!reads.every((call) => call.outcome === 'ok' && call.emptiness === 'declared-absent'))
return requested;
const kinds = reads.map((call) => ((0, readings_js_1.isAmongRow)(call) ? 'not-found' : 'none'));
const first = kinds[0];
return first !== undefined && kinds.every((kind) => kind === first) ? first : undefined;
}
exports.deriveCitationKind = deriveCitationKind;
const SUBJECT_LIMIT = 256;
const scalar = (value) => (typeof value === 'string' && value.length > 0 && value.length <= SUBJECT_LIMIT) ||
(typeof value === 'number' && Number.isFinite(value));
/** Each recorded population value remains a separate subject. An unavailable,
* all-populations or non-scalar subject cannot be validated against model prose. */
function subjectsOf(call) {
const population = ((0, readings_js_1.scopeOf)(call) ?? []).filter((item) => item.facet === 'population');
if (population.length === 0) {
const name = (0, readings_js_1.isAmongRow)(call) ? call.row?.lookedFor : undefined;
return scalar(name) ? [name] : undefined;
}
if (population.length > SUBJECT_LIMIT ||
population.some((item) => item.from === 'host' || item.any === true || !scalar(item.value)))
return undefined;
return population.map((item) => item.value);
}
/** Canonical public population values only; never model text or a first-value
* shortcut. The delivery adapter may expose a scalar only when this has one value. */
function derivedSubjects(reads) {
const result = [];
for (const call of reads) {
const values = subjectsOf(call);
if (values === undefined)
return [];
for (const value of values) {
if (!result.some((known) => (0, readings_js_1.sameValue)(known, value)))
result.push(value);
if (result.length > SUBJECT_LIMIT)
return [];
}
}
return result;
}
exports.derivedSubjects = derivedSubjects;
/** Bounded text normalization, without deleting hidden characters or accents. */
function normalized(value) {
const raw = String(value);
if (raw.length > SUBJECT_LIMIT)
return undefined;
const text = (0, normalize_js_1.textOf)(raw);
return text.length > 0 && text.length <= SUBJECT_LIMIT * 4 && !/[\p{Cc}\p{Cf}]/u.test(text)
? text
: undefined;
}
/** A supplied label must contain EVERY recorded subject. This checks the label's
* scope only; its surrounding prose and any negative wording remain unchecked. */
function aboutContainsSubjects(about, reads) {
const phrase = normalized(about);
const values = derivedSubjects(reads);
const tokens = phrase === undefined ? [] : (0, matcher_js_1.tokensOf)(phrase);
return (phrase !== undefined &&
values.length > 0 &&
values.every((value) => {
const subject = normalized(value);
return (subject !== undefined && ((0, readings_js_1.sameValue)(phrase, subject) || (0, matcher_js_1.holdsName)(phrase, tokens, subject)));
}));
}
exports.aboutContainsSubjects = aboutContainsSubjects;
function subjects(call) {
return ((0, readings_js_1.scopeOf)(call) ?? []).filter((item) => item.facet === 'subject').map((item) => item.what);
}
/** Cross-tool peers need the app's same declared subject. Within one tool an
* undeclared subject cannot prove a reading unrelated to its sibling. */
function sameRecordedSubject(a, b) {
const left = subjects(a);
const right = subjects(b);
if (left.length === 0 || right.length === 0)
return a.toolName === b.toolName;
return left.some((l) => right.some((r) => (0, readings_js_1.sameValue)(l, r)));
}
exports.sameRecordedSubject = sameRecordedSubject;
function populations(call) {
const groups = new Map();
for (const item of (0, readings_js_1.scopeOf)(call) ?? []) {
if (item.facet !== 'population' || item.argument === undefined)
continue;
groups.set(item.argument, [...(groups.get(item.argument) ?? []), item]);
}
return groups;
}
const concrete = (call, argument, items) => call.row !== undefined &&
!call.row.gaps.some((gap) => gap.kind === 'match' && gap.argument === argument) &&
items.length > 0 &&
items.every((item) => item.any !== true && item.value !== undefined);
/** Only the runtime-admitted resolution attached to the sent value supplies
* aliases. No token, substring or model-answer match establishes overlap. */
function names(item) {
return [
item.value,
item.resolved?.id,
item.resolved?.name,
...(item.resolved?.aliases ?? []),
].filter((value) => value !== undefined);
}
/** Population values in one argument are alternatives; different arguments
* are separate dimensions. A shared concrete dimension can prove disjointness.
* Missing, differently named, or `any` dimensions cannot. */
function populationsMayOverlap(a, b) {
const left = populations(a);
const right = populations(b);
for (const [argument, l] of left) {
const r = right.get(argument);
if (r === undefined || !concrete(a, argument, l) || !concrete(b, argument, r))
continue;
if (!l.some((li) => r.some((ri) => names(li).some((lv) => names(ri).some((rv) => (0, readings_js_1.sameValue)(lv, rv))))))
return false;
}
return true;
}
exports.populationsMayOverlap = populationsMayOverlap;
//# sourceMappingURL=derive.js.map