agentfootprint 9.76.1 → 9.78.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/CHANGELOG.md +188 -0
  2. package/CLAUDE.md +2 -0
  3. package/dist/core/Agent.js +86 -0
  4. package/dist/core/Agent.js.map +1 -1
  5. package/dist/core/agent/integrityFindings.js +47 -0
  6. package/dist/core/agent/integrityFindings.js.map +1 -0
  7. package/dist/core/agent/stages/callLLM.js +28 -29
  8. package/dist/core/agent/stages/callLLM.js.map +1 -1
  9. package/dist/core/agent/stages/toolCalls.js +184 -0
  10. package/dist/core/agent/stages/toolCalls.js.map +1 -1
  11. package/dist/core/tools.js +9 -1
  12. package/dist/core/tools.js.map +1 -1
  13. package/dist/esm/core/Agent.d.ts +10 -0
  14. package/dist/esm/core/Agent.js +86 -0
  15. package/dist/esm/core/Agent.js.map +1 -1
  16. package/dist/esm/core/agent/integrityFindings.d.ts +27 -0
  17. package/dist/esm/core/agent/integrityFindings.js +43 -0
  18. package/dist/esm/core/agent/integrityFindings.js.map +1 -0
  19. package/dist/esm/core/agent/stages/callLLM.d.ts +25 -0
  20. package/dist/esm/core/agent/stages/callLLM.js +25 -26
  21. package/dist/esm/core/agent/stages/callLLM.js.map +1 -1
  22. package/dist/esm/core/agent/stages/toolCalls.d.ts +43 -0
  23. package/dist/esm/core/agent/stages/toolCalls.js +185 -1
  24. package/dist/esm/core/agent/stages/toolCalls.js.map +1 -1
  25. package/dist/esm/core/agent/types.d.ts +112 -0
  26. package/dist/esm/core/tools.d.ts +63 -0
  27. package/dist/esm/core/tools.js +17 -0
  28. package/dist/esm/core/tools.js.map +1 -1
  29. package/dist/esm/index.d.ts +3 -0
  30. package/dist/esm/index.js +18 -0
  31. package/dist/esm/index.js.map +1 -1
  32. package/dist/esm/integrity/argumentLeaves.d.ts +33 -0
  33. package/dist/esm/integrity/argumentLeaves.js +49 -0
  34. package/dist/esm/integrity/argumentLeaves.js.map +1 -0
  35. package/dist/esm/integrity/column-types/check.d.ts +156 -0
  36. package/dist/esm/integrity/column-types/check.js +363 -0
  37. package/dist/esm/integrity/column-types/check.js.map +1 -0
  38. package/dist/esm/integrity/column-types/types.d.ts +121 -0
  39. package/dist/esm/integrity/column-types/types.js +107 -0
  40. package/dist/esm/integrity/column-types/types.js.map +1 -0
  41. package/dist/esm/integrity/disposition/lifecycle.d.ts +31 -1
  42. package/dist/esm/integrity/disposition/lifecycle.js +54 -1
  43. package/dist/esm/integrity/disposition/lifecycle.js.map +1 -1
  44. package/dist/esm/integrity/empty-lookup/check.d.ts +140 -0
  45. package/dist/esm/integrity/empty-lookup/check.js +212 -0
  46. package/dist/esm/integrity/empty-lookup/check.js.map +1 -0
  47. package/dist/esm/integrity/finding/types.d.ts +23 -2
  48. package/dist/esm/integrity/finding/types.js.map +1 -1
  49. package/dist/esm/integrity/unsupported-argument/check.js +11 -27
  50. package/dist/esm/integrity/unsupported-argument/check.js.map +1 -1
  51. package/dist/esm/lib/mcp/toolExtras.d.ts +17 -2
  52. package/dist/esm/lib/mcp/toolExtras.js +5 -2
  53. package/dist/esm/lib/mcp/toolExtras.js.map +1 -1
  54. package/dist/esm/lib/trace-toolpack/traceToolpack.js +8 -4
  55. package/dist/esm/lib/trace-toolpack/traceToolpack.js.map +1 -1
  56. package/dist/index.js +26 -3
  57. package/dist/index.js.map +1 -1
  58. package/dist/integrity/argumentLeaves.js +54 -0
  59. package/dist/integrity/argumentLeaves.js.map +1 -0
  60. package/dist/integrity/column-types/check.js +368 -0
  61. package/dist/integrity/column-types/check.js.map +1 -0
  62. package/dist/integrity/column-types/types.js +112 -0
  63. package/dist/integrity/column-types/types.js.map +1 -0
  64. package/dist/integrity/disposition/lifecycle.js +54 -1
  65. package/dist/integrity/disposition/lifecycle.js.map +1 -1
  66. package/dist/integrity/empty-lookup/check.js +217 -0
  67. package/dist/integrity/empty-lookup/check.js.map +1 -0
  68. package/dist/integrity/finding/types.js.map +1 -1
  69. package/dist/integrity/unsupported-argument/check.js +13 -29
  70. package/dist/integrity/unsupported-argument/check.js.map +1 -1
  71. package/dist/lib/mcp/toolExtras.js +4 -1
  72. package/dist/lib/mcp/toolExtras.js.map +1 -1
  73. package/dist/lib/trace-toolpack/traceToolpack.js +8 -4
  74. package/dist/lib/trace-toolpack/traceToolpack.js.map +1 -1
  75. package/dist/types/core/Agent.d.ts +10 -0
  76. package/dist/types/core/Agent.d.ts.map +1 -1
  77. package/dist/types/core/agent/integrityFindings.d.ts +28 -0
  78. package/dist/types/core/agent/integrityFindings.d.ts.map +1 -0
  79. package/dist/types/core/agent/stages/callLLM.d.ts +25 -0
  80. package/dist/types/core/agent/stages/callLLM.d.ts.map +1 -1
  81. package/dist/types/core/agent/stages/toolCalls.d.ts +43 -0
  82. package/dist/types/core/agent/stages/toolCalls.d.ts.map +1 -1
  83. package/dist/types/core/agent/types.d.ts +112 -0
  84. package/dist/types/core/agent/types.d.ts.map +1 -1
  85. package/dist/types/core/tools.d.ts +63 -0
  86. package/dist/types/core/tools.d.ts.map +1 -1
  87. package/dist/types/index.d.ts +3 -0
  88. package/dist/types/index.d.ts.map +1 -1
  89. package/dist/types/integrity/argumentLeaves.d.ts +34 -0
  90. package/dist/types/integrity/argumentLeaves.d.ts.map +1 -0
  91. package/dist/types/integrity/column-types/check.d.ts +157 -0
  92. package/dist/types/integrity/column-types/check.d.ts.map +1 -0
  93. package/dist/types/integrity/column-types/types.d.ts +122 -0
  94. package/dist/types/integrity/column-types/types.d.ts.map +1 -0
  95. package/dist/types/integrity/disposition/lifecycle.d.ts +31 -1
  96. package/dist/types/integrity/disposition/lifecycle.d.ts.map +1 -1
  97. package/dist/types/integrity/empty-lookup/check.d.ts +141 -0
  98. package/dist/types/integrity/empty-lookup/check.d.ts.map +1 -0
  99. package/dist/types/integrity/finding/types.d.ts +23 -2
  100. package/dist/types/integrity/finding/types.d.ts.map +1 -1
  101. package/dist/types/integrity/unsupported-argument/check.d.ts.map +1 -1
  102. package/dist/types/lib/mcp/toolExtras.d.ts +17 -2
  103. package/dist/types/lib/mcp/toolExtras.d.ts.map +1 -1
  104. package/dist/types/lib/trace-toolpack/traceToolpack.d.ts.map +1 -1
  105. package/package.json +1 -1
