agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
119 lines (118 loc) • 6.26 kB
TypeScript
/**
* semantics/check — the `check:semantics` gate core (9.53.0).
*
* Pattern: humble shell (the tool-lint precedent) — ALL behavior lives here,
* unit-tested; `bin/agentfootprint-check-semantics.mjs` only resolves
* the built module and maps the exit code. Consumers wire the bin
* into their own `check:` scripts beside `check:tools`.
* Role: lib/ layer, pure. Judges SAMPLE RESULTS against the semantic
* vocabulary and the per-class rules; never executes a tool, never
* touches a network — the samples come from the consumer's own mock
* tools or fixtures, the same way `check:tools` receives a dumped
* catalog.
* Emits: N/A.
*
* ## Severity follows provability (the skillGraph check-up law)
*
* Only what the declaration PROVES wrong is an error:
*
* • a `'triage'` / `'inventory'` tool whose sample result declares no
* coverage — the CLASS declared the requirement, the sample violates it;
* • a marker-bearing envelope with faults (series without grain, data
* without provenance, a counter-looking aggregation with `is_counter`
* unstated, malformed shapes) — the marker declared the vocabulary, the
* shape violates it.
*
* Everything else warns: a classed tool with NO samples (the gate cannot
* check what it cannot see — but silence must not read as a pass), and an
* inventory whose facts carry no render hint (useful, not provably harmful).
*
* ## The overclaim rules (`overclaim.ts`) — warnings until a later major
*
* A declaration is a claim, so it is checked like one: a refusal that says
* what does not exist (`refusal-asserts-absence`), an absence that calls the
* input wrong (`absence-asserts-wrongness`), and every `among.authoritative`
* listed for review (`authoritative-names`). So no consumer's CI breaks on
* upgrade they WARN, and only for samples that use the new fields — a
* catalog's `refusals`, an absence minted with `among` — so an app using
* none of them sees no new finding. `strict: true` (the CLI's `--strict`)
* makes the first two errors; `rules: ['overclaim']` makes them errors AND
* judges every sample (any absence, any result whose status says it read
* nothing). `authoritative-names` is a review item and stays a warning.
*/
import type { ToolSearch } from './coverage/search/types.js';
import { type OverclaimCode, type SemanticsRuleSet } from './overclaim.js';
import { type ToolResultClass } from './types.js';
import { type ProjectionIssue } from './projection.js';
/**
* One tool's row in the semantics catalog: its name, its DECLARED result
* class (from `defineTool({ resultClass })`), and sample results to judge —
* typically what the consumer's mock tools return, dumped to JSON the same
* way `check:tools` dumps its catalog.
*/
export interface SemanticsCatalogEntry {
readonly name: string;
readonly resultClass?: ToolResultClass;
readonly results: readonly unknown[];
/**
* The tool's `search` declaration (`defineTool({ search })`), when it has
* one — dumped with the catalog. The gate lists its review items
* (`source-said`, `said-overlap`) as warnings, and reads a sample
* `checked: 'declared'` as declared coverage. Structure was judged at
* `defineTool`; the gate does not re-judge it.
*/
readonly search?: ToolSearch;
/** Optional paired adapter fixtures. Exact preservation, not inference or factual validation. */
readonly projections?: readonly {
readonly before: unknown;
readonly after: unknown;
}[];
/**
* Optional samples of what the tool says when it REFUSES a call — the
* message it throws, the value it returns, or `{ text, argument?, value? }`
* naming what the refused call looked up. A refused call read nothing, so
* each is judged by `refusal-asserts-absence`, on its sentences about what
* was looked up.
*/
readonly refusals?: readonly unknown[];
}
/** How the gate runs — both optional; omitted, the overclaim rules warn. */
export interface CheckSemanticsOptions {
/**
* Opt-in rule sets. `'overclaim'` makes `refusal-asserts-absence` and
* `absence-asserts-wrongness` errors and judges EVERY sample with them, not
* only the samples that use the new fields.
*/
readonly rules?: readonly SemanticsRuleSet[];
/** The CLI's `--strict`: the same two rules are errors, over the same samples. */
readonly strict?: boolean;
}
/** Every code one finding can carry. The four envelope codes are the
* recognizer's own (`SemanticIssueCode`); the rest are the class rules. */
export type SemanticsFindingCode = 'malformed-semantics' | 'series-without-grain' | 'counter-aggregation-unstated' | 'data-without-provenance' | 'triage-without-coverage' | 'inventory-without-coverage' | 'inventory-without-render' | 'unsampled-tool-class' | 'source-said' | 'said-overlap' | 'declared-without-search' | OverclaimCode | ProjectionIssue['code'];
/** One finding — names the TOOL and the FIELD, so the build log points at
* the line to fix, never at the suite. */
export interface SemanticsFinding {
readonly tool: string;
readonly code: SemanticsFindingCode;
readonly severity: 'error' | 'warning';
/** The offending / missing field, dot-pathed ('coverage', 'grain.is_counter'). */
readonly field: string;
/** Which sample result (0-based) — absent for per-tool findings. */
readonly resultIndex?: number;
readonly message: string;
}
/** The gate's verdict. `ok` = no errors (warnings never fail it; the CLI's
* `--strict` recomputes the exit code over warnings too). */
export interface SemanticsReport {
readonly ok: boolean;
readonly findings: readonly SemanticsFinding[];
readonly checkedTools: number;
readonly checkedResults: number;
}
/**
* Judge a semantics catalog. Pure; throws only on a malformed CATALOG (a
* definition-side mistake — the CLI maps it to exit code 2), never on a
* malformed RESULT (that is a finding, the thing this gate exists to report).
*/
export declare function checkSemantics(entries: readonly SemanticsCatalogEntry[], options?: CheckSemanticsOptions): SemanticsReport;