testguard-cli 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/CHANGELOG.md +255 -0
  2. package/README.md +275 -31
  3. package/package.json +1 -1
  4. package/spec/GATE-SEMANTICS.md +58 -1
  5. package/spec/README.md +1 -0
  6. package/spec/lib/validate.mjs +57 -1
  7. package/spec/schemas/baseline.schema.json +8 -0
  8. package/spec/schemas/common.schema.json +5 -1
  9. package/spec/schemas/evidence.schema.json +374 -63
  10. package/spec/schemas/replay.schema.json +85 -0
  11. package/spec/schemas/status.schema.json +256 -54
  12. package/src/admit/admit.mjs +17 -0
  13. package/src/baseline/baseline.mjs +31 -2
  14. package/src/brief/brief.mjs +71 -7
  15. package/src/claims/removed.mjs +161 -0
  16. package/src/cli.mjs +53 -10
  17. package/src/commands/admit.mjs +116 -0
  18. package/src/commands/baseline.mjs +17 -3
  19. package/src/commands/brief.mjs +13 -7
  20. package/src/commands/claims.mjs +35 -6
  21. package/src/commands/init.mjs +19 -6
  22. package/src/commands/mcp.mjs +12 -0
  23. package/src/commands/probe.mjs +11 -3
  24. package/src/commands/replay.mjs +63 -0
  25. package/src/commands/status.mjs +3 -1
  26. package/src/git.mjs +5 -0
  27. package/src/init/init.mjs +182 -29
  28. package/src/init/templates/SKILL.md +3 -3
  29. package/src/mcp/server.mjs +104 -0
  30. package/src/mcp/tools.mjs +114 -0
  31. package/src/probe/contention.mjs +60 -0
  32. package/src/probe/discover.mjs +12 -3
  33. package/src/probe/independence.mjs +59 -0
  34. package/src/probe/mocks.mjs +120 -0
  35. package/src/probe/probe.mjs +126 -18
  36. package/src/probe/rank.mjs +73 -15
  37. package/src/probe/runners/index.mjs +49 -5
  38. package/src/probe/runners/jest.mjs +3 -3
  39. package/src/probe/runners/playwright.mjs +133 -0
  40. package/src/probe/runners/shared.mjs +70 -13
  41. package/src/probe/runners/vitest.mjs +4 -3
  42. package/src/render.mjs +2 -2
  43. package/src/replay/label.mjs +64 -0
  44. package/src/replay/replay.mjs +309 -0
  45. package/src/scaffold/producers.mjs +217 -6
  46. package/src/scaffold/scaffold.mjs +9 -3
  47. package/src/status/status.mjs +47 -6
@@ -43,7 +43,7 @@
43
43
  ]
44
44
  },
45
45
  "faultClass": {
46
- "description": "Taxonomy of the injected change. Required on every fault so calibration (fault class → real-bug predictiveness) can be computed later.",
46
+ "description": "Taxonomy of the injected change. Required on every fault so calibration (fault class → real-bug predictiveness) can be computed later. `field-dropped` = a field removed from a persisted/transmitted object, allow-list, schema or merge; `argument-swapped` = a call kept with a parameter-derived argument replaced by a trivial value; `element-removed` = a UI element no longer rendered; `handler-dropped` = an event handler no longer wired.",
47
47
  "enum": [
48
48
  "guard-removed",
49
49
  "condition-forced",
@@ -53,6 +53,10 @@
53
53
  "call-removed",
54
54
  "return-altered",
55
55
  "exception-swallowed",
56
+ "field-dropped",
57
+ "argument-swapped",
58
+ "element-removed",
59
+ "handler-dropped",
56
60
  "other"
57
61
  ]
58
62
  },
@@ -1,45 +1,123 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "urn:guard-spec:v1:evidence",
4
- "title": "Guard spec v1 — evidence file",
4
+ "title": "Guard spec v1 \u2014 evidence file",
5
5
  "description": "One run's findings, on disk, in a shape an auditor can read without the tool. Each record binds a claim to a subject (for TestGuard, an injected fault) and a verdict, with every run that produced the verdict.",