@@ -0,0 +1,363 @@
1
+ /**
2
+ * column-types — the tool declared what its rows contain, and the rows say
3
+ * otherwise. Filed at the WRITE seam, at the moment the tool answers.
4
+ *
5
+ * Pattern: pure function over (one declaration, one finished rowset); the
6
+ * SHAPE check, one storey below `empty-lookup` — that one asks
7
+ * whether the answer had any rows, this one asks what is in them.
8
+ * Role: the decidable fragment of "is this rowset the rowset it claims to
9
+ * be?".
10
+ *
11
+ * ── THE MEASURED FAILURES. Three, and they are one shape ────────────────
12
+ *
13
+ * 1. A mapping report wrote `str(m.get("logical_unit_number") or "")`. LUN 0
14
+ * is falsy, so on 2,094 mappings LUN 0 was stored as an EMPTY STRING —
15
+ * and a host group missing the LUN an initiator probes first became
16
+ * indistinguishable from one that had it. The column was numeric; the
17
+ * value was `''`; nothing anywhere disagreed.
18
+ * 2. A capacity view rendered `round(mib / 1024, 1)`, so an 8 MiB disk came
19
+ * out as `0.0 GB` — which reads as NO DISK, i.e. a provisioning failure,
20
+ * during a live desktop-fleet incident.
21
+ * 3. Earlier in the same application, a whole family of tools returned their
22
+ * numbers as quoted strings (`"1240"`). Every chart silently went blank,
23
+ * because nothing downstream could tell a measure from a label.
24
+ *
25
+ * All three are "a number became something else, and nothing noticed at the
26
+ * seam". The library already lets a tool declare what its result IS
27
+ * (`resultKind`, 9.70.0). It did not let a tool declare what its result
28
+ * CONTAINS, so there was nothing for a rowset to be wrong against.
29
+ *
30
+ * ── THE CEILING, and case 2 is the whole argument for stating it ────────
31
+ * This judges TYPE, never MEANING. It can see that a column declared
32
+ * `number` holds a string. It can NEVER see that the string should have been
33
+ * `0`, or that `0.0` should have been `0.0078`. Case 2 above passes this
34
+ * check cleanly — `0.0` is a perfectly good number — and the check says so
35
+ * out loud rather than letting a green row imply otherwise. The ceiling ships
36
+ * as {@link COLUMN_TYPE_CEILING} and is quoted verbatim into every finding,
37
+ * the `EMPTY_LOOKUP_CEILING` law: one owner for the bound, so it cannot drift
38
+ * out of a message and leave a reader believing the library knows more than
39
+ * it does.
40
+ *
41
+ * ── TWO FINDINGS, because the field bug turned on the difference ────────
42
+ * • `column-type-mismatch` — the column is THERE and holds the wrong thing.
43
+ * Cases 1 and 3.
44
+ * • `missing-column` — the column the author declared is in NO row of the
45
+ * result. Nothing to type-check; the promise was broken one level up.
46
+ * Collapsing them would recreate the exact ambiguity the LUN report died of:
47
+ * "the value is not what it should be" and "the value is not there" send a
48
+ * person to two different files, and a checker that says only "something is
49
+ * off with logical_unit_number" has helped with neither.
50
+ *
51
+ * ── DECLARED, NEVER INFERRED ────────────────────────────────────────────
52
+ * A tool is this check's subject only because its author wrote
53
+ * `resultColumns`. Nothing here sniffs a type off the data — sniffing is
54
+ * precisely what the consumers do today, and precisely what produced the
55
+ * failures above: one stray `''` demotes a numeric column to text, and the
56
+ * demotion is silent.
57
+ *
58
+ * ── WHAT IS NOT JUDGED ──────────────────────────────────────────────────
59
+ * See {@link readRowset}. A result is read only when it is an array of plain
60
+ * objects with at least one row. Everything else — prose, a `null`, a bespoke
61
+ * `{ rows: [...] }` wrapper, a claim ticket, AND the zero-row result — is
62
+ * `not-applicable`, filed as a ROW. The zero-row case belongs to the
63
+ * neighbour (`empty-lookup`) and is deliberately not stolen: an empty result
64
+ * has no columns to be wrong about, and filing `missing-column` for every
65
+ * declared column of an empty answer would turn one honest emptiness into a
66
+ * pile of false accusations.
67
+ */
68
+ import { MAX_QUOTED_CHARS, clipValue } from '../argumentLeaves.js';
69
+ import { normalizeColumns } from './types.js';
70
+ /**
71
+ * THE CEILING, as one string with one owner.
72
+ *
73
+ * Quoted verbatim into every finding's message, into this folder's README and
74
+ * into the docs page, so the bound cannot drift out of one of them.
75
+ */
76
+ export const COLUMN_TYPE_CEILING = 'This judges TYPE, never MEANING — it can see that a column declared `number` holds a ' +
77
+ 'string, and it can never see that the string should have been 0, or that a 0.0 should ' +
78
+ 'have been an 8; a column whose every value has its declared type passes here and can ' +
79
+ 'still be wrong.';
80
+ /**
81
+ * READ a finished result as a rowset, or decline to.
82
+ *
83
+ * The one readable shape, and why only this one: an ARRAY OF PLAIN OBJECTS
84
+ * with at least one row. That is what a rowset is on this wire, it is what
85
+ * every consumer named in the docs page already expects, and it is the same
86
+ * `Array.isArray` law the neighbouring check reads by — the two must never
87
+ * disagree about what a rowset is.
88
+ *
89
+ * `undefined` (⇒ `not-applicable`, a ROW) for everything else:
90
+ * • a non-array — prose, a `null`, a `{ rows: [...] }` wrapper, a ticket;
91
+ * • an array holding anything that is not a plain object — a list of
92
+ * strings has no columns, and inventing some is how a checker starts
93
+ * lying;
94
+ * • an array of ZERO rows — an empty answer has no columns to be wrong
95
+ * about, and it is the neighbour's subject, not this one's.
96
+ */
97
+ export function readRowset(value) {
98
+ if (!Array.isArray(value) || value.length === 0)
99
+ return undefined;
100
+ for (const row of value) {
101
+ if (typeof row !== 'object' || row === null || Array.isArray(row))
102
+ return undefined;
103
+ }
104
+ return { rows: value };
105
+ }
106
+ /** Most columns named in one refusal sentence — a 200-column declaration
107
+ * must not be able to write the context window. */
108
+ const MAX_NAMED_IN_REFUSAL = 5;
109
+ /**
110
+ * Judge one finished call to a tool that declared its result's columns.
111
+ *
112
+ * @param call the call, the declaration, and what the library could read of
113
+ * the result. The caller has already established the tool declared
114
+ * `resultColumns` and that the dial is on; nothing here re-decides arming.
115
+ * @param epoch the run iteration, stamped on every witness.
116
+ */
117
+ export function columnTypesOf(call, epoch) {
118
+ // A shape the library cannot read as a rowset is not a pass and not a
119
+ // fail. It is the check meeting a subject that is out of its scope BY
120
+ // RULE, which is what `not-applicable` was minted for.
121
+ if (call.reading === undefined)
122
+ return { findings: [], disposition: 'not-applicable' };
123
+ const rows = call.reading.rows;
124
+ const subject = { kind: 'tool', id: call.toolName };
125
+ const findings = [];
126
+ const violations = [];
127
+ const missing = [];
128
+ for (const column of normalizeColumns(call.columns)) {
129
+ // ABSENT FROM EVERY ROW is its own finding, and it is checked FIRST so a
130
+ // column nobody delivered can never also be reported as mistyped. The
131
+ // two would be counting the same silence twice, and a reader chasing
132
+ // both would find one bug.
133
+ if (rows.every((row) => !(column.name in row))) {
134
+ missing.push(column.name);
135
+ findings.push({
136
+ kind: 'missing-column',
137
+ seam: 'write',
138
+ subjects: [subject],
139
+ // The column name IS the discriminator: two columns of one result
140
+ // are two findings, exactly as two arguments of one call are at the
141
+ // choice seam.
142
+ predicate: column.name,
143
+ witnesses: [
144
+ {
145
+ subject,
146
+ predicate: column.name,
147
+ value: `declared ${column.type}`,
148
+ epoch,
149
+ stratum: 'asserted',
150
+ provenance: `Tool.resultColumns on '${call.toolName}', as its author wrote it`,
151
+ },
152
+ {
153
+ subject,
154
+ predicate: column.name,
155
+ value: 'present in no row',
156
+ epoch,
157
+ stratum: 'asserted',
158
+ provenance: `tool call ${call.toolCallId}: the result, as the tool returned it (${rows.length} row${rows.length === 1 ? '' : 's'})`,
159
+ },
160
+ ],
161
+ epoch,
162
+ message: `'${call.toolName}' declares a column '${column.name}' (${column.type}) that is in ` +
163
+ `NONE of the ${rows.length} row${rows.length === 1 ? '' : 's'} it returned. This is ` +
164
+ `not a mistyped value — the column is simply not there, so anything downstream that ` +
165
+ `keys on it reads a rowset that cannot answer, and a row missing the column is ` +
166
+ `indistinguishable from a row whose value is nothing. ${COLUMN_TYPE_CEILING} ` +
167
+ `Call id ${call.toolCallId}. ${outcomeClause(call.mode)}`,
168
+ });
169
+ continue;
170
+ }
171
+ const violation = judgeColumn(rows, column.name, column.type, column.nullable);
172
+ if (violation === undefined)
173
+ continue;
174
+ violations.push(violation);
175
+ findings.push({
176
+ kind: 'column-type-mismatch',
177
+ seam: 'write',
178
+ subjects: [subject],
179
+ predicate: column.name,
180
+ witnesses: [
181
+ {
182
+ subject,
183
+ predicate: column.name,
184
+ value: `declared ${column.type}${column.nullable ? ' (nullable)' : ''}`,
185
+ epoch,
186
+ stratum: 'asserted',
187
+ provenance: `Tool.resultColumns on '${call.toolName}', as its author wrote it`,
188
+ },
189
+ {
190
+ subject,
191
+ predicate: column.name,
192
+ value: `${violation.rows} of ${violation.ofRows} rows hold ${violation.got} — first: ${violation.sample}`,
193
+ epoch,
194
+ stratum: 'asserted',
195
+ provenance: `tool call ${call.toolCallId}: the result, as the tool returned it`,
196
+ },
197
+ ],
198
+ epoch,
199
+ message: `'${call.toolName}' declares column '${column.name}' as ${column.type}, and ` +
200
+ `${violation.rows} of ${violation.ofRows} row${violation.ofRows === 1 ? '' : 's'} hold ` +
201
+ `something else — the first is ${violation.sample} (${violation.got}). ` +
202
+ `${nullableHint(column.nullable, violation.got)}${COLUMN_TYPE_CEILING} ` +
203
+ `Call id ${call.toolCallId}. ${outcomeClause(call.mode)}`,
204
+ });
205
+ }
206
+ if (findings.length === 0)
207
+ return { findings, disposition: 'checked-pass' };
208
+ return {
209
+ findings,
210
+ disposition: 'checked-fail',
211
+ ...(call.mode === 'enforce' && {
212
+ refusal: refusalSentence(call.toolName, violations, missing),
213
+ }),
214
+ };
215
+ }
216
+ // ─── Judging one column ────────────────────────────────────────────
217
+ /**
218
+ * Walk every row of one declared column and answer with the disagreement, or
219
+ * `undefined` when there is none.
220
+ *
221
+ * COUNTED, not sampled: the row count is what turns "somebody's data is odd"
222
+ * into "2,094 mappings are wrong", which is the sentence that got the field
223
+ * bug fixed. The value ECHOED is the first offender only — one is enough to
224
+ * recognize the shape, and echoing every one would put a tool's whole result
225
+ * into a finding message.
226
+ */
227
+ function judgeColumn(rows, name, type, nullable) {
228
+ let count = 0;
229
+ let sample;
230
+ let got = '';
231
+ for (const row of rows) {
232
+ const present = name in row;
233
+ const value = present ? row[name] : undefined;
234
+ // "No value" is one idea with three spellings, and they are judged
235
+ // identically: an absent key, a `null` and an `undefined` all say the
236
+ // row has nothing here. `nullable` is the author's word for "that is
237
+ // fine"; without it, nothing is a violation like any other, because the
238
+ // recorded failure was a value that went missing and left a placeholder.
239
+ const empty = !present || value === null || value === undefined;
240
+ if (empty) {
241
+ if (nullable)
242
+ continue;
243
+ count += 1;
244
+ if (sample === undefined) {
245
+ sample = present ? String(value) : 'no such key';
246
+ got = present ? (value === null ? 'null' : 'undefined') : 'missing';
247
+ }
248
+ continue;
249
+ }
250
+ if (matchesType(value, type))
251
+ continue;
252
+ count += 1;
253
+ if (sample === undefined) {
254
+ sample = renderValue(value);
255
+ got = typeName(value);
256
+ }
257
+ }
258
+ if (count === 0 || sample === undefined)
259
+ return undefined;
260
+ return { column: name, declared: type, rows: count, ofRows: rows.length, sample, got };
261
+ }
262
+ /**
263
+ * Does one value have the declared type?
264
+ *
265
+ * The bias is the evidence gate's bias, for the evidence gate's reason: a
266
+ * missed mismatch is a miss, a false accusation costs a real person a real
267
+ * investigation. So `date` accepts anything `Date.parse` accepts, which is
268
+ * permissive enough that a `'2024'` passes — a known, stated false negative,
269
+ * and the right direction to be wrong in.
270
+ */
271
+ function matchesType(value, type) {
272
+ switch (type) {
273
+ case 'number':
274
+ // FINITE. `NaN` and the infinities are numbers that mean "no number",
275
+ // they serialize to `null` over JSON, and an axis handed one draws
276
+ // nothing — which is failure 3's symptom exactly.
277
+ return typeof value === 'number' && Number.isFinite(value);
278
+ case 'string':
279
+ return typeof value === 'string';
280
+ case 'boolean':
281
+ return typeof value === 'boolean';
282
+ case 'date':
283
+ if (value instanceof Date)
284
+ return !Number.isNaN(value.getTime());
285
+ return typeof value === 'string' && !Number.isNaN(Date.parse(value));
286
+ }
287
+ }
288
+ /** What a value IS, in the words a person would use. */
289
+ function typeName(value) {
290
+ if (Array.isArray(value))
291
+ return 'array';
292
+ if (value instanceof Date)
293
+ return 'an invalid Date';
294
+ if (typeof value === 'number')
295
+ return Number.isFinite(value) ? 'number' : `the number ${value}`;
296
+ if (typeof value === 'object')
297
+ return 'object';
298
+ return typeof value;
299
+ }
300
+ /** One value, quoted the way a person would recognize it and clipped so a
301
+ * 5 MB cell cannot ride a finding into the record. */
302
+ function renderValue(value) {
303
+ if (typeof value === 'string')
304
+ return `"${clipValue(value)}"`;
305
+ if (typeof value === 'number' || typeof value === 'boolean')
306
+ return String(value);
307
+ if (value instanceof Date)
308
+ return `a Date`;
309
+ let text;
310
+ try {
311
+ text = JSON.stringify(value) ?? String(value);
312
+ }
313
+ catch {
314
+ text = String(value);
315
+ }
316
+ return text.length > MAX_QUOTED_CHARS ? `${text.slice(0, MAX_QUOTED_CHARS - 1)}…` : text;
317
+ }
318
+ /**
319
+ * The one-word fix, named in the message that reports the problem.
320
+ *
321
+ * Only when the offender is an ABSENCE — that is the case where the author
322
+ * may simply have meant "this column can be empty", and a finding that
323
+ * reports a legitimate null without naming the word that legitimizes it is
324
+ * the noise that gets a check switched off.
325
+ */
326
+ function nullableHint(nullable, got) {
327
+ if (nullable)
328
+ return '';
329
+ if (got !== 'null' && got !== 'undefined' && got !== 'missing')
330
+ return '';
331
+ return ('If this column may legitimately carry no value, say so once — ' +
332
+ "`{ type: '…', nullable: true }` — and rows with nothing in them stop being violations. ");
333
+ }
334
+ /** What the boundary did, stated in the finding rather than assumed by it. */
335
+ function outcomeClause(mode) {
336
+ return mode === 'enforce'
337
+ ? 'The result was REFUSED: the model reads a teaching sentence instead of these rows.'
338
+ : 'Nothing here blocked the call, changed the result, or retried anything — the model reads the rows exactly as the tool returned them.';
339
+ }
340
+ /**
341
+ * The teaching refusal, for `enforce`.
342
+ *
343
+ * The `applyResultCeiling` sentence shape, deliberately: what was wrong, what
344
+ * to do about it, and "No data was returned" — because a model that is handed
345
+ * a partial or truncated answer cannot tell the data ends where the cut
346
+ * happened, and fabricates from the part it saw.
347
+ */
348
+ function refusalSentence(toolName, violations, missing) {
349
+ const parts = [];
350
+ for (const v of violations.slice(0, MAX_NAMED_IN_REFUSAL)) {
351
+ parts.push(`'${v.column}' is declared ${v.declared} and ${v.rows} of ${v.ofRows} rows hold ` +
352
+ `${v.got} (first: ${v.sample})`);
353
+ }
354
+ for (const name of missing.slice(0, Math.max(0, MAX_NAMED_IN_REFUSAL - parts.length))) {
355
+ parts.push(`'${name}' is declared but present in no row`);
356
+ }
357
+ const more = violations.length + missing.length - parts.length;
358
+ const tail = more > 0 ? `, and ${more} more` : '';
359
+ return (`Result rejected: ${toolName} returned rows that disagree with the columns it declares — ` +
360
+ `${parts.join('; ')}${tail}. Fix the tool so the column holds what it declares, or change ` +
361
+ `the declaration. No data was returned.`);
362
+ }
363
+ //# sourceMappingURL=check.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check.js","sourceRoot":"","sources":["../../../../src/integrity/column-types/check.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,121 @@
1
+ /**
2
+ * The COLUMN-TYPE CONTRACT's vocabulary — what a tool may say about the
3
+ * columns of the rowset it returns, and the one rule that judges the saying.
4
+ *
5
+ * Pattern: closed vocabulary + definition-time assert (the `resultCeiling` /
6
+ * `resultClass` law: a declaration this library cannot honour fails
7
+ * at `defineTool`, never at the first row of the first run).
8
+ * Role: leaf. `src/integrity/` imports nothing outside itself, so the
9
+ * vocabulary lives HERE and `core/tools.ts` reaches in for it —
10
+ * never the other way round.
11
+ *
12
+ * ── Why the words are these words ───────────────────────────────────────
13
+ * They are NOT invented. `number | string | boolean | date` is the vocabulary
14
+ * this ecosystem's rowset consumers already speak — a column-type union with
15
+ * exactly these members is what the visualization layer sniffs its way to
16
+ * today. Declaring in the consumer's own words is the `resultKind` law
17
+ * (9.70.0) restated one level down: the tool's result in the CONSUMER's
18
+ * vocabulary, not the framework's.
19
+ *
20
+ * The one member deliberately NOT carried over is `unknown`. A sniffer needs
21
+ * that word — it is what "I looked at the values and could not tell" sounds
22
+ * like. A DECLARATION has no use for it: an author who does not know what a
23
+ * column holds should not name the column, and `columns: { x: 'unknown' }`
24
+ * would be a promise about nothing that the checker would then have to
25
+ * pretend to verify.
26
+ *
27
+ * ── Two spellings, one meaning ──────────────────────────────────────────
28
+ * A column maps to a bare type (`lun: 'number'`) or to an object form
29
+ * (`note: { type: 'string', nullable: true }`). That is this library's house
30
+ * pattern for exactly this shape — `CostBudget` takes a bare number or
31
+ * `{ usd, onExceed }`, `artifacts` takes a bare store or `{ store, placement }`
32
+ * — and it is normalized ONCE, here, so every reader downstream sees one
33
+ * shape and no downstream file learns that there were two.
34
+ */
35
+ /**
36
+ * What a declared column holds.
37
+ *
38
+ * | word | what a value must be |
39
+ * | --- | --- |
40
+ * | `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 |
41
+ * | `string` | a JavaScript string, including the empty one (emptiness is meaning, and meaning is above this check's ceiling) |
42
+ * | `boolean` | `true` or `false` — never `'true'`, never `0`, never `1` |
43
+ * | `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) |
44
+ */
45
+ export type ColumnType = 'number' | 'string' | 'boolean' | 'date';
46
+ /** The closed set, in one place, for the assert and for its own error message. */
47
+ export declare const COLUMN_TYPES: readonly ColumnType[];
48
+ /** The object spelling of one column's declaration. */
49
+ export interface ColumnDeclaration {
50
+ /** What the column holds. */
51
+ readonly type: ColumnType;
52
+ /**
53
+ * `true` — a row of this column may legitimately carry NO VALUE (`null`,
54
+ * `undefined`, or the key simply not set on that row), and such a row is
55
+ * never a type violation.
56
+ *
57
+ * Default `false`, and the default is the strict one ON PURPOSE. The field
58
+ * failure this check is built from was a value that went missing and left
59
+ * an empty string behind; had the same code left a `null` behind, the
60
+ * defect would have been identical and a lenient default would have waved
61
+ * it through. One word turns it off, and every finding names that word — so
62
+ * a legitimate null column costs a one-word edit, while a silent default
63
+ * would cost the bug.
64
+ *
65
+ * `nullable` is a promise about VALUES. It is NOT a promise about the
66
+ * column's existence: a declared column that appears in no row at all is a
67
+ * `missing-column` finding whether or not it is nullable, because the
68
+ * declaration named a column and the result has no such column. The valve
69
+ * for "this column may or may not be there" is to not declare it —
70
+ * unlisted columns are allowed and unjudged.
71
+ */
72
+ readonly nullable?: boolean;
73
+ }
74
+ /**
75
+ * What a tool declares about the columns of its rowset — column name to type.
76
+ *
77
+ * OPEN, NEVER CLOSED. A declaration is a promise about what it NAMES, not a
78
+ * schema of everything the result may contain: a column nobody listed is
79
+ * allowed and is never judged. Two reasons, and both are the same reason.
80
+ *
81
+ * • A closed schema punishes the wrong party. The day the backend adds a
82
+ * column, every one of these tools starts filing findings about a change
83
+ * that broke nothing — and a check that cries about correct behaviour is
84
+ * a check people switch off, which is how the failure it exists to catch
85
+ * gets back in.
86
+ * • It is the rule the neighbouring boundary already keeps.
87
+ * `toolArgsValidation` is permissive on keywords it does not know and
88
+ * enforces `additionalProperties: false` only when an author explicitly
89
+ * asks for it. Two validators at one seam disagreeing about whether
90
+ * silence means "allowed" would be a worse defect than either could
91
+ * catch.
92
+ */
93
+ export type ToolResultColumns = Readonly<Record<string, ColumnType | ColumnDeclaration>>;
94
+ /** One column's declaration after normalization — the ONLY shape any reader
95
+ * downstream of `normalizeColumns` ever sees. */
96
+ export interface NormalizedColumn {
97
+ readonly name: string;
98
+ readonly type: ColumnType;
99
+ readonly nullable: boolean;
100
+ }
101
+ /**
102
+ * Both spellings into one list, in declaration order.
103
+ *
104
+ * Order is kept because it is the author's order, and a finding that names
105
+ * columns in the order the author wrote them is a finding they can scan
106
+ * against their own source.
107
+ */
108
+ export declare function normalizeColumns(columns: ToolResultColumns): readonly NormalizedColumn[];
109
+ /**
110
+ * Refuse a `resultColumns` this library cannot honour, at definition time —
111
+ * naming the tool, the column and the fix.
112
+ *
113
+ * Exported beside {@link ColumnType} and called from `defineTool`, so a
114
+ * misspelled type fails on the line that wrote it rather than at the first
115
+ * rowset of the first armed run. Also called by the MCP ingest
116
+ * (`readToolExtras`) on a bag from a server this process does not control —
117
+ * which is why every read below goes through a fallback: a `null`, a number
118
+ * or an array must reach the teaching refusal, never blow up on the way to
119
+ * it.
120
+ */
121
+ export declare function assertResultColumns(toolName: string, columns: unknown): void;
@@ -0,0 +1,107 @@
1
+ /**
2
+ * The COLUMN-TYPE CONTRACT's vocabulary — what a tool may say about the
3
+ * columns of the rowset it returns, and the one rule that judges the saying.
4
+ *
5
+ * Pattern: closed vocabulary + definition-time assert (the `resultCeiling` /
6
+ * `resultClass` law: a declaration this library cannot honour fails
7
+ * at `defineTool`, never at the first row of the first run).
8
+ * Role: leaf. `src/integrity/` imports nothing outside itself, so the
9
+ * vocabulary lives HERE and `core/tools.ts` reaches in for it —
10
+ * never the other way round.
11
+ *
12
+ * ── Why the words are these words ───────────────────────────────────────
13
+ * They are NOT invented. `number | string | boolean | date` is the vocabulary
14
+ * this ecosystem's rowset consumers already speak — a column-type union with
15
+ * exactly these members is what the visualization layer sniffs its way to
16
+ * today. Declaring in the consumer's own words is the `resultKind` law
17
+ * (9.70.0) restated one level down: the tool's result in the CONSUMER's
18
+ * vocabulary, not the framework's.
19
+ *
20
+ * The one member deliberately NOT carried over is `unknown`. A sniffer needs
21
+ * that word — it is what "I looked at the values and could not tell" sounds
22
+ * like. A DECLARATION has no use for it: an author who does not know what a
23
+ * column holds should not name the column, and `columns: { x: 'unknown' }`
24
+ * would be a promise about nothing that the checker would then have to
25
+ * pretend to verify.
26
+ *
27
+ * ── Two spellings, one meaning ──────────────────────────────────────────
28
+ * A column maps to a bare type (`lun: 'number'`) or to an object form
29
+ * (`note: { type: 'string', nullable: true }`). That is this library's house
30
+ * pattern for exactly this shape — `CostBudget` takes a bare number or
31
+ * `{ usd, onExceed }`, `artifacts` takes a bare store or `{ store, placement }`
32
+ * — and it is normalized ONCE, here, so every reader downstream sees one
33
+ * shape and no downstream file learns that there were two.
34
+ */
35
+ /** The closed set, in one place, for the assert and for its own error message. */
36
+ export const COLUMN_TYPES = ['number', 'string', 'boolean', 'date'];
37
+ /**
38
+ * Both spellings into one list, in declaration order.
39
+ *
40
+ * Order is kept because it is the author's order, and a finding that names
41
+ * columns in the order the author wrote them is a finding they can scan
42
+ * against their own source.
43
+ */
44
+ export function normalizeColumns(columns) {
45
+ const out = [];
46
+ for (const [name, declared] of Object.entries(columns)) {
47
+ if (typeof declared === 'string') {
48
+ out.push({ name, type: declared, nullable: false });
49
+ continue;
50
+ }
51
+ out.push({ name, type: declared.type, nullable: declared.nullable === true });
52
+ }
53
+ return out;
54
+ }
55
+ /**
56
+ * Refuse a `resultColumns` this library cannot honour, at definition time —
57
+ * naming the tool, the column and the fix.
58
+ *
59
+ * Exported beside {@link ColumnType} and called from `defineTool`, so a
60
+ * misspelled type fails on the line that wrote it rather than at the first
61
+ * rowset of the first armed run. Also called by the MCP ingest
62
+ * (`readToolExtras`) on a bag from a server this process does not control —
63
+ * which is why every read below goes through a fallback: a `null`, a number
64
+ * or an array must reach the teaching refusal, never blow up on the way to
65
+ * it.
66
+ */
67
+ export function assertResultColumns(toolName, columns) {
68
+ if (columns === undefined)
69
+ return;
70
+ if (typeof columns !== 'object' || columns === null || Array.isArray(columns)) {
71
+ throw new Error(`defineTool: tool '${toolName}' declares resultColumns ${JSON.stringify(columns)}, which ` +
72
+ `is not a map of column name to type. Write it as ` +
73
+ `{ logical_unit_number: 'number', host_group: 'string' } — or omit the field, which ` +
74
+ `means this tool promises nothing about its columns and none are ever judged.`);
75
+ }
76
+ const entries = Object.entries(columns);
77
+ if (entries.length === 0) {
78
+ throw new Error(`defineTool: tool '${toolName}' declares an EMPTY resultColumns. A declaration that names ` +
79
+ `no column promises nothing, and omitting the field is how "nothing promised" is said ` +
80
+ `— the two must not be different spellings of the same silence.`);
81
+ }
82
+ for (const [name, declared] of entries) {
83
+ if (name.trim().length === 0) {
84
+ throw new Error(`defineTool: tool '${toolName}' declares a resultColumns entry with a blank column ` +
85
+ `name. A column name is what a finding points at, and a blank one points nowhere.`);
86
+ }
87
+ const type = typeof declared === 'string' ? declared : declared?.type;
88
+ if (typeof declared === 'object' && declared !== null && !Array.isArray(declared)) {
89
+ const nullable = declared.nullable;
90
+ if (nullable !== undefined && typeof nullable !== 'boolean') {
91
+ throw new Error(`defineTool: tool '${toolName}' declares resultColumns['${name}'].nullable = ` +
92
+ `${JSON.stringify(nullable)}, which is not a boolean. \`true\` says a row of this ` +
93
+ `column may carry no value; omit it (or \`false\`) and a missing value is a ` +
94
+ `violation like any other.`);
95
+ }
96
+ }
97
+ if (typeof type !== 'string' || !COLUMN_TYPES.includes(type)) {
98
+ throw new Error(`defineTool: tool '${toolName}' declares column '${name}' as ${JSON.stringify(type)}, ` +
99
+ `which is not a column type this library has. The types are: ` +
100
+ `${COLUMN_TYPES.join(', ')} — a bare word ('number') or the object form ` +
101
+ `({ type: 'number', nullable: true }). There is deliberately no 'unknown': a column ` +
102
+ `whose type you do not know is a column to leave undeclared, and unlisted columns ` +
103
+ `are allowed and never judged.`);
104
+ }
105
+ }
106
+ }
107
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../src/integrity/column-types/types.ts"],"names":[],"mappings":"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"}
@@ -10,7 +10,14 @@
10
10
  * compose-invariant: a mount kernel is configured; dangling: at
