UNPKG

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
/** * 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;