UNPKG

agentfootprint

Version:

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

149 lines • 7 kB
/** Authored scope projection over the arguments actually delivered to execute. */ import { copyJson, ownData } from '../../lib/boundedJson.js'; import { canonicalJson } from '../../lib/canonicalJson.js'; import { compareInstants, instantOf } from '../../time/instant.js'; import { periodProblem } from '../../semantics/coverage/period.js'; export const BINDING_LIMITS = Object.freeze({ text: 512, resultIdText: 3104, dimensions: 16, path: 16, scopeBytes: 8192, resultBytes: 256 * 1024, snapshotBytes: 1024 * 1024, results: 256, }); export function boundedText(value) { return typeof value === 'string' && value.trim() !== '' && value.length <= BINDING_LIMITS.text; } export function boundedResultId(value) { return (typeof value === 'string' && value.startsWith('claim-result/') && value.length <= BINDING_LIMITS.resultIdText); } /** Shallow descriptor-only admission for authored arrays containing callbacks. */ export function ownConfigArray(value, maximum) { if (!Array.isArray(value) || Object.getPrototypeOf(value) !== Array.prototype) throw new Error('Expected a plain configuration array.'); const length = Object.getOwnPropertyDescriptor(value, 'length')?.value; if (typeof length !== 'number' || !Number.isSafeInteger(length) || length > maximum || Reflect.ownKeys(value).length !== length + 1) throw new Error('Invalid configuration array.'); const items = []; for (let index = 0; index < length; index++) { const descriptor = Object.getOwnPropertyDescriptor(value, String(index)); if (!descriptor?.enumerable || !('value' in descriptor)) throw new Error('Expected dense configuration data.'); items.push(descriptor.value); } return Object.freeze(items); } /** Inspect authored configuration without evaluating getters or inherited fields. */ export function scopeDeclarationOf(raw) { const source = ownData(raw, ['id', 'version', 'dimensions', 'periodDimension']); const sourceDimensions = ownConfigArray(source.dimensions, BINDING_LIMITS.dimensions); if (!boundedText(source.id) || !boundedText(source.version) || sourceDimensions.length === 0) throw new Error('Invalid scope declaration.'); const names = new Set(); const dimensions = sourceDimensions.map((rawDimension) => { const dimension = ownData(rawDimension, ['name', 'argumentPath', 'default', 'normalize']); const path = copyJson(dimension.argumentPath, BINDING_LIMITS.scopeBytes, true); if (!boundedText(dimension.name) || names.has(dimension.name) || !Array.isArray(path) || path.length === 0 || path.length > BINDING_LIMITS.path || !path.every(boundedText) || (Object.hasOwn(dimension, 'normalize') && typeof dimension.normalize !== 'function')) throw new Error('Invalid scope dimension.'); names.add(dimension.name); return Object.freeze({ name: dimension.name, argumentPath: path, ...(Object.hasOwn(dimension, 'default') && { default: copyJson(dimension.default, BINDING_LIMITS.scopeBytes, true), }), ...(typeof dimension.normalize === 'function' && { normalize: dimension.normalize, }), }); }); if (Object.hasOwn(source, 'periodDimension') && (!boundedText(source.periodDimension) || !names.has(source.periodDimension))) throw new Error('Invalid period dimension.'); return Object.freeze({ id: source.id, version: source.version, dimensions: Object.freeze(dimensions), ...(typeof source.periodDimension === 'string' && { periodDimension: source.periodDimension }), }); } /** Own data properties only: inherited names and accessors supply no scope. */ function argumentAt(args, path) { if (path.length === 0 || path.length > BINDING_LIMITS.path || !path.every(boundedText)) throw new Error('Invalid scope path.'); let cursor = args; for (const key of path) { if (cursor === null || typeof cursor !== 'object') return undefined; const descriptor = Object.getOwnPropertyDescriptor(cursor, key); if (descriptor === undefined) { if (key in cursor) throw new Error('Inherited scope is not executed own-data.'); return undefined; } if (!descriptor.enumerable || !('value' in descriptor)) throw new Error('Scope requires own data.'); cursor = descriptor.value; } return cursor; } export function executedScope(args, declaration) { declaration = scopeDeclarationOf(declaration); const scope = {}; for (const dimension of declaration.dimensions) { if (!boundedText(dimension.name) || Object.hasOwn(scope, dimension.name)) throw new Error('Invalid scope dimension.'); let value = argumentAt(args, dimension.argumentPath); if (value === undefined && Object.hasOwn(dimension, 'default')) value = dimension.default; if (value === undefined) throw new Error('Required scope is missing.'); // Detach BEFORE invoking an authored normalizer: it cannot mutate dispatch. value = copyJson(value, BINDING_LIMITS.scopeBytes, true); if (dimension.normalize !== undefined) value = dimension.normalize(value); Object.defineProperty(scope, dimension.name, { value: copyJson(value, BINDING_LIMITS.scopeBytes, true), enumerable: true, }); } if (declaration.periodDimension !== undefined && !Object.hasOwn(scope, declaration.periodDimension)) throw new Error('The period dimension is missing.'); return copyJson(scope, BINDING_LIMITS.scopeBytes, true); } /** Exact declared dimensions; no inferred subset, defaults or currency conversion. */ export function scopesMatch(evidence, intended) { const expected = ownData(copyJson(intended, BINDING_LIMITS.scopeBytes, true)); return canonicalJson(evidence) === canonicalJson(expected); } /** The existing period/instant owners decide validity and temporal equivalence. */ export function queriedPeriodMatches(scopeValue, period) { const query = ownData(scopeValue, ['from', 'to']); if (periodProblem({ queried: query, held: 'unknown' }, 'camel') !== undefined) return false; const from = instantOf(query.from, 'lenient'); const to = instantOf(query.to, 'lenient'); const actualFrom = instantOf(period.queried.from, 'lenient'); const actualTo = instantOf(period.queried.to, 'lenient'); return (from !== undefined && to !== undefined && actualFrom !== undefined && actualTo !== undefined && compareInstants(from, actualFrom) === 0 && compareInstants(to, actualTo) === 0); } //# sourceMappingURL=scope.js.map