6
6
  "type": "object",
7
- "required": ["schemaVersion", "tool", "run", "records"],
7
+ "required": [
8
+ "schemaVersion",
9
+ "tool",
10
+ "run",
11
+ "records"
12
+ ],
8
13
  "properties": {
9
- "$schema": { "type": "string" },
10
- "schemaVersion": { "const": 1 },
11
- "tool": { "$ref": "urn:guard-spec:v1:common#/$defs/tool" },
14
+ "$schema": {
15
+ "type": "string"
16
+ },
17
+ "schemaVersion": {
18
+ "const": 1
19
+ },
20
+ "tool": {
21
+ "$ref": "urn:guard-spec:v1:common#/$defs/tool"
22
+ },
12
23
  "run": {
13
24
  "type": "object",
14
- "required": ["id", "startedAt", "repo", "confirmRuns", "mode"],
25
+ "required": [
26
+ "id",
27
+ "startedAt",
28
+ "repo",
29
+ "confirmRuns",
30
+ "mode"
31
+ ],
15
32
  "properties": {
16
- "id": { "$ref": "urn:guard-spec:v1:common#/$defs/identifier" },
17
- "startedAt": { "$ref": "urn:guard-spec:v1:common#/$defs/isoDateTime" },
18
- "finishedAt": { "$ref": "urn:guard-spec:v1:common#/$defs/isoDateTime" },
33
+ "id": {
34
+ "$ref": "urn:guard-spec:v1:common#/$defs/identifier"
35
+ },
36
+ "startedAt": {
37
+ "$ref": "urn:guard-spec:v1:common#/$defs/isoDateTime"
38
+ },
39
+ "finishedAt": {
40
+ "$ref": "urn:guard-spec:v1:common#/$defs/isoDateTime"
41
+ },
19
42
  "repo": {
20
43
  "type": "object",
21
- "required": ["head", "dirty"],
44
+ "required": [
45
+ "head",
46
+ "dirty"
47
+ ],
22
48
  "properties": {
23
- "head": { "$ref": "urn:guard-spec:v1:common#/$defs/gitSha" },
24
- "dirty": { "type": "boolean" },
49
+ "head": {
50
+ "$ref": "urn:guard-spec:v1:common#/$defs/gitSha"
51
+ },
52
+ "dirty": {
53
+ "type": "boolean"
54
+ },
25
55
  "snapshot": {
26
56
  "description": "When the working tree was probed instead of `head`: the dangling commit that captured it. Not a ref; may be garbage-collected.",
27
57
  "$ref": "urn:guard-spec:v1:common#/$defs/gitSha"
58
+ },
59
+ "ignoredDirty": {
60
+ "description": "Defender/target files that had uncommitted changes when `head` was probed as committed (an explicit --ref, or --ignore-dirty). The working-tree versions of these files are NOT what the verdicts describe.",
61
+ "type": "array",
62
+ "minItems": 1,
63
+ "items": {
64
+ "$ref": "urn:guard-spec:v1:common#/$defs/repoPath"
65
+ }
28
66
  }
29
67
  },
30
68
  "additionalProperties": false
31
69
  },
32
70
  "runner": {
33
71
  "type": "object",
34
- "required": ["name"],
72
+ "required": [
73
+ "name"
74
+ ],
75
+ "properties": {
76
+ "name": {
77
+ "$ref": "urn:guard-spec:v1:common#/$defs/identifier"
78
+ },
79
+ "version": {
80
+ "type": "string",
81
+ "maxLength": 64
82
+ },
83
+ "source": {
84
+ "description": "`project`: the runner package resolved from the project and its own bin script ran. `path`: no such package in the project; an executable of that name on PATH ran instead (a legitimate monorepo setup, but not the project's pinned version).",
85
+ "enum": [
86
+ "project",
87
+ "path"
88
+ ]
89
+ }
90
+ },
91
+ "additionalProperties": false
92
+ },
93
+ "serial": {
94
+ "description": "True when the defenders were run one test file at a time. Recorded because a serial run is slower and its durations are not comparable with a parallel one.",
95
+ "type": "boolean"
96
+ },
97
+ "contention": {
98
+ "description": "Other test runners that were already running when this probe started. Best effort, never a verdict: it exists so a reader of a TIMEOUT or FLAKY-DEFENDER can tell the machine was loaded.",
99
+ "type": "object",
100
+ "required": ["detected", "runners"],
35
101
  "properties": {
36
- "name": { "$ref": "urn:guard-spec:v1:common#/$defs/identifier" },
37
- "version": { "type": "string", "maxLength": 64 }
102
+ "detected": { "const": true },
103
+ "runners": {
104
+ "type": "array",
105
+ "minItems": 1,
106
+ "items": {
107
+ "type": "object",
108
+ "required": ["pid", "command"],
109
+ "properties": {
110
+ "pid": { "type": "integer", "minimum": 1 },
111
+ "command": { "type": "string", "minLength": 1, "maxLength": 200 }
112
+ },
113
+ "additionalProperties": false
114
+ }
115
+ }
38
116
  },
39
117
  "additionalProperties": false
40
118
  },
41
119
  "confirmRuns": {
42
- "description": "N — how many times the defenders were run both unmodified (baseline) and with each fault applied.",
120
+ "description": "N \u2014 how many times the defenders were run both unmodified (baseline) and with each fault applied.",
43
121
  "type": "integer",
44
122
  "minimum": 1
45
123
  },
@@ -49,33 +127,80 @@
49
127
  },
50
128
  "mode": {
51
129
  "description": "`worktree` = faults were applied in a scratch git worktree (default). `in-place` = the user's working tree was mutated and restored.",
52
- "enum": ["worktree", "in-place"]
130
+ "enum": [
131
+ "worktree",
132
+ "in-place"
133
+ ]
134
+ },
135
+ "runners": {
136
+ "description": "Every runner that produced part of this evidence when defenders were split by file (the project runner plus, e.g., Playwright for the files under its testDir). Absent when one runner ran everything.",
137
+ "type": "array",
138
+ "minItems": 1,
139
+ "items": {
140
+ "type": "object",
141
+ "required": [
142
+ "name"
143
+ ],
144
+ "properties": {
145
+ "name": {
146
+ "$ref": "urn:guard-spec:v1:common#/$defs/identifier"
147
+ },
148
+ "version": {
149
+ "type": "string",
150
+ "maxLength": 64
151
+ }
152
+ },
153
+ "additionalProperties": false
154
+ }
53
155
  }
54
156
  },
55
157
  "additionalProperties": false
56
158
  },
