UNPKG

agentfootprint

Version:

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

112 lines • 6.42 kB
"use strict"; /** * 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. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.assertResultColumns = exports.normalizeColumns = exports.COLUMN_TYPES = void 0; /** The closed set, in one place, for the assert and for its own error message. */ exports.COLUMN_TYPES = ['number', 'string', 'boolean', 'date']; /** * 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. */ function normalizeColumns(columns) { const out = []; for (const [name, declared] of Object.entries(columns)) { if (typeof declared === 'string') { out.push({ name, type: declared, nullable: false }); continue; } out.push({ name, type: declared.type, nullable: declared.nullable === true }); } return out; } exports.normalizeColumns = normalizeColumns; /** * 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. */ function assertResultColumns(toolName, columns) { if (columns === undefined) return; if (typeof columns !== 'object' || columns === null || Array.isArray(columns)) { throw new Error(`defineTool: tool '${toolName}' declares resultColumns ${JSON.stringify(columns)}, which ` + `is not a map of column name to type. Write it as ` + `{ logical_unit_number: 'number', host_group: 'string' } — or omit the field, which ` + `means this tool promises nothing about its columns and none are ever judged.`); } const entries = Object.entries(columns); if (entries.length === 0) { throw new Error(`defineTool: tool '${toolName}' declares an EMPTY resultColumns. A declaration that names ` + `no column promises nothing, and omitting the field is how "nothing promised" is said ` + `— the two must not be different spellings of the same silence.`); } for (const [name, declared] of entries) { if (name.trim().length === 0) { throw new Error(`defineTool: tool '${toolName}' declares a resultColumns entry with a blank column ` + `name. A column name is what a finding points at, and a blank one points nowhere.`); } const type = typeof declared === 'string' ? declared : declared?.type; if (typeof declared === 'object' && declared !== null && !Array.isArray(declared)) { const nullable = declared.nullable; if (nullable !== undefined && typeof nullable !== 'boolean') { throw new Error(`defineTool: tool '${toolName}' declares resultColumns['${name}'].nullable = ` + `${JSON.stringify(nullable)}, which is not a boolean. \`true\` says a row of this ` + `column may carry no value; omit it (or \`false\`) and a missing value is a ` + `violation like any other.`); } } if (typeof type !== 'string' || !exports.COLUMN_TYPES.includes(type)) { throw new Error(`defineTool: tool '${toolName}' declares column '${name}' as ${JSON.stringify(type)}, ` + `which is not a column type this library has. The types are: ` + `${exports.COLUMN_TYPES.join(', ')} — a bare word ('number') or the object form ` + `({ type: 'number', nullable: true }). There is deliberately no 'unknown': a column ` + `whose type you do not know is a column to leave undeclared, and unlisted columns ` + `are allowed and never judged.`); } } } exports.assertResultColumns = assertResultColumns; //# sourceMappingURL=types.js.map