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