agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
122 lines (121 loc) • 6.73 kB
TypeScript
/**
* The COLUMN-TYPE CONTRACT's vocabulary — what a tool may say about the
* columns of the rowset it returns, and the one rule that judges the saying.
*
* Pattern: closed vocabulary + definition-time assert (the `resultCeiling` /
* `resultClass` law: a declaration this library cannot honour fails
* at `defineTool`, never at the first row of the first run).
* Role: leaf. `src/integrity/` imports nothing outside itself, so the
* vocabulary lives HERE and `tools/tools.ts` reaches in for it —
* never the other way round.
*
* ── Why the words are these words ───────────────────────────────────────
* They are NOT invented. `number | string | boolean | date` is the vocabulary
* this ecosystem's rowset consumers already speak — a column-type union with
* exactly these members is what the visualization layer sniffs its way to
* today. Declaring in the consumer's own words is the `resultKind` law
* (9.70.0) restated one level down: the tool's result in the CONSUMER's
* vocabulary, not the framework's.
*
* The one member deliberately NOT carried over is `unknown`. A sniffer needs
* that word — it is what "I looked at the values and could not tell" sounds
* like. A DECLARATION has no use for it: an author who does not know what a
* column holds should not name the column, and `columns: { x: 'unknown' }`
* would be a promise about nothing that the checker would then have to
* pretend to verify.
*
* ── Two spellings, one meaning ──────────────────────────────────────────
* A column maps to a bare type (`lun: 'number'`) or to an object form
* (`note: { type: 'string', nullable: true }`). That is this library's house
* pattern for exactly this shape — `CostBudget` takes a bare number or
* `{ usd, onExceed }`, `artifacts` takes a bare store or `{ store, placement }`
* — and it is normalized ONCE, here, so every reader downstream sees one
* shape and no downstream file learns that there were two.
*/
/**
* What a declared column holds.
*
* | word | what a value must be |
* | --- | --- |
* | `number` | a JavaScript number that is FINITE — `NaN` and the infinities are a number that means "no number", and a chart handed one draws nothing |
* | `string` | a JavaScript string, including the empty one (emptiness is meaning, and meaning is above this check's ceiling) |
* | `boolean` | `true` or `false` — never `'true'`, never `0`, never `1` |
* | `date` | a valid `Date` instance, or a string `Date.parse` accepts (an epoch NUMBER is a `number`; say so and the axis picker stops guessing) |
*/
export type ColumnType = 'number' | 'string' | 'boolean' | 'date';
/** The closed set, in one place, for the assert and for its own error message. */
export declare const COLUMN_TYPES: readonly ColumnType[];
/** The object spelling of one column's declaration. */
export interface ColumnDeclaration {
/** What the column holds. */
readonly type: ColumnType;
/**
* `true` — a row of this column may legitimately carry NO VALUE (`null`,
* `undefined`, or the key simply not set on that row), and such a row is
* never a type violation.
*
* Default `false`, and the default is the strict one ON PURPOSE. The field
* failure this check is built from was a value that went missing and left
* an empty string behind; had the same code left a `null` behind, the
* defect would have been identical and a lenient default would have waved
* it through. One word turns it off, and every finding names that word — so
* a legitimate null column costs a one-word edit, while a silent default
* would cost the bug.
*
* `nullable` is a promise about VALUES. It is NOT a promise about the
* column's existence: a declared column that appears in no row at all is a
* `missing-column` finding whether or not it is nullable, because the
* declaration named a column and the result has no such column. The valve
* for "this column may or may not be there" is to not declare it —
* unlisted columns are allowed and unjudged.
*/
readonly nullable?: boolean;
}
/**
* What a tool declares about the columns of its rowset — column name to type.
*
* OPEN, NEVER CLOSED. A declaration is a promise about what it NAMES, not a
* schema of everything the result may contain: a column nobody listed is
* allowed and is never judged. Two reasons, and both are the same reason.
*
* • A closed schema punishes the wrong party. The day the backend adds a
* column, every one of these tools starts filing findings about a change
* that broke nothing — and a check that cries about correct behaviour is
* a check people switch off, which is how the failure it exists to catch
* gets back in.
* • It is the rule the neighbouring boundary already keeps.
* `toolArgsValidation` is permissive on keywords it does not know and
* enforces `additionalProperties: false` only when an author explicitly
* asks for it. Two validators at one seam disagreeing about whether
* silence means "allowed" would be a worse defect than either could
* catch.
*/
export type ToolResultColumns = Readonly<Record<string, ColumnType | ColumnDeclaration>>;
/** One column's declaration after normalization — the ONLY shape any reader
* downstream of `normalizeColumns` ever sees. */
export interface NormalizedColumn {
readonly name: string;
readonly type: ColumnType;
readonly nullable: boolean;
}
/**
* Both spellings into one list, in declaration order.
*
* Order is kept because it is the author's order, and a finding that names
* columns in the order the author wrote them is a finding they can scan
* against their own source.
*/
export declare function normalizeColumns(columns: ToolResultColumns): readonly NormalizedColumn[];
/**
* Refuse a `resultColumns` this library cannot honour, at definition time —
* naming the tool, the column and the fix.
*
* Exported beside {@link ColumnType} and called from `defineTool`, so a
* misspelled type fails on the line that wrote it rather than at the first
* rowset of the first armed run. Also called by the MCP ingest
* (`readToolExtras`) on a bag from a server this process does not control —
* which is why every read below goes through a fallback: a `null`, a number
* or an array must reach the teaching refusal, never blow up on the way to
* it.
*/
export declare function assertResultColumns(toolName: string, columns: unknown): void;