agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
253 lines • 11.3 kB
JavaScript
;
/**
* phrases — the prose grammar's CLOSED phrase packs: data, versioned and hashed.
*
* Pattern: plain frozen data. The matcher (`matcher.ts · matchPhrase`) is the only code that reads
* it, and the hash pins every number measured with it to the grammar it measured
* (`bench/negative/RULE.md`).
* Role: integrity/ layer (L3), read by `detect.ts` (claims, code and language notes) and
* `containment.ts` (source and window markers, unbounded words), and by the free
* replay (`bench/negative/`). The answer bench keeps its own registered readers, frozen;
* the hedge and source packs began as its lists.
*
* A phrase is a list of SLOTS matched against consecutive tokens of one unit, with only spaces
* between them (so "No, the deploy…" is not "no the"):
* - `a|b|c` — one token, any of the alternatives (tokens are NFC, case-folded);
* - a trailing `?` — the slot may be skipped;
* - `<word>` — any token that starts with a letter;
* - `!a|b` — any letter token EXCEPT these;
* - `<num>` — digits, or a number word (`two` … `twenty`);
* - `<dur>` — a look-back spelling (`24h`, `7d`);
* - `^` — only at the start of the unit (first slot only).
*
* The packs:
* - `none` and `complete` — the CLAIMS: non-existence, and completeness in a frame only
* (never a bare "every" or "zero");
* - `unbounded` — words that make any claim wider than any search ("never", "anywhere");
* - `source` — words that put the negative on the lookup ("the lookup returned nothing"),
* which scope a claim to the source, as a `source.said` word does;
* - `window` — the window a claim names ("in the last 2 hours");
* - `hedge` — words that voice doubt. They are NOT scope: a hedged claim is still a flat claim
* (design § 5.2). The pack exists so that law is data a test can walk.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.resolveNegativeClaimPhrases = exports.NEGATIVE_CLAIM_PHRASES_HASH = exports.NEGATIVE_CLAIM_PHRASES = exports.NEGATIVE_CLAIM_PHRASES_VERSION = void 0;
const sha256_js_1 = require("../../lib/sha256.js");
const stableJson_js_1 = require("../../lib/stableJson.js");
const boundedJson_js_1 = require("../../lib/boundedJson.js");
const matcher_js_1 = require("./matcher.js");
/** Bump with ANY edit to the packs or to what a slot means — the test pins version → hash. */
exports.NEGATIVE_CLAIM_PHRASES_VERSION = '1';
const phrase = (id, words) => Object.freeze({ id, words: Object.freeze(words.split(' ')) });
/** Literal phrases, one slot per word (the hedge and source packs). */
const literal = (pack, text) => phrase(`${pack}.${text.replace(/[^a-z0-9]+/g, '-')}`, text);
const BE = 'is|are|was|were';
const BE_NOT = "isn't|aren't|wasn't|weren't";
const NONE = [
phrase('none.there-are-no', `there ${BE} no`),
phrase('none.there-have-been-no', 'there have|has been no'),
phrase('none.there-may-be-no', 'there may|might|could|will|would|should|must be no'),
phrase('none.there-seem-to-be-no', 'there seem|seems|appear|appears to be no'),
phrase('none.there-are-not-any', `there ${BE} not any`),
phrase('none.there-aren-t-any', `there ${BE_NOT} any`),
phrase('none.there-have-not-been-any', 'there has|have|had not been any'),
phrase('none.there-haven-t-been-any', "there hasn't|haven't|hadn't been any"),
phrase('none.has-no', 'has|have|had no'),
phrase('none.does-not-have-any', 'does|do|did not have any'),
phrase('none.doesn-t-have-any', "doesn't|don't|didn't have any"),
phrase('none.no-such', 'no such'),
phrase('none.does-not-exist', 'does|do|did not exist'),
phrase('none.doesn-t-exist', "doesn't|don't|didn't exist"),
phrase('none.is-not-in', `${BE} not in|anywhere`),
phrase('none.isn-t-in', `${BE_NOT} in|anywhere`),
phrase('none.is-not-found', `${BE} not found`),
phrase('none.isn-t-found', `${BE_NOT} found`),
phrase('none.none-of', 'none of'),
phrase('none.none-are', `none ${BE}|exist|exists`),
phrase('none.nothing-is', 'nothing is|was|has|have'),
phrase('none.nothing-deployed', 'nothing deployed|firing|open|running'),
phrase('none.none', 'none'),
phrase('none.zero-noun', '0 <word>'),
phrase('none.no-lead', '^ no !other|doubt|matter|way|longer|more|less|later|earlier|problem'),
phrase('none.no-noun-is', `no <word> <word>? ${BE}|exist|exists`),
phrase('none.never-had', 'never had|has|have|existed|exist|exists|been|occurred|happened'),
];
const COMPLETE = [
phrase('complete.these-are-all', 'these|those are|were all|every|everything'),
phrase('complete.these-are-the-only', 'these|those are|were the only'),
phrase('complete.that-is-all', 'that|this is|was all|every|everything'),
phrase('complete.that-s-all', "that's all|every|everything"),
phrase('complete.that-is-the-only', 'that|this is|was the only'),
phrase('complete.that-s-the-only', "that's the only"),
phrase('complete.the-complete-list', 'the complete|full|entire|whole list|set|inventory'),
phrase('complete.all-n', 'all <num>'),
phrase('complete.nothing-else', 'nothing else'),
phrase('complete.no-other', 'no other'),
];
const UNBOUNDED = [
phrase('unbounded.never', 'never'),
phrase('unbounded.ever', 'ever'),
phrase('unbounded.anywhere', 'anywhere'),
phrase('unbounded.at-all', 'at all'),
phrase('unbounded.across-all', 'across all'),
phrase('unbounded.in-the-estate', 'in|across|throughout the|your|our|this estate|company|organization|organisation|environment|enterprise'),
];
/** The answer bench's attribution list, minus "according to" and "based on" — hedges, never scope. */
const SOURCE = [
'the lookup returned nothing',
"couldn't find",
'could not find',
"didn't find",
'did not find',
'found no',
'found nothing',
'returned no',
'returned nothing',
'returned an empty',
'returned empty',
'came back empty',
'shows no',
'show no',
'lists no',
'no results',
'no matching',
'matched nothing',
].map((text) => literal('source', text));
const UNIT = 'minute|minutes|hour|hours|day|days|week|weeks';
const WINDOW = [
phrase('window.last-n', `in|over|during|for|within? the? last|past|previous <num>? ${UNIT}`),
phrase('window.last-duration', 'in|over|during|for|within? the? last|past|previous <dur>'),
];
/** The answer bench's hedge list, plus the two § 5.2 names ("based on", "according to"). */
const HEDGE = [
'not sure',
'unsure',
'uncertain',
'unclear',
"can't confirm",
'cannot confirm',
"couldn't confirm",
'could not confirm',
'unable to confirm',
'unable to verify',
"can't verify",
'cannot verify',
"can't tell",
'cannot tell',
"don't know",
'do not know',
'not able to',
'no way to',
'may not',
'might not',
'may be',
'might be',
'possibly',
"it's possible",
'it is possible',
'appears to',
'seems',
'not checked',
"wasn't checked",
"weren't checked",
'not covered',
"doesn't cover",
'does not cover',
"didn't cover",
'did not cover',
'only covers',
'only checked',
'only keeps',
'only holds',
'only shows',
'only includes',
'only returns',
'only lists',
'only has',
'only goes back',
'only reaches',
'limited to',
'partial',
'incomplete',
'timed out',
'not collected',
"couldn't check",
'could not check',
"didn't check",
'did not check',
'not included',
"wasn't reached",
'not reached',
'no data for',
'no information',
"don't have",
'do not have',
"doesn't have",
'does not have',
'not available',
"isn't available",
'caveat',
'keep in mind',
'according to',
'based on',
].map((text) => literal('hedge', text));
/** The built-in packs — closed and frozen, independent of app extensions. */
exports.NEGATIVE_CLAIM_PHRASES = Object.freeze({
none: Object.freeze([...NONE]),
complete: Object.freeze([...COMPLETE]),
unbounded: Object.freeze([...UNBOUNDED]),
source: Object.freeze([...SOURCE]),
window: Object.freeze([...WINDOW]),
hedge: Object.freeze([...HEDGE]),
});
/** sha256 of the packs' stable JSON — what a measured number names. */
exports.NEGATIVE_CLAIM_PHRASES_HASH = (0, sha256_js_1.sha256Hex)((0, stableJson_js_1.stableJson)(exports.NEGATIVE_CLAIM_PHRASES) ?? '');
const DEFAULT_PHRASES = Object.freeze({
packs: exports.NEGATIVE_CLAIM_PHRASES,
version: exports.NEGATIVE_CLAIM_PHRASES_VERSION,
hash: exports.NEGATIVE_CLAIM_PHRASES_HASH,
});
/** Resolve at configuration time. At most 32 literal phrases of 128 UTF-16
* units each; regexes and the built-in slot DSL are rejected as patterns.
* The full effective-pack hash names any extension without changing the
* built-in version/hash or introducing executable predicates. */
function resolveNegativeClaimPhrases(extensions) {
if (extensions === undefined)
return DEFAULT_PHRASES;
const source = (0, boundedJson_js_1.ownData)((0, boundedJson_js_1.copyJson)(extensions, 32 * 128 * 6 + 1024, true), ['none', 'complete']);
const extra = { none: [], complete: [] };
let count = 0;
for (const kind of ['none', 'complete']) {
const values = source[kind];
if (values === undefined)
continue;
if (!Array.isArray(values))
throw new Error(`Negative prose phrases.${kind} must be an array.`);
count += values.length;
if (count > 32)
throw new Error('Negative prose phrases accept at most 32 extensions.');
for (const [index, value] of values.entries()) {
if (typeof value !== 'string' || value.trim().length === 0 || value.length > 128)
throw new Error('A negative prose phrase must contain 1–128 characters.');
if (/[\p{Cc}\p{Cf}]/u.test(value) ||
[...'\\|^$*+?()[]{}<>!'].some((token) => value.includes(token)) ||
/^\/.*\/[a-z]*$/iu.test(value))
throw new Error('Negative prose phrases are literal text, not regexes or slot patterns.');
const words = (0, matcher_js_1.tokensOf)(value).map((token) => token.text);
if (words.length === 0)
throw new Error('A negative prose phrase must contain words.');
extra[kind].push(Object.freeze({ id: `${kind}.app.${index}`, words: Object.freeze(words), literal: true }));
}
}
if (count === 0)
return DEFAULT_PHRASES;
const packs = Object.freeze({
...exports.NEGATIVE_CLAIM_PHRASES,
none: Object.freeze([...exports.NEGATIVE_CLAIM_PHRASES.none, ...extra.none]),
complete: Object.freeze([...exports.NEGATIVE_CLAIM_PHRASES.complete, ...extra.complete]),
});
const hash = (0, sha256_js_1.sha256Hex)((0, stableJson_js_1.stableJson)(packs) ?? '');
return Object.freeze({ packs, hash, version: `${exports.NEGATIVE_CLAIM_PHRASES_VERSION}+${hash}` });
}
exports.resolveNegativeClaimPhrases = resolveNegativeClaimPhrases;
//# sourceMappingURL=phrases.js.map