57
159
  "records": {
58
160
  "type": "array",
59
- "items": { "$ref": "#/$defs/record" }
161
+ "items": {
162
+ "$ref": "#/$defs/record"
163
+ }
60
164
  }
61
165
  },
62
166
  "additionalProperties": false,
63
167
  "$defs": {
64
168
  "testRun": {
65
169
  "type": "object",
66
- "required": ["outcome", "durationMs"],
170
+ "required": [
171
+ "outcome",
172
+ "durationMs"
173
+ ],
67
174
  "properties": {
68
175
  "outcome": {
69
176
  "description": "`fail` means at least one test failed with the suite otherwise loading and running. `error` means the suite could not load or run (import/syntax error). `timeout` means the runner exceeded its budget.",
70
- "enum": ["pass", "fail", "timeout", "error"]
177
+ "enum": [
178
+ "pass",
179
+ "fail",
180
+ "timeout",
181
+ "error"
182
+ ]
71
183
  },
72
184
  "tests": {
73
185
  "type": "object",
74
- "required": ["total", "passed", "failed"],
186
+ "required": [
187
+ "total",
188
+ "passed",
189
+ "failed"
190
+ ],
75
191
  "properties": {
76
- "total": { "type": "integer", "minimum": 0 },
77
- "passed": { "type": "integer", "minimum": 0 },
78
- "failed": { "type": "integer", "minimum": 0 }
192
+ "total": {
193
+ "type": "integer",
194
+ "minimum": 0
195
+ },
196
+ "passed": {
197
+ "type": "integer",
198
+ "minimum": 0
199
+ },
200
+ "failed": {
201
+ "type": "integer",
202
+ "minimum": 0
203
+ }
79
204
  },
80
205
  "additionalProperties": false
81
206
  },
@@ -84,13 +209,24 @@
84
209
  "type": "integer",
85
210
  "minimum": 0
86
211
  },
87
- "durationMs": { "type": "integer", "minimum": 0 }
212
+ "durationMs": {
213
+ "type": "integer",
214
+ "minimum": 0
215
+ }
88
216
  },
89
217
  "additionalProperties": false
90
218
  },
