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.
- package/CHANGELOG.md +188 -0
- package/CLAUDE.md +2 -0
- package/dist/core/Agent.js +86 -0
- package/dist/core/Agent.js.map +1 -1
- package/dist/core/agent/integrityFindings.js +47 -0
- package/dist/core/agent/integrityFindings.js.map +1 -0
- package/dist/core/agent/stages/callLLM.js +28 -29
- package/dist/core/agent/stages/callLLM.js.map +1 -1
- package/dist/core/agent/stages/toolCalls.js +184 -0
- package/dist/core/agent/stages/toolCalls.js.map +1 -1
- package/dist/core/tools.js +9 -1
- package/dist/core/tools.js.map +1 -1
- package/dist/esm/core/Agent.d.ts +10 -0
- package/dist/esm/core/Agent.js +86 -0
- package/dist/esm/core/Agent.js.map +1 -1
- package/dist/esm/core/agent/integrityFindings.d.ts +27 -0
- package/dist/esm/core/agent/integrityFindings.js +43 -0
- package/dist/esm/core/agent/integrityFindings.js.map +1 -0
- package/dist/esm/core/agent/stages/callLLM.d.ts +25 -0
- package/dist/esm/core/agent/stages/callLLM.js +25 -26
- package/dist/esm/core/agent/stages/callLLM.js.map +1 -1
- package/dist/esm/core/agent/stages/toolCalls.d.ts +43 -0
- package/dist/esm/core/agent/stages/toolCalls.js +185 -1
- package/dist/esm/core/agent/stages/toolCalls.js.map +1 -1
- package/dist/esm/core/agent/types.d.ts +112 -0
- package/dist/esm/core/tools.d.ts +63 -0
- package/dist/esm/core/tools.js +17 -0
- package/dist/esm/core/tools.js.map +1 -1
- package/dist/esm/index.d.ts +3 -0
- package/dist/esm/index.js +18 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/integrity/argumentLeaves.d.ts +33 -0
- package/dist/esm/integrity/argumentLeaves.js +49 -0
- package/dist/esm/integrity/argumentLeaves.js.map +1 -0
- package/dist/esm/integrity/column-types/check.d.ts +156 -0
- package/dist/esm/integrity/column-types/check.js +363 -0
- package/dist/esm/integrity/column-types/check.js.map +1 -0
- package/dist/esm/integrity/column-types/types.d.ts +121 -0
- package/dist/esm/integrity/column-types/types.js +107 -0
- package/dist/esm/integrity/column-types/types.js.map +1 -0
- package/dist/esm/integrity/disposition/lifecycle.d.ts +31 -1
- package/dist/esm/integrity/disposition/lifecycle.js +54 -1
- package/dist/esm/integrity/disposition/lifecycle.js.map +1 -1
- package/dist/esm/integrity/empty-lookup/check.d.ts +140 -0
- package/dist/esm/integrity/empty-lookup/check.js +212 -0
- package/dist/esm/integrity/empty-lookup/check.js.map +1 -0
- package/dist/esm/integrity/finding/types.d.ts +23 -2
- package/dist/esm/integrity/finding/types.js.map +1 -1
- package/dist/esm/integrity/unsupported-argument/check.js +11 -27
- package/dist/esm/integrity/unsupported-argument/check.js.map +1 -1
- package/dist/esm/lib/mcp/toolExtras.d.ts +17 -2
- package/dist/esm/lib/mcp/toolExtras.js +5 -2
- package/dist/esm/lib/mcp/toolExtras.js.map +1 -1
- package/dist/esm/lib/trace-toolpack/traceToolpack.js +8 -4
- package/dist/esm/lib/trace-toolpack/traceToolpack.js.map +1 -1
- package/dist/index.js +26 -3
- package/dist/index.js.map +1 -1
- package/dist/integrity/argumentLeaves.js +54 -0
- package/dist/integrity/argumentLeaves.js.map +1 -0
- package/dist/integrity/column-types/check.js +368 -0
- package/dist/integrity/column-types/check.js.map +1 -0
- package/dist/integrity/column-types/types.js +112 -0
- package/dist/integrity/column-types/types.js.map +1 -0
- package/dist/integrity/disposition/lifecycle.js +54 -1
- package/dist/integrity/disposition/lifecycle.js.map +1 -1
- package/dist/integrity/empty-lookup/check.js +217 -0
- package/dist/integrity/empty-lookup/check.js.map +1 -0
- package/dist/integrity/finding/types.js.map +1 -1
- package/dist/integrity/unsupported-argument/check.js +13 -29
- package/dist/integrity/unsupported-argument/check.js.map +1 -1
- package/dist/lib/mcp/toolExtras.js +4 -1
- package/dist/lib/mcp/toolExtras.js.map +1 -1
- package/dist/lib/trace-toolpack/traceToolpack.js +8 -4
- package/dist/lib/trace-toolpack/traceToolpack.js.map +1 -1
- package/dist/types/core/Agent.d.ts +10 -0
- package/dist/types/core/Agent.d.ts.map +1 -1
- package/dist/types/core/agent/integrityFindings.d.ts +28 -0
- package/dist/types/core/agent/integrityFindings.d.ts.map +1 -0
- package/dist/types/core/agent/stages/callLLM.d.ts +25 -0
- package/dist/types/core/agent/stages/callLLM.d.ts.map +1 -1
- package/dist/types/core/agent/stages/toolCalls.d.ts +43 -0
- package/dist/types/core/agent/stages/toolCalls.d.ts.map +1 -1
- package/dist/types/core/agent/types.d.ts +112 -0
- package/dist/types/core/agent/types.d.ts.map +1 -1
- package/dist/types/core/tools.d.ts +63 -0
- package/dist/types/core/tools.d.ts.map +1 -1
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/integrity/argumentLeaves.d.ts +34 -0
- package/dist/types/integrity/argumentLeaves.d.ts.map +1 -0
- package/dist/types/integrity/column-types/check.d.ts +157 -0
- package/dist/types/integrity/column-types/check.d.ts.map +1 -0
- package/dist/types/integrity/column-types/types.d.ts +122 -0
- package/dist/types/integrity/column-types/types.d.ts.map +1 -0
- package/dist/types/integrity/disposition/lifecycle.d.ts +31 -1
- package/dist/types/integrity/disposition/lifecycle.d.ts.map +1 -1
- package/dist/types/integrity/empty-lookup/check.d.ts +141 -0
- package/dist/types/integrity/empty-lookup/check.d.ts.map +1 -0
- package/dist/types/integrity/finding/types.d.ts +23 -2
- package/dist/types/integrity/finding/types.d.ts.map +1 -1
- package/dist/types/integrity/unsupported-argument/check.d.ts.map +1 -1
- package/dist/types/lib/mcp/toolExtras.d.ts +17 -2
- package/dist/types/lib/mcp/toolExtras.d.ts.map +1 -1
- package/dist/types/lib/trace-toolpack/traceToolpack.d.ts.map +1 -1
- 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,
|