11
11
  * least one tool declared `argumentsFrom`, which arms BOTH the
12
12
  * compose-seam dangling-reference check and the choice-seam
13
- * unsupported-argument check off the same declaration).
13
+ * unsupported-argument check off the same declaration;
14
+ * empty-lookup: that same declaration AND the operator's
15
+ * `noticeEmptyLookups` dial — two halves, because an advisory that
16
+ * armed itself off a declaration made for something else would not
17
+ * be opt-in at all; column-types: `Tool.resultColumns` AND the
18
+ * operator's `checkColumnTypes` dial off `'off'` — the same two
19
+ * halves, arming BOTH `column-type-mismatch` and `missing-column`
20
+ * off the one declaration).
14
21
  * Registration is what makes silence auditable — an unregistered
15
22
  * check is honest
16
23
  * absence, a registered one that never notes is the wiring rot
@@ -45,6 +52,29 @@ export interface IntegrityChecksPresent {
45
52
  readonly dangling: boolean;
46
53
  /** A `.claims()` contract is declared (9.61.0). */
47
54
  readonly claim?: boolean;
55
+ /**
56
+ * The write seam's `empty-lookup` notice (9.77.0) — armed only when BOTH
57
+ * halves are true: the operator turned `noticeEmptyLookups` on AND at least
58
+ * one tool declared `argumentsFrom`. Deliberately NOT armed by the
59
+ * declaration alone, unlike its two siblings: this check would otherwise
60
+ * start filing advisories in every app that already declares
61
+ * `argumentsFrom` for the other two, and an absent dial must leave a run
62
+ * byte-identical. Absent → a registered `not-applicable` ROW, never
63
+ * silence.
64
+ */
65
+ readonly emptyLookup?: boolean;
66
+ /**
67
+ * The write seam's COLUMN-TYPE CONTRACT (9.78.0) — armed only when BOTH
68
+ * halves are true: the operator set `checkColumnTypes` to something other
69
+ * than `'off'` AND at least one tool declared `resultColumns`. The same
70
+ * two-halves law as `empty-lookup`, for the same reason: a declaration is
71
+ * a promise a consumer may already be reading for its own purposes, and
72
+ * arming a boundary check off it alone would make an absent dial change a
73
+ * run's bytes. ONE FLAG, TWO CHECKS — `column-type-mismatch` and
74
+ * `missing-column` are the same declaration read two ways, and nothing can
75
+ * arm one without arming the other.
76
+ */
77
+ readonly columnTypes?: boolean;
48
78
  }
49
79
  /**
50
80
  * Start one run's ledger: register the present checks and, in dev posture,