@orkestrel/probe 0.0.1
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/LICENSE +21 -0
- package/README.md +81 -0
- package/dist/bin/main.js +14 -0
- package/dist/bin/main.js.map +1 -0
- package/dist/src/core/index.cjs +735 -0
- package/dist/src/core/index.cjs.map +1 -0
- package/dist/src/core/index.d.cts +1134 -0
- package/dist/src/core/index.d.ts +1134 -0
- package/dist/src/core/index.js +706 -0
- package/dist/src/core/index.js.map +1 -0
- package/dist/src/server/index.cjs +2346 -0
- package/dist/src/server/index.cjs.map +1 -0
- package/dist/src/server/index.d.cts +941 -0
- package/dist/src/server/index.d.ts +941 -0
- package/dist/src/server/index.js +2321 -0
- package/dist/src/server/index.js.map +1 -0
- package/package.json +131 -0
|
@@ -0,0 +1,1134 @@
|
|
|
1
|
+
import { ArrayShape } from '@orkestrel/contract';
|
|
2
|
+
import { EmitterErrorHandler } from '@orkestrel/emitter';
|
|
3
|
+
import { EmitterHooks } from '@orkestrel/emitter';
|
|
4
|
+
import { EmitterInterface } from '@orkestrel/emitter';
|
|
5
|
+
import { Guard } from '@orkestrel/contract';
|
|
6
|
+
import { LiteralShape } from '@orkestrel/contract';
|
|
7
|
+
import { ObjectShape } from '@orkestrel/contract';
|
|
8
|
+
import { StringShape } from '@orkestrel/contract';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Carries the candidate drafts a claim asserts about and the test that exercises them.
|
|
12
|
+
*
|
|
13
|
+
* @remarks
|
|
14
|
+
* `files` and `test` belong to different TypeScript projects. Each draft in `files` is checked
|
|
15
|
+
* against the project its claim names, and `test` is checked against the root project, because a
|
|
16
|
+
* test needs the Vitest and Node globals the scoped projects remove. A direct type-stage call that
|
|
17
|
+
* supplies no project infers one from each candidate path.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* const subject: Case = {
|
|
22
|
+
* files: [{ path: 'src/core/greeting.ts', text: "export const GREETING = 'hi'\n" }],
|
|
23
|
+
* test: {
|
|
24
|
+
* path: 'tmp/probe/greeting.test.ts',
|
|
25
|
+
* text: "import { expect, test } from 'vitest'\nimport { GREETING } from '../../src/core/greeting.js'\ntest('greets', () => expect(GREETING).toBe('hi'))\n",
|
|
26
|
+
* },
|
|
27
|
+
* }
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export declare interface Case {
|
|
31
|
+
/** The candidate drafts the test imports, in the order the claim supplies them. */
|
|
32
|
+
readonly files: readonly Draft[];
|
|
33
|
+
/** The test that exercises those drafts. */
|
|
34
|
+
readonly test: Draft;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Describes the drafts a claim asserts about and the test that exercises them.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```ts
|
|
42
|
+
* const test = { path: 'tests/src/core/greeting.test.ts', text: '' }
|
|
43
|
+
* compileGuard(CASE_SHAPE)({ files: [], test }) // true
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
export declare const CASE_SHAPE: ObjectShape<{
|
|
47
|
+
files: ArrayShape<ObjectShape<{
|
|
48
|
+
path: StringShape;
|
|
49
|
+
text: StringShape;
|
|
50
|
+
}, false>>;
|
|
51
|
+
test: ObjectShape<{
|
|
52
|
+
path: StringShape;
|
|
53
|
+
text: StringShape;
|
|
54
|
+
}, false>;
|
|
55
|
+
}, false>;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Carries one stage's outcome: what it cost and what it reported.
|
|
59
|
+
*
|
|
60
|
+
* @remarks
|
|
61
|
+
* An empty `issues` list is the clean result. There is no separate pass flag, because passing is
|
|
62
|
+
* exactly the absence of issues and a stored flag could disagree with the list beside it.
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```ts
|
|
66
|
+
* const check: Check = { stage: 'lint', elapsed: 17, issues: [] }
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
export declare interface Check {
|
|
70
|
+
/** The stage that produced this outcome. */
|
|
71
|
+
readonly stage: Stage;
|
|
72
|
+
/** Milliseconds the stage took. */
|
|
73
|
+
readonly elapsed: number;
|
|
74
|
+
/** Every message the stage's tool reported, in the tool's own order. */
|
|
75
|
+
readonly issues: readonly Issue[];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Carries everything the service needs to produce one verdict.
|
|
80
|
+
*
|
|
81
|
+
* @remarks
|
|
82
|
+
* `project` names the TypeScript project the candidate drafts in both cases are checked against,
|
|
83
|
+
* because the root project admits host globals the scoped projects remove and would report green
|
|
84
|
+
* where the gate reports red. The test drafts remain on the root project for Vitest and Node globals.
|
|
85
|
+
*
|
|
86
|
+
* The control's candidate drafts must differ from the case's. A control byte-identical to its case
|
|
87
|
+
* cannot break, so it never produces the `origin: 'claimant'` issue a receipt requires, and the
|
|
88
|
+
* claim is unprovable however correct the case is.
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* ```ts
|
|
92
|
+
* const claim: Claim = {
|
|
93
|
+
* project: 'configs/src/tsconfig.core.json',
|
|
94
|
+
* case: {
|
|
95
|
+
* files: [{ path: 'src/core/greeting.ts', text: "export const GREETING = 'hi'\n" }],
|
|
96
|
+
* test: {
|
|
97
|
+
* path: 'tmp/probe/greeting.test.ts',
|
|
98
|
+
* text: "import { expect, test } from 'vitest'\nimport { GREETING } from '../../src/core/greeting.js'\ntest('greets', () => expect(GREETING).toBe('hi'))\n",
|
|
99
|
+
* },
|
|
100
|
+
* },
|
|
101
|
+
* control: {
|
|
102
|
+
* files: [{ path: 'src/core/greeting.ts', text: "export const GREETING: number = 'hi'\n" }],
|
|
103
|
+
* test: {
|
|
104
|
+
* path: 'tmp/probe/greeting.test.ts',
|
|
105
|
+
* text: "import { expect, test } from 'vitest'\nimport { GREETING } from '../../src/core/greeting.js'\ntest('greets', () => expect(GREETING).toBe('hi'))\n",
|
|
106
|
+
* },
|
|
107
|
+
* stage: 'type',
|
|
108
|
+
* reason: 'a string literal assigned to a number must not compile',
|
|
109
|
+
* },
|
|
110
|
+
* }
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
export declare interface Claim {
|
|
114
|
+
/** Workspace-relative path of the TypeScript project the candidate drafts are checked against. */
|
|
115
|
+
readonly project: string;
|
|
116
|
+
/** The drafts and test the claim asserts about. */
|
|
117
|
+
readonly case: Case;
|
|
118
|
+
/** The negative control that must break, and where. */
|
|
119
|
+
readonly control: Control;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Describes one claim and is the sole source of both the published tool schema and the guard
|
|
124
|
+
* applied to an arriving claim.
|
|
125
|
+
*
|
|
126
|
+
* @remarks
|
|
127
|
+
* The Model Context Protocol tool publishes `compileSchema(CLAIM_SHAPE)` and admits a call with
|
|
128
|
+
* `isClaim`. Deriving the advertised schema from this one value is what stops the two from drifting
|
|
129
|
+
* apart across a release on any member either can express. `isClaim` is narrower on the one member
|
|
130
|
+
* neither a schema keyword nor this shape can carry — the containment rule on `Draft.path` — and
|
|
131
|
+
* `validators.ts` names where.
|
|
132
|
+
*
|
|
133
|
+
* @example
|
|
134
|
+
* ```ts
|
|
135
|
+
* const schema = compileSchema(CLAIM_SHAPE)
|
|
136
|
+
* const admits = compileGuard(CLAIM_SHAPE)
|
|
137
|
+
* schema.type // 'object'
|
|
138
|
+
* admits({ project: 'tsconfig.json' }) // false
|
|
139
|
+
* ```
|
|
140
|
+
*/
|
|
141
|
+
export declare const CLAIM_SHAPE: ObjectShape<{
|
|
142
|
+
project: StringShape;
|
|
143
|
+
case: ObjectShape<{
|
|
144
|
+
files: ArrayShape<ObjectShape<{
|
|
145
|
+
path: StringShape;
|
|
146
|
+
text: StringShape;
|
|
147
|
+
}, false>>;
|
|
148
|
+
test: ObjectShape<{
|
|
149
|
+
path: StringShape;
|
|
150
|
+
text: StringShape;
|
|
151
|
+
}, false>;
|
|
152
|
+
}, false>;
|
|
153
|
+
control: ObjectShape<{
|
|
154
|
+
stage: LiteralShape<readonly ["type", "lint", "runtime"]>;
|
|
155
|
+
reason: StringShape;
|
|
156
|
+
files: ArrayShape<ObjectShape<{
|
|
157
|
+
path: StringShape;
|
|
158
|
+
text: StringShape;
|
|
159
|
+
}, false>>;
|
|
160
|
+
test: ObjectShape<{
|
|
161
|
+
path: StringShape;
|
|
162
|
+
text: StringShape;
|
|
163
|
+
}, false>;
|
|
164
|
+
}, false>;
|
|
165
|
+
}, false>;
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Computes the proof token a verdict carries, or returns nothing when the claim was not proven.
|
|
169
|
+
*
|
|
170
|
+
* @remarks
|
|
171
|
+
* A receipt is minted on these conditions together: both phases report one check per stage, the
|
|
172
|
+
* case carries no issue, the control carries at least one `origin: 'claimant'` issue
|
|
173
|
+
* at the stage it declared, and every other control stage carries no claimant issue. A control
|
|
174
|
+
* that fails somewhere else has falsified the instrument rather than the claim, so no receipt is
|
|
175
|
+
* minted for it.
|
|
176
|
+
*
|
|
177
|
+
* Both phases owe every stage, not the case alone. `stayed` reads the control entries a verdict
|
|
178
|
+
* carries, so a control that omits a stage entirely would otherwise read as a stage that stayed
|
|
179
|
+
* clean, and the receipt would certify an inspection that never ran. `prove` records every stage
|
|
180
|
+
* for both phases, so this condition refuses only a verdict a caller assembled by hand.
|
|
181
|
+
*
|
|
182
|
+
* The token names every condition the verdict was reached under, so a receipt read away from its
|
|
183
|
+
* verdict still says what was judged and what judged it: the claim's digest, the stage the control
|
|
184
|
+
* broke at, the tool versions, and the project the candidate drafts were checked against.
|
|
185
|
+
* The call's identity is deliberately absent. It carries no integrity and it is the only value
|
|
186
|
+
* stopping two honest runs of one claim from producing one comparable string, so leaving it out is
|
|
187
|
+
* what makes "re-run `prove` and compare tokens" a verification a reader can perform.
|
|
188
|
+
*
|
|
189
|
+
* The project goes last, and its own path and digest are joined by `@`. Split the token on
|
|
190
|
+
* `RECEIPT_SEPARATOR`, take fields 0 through 5, and rejoin the remainder with that separator to
|
|
191
|
+
* read the project field; inside it the digest is everything after the final `@`. That rule stays
|
|
192
|
+
* total for a workspace-relative project path containing either character.
|
|
193
|
+
*
|
|
194
|
+
* An `origin: 'instrument'` issue in either phase means the inspection did not complete and
|
|
195
|
+
* refuses the receipt. A workspace issue in the control does not decide whether the claimant's
|
|
196
|
+
* candidate broke, while one in the case still means that the case did not run clean.
|
|
197
|
+
*
|
|
198
|
+
* @param verdict - The verdict whose case and control checks decide the outcome
|
|
199
|
+
* @param stage - The stage the claim's control declared it must fail at
|
|
200
|
+
* @returns The receipt token, or `undefined` when any condition fails
|
|
201
|
+
*
|
|
202
|
+
* @example
|
|
203
|
+
* ```ts
|
|
204
|
+
* computeReceipt(proven, 'type')
|
|
205
|
+
* // 'probe:6ca20c3bff623031d3955b9d1a76d71d:type:typescript@6.0.3:oxlint@1.79.0:vitest@4.1.11:configs/src/tsconfig.core.json@3b674fdf121c85efb9ed1bab25ceeec8'
|
|
206
|
+
* computeReceipt(proven, 'lint') // undefined
|
|
207
|
+
* ```
|
|
208
|
+
*/
|
|
209
|
+
export declare function computeReceipt(verdict: Verdict, stage: Stage): string | undefined;
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Extends a case with the stage it must fail at and the reason it must fail there.
|
|
213
|
+
*
|
|
214
|
+
* @remarks
|
|
215
|
+
* A claim that cannot state what would falsify it cannot be proven, so the control is required
|
|
216
|
+
* rather than optional. `stage` is the axis the control varies: a control that fails at a stage
|
|
217
|
+
* other than the one it names has falsified the instrument, not the claim.
|
|
218
|
+
*
|
|
219
|
+
* @example
|
|
220
|
+
* ```ts
|
|
221
|
+
* const control: Control = {
|
|
222
|
+
* files: [{ path: 'src/core/greeting.ts', text: "export const GREETING: number = 'hi'\n" }],
|
|
223
|
+
* test: {
|
|
224
|
+
* path: 'tmp/probe/greeting.test.ts',
|
|
225
|
+
* text: "import { expect, test } from 'vitest'\nimport { GREETING } from '../../src/core/greeting.js'\ntest('greets', () => expect(GREETING).toBe('hi'))\n",
|
|
226
|
+
* },
|
|
227
|
+
* stage: 'type',
|
|
228
|
+
* reason: 'a string literal assigned to a number must not compile',
|
|
229
|
+
* }
|
|
230
|
+
* ```
|
|
231
|
+
*/
|
|
232
|
+
export declare interface Control extends Case {
|
|
233
|
+
/** The stage this control must report issues at. */
|
|
234
|
+
readonly stage: Stage;
|
|
235
|
+
/** Why the control fails there, in the claimant's own words. */
|
|
236
|
+
readonly reason: string;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Describes the negative control, which is a case plus where and why it must break.
|
|
241
|
+
*
|
|
242
|
+
* @remarks
|
|
243
|
+
* Built from `CASE_SHAPE`'s own properties rather than a second copy of them, so the control and
|
|
244
|
+
* the case cannot describe a draft differently.
|
|
245
|
+
*
|
|
246
|
+
* @example
|
|
247
|
+
* ```ts
|
|
248
|
+
* const test = { path: 'tests/src/core/greeting.test.ts', text: '' }
|
|
249
|
+
* compileGuard(CONTROL_SHAPE)({ files: [], test, stage: 'type', reason: 'must not compile' }) // true
|
|
250
|
+
* ```
|
|
251
|
+
*/
|
|
252
|
+
export declare const CONTROL_SHAPE: ObjectShape<{
|
|
253
|
+
stage: LiteralShape<readonly ["type", "lint", "runtime"]>;
|
|
254
|
+
reason: StringShape;
|
|
255
|
+
files: ArrayShape<ObjectShape<{
|
|
256
|
+
path: StringShape;
|
|
257
|
+
text: StringShape;
|
|
258
|
+
}, false>>;
|
|
259
|
+
test: ObjectShape<{
|
|
260
|
+
path: StringShape;
|
|
261
|
+
text: StringShape;
|
|
262
|
+
}, false>;
|
|
263
|
+
}, false>;
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Creates the failure raised when an instrument is used after it was torn down.
|
|
267
|
+
*
|
|
268
|
+
* @remarks
|
|
269
|
+
* Every resident entity in this package refuses this way, so the subject names which one in the
|
|
270
|
+
* message and the condition is the same for all of them. A caller that meets this builds a
|
|
271
|
+
* replacement rather than retrying: teardown is permanent.
|
|
272
|
+
*
|
|
273
|
+
* @param subject - The torn-down entity, named as the message names it, such as `type stage`
|
|
274
|
+
* @returns A typed teardown failure
|
|
275
|
+
*
|
|
276
|
+
* @example
|
|
277
|
+
* ```ts
|
|
278
|
+
* createDestroyedError('type stage').message // 'The type stage has been destroyed'
|
|
279
|
+
* createDestroyedError('probe').code // 'destroyed'
|
|
280
|
+
* ```
|
|
281
|
+
*/
|
|
282
|
+
export declare function createDestroyedError(subject: string): ProbeError;
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Carries one proposed file's location and its contents.
|
|
286
|
+
*
|
|
287
|
+
* @remarks
|
|
288
|
+
* A draft is a file as the claimant proposes it rather than as the workspace holds it. `path` is
|
|
289
|
+
* contained within the workspace, relative to its root, and need not exist on disk. The type and
|
|
290
|
+
* lint stages read the text from memory; only the runtime stage writes a file.
|
|
291
|
+
*
|
|
292
|
+
* @example
|
|
293
|
+
* ```ts
|
|
294
|
+
* const draft: Draft = { path: 'src/core/greeting.ts', text: "export const GREETING = 'hi'\n" }
|
|
295
|
+
* ```
|
|
296
|
+
*/
|
|
297
|
+
export declare interface Draft {
|
|
298
|
+
/** Workspace-relative path the stages resolve the text against. */
|
|
299
|
+
readonly path: string;
|
|
300
|
+
/** The file's full contents. */
|
|
301
|
+
readonly text: string;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Describes one proposed file a claim carries.
|
|
306
|
+
*
|
|
307
|
+
* @remarks
|
|
308
|
+
* `path` is constrained to a non-empty string because every stage resolves a file by it, so an
|
|
309
|
+
* empty path names no file. That minimum is the whole of the constraint here, and it is the one
|
|
310
|
+
* place the wire contract is wider than what this package admits: `isDraft` enforces the same
|
|
311
|
+
* minimum and also refuses an absolute path and one that traverses out of the workspace, which no
|
|
312
|
+
* JSON Schema keyword can express. A caller that satisfies this shape can still be refused, and
|
|
313
|
+
* `ProbeServer` names the member when it is.
|
|
314
|
+
*
|
|
315
|
+
* @example
|
|
316
|
+
* ```ts
|
|
317
|
+
* compileGuard(DRAFT_SHAPE)({ path: 'src/core/greeting.ts', text: '' }) // true
|
|
318
|
+
* ```
|
|
319
|
+
*/
|
|
320
|
+
export declare const DRAFT_SHAPE: ObjectShape<{
|
|
321
|
+
path: StringShape;
|
|
322
|
+
text: StringShape;
|
|
323
|
+
}, false>;
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Renders one stage's outcome as its summary line followed by every message it reported.
|
|
327
|
+
*
|
|
328
|
+
* @remarks
|
|
329
|
+
* A clean stage renders one line. The count appears even when it is zero, because that line is
|
|
330
|
+
* what shows a reader the stage ran; a silent stage and a skipped stage read the same.
|
|
331
|
+
*
|
|
332
|
+
* @param check - The stage outcome to render
|
|
333
|
+
* @returns The summary line, then one indented line per issue
|
|
334
|
+
*
|
|
335
|
+
* @example
|
|
336
|
+
* ```ts
|
|
337
|
+
* formatCheck({ stage: 'lint', elapsed: 17, issues: [] })
|
|
338
|
+
* // 'lint: 0 issues (17 ms)'
|
|
339
|
+
* ```
|
|
340
|
+
*/
|
|
341
|
+
export declare function formatCheck(check: Check): string;
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Renders one tool message as a single line an agent can classify and locate.
|
|
345
|
+
*
|
|
346
|
+
* @param issue - The message and location a stage reported
|
|
347
|
+
* @returns The bracketed origin, location, and message, separated by spaces
|
|
348
|
+
*
|
|
349
|
+
* @example
|
|
350
|
+
* ```ts
|
|
351
|
+
* const located: Issue = {
|
|
352
|
+
* origin: 'claimant',
|
|
353
|
+
* path: 'src/core/greeting.ts',
|
|
354
|
+
* message: 'not assignable',
|
|
355
|
+
* line: 1,
|
|
356
|
+
* }
|
|
357
|
+
* const whole: Issue = {
|
|
358
|
+
* origin: 'claimant',
|
|
359
|
+
* path: 'src/core/greeting.ts',
|
|
360
|
+
* message: 'not assignable',
|
|
361
|
+
* }
|
|
362
|
+
* formatIssue(located) // '[claimant] src/core/greeting.ts:1 not assignable'
|
|
363
|
+
* formatIssue(whole) // '[claimant] src/core/greeting.ts not assignable'
|
|
364
|
+
* ```
|
|
365
|
+
*/
|
|
366
|
+
export declare function formatIssue(issue: Issue): string;
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Renders one generated specification: the caller's own test text, then the marker naming the
|
|
370
|
+
* revision that wrote it.
|
|
371
|
+
*
|
|
372
|
+
* @remarks
|
|
373
|
+
* The marker is what makes a file in a target workspace attributable to this package. A generated
|
|
374
|
+
* specification carries text the caller supplied, so its name alone cannot separate it from a
|
|
375
|
+
* developer's own file, and a sweep reading the name alone deletes that file. The marker goes last
|
|
376
|
+
* so every line of the caller's text keeps the number a reported stack frame gives it, and the text
|
|
377
|
+
* gains a terminating newline first so the marker is never appended to a line of the test.
|
|
378
|
+
*
|
|
379
|
+
* @param text - The caller's own test text
|
|
380
|
+
* @param revision - The writing host's process id and a fresh UUID, joined by `-`
|
|
381
|
+
* @returns The bytes the runtime stage writes for that revision
|
|
382
|
+
*
|
|
383
|
+
* @example
|
|
384
|
+
* ```ts
|
|
385
|
+
* formatSpecification("test('greets', () => {})\n", '4821-9f0c')
|
|
386
|
+
* // "test('greets', () => {})\n// @orkestrel/probe generated specification 4821-9f0c\n"
|
|
387
|
+
* ```
|
|
388
|
+
*/
|
|
389
|
+
export declare function formatSpecification(text: string, revision: string): string;
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Renders a whole verdict as the text an agent reads.
|
|
393
|
+
*
|
|
394
|
+
* @remarks
|
|
395
|
+
* Every stage appears for both the case and the control, so no failing stage can be masked by an
|
|
396
|
+
* earlier one. The closing line always states the receipt or its absence, so a reader never has to
|
|
397
|
+
* infer from silence whether the claim was proven.
|
|
398
|
+
*
|
|
399
|
+
* The heading lines carry everything the receipt binds, each in the name-then-value form the
|
|
400
|
+
* toolchain line established: the call's identity, the claim it answered, the tools that ran, and
|
|
401
|
+
* the project that judged the candidates. A verdict carrying the control's reason renders it next,
|
|
402
|
+
* before either phase's checks. A reader comparing two verdicts therefore reads what differed
|
|
403
|
+
* without recomputing anything.
|
|
404
|
+
*
|
|
405
|
+
* @param verdict - The verdict to render
|
|
406
|
+
* @returns The identity, claim, toolchain, project, and reason when present, then every case and
|
|
407
|
+
* control stage, then the receipt line
|
|
408
|
+
*
|
|
409
|
+
* @example
|
|
410
|
+
* ```ts
|
|
411
|
+
* formatVerdict(verdict).split('\n')[1] // 'claim 6ca20c3bff623031d3955b9d1a76d71d'
|
|
412
|
+
* ```
|
|
413
|
+
*/
|
|
414
|
+
export declare function formatVerdict(verdict: Verdict): string;
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Checks whether a value carries a claim's candidate drafts and its test.
|
|
418
|
+
*
|
|
419
|
+
* @param value - The value to check
|
|
420
|
+
* @returns True if the value is a case; false otherwise
|
|
421
|
+
*
|
|
422
|
+
* @example
|
|
423
|
+
* ```ts
|
|
424
|
+
* const test = { path: 'tests/src/core/greeting.test.ts', text: 'test("greets", () => {})\n' }
|
|
425
|
+
* isCase({ files: [], test }) // true
|
|
426
|
+
* ```
|
|
427
|
+
*/
|
|
428
|
+
export declare const isCase: Guard<Case>;
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Checks whether a value carries one stage's outcome.
|
|
432
|
+
*
|
|
433
|
+
* @param value - The value to check
|
|
434
|
+
* @returns True if the value is a check; false otherwise
|
|
435
|
+
*
|
|
436
|
+
* @example
|
|
437
|
+
* ```ts
|
|
438
|
+
* isCheck({ stage: 'lint', elapsed: 17, issues: [] }) // true
|
|
439
|
+
* ```
|
|
440
|
+
*/
|
|
441
|
+
export declare const isCheck: Guard<Check>;
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Checks whether a value carries a project, a case, and a control.
|
|
445
|
+
*
|
|
446
|
+
* @remarks
|
|
447
|
+
* Exact rather than open: a claim is this package's own record, so an unknown member is a caller
|
|
448
|
+
* sending a contract this service does not implement rather than a wider caller it must tolerate.
|
|
449
|
+
*
|
|
450
|
+
* `CLAIM_SHAPE` is the wire contract's shape and this is the admission rule the tool enforces, and
|
|
451
|
+
* the rule is strictly narrower than the shape. They agree on every member but `Draft.path`: the
|
|
452
|
+
* shape constrains it to a non-empty string, while `isDraft` also refuses an absolute path
|
|
453
|
+
* and one that traverses out of the workspace, so `../../etc/hosts` satisfies
|
|
454
|
+
* `compileGuard(CLAIM_SHAPE)` and is refused here. `ProbeServer` advertises the shape and enforces
|
|
455
|
+
* this rule, so it names the refused member rather than reporting only that the claim was invalid.
|
|
456
|
+
*
|
|
457
|
+
* @param value - The value to check
|
|
458
|
+
* @returns True if the value is a claim; false otherwise
|
|
459
|
+
*
|
|
460
|
+
* @example
|
|
461
|
+
* ```ts
|
|
462
|
+
* isClaim({ project: 'tsconfig.json', case: subject, control }) // true
|
|
463
|
+
* isClaim({ project: 'tsconfig.json', case: subject }) // false
|
|
464
|
+
* ```
|
|
465
|
+
*/
|
|
466
|
+
export declare const isClaim: Guard<Claim>;
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* Checks whether a value carries a case plus the stage and reason it must fail for.
|
|
470
|
+
*
|
|
471
|
+
* @param value - The value to check
|
|
472
|
+
* @returns True if the value is a control; false otherwise
|
|
473
|
+
*
|
|
474
|
+
* @example
|
|
475
|
+
* ```ts
|
|
476
|
+
* const test = { path: 'tests/src/core/greeting.test.ts', text: 'test("greets", () => {})\n' }
|
|
477
|
+
* isControl({ files: [], test, stage: 'type', reason: 'must not compile' }) // true
|
|
478
|
+
* isControl({ files: [], test }) // false
|
|
479
|
+
* ```
|
|
480
|
+
*/
|
|
481
|
+
export declare const isControl: Guard<Control>;
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* Checks whether a value carries a proposed file's path and text.
|
|
485
|
+
*
|
|
486
|
+
* @remarks
|
|
487
|
+
* Rejects an empty, absolute, or workspace-escaping path. A contained relative path may contain
|
|
488
|
+
* `.` or may traverse to a parent that remains inside the workspace. Every stage resolves a file
|
|
489
|
+
* by that path, so admitting an escape here would defer the refusal until a stage had already
|
|
490
|
+
* accepted the draft. An `Issue` carries no such minimum because this package produces issues
|
|
491
|
+
* rather than admits them.
|
|
492
|
+
*
|
|
493
|
+
* @param value - The value to check
|
|
494
|
+
* @returns True if the value is a draft; false otherwise
|
|
495
|
+
*
|
|
496
|
+
* @example
|
|
497
|
+
* ```ts
|
|
498
|
+
* isDraft({ path: 'src/core/greeting.ts', text: 'export const GREETING = "hi"\n' }) // true
|
|
499
|
+
* isDraft({ path: '../../etc/hosts', text: '' }) // false
|
|
500
|
+
* isDraft({ path: 'src/core/greeting.ts' }) // false
|
|
501
|
+
* ```
|
|
502
|
+
*/
|
|
503
|
+
export declare const isDraft: Guard<Draft>;
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* Checks whether a value carries one message, its location, and the origin of the fault it names.
|
|
507
|
+
*
|
|
508
|
+
* @remarks
|
|
509
|
+
* `origin` is required rather than optional. Every issue this package produces sets it, and a
|
|
510
|
+
* receipt turns on it: a control's issue disproves the claim only when its origin names the
|
|
511
|
+
* claimant.
|
|
512
|
+
*
|
|
513
|
+
* @param value - The value to check
|
|
514
|
+
* @returns True if the value is an issue; false otherwise
|
|
515
|
+
*
|
|
516
|
+
* @example
|
|
517
|
+
* ```ts
|
|
518
|
+
* isIssue({ origin: 'claimant', path: 'src/core/greeting.ts', message: 'not assignable' }) // true
|
|
519
|
+
* isIssue({ path: 'src/core/greeting.ts', message: 'not assignable' }) // false
|
|
520
|
+
* ```
|
|
521
|
+
*/
|
|
522
|
+
export declare const isIssue: Guard<Issue>;
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* Checks whether a value names a party an issue carries.
|
|
526
|
+
*
|
|
527
|
+
* @param value - The value to check
|
|
528
|
+
* @returns True if the value is a party; false otherwise
|
|
529
|
+
*
|
|
530
|
+
* @example
|
|
531
|
+
* ```ts
|
|
532
|
+
* isParty('claimant') // true
|
|
533
|
+
* isParty('workspace') // true
|
|
534
|
+
* isParty('instrument') // true
|
|
535
|
+
* isParty('stage') // false
|
|
536
|
+
* ```
|
|
537
|
+
*/
|
|
538
|
+
export declare const isParty: Guard<Party>;
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* Checks whether an unknown value is a {@link ProbeError}.
|
|
542
|
+
*
|
|
543
|
+
* @remarks
|
|
544
|
+
* Recognition combines the global own-property brand with the native `Error` base, the subclass
|
|
545
|
+
* prototype, the fixed name, a party {@link PROBE_PARTIES} declares, and a condition
|
|
546
|
+
* {@link PROBE_ERROR_CODES} declares. The brand carries recognition across duplicate installations
|
|
547
|
+
* and across an ESM and a CommonJS copy of this package, where the two classes are different values.
|
|
548
|
+
* A plain `Error`, a property-only lookalike, and a branded value carrying an undeclared origin or
|
|
549
|
+
* condition all stay outside the type.
|
|
550
|
+
*
|
|
551
|
+
* @param value - The value to inspect
|
|
552
|
+
* @returns True only for a `ProbeError` instance; false otherwise
|
|
553
|
+
*
|
|
554
|
+
* @example
|
|
555
|
+
* ```ts
|
|
556
|
+
* isProbeError(createDestroyedError('probe')) // true
|
|
557
|
+
* isProbeError(new Error('The probe has been destroyed')) // false
|
|
558
|
+
* ```
|
|
559
|
+
*/
|
|
560
|
+
export declare function isProbeError(value: unknown): value is ProbeError;
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* Checks whether a value names one resolved TypeScript project and what it contained.
|
|
564
|
+
*
|
|
565
|
+
* @remarks
|
|
566
|
+
* Rejects an empty path and an empty digest. A verdict names the project that judged it, so a
|
|
567
|
+
* blank member here would report a project the type stage never applied.
|
|
568
|
+
*
|
|
569
|
+
* @param value - The value to check
|
|
570
|
+
* @returns True if the value is a project; false otherwise
|
|
571
|
+
*
|
|
572
|
+
* @example
|
|
573
|
+
* ```ts
|
|
574
|
+
* isProject({ path: 'configs/src/tsconfig.core.json', digest: '3b674fdf121c85ef' }) // true
|
|
575
|
+
* isProject({ path: 'configs/src/tsconfig.core.json', digest: '' }) // false
|
|
576
|
+
* ```
|
|
577
|
+
*/
|
|
578
|
+
export declare const isProject: Guard<Project>;
|
|
579
|
+
|
|
580
|
+
/**
|
|
581
|
+
* Checks whether a value names a stage.
|
|
582
|
+
*
|
|
583
|
+
* @param value - The value to check
|
|
584
|
+
* @returns True if the value is a stage name; false otherwise
|
|
585
|
+
*
|
|
586
|
+
* @example
|
|
587
|
+
* ```ts
|
|
588
|
+
* isStage('lint') // true
|
|
589
|
+
* isStage('typecheck') // false
|
|
590
|
+
* ```
|
|
591
|
+
*/
|
|
592
|
+
export declare const isStage: Guard<Stage>;
|
|
593
|
+
|
|
594
|
+
/**
|
|
595
|
+
* Carries one message a stage reported, where it reported it, and whose fault it names.
|
|
596
|
+
*
|
|
597
|
+
* @remarks
|
|
598
|
+
* The stage is not repeated here. An issue always arrives inside the `Check` that names its
|
|
599
|
+
* stage, so a second copy could only drift from the first.
|
|
600
|
+
*
|
|
601
|
+
* A `claimant` issue is a tool's diagnostic about a candidate draft, and nothing else. Every
|
|
602
|
+
* other claimant fault is thrown. Without this invariant, a bad test path could satisfy the
|
|
603
|
+
* receipt condition even though the test never ran.
|
|
604
|
+
*
|
|
605
|
+
* `path` is the workspace-relative path a reader can open, which is not always the path the tool
|
|
606
|
+
* named. Each stage maps its tool's own spelling back: the type stage from the compiler's absolute
|
|
607
|
+
* path, the lint stage from the document URI it opened, and the runtime stage from the generated
|
|
608
|
+
* specification it wrote to the test path the case declared.
|
|
609
|
+
*
|
|
610
|
+
* `line` is absent when the stage's tool reported no line, which happens for a whole-file
|
|
611
|
+
* diagnostic. A runtime failure is not one of those: a failure Vitest reported at a stack frame
|
|
612
|
+
* carries that frame's line.
|
|
613
|
+
*
|
|
614
|
+
* @example
|
|
615
|
+
* ```ts
|
|
616
|
+
* const issue: Issue = {
|
|
617
|
+
* origin: 'claimant',
|
|
618
|
+
* path: 'src/core/greeting.ts',
|
|
619
|
+
* message: "Type 'string' is not assignable to type 'number'.",
|
|
620
|
+
* line: 1,
|
|
621
|
+
* }
|
|
622
|
+
* ```
|
|
623
|
+
*/
|
|
624
|
+
export declare interface Issue {
|
|
625
|
+
/** The party that must act on this message. */
|
|
626
|
+
readonly origin: Party;
|
|
627
|
+
/** Workspace-relative path this message is reported against. */
|
|
628
|
+
readonly path: string;
|
|
629
|
+
/** The diagnostic or failure message. */
|
|
630
|
+
readonly message: string;
|
|
631
|
+
/** One-based line the tool reported, or absent when it reported none. */
|
|
632
|
+
readonly line?: number;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/**
|
|
636
|
+
* Checks whether a value names every resolved tool version.
|
|
637
|
+
*
|
|
638
|
+
* @param value - The value to check
|
|
639
|
+
* @returns True if the value is a toolchain; false otherwise
|
|
640
|
+
*
|
|
641
|
+
* @example
|
|
642
|
+
* ```ts
|
|
643
|
+
* isToolchain({ typescript: '6.0.3', oxlint: '1.79.0', vitest: '4.1.11' }) // true
|
|
644
|
+
* isToolchain({ typescript: '6.0.3' }) // false
|
|
645
|
+
* ```
|
|
646
|
+
*/
|
|
647
|
+
export declare const isToolchain: Guard<Toolchain>;
|
|
648
|
+
|
|
649
|
+
/**
|
|
650
|
+
* Checks whether a value carries a complete verdict.
|
|
651
|
+
*
|
|
652
|
+
* @remarks
|
|
653
|
+
* Accepts any check count, because arity is the coordinator's obligation rather than this guard's:
|
|
654
|
+
* a stage that could not start throws before a verdict is built, so a short list can only arrive
|
|
655
|
+
* from a caller that assembled one by hand.
|
|
656
|
+
*
|
|
657
|
+
* @param value - The value to check
|
|
658
|
+
* @returns True if the value is a verdict; false otherwise
|
|
659
|
+
*
|
|
660
|
+
* @example
|
|
661
|
+
* ```ts
|
|
662
|
+
* const basis = { id: '01J8Z0', digest: '6ca20c3b', toolchain, project, elapsed: 337 }
|
|
663
|
+
* isVerdict({ ...basis, case: [], control: [] }) // true
|
|
664
|
+
* isVerdict({ ...basis, case: [] }) // false
|
|
665
|
+
* ```
|
|
666
|
+
*/
|
|
667
|
+
export declare const isVerdict: Guard<Verdict>;
|
|
668
|
+
|
|
669
|
+
/**
|
|
670
|
+
* Checks whether one file's text is the generated specification written for one revision.
|
|
671
|
+
*
|
|
672
|
+
* @remarks
|
|
673
|
+
* Reads the marker `formatSpecification` writes, taken from that function rather than restated, so
|
|
674
|
+
* the writer and the reader cannot drift apart. An empty body renders the marker alone, which is
|
|
675
|
+
* why the comparison is built from a call rather than from a second copy of the text.
|
|
676
|
+
*
|
|
677
|
+
* @param text - The file's full contents
|
|
678
|
+
* @param revision - The revision the file's name declares
|
|
679
|
+
* @returns True if the text ends with this package's marker for that revision; false otherwise
|
|
680
|
+
*
|
|
681
|
+
* @example
|
|
682
|
+
* ```ts
|
|
683
|
+
* matchesSpecification(formatSpecification('', '4821-9f0c'), '4821-9f0c') // true
|
|
684
|
+
* matchesSpecification('export const NOTE = 1\n', '4821-9f0c') // false
|
|
685
|
+
* ```
|
|
686
|
+
*/
|
|
687
|
+
export declare function matchesSpecification(text: string, revision: string): boolean;
|
|
688
|
+
|
|
689
|
+
/**
|
|
690
|
+
* Names who must act on an issue or probe failure.
|
|
691
|
+
*
|
|
692
|
+
* @remarks
|
|
693
|
+
* `claimant` is the caller who wrote the claim: its input, selections, candidate draft, and
|
|
694
|
+
* lifecycle. `workspace` is the target tree probe borrows. `instrument` is this package.
|
|
695
|
+
*
|
|
696
|
+
* @example
|
|
697
|
+
* ```ts
|
|
698
|
+
* const party: Party = 'claimant'
|
|
699
|
+
* ```
|
|
700
|
+
*/
|
|
701
|
+
export declare type Party = (typeof PROBE_PARTIES)[number];
|
|
702
|
+
|
|
703
|
+
/**
|
|
704
|
+
* Lists the conditions that can end a probe operation.
|
|
705
|
+
*
|
|
706
|
+
* @remarks
|
|
707
|
+
* One list feeds the code union and the error guard, so a condition cannot be constructed by one
|
|
708
|
+
* and refused by the other. The guard reads this list rather than the class, which is what lets it
|
|
709
|
+
* refuse a lookalike carrying a condition this package never declared.
|
|
710
|
+
*
|
|
711
|
+
* @example
|
|
712
|
+
* ```ts
|
|
713
|
+
* PROBE_ERROR_CODES // ['refused', 'missing', 'malformed', 'destroyed', 'deadline']
|
|
714
|
+
* ```
|
|
715
|
+
*/
|
|
716
|
+
export declare const PROBE_ERROR_CODES: readonly ["refused", "missing", "malformed", "destroyed", "deadline"];
|
|
717
|
+
|
|
718
|
+
/**
|
|
719
|
+
* Lists the parties that can own action on an issue or probe failure.
|
|
720
|
+
*
|
|
721
|
+
* @remarks
|
|
722
|
+
* One list feeds the `Party` type and its guard, so a party cannot be admitted by one and refused
|
|
723
|
+
* by another.
|
|
724
|
+
*
|
|
725
|
+
* @example
|
|
726
|
+
* ```ts
|
|
727
|
+
* PROBE_PARTIES // ['claimant', 'workspace', 'instrument']
|
|
728
|
+
* ```
|
|
729
|
+
*/
|
|
730
|
+
export declare const PROBE_PARTIES: readonly ["claimant", "workspace", "instrument"];
|
|
731
|
+
|
|
732
|
+
/**
|
|
733
|
+
* Lists the stages a claim passes through, in the order a verdict reports them.
|
|
734
|
+
*
|
|
735
|
+
* @remarks
|
|
736
|
+
* One list feeds the `Stage` type, the wire shape, the stage guard, and the receipt computation, so
|
|
737
|
+
* a stage cannot be admitted by one and refused by another. The type derives from this list rather
|
|
738
|
+
* than standing beside it, so a stage the list omits is a stage no member of the union names.
|
|
739
|
+
*
|
|
740
|
+
* @example
|
|
741
|
+
* ```ts
|
|
742
|
+
* PROBE_STAGES // ['type', 'lint', 'runtime']
|
|
743
|
+
* ```
|
|
744
|
+
*/
|
|
745
|
+
export declare const PROBE_STAGES: readonly ["type", "lint", "runtime"];
|
|
746
|
+
|
|
747
|
+
/**
|
|
748
|
+
* Reports one probe failure under stable ownership and condition axes.
|
|
749
|
+
*
|
|
750
|
+
* @remarks
|
|
751
|
+
* Every failure raised while serving a claim arrives as one of these, so a consumer catching from
|
|
752
|
+
* `prove`, from a stage, or from a workspace leaf branches on {@link Party} and
|
|
753
|
+
* {@link ProbeErrorCode} rather than on message text. Narrow a caught value with
|
|
754
|
+
* {@link isProbeError} before reading either member. A pure leaf called directly with a value
|
|
755
|
+
* `isClaim` would refuse raises the host's own fault unchanged, because those leaves are written to
|
|
756
|
+
* be total over a validated claim rather than over every value.
|
|
757
|
+
*
|
|
758
|
+
* @example
|
|
759
|
+
* ```ts
|
|
760
|
+
* const error = new ProbeError('Path escapes the workspace: ../secrets.env', {
|
|
761
|
+
* origin: 'claimant',
|
|
762
|
+
* code: 'refused',
|
|
763
|
+
* context: { path: '../secrets.env' },
|
|
764
|
+
* })
|
|
765
|
+
* error.origin // 'claimant'
|
|
766
|
+
* error.code // 'refused'
|
|
767
|
+
* error.context?.path // '../secrets.env'
|
|
768
|
+
* ```
|
|
769
|
+
*/
|
|
770
|
+
export declare class ProbeError extends Error {
|
|
771
|
+
readonly name = "ProbeError";
|
|
772
|
+
readonly origin: Party;
|
|
773
|
+
readonly code: ProbeErrorCode;
|
|
774
|
+
readonly context?: ProbeErrorContext;
|
|
775
|
+
/**
|
|
776
|
+
* Creates one classified probe failure.
|
|
777
|
+
*
|
|
778
|
+
* @param message - Human-readable description of what failed
|
|
779
|
+
* @param options - The ownership, condition, optional structured context, and optional cause
|
|
780
|
+
*/
|
|
781
|
+
constructor(message: string, options: ProbeErrorOptions);
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
/**
|
|
785
|
+
* Names the condition that ended a probe operation, derived from {@link PROBE_ERROR_CODES}.
|
|
786
|
+
*
|
|
787
|
+
* @remarks
|
|
788
|
+
* Each condition names a different repair. `refused` changes the rejected value. `missing` creates
|
|
789
|
+
* or installs the named thing. `malformed` repairs the value read against a contract. `destroyed`
|
|
790
|
+
* builds a replacement. `deadline` changes the budget or the work it bounds according to the
|
|
791
|
+
* failure's {@link Party}.
|
|
792
|
+
*
|
|
793
|
+
* @example
|
|
794
|
+
* ```ts
|
|
795
|
+
* const code: ProbeErrorCode = 'missing'
|
|
796
|
+
* ```
|
|
797
|
+
*/
|
|
798
|
+
export declare type ProbeErrorCode = (typeof PROBE_ERROR_CODES)[number];
|
|
799
|
+
|
|
800
|
+
/**
|
|
801
|
+
* Carries the structured detail one probe failure reports beside its message.
|
|
802
|
+
*
|
|
803
|
+
* @remarks
|
|
804
|
+
* Every member is absent unless the failure has that value to report, so a reader branches on
|
|
805
|
+
* presence rather than on a sentinel. `value` is the rejected input itself, carried unchanged, so
|
|
806
|
+
* treat it as untrusted when rendering it.
|
|
807
|
+
*
|
|
808
|
+
* @example
|
|
809
|
+
* ```ts
|
|
810
|
+
* const context: ProbeErrorContext = { stage: 'runtime', deadline: 30_000 }
|
|
811
|
+
* ```
|
|
812
|
+
*/
|
|
813
|
+
export declare interface ProbeErrorContext {
|
|
814
|
+
/** The stage the failure belongs to, when one stage owns it. */
|
|
815
|
+
readonly stage?: Stage;
|
|
816
|
+
/**
|
|
817
|
+
* The path involved, spelled as the refusing operation named it: workspace-relative for an
|
|
818
|
+
* input a caller supplied, absolute for a file this package resolved.
|
|
819
|
+
*/
|
|
820
|
+
readonly path?: string;
|
|
821
|
+
/** The workspace-relative TypeScript project involved, for a project failure. */
|
|
822
|
+
readonly project?: string;
|
|
823
|
+
/** The installed package name involved, for a workspace toolchain failure. */
|
|
824
|
+
readonly name?: string;
|
|
825
|
+
/** Milliseconds the expired budget allowed, for a deadline failure. */
|
|
826
|
+
readonly deadline?: number;
|
|
827
|
+
/** The rejected value, for an input the guards refused. */
|
|
828
|
+
readonly value?: unknown;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
/**
|
|
832
|
+
* Configures one probe failure at construction.
|
|
833
|
+
*
|
|
834
|
+
* @remarks
|
|
835
|
+
* `origin` and `code` are required, because a failure a consumer cannot branch on is the failure
|
|
836
|
+
* this type exists to replace. `cause` carries the underlying fault where one ended the operation,
|
|
837
|
+
* and reaches the native `Error` option of the same name.
|
|
838
|
+
*
|
|
839
|
+
* @example
|
|
840
|
+
* ```ts
|
|
841
|
+
* const options: ProbeErrorOptions = {
|
|
842
|
+
* origin: 'claimant',
|
|
843
|
+
* code: 'refused',
|
|
844
|
+
* context: { path: '../secrets.env' },
|
|
845
|
+
* }
|
|
846
|
+
* ```
|
|
847
|
+
*/
|
|
848
|
+
export declare interface ProbeErrorOptions {
|
|
849
|
+
/** The party that must act on this failure. */
|
|
850
|
+
readonly origin: Party;
|
|
851
|
+
/** The condition that ended the operation. */
|
|
852
|
+
readonly code: ProbeErrorCode;
|
|
853
|
+
/** Structured detail about the failure. */
|
|
854
|
+
readonly context?: ProbeErrorContext;
|
|
855
|
+
/** The underlying fault, when one ended the operation. */
|
|
856
|
+
readonly cause?: unknown;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
/**
|
|
860
|
+
* Reports what a probe observes while it serves.
|
|
861
|
+
*
|
|
862
|
+
* @remarks
|
|
863
|
+
* `arm` fires when the boot control has reported red and the service will answer calls; a probe
|
|
864
|
+
* arriving before it awaits that step rather than starting a second one. `expire` fires when the
|
|
865
|
+
* coordinator's own deadline destroyed a stage and a replacement took its place, which is the only
|
|
866
|
+
* way a synchronous infinite loop is ever reported.
|
|
867
|
+
*
|
|
868
|
+
* @example
|
|
869
|
+
* ```ts
|
|
870
|
+
* const hooks: EmitterHooks<ProbeEventMap> = { prove: (verdict) => console.log(verdict.id) }
|
|
871
|
+
* ```
|
|
872
|
+
*/
|
|
873
|
+
export declare type ProbeEventMap = {
|
|
874
|
+
/** The boot control reported red and the instrument has begun answering calls. */
|
|
875
|
+
readonly arm: readonly [toolchain: Toolchain];
|
|
876
|
+
/** A claim was answered. */
|
|
877
|
+
readonly prove: readonly [verdict: Verdict];
|
|
878
|
+
/** The coordinator's deadline fired at one stage and that stage was replaced before this event. */
|
|
879
|
+
readonly expire: readonly [claim: Claim];
|
|
880
|
+
/** A fault surfaced for observation. */
|
|
881
|
+
readonly error: readonly [error: unknown];
|
|
882
|
+
};
|
|
883
|
+
|
|
884
|
+
/**
|
|
885
|
+
* Answers a claim with type, lint, and runtime evidence in one call.
|
|
886
|
+
*
|
|
887
|
+
* @remarks
|
|
888
|
+
* Warming begins at construction and `prove` awaits it, so there is no `start`: the harness owns
|
|
889
|
+
* the process and a restart is a new process rather than a second lifecycle.
|
|
890
|
+
*
|
|
891
|
+
* `prove` re-reads the target workspace before it answers, so a file edited since the last call is
|
|
892
|
+
* judged as it stands on disk rather than as a warm service remembers it. The resident readers key
|
|
893
|
+
* that sweep differently: the runtime stage compares each workspace module's contents, and the type
|
|
894
|
+
* stage versions a disk file by its modification time.
|
|
895
|
+
*
|
|
896
|
+
* @example
|
|
897
|
+
* ```ts
|
|
898
|
+
* const verdict = await probe.prove(claim)
|
|
899
|
+
* console.log(formatVerdict(verdict))
|
|
900
|
+
* await probe.destroy()
|
|
901
|
+
* ```
|
|
902
|
+
*/
|
|
903
|
+
export declare interface ProbeInterface {
|
|
904
|
+
/** Observation surface for arming, answers, deadline expiry, and faults. */
|
|
905
|
+
readonly emitter: EmitterInterface<ProbeEventMap>;
|
|
906
|
+
/** The tool versions resolved from the workspace at construction. */
|
|
907
|
+
readonly toolchain: Toolchain;
|
|
908
|
+
/**
|
|
909
|
+
* Answers one claim with every stage's evidence.
|
|
910
|
+
*
|
|
911
|
+
* @param claim - The case, its control, and the project the candidate drafts in both cases
|
|
912
|
+
* are checked against
|
|
913
|
+
* @returns The verdict, carrying one check per stage for both the case and the control
|
|
914
|
+
* @throws When a stage cannot start, so no verdict ever reports a stage that did not run
|
|
915
|
+
*/
|
|
916
|
+
prove(claim: Claim): Promise<Verdict>;
|
|
917
|
+
/**
|
|
918
|
+
* Tears down the resident engines and releases the processes they hold.
|
|
919
|
+
*
|
|
920
|
+
* @returns A promise that settles when every engine has released its resources
|
|
921
|
+
*/
|
|
922
|
+
destroy(): Promise<void>;
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
/**
|
|
926
|
+
* Configures a probe.
|
|
927
|
+
*
|
|
928
|
+
* @remarks
|
|
929
|
+
* `workspace` is the target root whose installed `typescript`, `oxlint`, and `vitest` the stages
|
|
930
|
+
* resolve, and whose files each stage re-reads before it answers. Default: the current working
|
|
931
|
+
* directory. `deadline` is the coordinator's milliseconds budget for one active stage inspection.
|
|
932
|
+
* Queue wait is not charged to that inspection; the inspections and runtime recoveries ahead of it
|
|
933
|
+
* carry their own bounds. The runtime deadline lives outside the worker because a test timeout
|
|
934
|
+
* expressed in worker configuration cannot fire while that worker spins. One runtime inspection in
|
|
935
|
+
* every 64 also pays the resident runner's replacement, so budget `deadline` against that
|
|
936
|
+
* inspection rather than the common one.
|
|
937
|
+
*
|
|
938
|
+
* @example
|
|
939
|
+
* ```ts
|
|
940
|
+
* const options: ProbeOptions = { workspace: '/srv/checkout', deadline: 30_000 }
|
|
941
|
+
* ```
|
|
942
|
+
*/
|
|
943
|
+
export declare interface ProbeOptions {
|
|
944
|
+
/** Initial listeners, wired at construction. */
|
|
945
|
+
readonly on?: EmitterHooks<ProbeEventMap>;
|
|
946
|
+
/** Handler for a listener throw. */
|
|
947
|
+
readonly error?: EmitterErrorHandler;
|
|
948
|
+
/** Target workspace root. Default: the current working directory. */
|
|
949
|
+
readonly workspace?: string;
|
|
950
|
+
/**
|
|
951
|
+
* Milliseconds one active stage inspection may take; an expired stage is replaced. Default:
|
|
952
|
+
* 30,000 ms.
|
|
953
|
+
*/
|
|
954
|
+
readonly deadline?: number;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
/**
|
|
958
|
+
* Names the TypeScript project that judged a verdict's candidate drafts.
|
|
959
|
+
*
|
|
960
|
+
* @remarks
|
|
961
|
+
* A verdict is an assertion about code under one compiler configuration, and the same code passes
|
|
962
|
+
* under one project and fails under another, so a verdict that omits the project states less than
|
|
963
|
+
* it appears to. `path` names which project file, in the resolved workspace-relative spelling
|
|
964
|
+
* rather than the caller's. `digest` names what that project contained, because a receipt travels
|
|
965
|
+
* away from the workspace that minted it and a path alone is a claim about a name.
|
|
966
|
+
*
|
|
967
|
+
* @example
|
|
968
|
+
* ```ts
|
|
969
|
+
* const project: Project = {
|
|
970
|
+
* path: 'configs/src/tsconfig.core.json',
|
|
971
|
+
* digest: '3b674fdf121c85efb9ed1bab25ceeec8',
|
|
972
|
+
* }
|
|
973
|
+
* ```
|
|
974
|
+
*/
|
|
975
|
+
export declare interface Project {
|
|
976
|
+
/** Resolved workspace-relative path of the project file the type stage applied. */
|
|
977
|
+
readonly path: string;
|
|
978
|
+
/** Digest of that project's resolved compiler options, workspace-relative and key-sorted. */
|
|
979
|
+
readonly digest: string;
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
/**
|
|
983
|
+
* Names the leading token every receipt carries.
|
|
984
|
+
*
|
|
985
|
+
* @remarks
|
|
986
|
+
* A receipt travels away from the verdict that minted it — an agent pastes it into the promotion
|
|
987
|
+
* action — so it names itself rather than relying on where it was found.
|
|
988
|
+
*
|
|
989
|
+
* @example
|
|
990
|
+
* ```ts
|
|
991
|
+
* const receipt =
|
|
992
|
+
* 'probe:6ca20c3bff623031d3955b9d1a76d71d:type:typescript@6.0.3:oxlint@1.79.0:vitest@4.1.11:configs/src/tsconfig.core.json@3b674fdf121c85efb9ed1bab25ceeec8'
|
|
993
|
+
* receipt.startsWith(RECEIPT_PREFIX) // true
|
|
994
|
+
* ```
|
|
995
|
+
*/
|
|
996
|
+
export declare const RECEIPT_PREFIX = "probe";
|
|
997
|
+
|
|
998
|
+
/**
|
|
999
|
+
* Names the character joining a receipt's tokens.
|
|
1000
|
+
*
|
|
1001
|
+
* @remarks
|
|
1002
|
+
* A project path can contain this character, so the project field goes last and a reader rejoins
|
|
1003
|
+
* every field from index 6 onward with this separator rather than expecting a fixed count.
|
|
1004
|
+
*
|
|
1005
|
+
* @example
|
|
1006
|
+
* ```ts
|
|
1007
|
+
* const receipt =
|
|
1008
|
+
* 'probe:6ca20c3bff623031d3955b9d1a76d71d:type:typescript@6.0.3:oxlint@1.79.0:vitest@4.1.11:configs/src/tsconfig.core.json@3b674fdf121c85efb9ed1bab25ceeec8'
|
|
1009
|
+
* receipt.split(RECEIPT_SEPARATOR).length // 7
|
|
1010
|
+
* ```
|
|
1011
|
+
*/
|
|
1012
|
+
export declare const RECEIPT_SEPARATOR = ":";
|
|
1013
|
+
|
|
1014
|
+
/**
|
|
1015
|
+
* Names an inspection a claim passes through, derived from {@link PROBE_STAGES}.
|
|
1016
|
+
*
|
|
1017
|
+
* @remarks
|
|
1018
|
+
* They are irreducible modes rather than labels: each reads a different tool, and a verdict that
|
|
1019
|
+
* omits one hides a defect the agent would then believe it had fixed.
|
|
1020
|
+
*
|
|
1021
|
+
* @example
|
|
1022
|
+
* ```ts
|
|
1023
|
+
* const stage: Stage = 'lint'
|
|
1024
|
+
* ```
|
|
1025
|
+
*/
|
|
1026
|
+
export declare type Stage = (typeof PROBE_STAGES)[number];
|
|
1027
|
+
|
|
1028
|
+
/**
|
|
1029
|
+
* Names the tool versions a verdict was produced with.
|
|
1030
|
+
*
|
|
1031
|
+
* @remarks
|
|
1032
|
+
* A probe is worth running only when its verdict predicts the gate's verdict, which holds only
|
|
1033
|
+
* when the probe and the gate read one installed copy of each tool. Carrying the resolved versions
|
|
1034
|
+
* on the verdict makes that claim checkable rather than assumed.
|
|
1035
|
+
*
|
|
1036
|
+
* @example
|
|
1037
|
+
* ```ts
|
|
1038
|
+
* const toolchain: Toolchain = { typescript: '6.0.3', oxlint: '1.79.0', vitest: '4.1.11' }
|
|
1039
|
+
* ```
|
|
1040
|
+
*/
|
|
1041
|
+
export declare interface Toolchain {
|
|
1042
|
+
/** Resolved `typescript` version the type stage ran. */
|
|
1043
|
+
readonly typescript: string;
|
|
1044
|
+
/** Resolved `oxlint` version the lint stage ran. */
|
|
1045
|
+
readonly oxlint: string;
|
|
1046
|
+
/** Resolved `vitest` version the runtime stage ran. */
|
|
1047
|
+
readonly vitest: string;
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
/**
|
|
1051
|
+
* Carries the full result of one claim: every stage, for both the case and its control.
|
|
1052
|
+
*
|
|
1053
|
+
* @remarks
|
|
1054
|
+
* A verdict exists only when every stage ran on both the case and the control, so `case`
|
|
1055
|
+
* and `control` each hold one entry per stage. A stage that cannot start throws instead, which is
|
|
1056
|
+
* why no member here models a missing stage. `receipt` is present only when the case reported no
|
|
1057
|
+
* issue, the control reported an `origin: 'claimant'` issue at the stage it declared,
|
|
1058
|
+
* every other control stage reported no claimant issue, and neither phase reported an
|
|
1059
|
+
* `origin: 'instrument'` issue.
|
|
1060
|
+
*
|
|
1061
|
+
* `id` identifies this call and `digest` identifies the claim it answered, so two calls over one
|
|
1062
|
+
* claim share a digest and differ in their identity. `digest` and `project` are required, because
|
|
1063
|
+
* the type stage always runs and a claim always names a project, so no verdict exists without
|
|
1064
|
+
* either value. A verdict returned by `Probe` also carries the control's claimant-authored
|
|
1065
|
+
* `reason` unchanged. A hand-built verdict may omit it because the receipt helper needs only the
|
|
1066
|
+
* recorded checks and declared stage.
|
|
1067
|
+
*
|
|
1068
|
+
* `digest` covers these things: the case bytes, the control bytes including this `reason`, and the
|
|
1069
|
+
* workspace they were read against. The reason is part of the control, so two claims differing only
|
|
1070
|
+
* in its prose are two claims and digest differently — a claimant who restates the falsifier has
|
|
1071
|
+
* answered a different question, and a digest that excluded it would describe less than the claim.
|
|
1072
|
+
* The workspace enters because every absolute string in a claim is rewritten relative to it before
|
|
1073
|
+
* hashing, so a claim carrying no absolute string digests the same in every workspace and one that
|
|
1074
|
+
* carries any digests per workspace. `receipt` carries this digest as a field, so the same inputs
|
|
1075
|
+
* reach the token.
|
|
1076
|
+
*
|
|
1077
|
+
* @example
|
|
1078
|
+
* ```ts
|
|
1079
|
+
* const broke: Issue = {
|
|
1080
|
+
* origin: 'claimant',
|
|
1081
|
+
* path: 'src/core/farewell.ts',
|
|
1082
|
+
* message: 'not assignable',
|
|
1083
|
+
* line: 1,
|
|
1084
|
+
* }
|
|
1085
|
+
* const verdict: Verdict = {
|
|
1086
|
+
* id: '88a5addc-7d33-40dc-9a5a-104b71f8787d',
|
|
1087
|
+
* digest: '6ca20c3bff623031d3955b9d1a76d71d',
|
|
1088
|
+
* toolchain: { typescript: '6.0.3', oxlint: '1.79.0', vitest: '4.1.11' },
|
|
1089
|
+
* project: {
|
|
1090
|
+
* path: 'configs/src/tsconfig.core.json',
|
|
1091
|
+
* digest: '3b674fdf121c85efb9ed1bab25ceeec8',
|
|
1092
|
+
* },
|
|
1093
|
+
* reason: 'a number annotation must reject the string literal beside it',
|
|
1094
|
+
* case: [
|
|
1095
|
+
* { stage: 'type', elapsed: 61, issues: [] },
|
|
1096
|
+
* { stage: 'lint', elapsed: 17, issues: [] },
|
|
1097
|
+
* { stage: 'runtime', elapsed: 259, issues: [] },
|
|
1098
|
+
* ],
|
|
1099
|
+
* control: [
|
|
1100
|
+
* { stage: 'type', elapsed: 58, issues: [broke] },
|
|
1101
|
+
* { stage: 'lint', elapsed: 16, issues: [] },
|
|
1102
|
+
* { stage: 'runtime', elapsed: 254, issues: [] },
|
|
1103
|
+
* ],
|
|
1104
|
+
* elapsed: 549,
|
|
1105
|
+
* receipt:
|
|
1106
|
+
* 'probe:6ca20c3bff623031d3955b9d1a76d71d:type:typescript@6.0.3:oxlint@1.79.0:vitest@4.1.11:configs/src/tsconfig.core.json@3b674fdf121c85efb9ed1bab25ceeec8',
|
|
1107
|
+
* }
|
|
1108
|
+
* ```
|
|
1109
|
+
*/
|
|
1110
|
+
export declare interface Verdict {
|
|
1111
|
+
/** The revision identity this verdict was produced for, fresh per call. */
|
|
1112
|
+
readonly id: string;
|
|
1113
|
+
/** Digest of the case and control this verdict answers. */
|
|
1114
|
+
readonly digest: string;
|
|
1115
|
+
/** The tool versions that produced it. */
|
|
1116
|
+
readonly toolchain: Toolchain;
|
|
1117
|
+
/** The TypeScript project the candidate drafts were judged against. */
|
|
1118
|
+
readonly project: Project;
|
|
1119
|
+
/**
|
|
1120
|
+
* The claimant's explanation for the selected control, when the verdict came from a claim. Part
|
|
1121
|
+
* of the control, so it enters `digest` and through it the receipt token.
|
|
1122
|
+
*/
|
|
1123
|
+
readonly reason?: string;
|
|
1124
|
+
/** One outcome per stage for the claim's case. */
|
|
1125
|
+
readonly case: readonly Check[];
|
|
1126
|
+
/** One outcome per stage for the claim's control. */
|
|
1127
|
+
readonly control: readonly Check[];
|
|
1128
|
+
/** Milliseconds the whole call took, including both the case and the control. */
|
|
1129
|
+
readonly elapsed: number;
|
|
1130
|
+
/** The proof token, absent when either phase reports an instrument failure. */
|
|
1131
|
+
readonly receipt?: string;
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
export { }
|