91
219
  "record": {
92
220
  "type": "object",
93
- "required": ["fingerprint", "claim", "subject", "verdict", "detail", "defenders", "inputs"],
221
+ "required": [
222
+ "fingerprint",
223
+ "claim",
224
+ "subject",
225
+ "verdict",
226
+ "detail",
227
+ "defenders",
228
+ "inputs"
229
+ ],
94
230
  "properties": {
95
231
  "fingerprint": {
96
232
  "description": "sha256 of `<claim.id>\\n<subject.id>\\n<subject.file>\\n<verdict>`. Derived from identities and outcome, never from `find`/`replace`, so repairing a rotted anchor does not churn the baseline.",
@@ -98,46 +234,95 @@
98
234
  },
99
235
  "claim": {
100
236
  "type": "object",
101
- "required": ["id", "statement", "severity", "source"],
237
+ "required": [
238
+ "id",
239
+ "statement",
240
+ "severity",
241
+ "source"
242
+ ],
102
243
  "properties": {
103
- "id": { "$ref": "urn:guard-spec:v1:common#/$defs/identifier" },
104
- "statement": { "type": "string", "minLength": 1, "maxLength": 2000 },
105
- "severity": { "$ref": "urn:guard-spec:v1:common#/$defs/severity" },
244
+ "id": {
245
+ "$ref": "urn:guard-spec:v1:common#/$defs/identifier"
246
+ },
247
+ "statement": {
248
+ "type": "string",
249
+ "minLength": 1,
250
+ "maxLength": 2000
251
+ },
252
+ "severity": {
253
+ "$ref": "urn:guard-spec:v1:common#/$defs/severity"
254
+ },
106
255
  "source": {
107
256
  "type": "object",
108
- "required": ["kind"],
257
+ "required": [
258
+ "kind"
259
+ ],
109
260
  "properties": {
110
- "kind": { "enum": ["spec", "adr", "annotation", "comment", "manual"] },
111
- "ref": { "type": "string", "maxLength": 512 }
261
+ "kind": {
262
+ "enum": [
263
+ "spec",
264
+ "adr",
265
+ "annotation",
266
+ "comment",
267
+ "manual"
268
+ ]
269
+ },
270
+ "ref": {
271
+ "type": "string",
272
+ "maxLength": 512
273
+ }
112
274
  },
113
275
  "additionalProperties": false
114
276
  },
115
- "producedBy": { "$ref": "urn:guard-spec:v1:common#/$defs/provenance" }
277
+ "producedBy": {
278
+ "$ref": "urn:guard-spec:v1:common#/$defs/provenance"
279
+ }
116
280
  },
117
281
  "additionalProperties": false
118
282
  },
119
283
  "subject": {
120
284
  "description": "What was tried against the claim. For TestGuard `kind` is `fault`. Other adopters use their own kinds (e.g. `doc-statement`, `finding`); extra properties are permitted here and nowhere else.",
121
285
  "type": "object",
122
- "required": ["kind", "id"],
286
+ "required": [
287
+ "kind",
288
+ "id"
289
+ ],
123
290
  "properties": {
124
- "kind": { "$ref": "urn:guard-spec:v1:common#/$defs/identifier" },
125
- "id": { "$ref": "urn:guard-spec:v1:common#/$defs/identifier" },
126
- "description": { "type": "string", "maxLength": 2000 },
127
- "file": { "$ref": "urn:guard-spec:v1:common#/$defs/repoPath" },
128
- "faultClass": { "$ref": "urn:guard-spec:v1:common#/$defs/faultClass" },
129
- "producedBy": { "$ref": "urn:guard-spec:v1:common#/$defs/provenance" },
291
+ "kind": {
292
+ "$ref": "urn:guard-spec:v1:common#/$defs/identifier"
293
+ },
294
+ "id": {
295
+ "$ref": "urn:guard-spec:v1:common#/$defs/identifier"
296
+ },
297
+ "description": {
298
+ "type": "string",
299
+ "maxLength": 2000
300
+ },
301
+ "file": {
302
+ "$ref": "urn:guard-spec:v1:common#/$defs/repoPath"
303
+ },
304
+ "faultClass": {
305
+ "$ref": "urn:guard-spec:v1:common#/$defs/faultClass"
306
+ },
307
+ "producedBy": {
308
+ "$ref": "urn:guard-spec:v1:common#/$defs/provenance"
309
+ },
130
310
  "contentHash": {
131
- "description": "sha256 of the subject's content (for a fault: find + newline + replace). Excluded from the fingerprint on purpose, so repairing an anchor does not churn a baseline — and recorded here on purpose, so weakening a fault to make a finding disappear is visible.",
311
+ "description": "sha256 of the subject's content (for a fault: find + newline + replace). Excluded from the fingerprint on purpose, so repairing an anchor does not churn a baseline \u2014 and recorded here on purpose, so weakening a fault to make a finding disappear is visible.",
132
312
  "$ref": "urn:guard-spec:v1:common#/$defs/sha256"
133
313
  }
134
314
  },
135
315
  "additionalProperties": true
136
316
  },
137
- "verdict": { "$ref": "urn:guard-spec:v1:common#/$defs/verdict" },
317
+ "verdict": {
318
+ "$ref": "urn:guard-spec:v1:common#/$defs/verdict"
319
+ },
138
320
  "detail": {
139
321
  "type": "object",
140
- "required": ["baselineRuns", "probeRuns"],
322
+ "required": [
323
+ "baselineRuns",
324
+ "probeRuns"
325
+ ],
141
326
  "properties": {
142
327
  "reason": {
143
328
  "description": "Machine-readable qualifier, e.g. `anchor-missing`, `anchor-ambiguous`, `defenders-not-green`, `replacement-does-not-compile`.",
@@ -147,34 +332,79 @@
147
332
  "baselineRuns": {
148
333
  "description": "Defenders run on unmodified source, N times. Anything short of N/N pass is `flaky-defender`.",
149
334
  "type": "array",
150
- "items": { "$ref": "#/$defs/testRun" }
335
+ "items": {
336
+ "$ref": "#/$defs/testRun"
337
+ }
151
338
  },
152
339
  "probeRuns": {
153
340
  "description": "Defenders run with the fault applied, N times.",
154
341
  "type": "array",
155
- "items": { "$ref": "#/$defs/testRun" }
342
+ "items": {
343
+ "$ref": "#/$defs/testRun"
344
+ }
156
345
  },
157
346
  "escalated": {
158
347
  "description": "True when the fault survived its declared defenders and a broader test set was then run.",
159
348
  "type": "boolean"
160
349
  },
350
+ "escalationStoppedEarly": {
351
+ "description": "Why the escalation loop ran fewer than N times. `no-common-failure`: no test failed in every run so far, so no further run could name an undeclared killer. Stopping can only fail to NAME a killer, never upgrade a verdict.",
352
+ "enum": ["no-common-failure"]
353
+ },
161
354
  "escalationRuns": {
162
355
  "type": "array",
163
- "items": { "$ref": "#/$defs/testRun" }
356
+ "items": {
357
+ "$ref": "#/$defs/testRun"
358
+ }
164
359
  },
165
360
  "undeclaredKillers": {
166
361
  "description": "With reason `killed-by-undeclared-tests`: the tests (as `file::name`) that failed in every escalation run. Names what to add to `defendedBy`.",
167
362
  "type": "array",
168
363
  "minItems": 1,
169
- "items": { "type": "string", "minLength": 1, "maxLength": 1024 }
364
+ "items": {
365
+ "type": "string",
366
+ "minLength": 1,
367
+ "maxLength": 1024
368
+ }
170
369
  },
171
- "anchor": {
370
+ "flakeRate": {
371
+ "description": "Observed instability of the defenders on UNMODIFIED source: `failures` of `runs` baseline runs failed with the source untouched. Only runs that actually ran are counted (`pass`/`fail`); a load error or a timeout measures nothing about stability and is excluded from both numbers. `failures > 0` means the verdict is `flaky-defender`; the degree is for whoever fixes the defender, and it is the first input to calibration.",
372
+ "type": "object",
373
+ "required": ["runs", "failures"],
374
+ "properties": {
375
+ "runs": { "type": "integer", "minimum": 1 },
376
+ "failures": { "type": "integer", "minimum": 0 }
377
+ },
378
+ "additionalProperties": false
379
+ },
380
+ "independence": {
381
+ "description": "L3 signal, on `killed` records only: was the killing defender last touched by the same change (or the same author) as the fault's target? A signal for ranking and reporting; it never changes a verdict.",
382
+ "type": "object",
383
+ "required": ["class"],
384
+ "properties": {
385
+ "class": { "enum": ["co-authored", "separate-change", "unknown"] },
386
+ "defenderCommit": { "$ref": "urn:guard-spec:v1:common#/$defs/gitSha" },
387
+ "targetCommit": { "$ref": "urn:guard-spec:v1:common#/$defs/gitSha" },
388
+ "sameAuthor": { "type": "boolean" }
389
+ },
390
+ "additionalProperties": false
391
+ },
392
+ "anchor": {
172
393
  "description": "With an anchor-related reason: how many times `find` occurred versus how many were expected.",
173
394
  "type": "object",
174
- "required": ["hits", "expected"],
395
+ "required": [
396
+ "hits",
397
+ "expected"
398
+ ],
175
399
  "properties": {
176
- "hits": { "type": "integer", "minimum": 0 },
177
- "expected": { "type": "integer", "minimum": 1 }
400
+ "hits": {
401
+ "type": "integer",
402
+ "minimum": 0
403
+ },
404
+ "expected": {
405
+ "type": "integer",
406
+ "minimum": 1
407
+ }
178
408
  },
179
409
  "additionalProperties": false
180
410
  }
@@ -183,14 +413,79 @@
183
413
  },
184
414
  "defenders": {
185
415
  "type": "object",
186
- "required": ["requested", "resolved", "nocover"],
416
+ "required": [
417
+ "requested",
418
+ "resolved",
419
+ "nocover"
420
+ ],
187
421
  "properties": {
188
- "requested": { "type": "array", "items": { "type": "string" } },
189
- "resolved": { "type": "array", "items": { "$ref": "urn:guard-spec:v1:common#/$defs/repoPath" } },
190
- "nocover": { "type": "boolean" },
422
+ "requested": {
423
+ "type": "array",
424
+ "items": {
425
+ "type": "string"
426
+ }
427
+ },
428
+ "resolved": {
429
+ "type": "array",
430
+ "items": {
431
+ "$ref": "urn:guard-spec:v1:common#/$defs/repoPath"
432
+ }
433
+ },
434
+ "nocover": {
435
+ "type": "boolean"
436
+ },
191
437
  "discovered": {
192
438
  "description": "True when no defenders were declared and `resolved` was found by static import of the subject's file.",
193
439
  "type": "boolean"
440
+ },
441
+ "mocking": {
442
+ "description": "Candidate test files that import the subject's file but mock it (`vi.mock`/`jest.mock`). Discovered candidates that mock are excluded from `resolved`; a declared defender that mocks stays in `resolved` and is listed here, because a declared defender that mocks the subject is a broken evidence chain.",
443
+ "type": "array",
444
+ "items": {
445
+ "$ref": "urn:guard-spec:v1:common#/$defs/repoPath"
446
+ }
447
+ },
448
+ "signals": {
449
+ "description": "Static, annotation-grade signals about the defenders. They never change a verdict. `mocked-never-asserted`: the file mocks the subject and never asserts on anything imported from it. `unasserted-annotated`: the same, but the author annotated the mock with a reason (`// unasserted: <why>`), recorded here \u2014 silenced, never hidden.",
450
+ "type": "array",
451
+ "items": {
452
+ "type": "object",
453
+ "required": [
454
+ "file",
455
+ "signal"
456
+ ],
457
+ "properties": {
458
+ "file": {
459
+ "$ref": "urn:guard-spec:v1:common#/$defs/repoPath"
460
+ },
461
+ "signal": {
462
+ "enum": [
463
+ "mocked-never-asserted",
464
+ "unasserted-annotated"
465
+ ]
466
+ },
467
+ "reason": {
468
+ "type": "string",
469
+ "minLength": 1,
470
+ "maxLength": 512
471
+ }
472
+ },
473
+ "additionalProperties": false
474
+ }
475
+ },
476
+ "byRunner": {
477
+ "description": "When the resolved defenders ran under more than one runner: runner name \u2192 the files it ran. Every file appears in `resolved`. Absent when the project runner ran them all.",
478
+ "type": "object",
479
+ "propertyNames": {
480
+ "$ref": "urn:guard-spec:v1:common#/$defs/identifier"
481
+ },
482
+ "additionalProperties": {
483
+ "type": "array",
484
+ "minItems": 1,
485
+ "items": {
486
+ "$ref": "urn:guard-spec:v1:common#/$defs/repoPath"
487
+ }
488
+ }
194
489
  }
195
490
  },
196
491
  "additionalProperties": false
@@ -198,12 +493,19 @@
198
493
  "inputs": {
199
494
  "description": "Content hashes of everything the verdict depends on, so an unchanged claim can reuse its prior verdict.",
200
495
  "type": "object",
201
- "required": ["targetHash", "defenderHashes"],
496
+ "required": [
497
+ "targetHash",
498
+ "defenderHashes"
499
+ ],
202
500
  "properties": {
203
- "targetHash": { "$ref": "urn:guard-spec:v1:common#/$defs/sha256" },
501
+ "targetHash": {
502
+ "$ref": "urn:guard-spec:v1:common#/$defs/sha256"
503
+ },
204
504
  "defenderHashes": {
205
505
  "type": "object",
206
- "additionalProperties": { "$ref": "urn:guard-spec:v1:common#/$defs/sha256" }
506
+ "additionalProperties": {
507
+ "$ref": "urn:guard-spec:v1:common#/$defs/sha256"
508
+ }
207
509
  }
208
510
  },
209
511
  "additionalProperties": false
@@ -215,21 +517,30 @@
215
517
  "rank": {
216
518
  "description": "Additive ordering signal. Never alters `verdict`.",
217
519
  "type": "object",
218
- "required": ["score"],
520
+ "required": [
521
+ "score"
522
+ ],
219
523
  "properties": {
220
- "score": { "type": "number" },
524
+ "score": {
525
+ "type": "number"
526
+ },
221
527
  "blastRadius": {
222
528
  "description": "Number of source files that import the subject's file, transitively or directly as the tool documents.",
223
529
  "type": "integer",
224
530
  "minimum": 0
225
531
  },
226
- "tier": { "type": "string", "maxLength": 64 }
532
+ "tier": {
533
+ "type": "string",
534
+ "maxLength": 64
535
+ }
227
536
  },
228
537
  "additionalProperties": false
229
538
  },
230
539
  "annotations": {
231
540
  "type": "array",
232
- "items": { "$ref": "urn:guard-spec:v1:common#/$defs/annotation" }
541
+ "items": {
542
+ "$ref": "urn:guard-spec:v1:common#/$defs/annotation"
543
+ }
233
544
  }
234
545
  },
235
546
  "additionalProperties": false