agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
141 lines • 7.47 kB
TypeScript
/**
* empty-lookup — the run itself produced this identifier, and the lookup for
* it found nothing. Filed at the WRITE seam, as an advisory, never an
* accusation.
*
* Pattern: pure function over (one finished call, the results its declared
* producers served); the domain check, one seam later than
* `unsupported-argument`.
* Role: the decidable fragment of "did this tool answer, or did it only
* appear to answer?".
*
* THE MEASURED FAILURE. A triage agent's reverse-lookup tool filtered a
* column before a pivot, so the column did not exist yet and EVERY reverse
* lookup returned an empty result — for every identifier, always. The tool
* then answered SUCCESSFULLY with an empty list, and the agent reported in a
* table, with confidence, that the device was "not currently logged in to any
* port on the collected switches" and advised checking physical cabling. It
* was logged in the whole time. Nothing in the framework noticed, because an
* empty result from a broken filter is byte-identical to an empty result from
* a genuine absence.
*
* WHAT THE LIBRARY ALREADY KNOWS, and this check is nothing more than the
* join of the two:
* (a) the identifier was GROUNDED — it came out of an earlier tool result
* in THIS run, from a tool the consumer's author named in
* `Tool.argumentsFrom`;
* (b) the lookup keyed on it came back EMPTY.
* When the run produced the identifier and the lookup for it finds nothing,
* that is worth filing. It is not worth accusing anyone of.
*
* THE CEILING, and it is the whole reason this is an advisory: an empty
* answer can be perfectly true. The device may exist and simply have no
* logins right now. NOTHING IN THIS FILE CAN TELL THOSE APART, and nothing in
* this file pretends to — the finding says "here is a pair worth a look", the
* record carries the ceiling sentence verbatim, and the same finding is filed
* for the true absence and the broken filter alike. A check that guessed
* which one it was looking at would be inventing the certainty the field
* failure was made of.
*
* DECLARED, NEVER INFERRED — the `argumentsFrom` precedent, THIRD use. A tool
* is this check's subject only because its author said its arguments come
* from another tool's results. One declaration now arms three seams:
* `dangling-reference` asks whether the ground is still in reach when the
* tool is OFFERED, `unsupported-argument` whether the value the model chose
* came from that ground when the tool was CALLED, and this whether the ground
* the run really did serve then found anything when the tool ANSWERED.
*
* WHAT COUNTS AS EMPTY — and what this refuses to judge. See
* {@link readLookupResult}. A rowset is counted, never interpreted; an
* absence is read from the marker its own helper mints. Every other shape a
* tool has ever returned — a sentence, an object, a `null`, a claim ticket —
* is BESPOKE: the library cannot see whether it holds rows, so the encounter
* is `not-applicable` and says so in the ledger. That row is the point. A
* check that silently skipped what it could not read would be the decoration
* the disposition family exists to make impossible.
*
* Detection only. Nothing here blocks, retries, rewrites or delays anything;
* the result the model reads is the result the tool returned.
*/
import type { Disposition } from '../disposition/types.js';
import type { ContextError } from '../finding/types.js';
/**
* THE CEILING, as one string with one owner.
*
* Quoted verbatim into every finding's message, into the check's README and
* into its docs page, so the bound cannot drift out of one of them and leave
* a reader thinking the library knows more than it does.
*/
export declare const EMPTY_LOOKUP_CEILING: string;
/** What the library could read about a finished lookup's result. */
export interface LookupResultReading {
/** `'rowset'` — an array, counted. `'absence'` — the `absent()` envelope. */
readonly shape: 'rowset' | 'absence';
readonly empty: boolean;
/** Rows counted, for a rowset. Absent for an absence, which declares itself. */
readonly rows?: number;
}
/**
* READ a finished result's shape, or decline to.
*
* The two readable shapes, and why only these two:
*
* • a **rowset** — an array. Its length is COUNTED, not interpreted: zero
* rows is zero rows, and no judgement about what the tool meant is
* involved. This is the shape the recorded failure actually returned.
* • an **absence** — the `absent()` envelope, whose author said in so many
* words "the search ran and matched nothing".
*
* Everything else returns `undefined`, which the caller files as
* `not-applicable`. A prose sentence, a bespoke `{ rows: [...] }` wrapper, a
* `null`, a placement claim ticket — the library cannot see rows in any of
* them, and guessing is how a checker starts lying.
*
* @param value the tool's own answer, as it returned it.
* @param declaredAbsence whether `readAbsence` recognized the value. PASSED
* IN rather than re-derived here on purpose: the `af_absent` marker has
* exactly one owner (`semantics/coverage/absent.ts`), a second spelling of
* a reserved word would eventually disagree with the first, and
* `src/integrity/` stays a leaf that imports no agent code.
*/
export declare function readLookupResult(value: unknown, declaredAbsence: boolean): LookupResultReading | undefined;
/** One finished call to a tool whose author declared where its arguments come from. */
export interface EmptyLookupCall {
/** The CONSUMER — the tool that was called and answered. */
readonly toolName: string;
/** The provider's id for this call — what the witness points a reader at. */
readonly toolCallId: string;
readonly args: Readonly<Record<string, unknown>>;
/** `Tool.argumentsFrom` — the tools whose results were meant to ground these arguments. */
readonly argumentsFrom: readonly string[];
/** What the library could read about the result — `undefined` = bespoke shape. */
readonly reading: LookupResultReading | undefined;
}
/** One earlier tool result this run served, with the tool that produced it. */
export interface ProducedResult {
readonly toolName: string;
/** The result exactly as the run served it into the conversation. */
readonly text: string;
}
/** One encounter's outcome: the findings, and the disposition it earns. */
export interface EmptyLookupEncounter {
readonly findings: readonly ContextError[];
/**
* The ledger row this encounter files — computed HERE, beside the rules
* that decide it, so "the library refused to judge this shape" is provable
* without a live agent.
*/
readonly disposition: Disposition;
}
/**
* Judge one finished call to an armed tool.
*
* @param call the call and what the library could read of its result. The
* caller has already established the tool declared `argumentsFrom`;
* nothing here re-decides who is a subject.
* @param produced every tool result this run served BEFORE this call, with
* the tool that produced each. Filtered to the declared producers here.
* @param epoch the run iteration, stamped on every witness.
*/
export declare function emptyLookupOf(call: EmptyLookupCall, produced: readonly ProducedResult[], epoch: number): EmptyLookupEncounter;
//# sourceMappingURL=check.d.ts.map