testguard-cli 0.6.0 → 0.8.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 +489 -0
- package/README.md +287 -34
- package/package.json +2 -2
- package/spec/GATE-SEMANTICS.md +210 -8
- package/spec/README.md +32 -11
- package/spec/lib/validate.mjs +125 -14
- package/spec/lib/wilson.mjs +112 -0
- package/spec/schemas/baseline.schema.json +7 -7
- package/spec/schemas/brief.schema.json +12 -12
- package/spec/schemas/calibration.schema.json +96 -26
- package/spec/schemas/claims.schema.json +129 -25
- package/spec/schemas/common.schema.json +127 -29
- package/spec/schemas/evidence.schema.json +175 -73
- package/spec/schemas/gate.schema.json +12 -12
- package/spec/schemas/ignore.schema.json +4 -4
- package/spec/schemas/replay.schema.json +10 -10
- package/spec/schemas/status.schema.json +25 -25
- package/src/cli.mjs +20 -5
- package/src/commands/claims.mjs +22 -2
- package/src/commands/probe.mjs +24 -4
- package/src/commands/replay.mjs +1 -1
- package/src/gate/changed.mjs +9 -2
- package/src/probe/attribution.mjs +123 -0
- package/src/probe/classify.mjs +18 -2
- package/src/probe/cost.mjs +205 -0
- package/src/probe/discover.mjs +14 -6
- package/src/probe/inject.mjs +41 -9
- package/src/probe/mocks.mjs +2 -0
- package/src/probe/probe.mjs +235 -52
- package/src/probe/progress.mjs +87 -0
- package/src/probe/pyimports.mjs +287 -0
- package/src/probe/rank.mjs +2 -0
- package/src/probe/runners/index.mjs +38 -11
- package/src/probe/runners/jest.mjs +13 -1
- package/src/probe/runners/playwright.mjs +9 -0
- package/src/probe/runners/python/_testguard_pytest_plugin.py +132 -0
- package/src/probe/runners/python/_testguard_report.py +141 -0
- package/src/probe/runners/python/_testguard_unittest_main.py +168 -0
- package/src/probe/runners/python.mjs +316 -0
- package/src/probe/runners/shared.mjs +10 -1
- package/src/probe/runners/vitest.mjs +13 -1
- package/src/replay/replay.mjs +50 -22
- package/src/scaffold/producers.python.mjs +209 -0
- package/src/scaffold/scaffold.mjs +86 -32
- package/src/util/glob.mjs +7 -1
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
-
"$id": "urn:
|
|
4
|
-
"title": "
|
|
3
|
+
"$id": "urn:claimspec:v1:baseline",
|
|
4
|
+
"title": "claimspec v1 — baseline",
|
|
5
5
|
"description": "Frozen fingerprints of findings that existed when the baseline was taken. A finding whose fingerprint appears here is suppressed up to `count` occurrences; everything else is new and gates. Only non-passing verdicts are ever fingerprinted.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"required": ["schemaVersion", "tool", "createdAt", "fingerprints"],
|
|
8
8
|
"properties": {
|
|
9
9
|
"$schema": { "type": "string" },
|
|
10
10
|
"schemaVersion": { "const": 1 },
|
|
11
|
-
"tool": { "$ref": "urn:
|
|
12
|
-
"createdAt": { "$ref": "urn:
|
|
13
|
-
"head": { "$ref": "urn:
|
|
11
|
+
"tool": { "$ref": "urn:claimspec:v1:common#/$defs/tool" },
|
|
12
|
+
"createdAt": { "$ref": "urn:claimspec:v1:common#/$defs/isoDateTime" },
|
|
13
|
+
"head": { "$ref": "urn:claimspec:v1:common#/$defs/gitSha" },
|
|
14
14
|
"dirty": {
|
|
15
15
|
"description": "Whether the working tree had uncommitted changes when the evidence behind this baseline was taken.",
|
|
16
16
|
"type": "boolean"
|
|
17
17
|
},
|
|
18
18
|
"snapshot": {
|
|
19
19
|
"description": "When the evidence behind this baseline was a working-tree snapshot: that throwaway commit. `head` is then the parent of the commit that will carry the tests; `baseline --restamp` moves `head` once a clean probe reproduces the same fingerprints.",
|
|
20
|
-
"$ref": "urn:
|
|
20
|
+
"$ref": "urn:claimspec:v1:common#/$defs/gitSha"
|
|
21
21
|
},
|
|
22
22
|
"restampedAt": {
|
|
23
23
|
"description": "Set when `head` was moved by `baseline --restamp` (same fingerprints, clean tree). `createdAt` keeps the original freeze time.",
|
|
24
|
-
"$ref": "urn:
|
|
24
|
+
"$ref": "urn:claimspec:v1:common#/$defs/isoDateTime"
|
|
25
25
|
},
|
|
26
26
|
"fingerprints": {
|
|
27
27
|
"type": "object",
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
-
"$id": "urn:
|
|
4
|
-
"title": "
|
|
3
|
+
"$id": "urn:claimspec:v1:brief",
|
|
4
|
+
"title": "claimspec v1 — agent brief",
|
|
5
5
|
"description": "What to tell an agent before it writes code. `text` is the rendered block for an additionalContext payload; `items` is the same content structured so other tools can compose briefs. Ranked, capped, and never a single score.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"required": ["schemaVersion", "tool", "generatedAt", "heading", "summary", "items", "text"],
|
|
8
8
|
"properties": {
|
|
9
9
|
"$schema": { "type": "string" },
|
|
10
10
|
"schemaVersion": { "const": 1 },
|
|
11
|
-
"tool": { "$ref": "urn:
|
|
12
|
-
"generatedAt": { "$ref": "urn:
|
|
13
|
-
"head": { "$ref": "urn:
|
|
11
|
+
"tool": { "$ref": "urn:claimspec:v1:common#/$defs/tool" },
|
|
12
|
+
"generatedAt": { "$ref": "urn:claimspec:v1:common#/$defs/isoDateTime" },
|
|
13
|
+
"head": { "$ref": "urn:claimspec:v1:common#/$defs/gitSha" },
|
|
14
14
|
"heading": {
|
|
15
15
|
"description": "The markdown heading that opens `text`, e.g. `## TEST BLINDSPOT CONTEXT`.",
|
|
16
16
|
"type": "string",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"required": ["ref", "files"],
|
|
52
52
|
"properties": {
|
|
53
53
|
"ref": { "type": "string", "minLength": 1, "maxLength": 256 },
|
|
54
|
-
"files": { "type": "array", "minItems": 1, "items": { "$ref": "urn:
|
|
54
|
+
"files": { "type": "array", "minItems": 1, "items": { "$ref": "urn:claimspec:v1:common#/$defs/uncoveredFile" } }
|
|
55
55
|
},
|
|
56
56
|
"additionalProperties": false
|
|
57
57
|
},
|
|
@@ -62,13 +62,13 @@
|
|
|
62
62
|
"type": "object",
|
|
63
63
|
"required": ["fingerprint", "claimId", "statement", "verdict", "severity", "isNew"],
|
|
64
64
|
"properties": {
|
|
65
|
-
"fingerprint": { "$ref": "urn:
|
|
66
|
-
"claimId": { "$ref": "urn:
|
|
67
|
-
"subjectId": { "$ref": "urn:
|
|
65
|
+
"fingerprint": { "$ref": "urn:claimspec:v1:common#/$defs/sha256" },
|
|
66
|
+
"claimId": { "$ref": "urn:claimspec:v1:common#/$defs/identifier" },
|
|
67
|
+
"subjectId": { "$ref": "urn:claimspec:v1:common#/$defs/identifier" },
|
|
68
68
|
"statement": { "type": "string", "minLength": 1, "maxLength": 2000 },
|
|
69
|
-
"verdict": { "$ref": "urn:
|
|
70
|
-
"severity": { "$ref": "urn:
|
|
71
|
-
"file": { "$ref": "urn:
|
|
69
|
+
"verdict": { "$ref": "urn:claimspec:v1:common#/$defs/verdict" },
|
|
70
|
+
"severity": { "$ref": "urn:claimspec:v1:common#/$defs/severity" },
|
|
71
|
+
"file": { "$ref": "urn:claimspec:v1:common#/$defs/repoPath" },
|
|
72
72
|
"rank": { "type": "number" },
|
|
73
73
|
"hint": {
|
|
74
74
|
"description": "One line the agent can act on, e.g. which assertion shape left the gap.",
|
|
@@ -1,54 +1,124 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
-
"$id": "urn:
|
|
4
|
-
"title": "
|
|
5
|
-
"description": "
|
|
3
|
+
"$id": "urn:claimspec:v1:calibration",
|
|
4
|
+
"title": "claimspec v1 — calibration",
|
|
5
|
+
"description": "The observed rate of a bucket's outcome — for testguard, the share of real escaped bugs of a fault class that the suite failed to catch, so a high p is bad — with a confidence interval and the sample size behind it. A label without n is a vibe; this is the format that stops it being one. p and ci are recomputable from n, positives, method and confidence, and the validator recomputes them. Everything beyond the primary buckets is optional and additive, as `method` is on claims and evidence: a document that omits `measures` or `source.caveat` stays valid and is UNATTRIBUTED — a consumer may read it and audit it, merge nothing into it, and quote no number from it.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"required": ["schemaVersion", "tool", "computedAt", "method", "confidence", "bucketBy", "source", "buckets"],
|
|
8
8
|
"properties": {
|
|
9
9
|
"$schema": { "type": "string" },
|
|
10
10
|
"schemaVersion": { "const": 1 },
|
|
11
|
-
"tool": { "$ref": "urn:
|
|
12
|
-
"computedAt": { "$ref": "urn:
|
|
11
|
+
"tool": { "$ref": "urn:claimspec:v1:common#/$defs/tool" },
|
|
12
|
+
"computedAt": { "$ref": "urn:claimspec:v1:common#/$defs/isoDateTime" },
|
|
13
13
|
"method": {
|
|
14
|
-
"description": "Interval method. `wilson` is the only method defined in v1.",
|
|
14
|
+
"description": "Interval method. `wilson` is the only method defined in v1; under it the point estimate is the observed proportion positives / n, and a smoothed or shrunk estimate is a different method.",
|
|
15
15
|
"enum": ["wilson"]
|
|
16
16
|
},
|
|
17
17
|
"confidence": { "type": "number", "exclusiveMinimum": 0, "exclusiveMaximum": 1 },
|
|
18
|
+
"measures": {
|
|
19
|
+
"description": "What `p` is the probability of — the one field that lets two calibrations be told apart. Registry in GATE-SEMANTICS.md: `escape-missed` (testguard: a real escaped bug of this class was not caught; high is bad) and `finding-real` (websec-validator: a reported finding of this class is a real vulnerability; high is good). Two documents merge only when this, `bucketBy` and `confidence` agree. Absent = unattributed: nothing may be quoted from the document.",
|
|
20
|
+
"type": "string",
|
|
21
|
+
"pattern": "^[a-z][a-z0-9-]*$",
|
|
22
|
+
"maxLength": 64
|
|
23
|
+
},
|
|
18
24
|
"bucketBy": {
|
|
19
|
-
"description": "The field findings are bucketed on, e.g. `faultClass` or `severity`.",
|
|
20
|
-
"$ref": "
|
|
25
|
+
"description": "The field findings are bucketed on, e.g. `faultClass` or `severity`. A compound key joins fields with `|` (`attackClass|confidence`), and every bucket key then has the same number of parts.",
|
|
26
|
+
"$ref": "#/$defs/bucketKey"
|
|
27
|
+
},
|
|
28
|
+
"minN": {
|
|
29
|
+
"description": "The producer's floor. A cell with fewer samples is not quoted on its own; the consumer backs off a tier, then to `fallback`, then to nothing. A consumer may raise this — a gate that blocks a merge wants more certainty than a report — and may never lower it: lowering it resurrects a cell the producer suppressed. Absent = the producer sets no floor and the consumer applies its own.",
|
|
30
|
+
"type": "integer",
|
|
31
|
+
"minimum": 0
|
|
21
32
|
},
|
|
22
33
|
"source": {
|
|
23
|
-
"description": "Where the ground-truth labels came from.",
|
|
34
|
+
"description": "Where the ground-truth labels came from. `kind` is the mechanism (`tool-oracle`: a tool confirming its own findings, as websec-validator's dynamic probes do). `corpus` names external ground-truth sources — a bug-replay's ground truth is the repository's own history, already named by `ref`, so it is usually absent there. `caveat` is the one sentence a consumer must show beside any quoted number; `limitation` is the longer story; `evidenceStatus` says whether the labels were reviewed. `detail` is the tool's own provenance, deliberately unconstrained, on the `methodDetail` pattern: what the first consumers put there is the evidence for what should become specified.",
|
|
24
35
|
"type": "object",
|
|
25
36
|
"required": ["kind"],
|
|
26
37
|
"properties": {
|
|
27
|
-
"kind": { "enum": ["bug-replay", "human-label", "mixed"] },
|
|
28
|
-
"ref": { "type": "string", "maxLength": 512 }
|
|
38
|
+
"kind": { "enum": ["bug-replay", "human-label", "tool-oracle", "mixed"] },
|
|
39
|
+
"ref": { "type": "string", "maxLength": 512 },
|
|
40
|
+
"corpus": {
|
|
41
|
+
"type": "array",
|
|
42
|
+
"minItems": 1,
|
|
43
|
+
"items": { "type": "string", "minLength": 1, "maxLength": 128 }
|
|
44
|
+
},
|
|
45
|
+
"caveat": { "type": "string", "minLength": 1, "maxLength": 512 },
|
|
46
|
+
"limitation": { "type": "string", "minLength": 1, "maxLength": 1024 },
|
|
47
|
+
"evidenceStatus": { "enum": ["verified", "unverified", "quarantined"] },
|
|
48
|
+
"detail": { "type": "object", "minProperties": 1, "additionalProperties": true }
|
|
29
49
|
},
|
|
30
50
|
"additionalProperties": false
|
|
31
51
|
},
|
|
32
|
-
"buckets": {
|
|
33
|
-
|
|
34
|
-
"
|
|
35
|
-
"
|
|
52
|
+
"buckets": { "$ref": "#/$defs/buckets" },
|
|
53
|
+
"backoff": {
|
|
54
|
+
"description": "Ordered coarser tiers consulted when a primary cell is below `minN`: websec-validator's `attackClass|confidence` backs off to `confidence`. Each tier is held to the same arithmetic as the primary. Absent = no backoff.",
|
|
55
|
+
"type": "array",
|
|
56
|
+
"minItems": 1,
|
|
57
|
+
"items": {
|
|
36
58
|
"type": "object",
|
|
37
|
-
"required": ["
|
|
59
|
+
"required": ["bucketBy", "buckets"],
|
|
38
60
|
"properties": {
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"p": { "type": "number", "minimum": 0, "maximum": 1 },
|
|
42
|
-
"ci": {
|
|
43
|
-
"type": "array",
|
|
44
|
-
"minItems": 2,
|
|
45
|
-
"maxItems": 2,
|
|
46
|
-
"items": { "type": "number", "minimum": 0, "maximum": 1 }
|
|
47
|
-
}
|
|
61
|
+
"bucketBy": { "$ref": "#/$defs/bucketKey" },
|
|
62
|
+
"buckets": { "$ref": "#/$defs/buckets" }
|
|
48
63
|
},
|
|
49
64
|
"additionalProperties": false
|
|
50
65
|
}
|
|
66
|
+
},
|
|
67
|
+
"fallback": {
|
|
68
|
+
"description": "What remains when no tier has a cell above `minN`: a labelled guess, not a measurement, and the shape says so — it has a `basis` and no `n`, and anything quoted from it carries n = 0, ci = [0, 1] and the basis. It lives in the document so that \"what did it assume when it had no data?\" has one auditable answer instead of one per consumer.",
|
|
69
|
+
"type": "object",
|
|
70
|
+
"required": ["basis", "values"],
|
|
71
|
+
"properties": {
|
|
72
|
+
"basis": { "const": "uncalibrated-prior" },
|
|
73
|
+
"values": {
|
|
74
|
+
"type": "object",
|
|
75
|
+
"minProperties": 1,
|
|
76
|
+
"propertyNames": { "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]*(\\|[A-Za-z0-9][A-Za-z0-9._-]*)*$" },
|
|
77
|
+
"additionalProperties": { "type": "number", "minimum": 0, "maximum": 1 }
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
"additionalProperties": false
|
|
51
81
|
}
|
|
52
82
|
},
|
|
53
|
-
"additionalProperties": false
|
|
83
|
+
"additionalProperties": false,
|
|
84
|
+
"$defs": {
|
|
85
|
+
"bucketKey": {
|
|
86
|
+
"type": "string",
|
|
87
|
+
"pattern": "^[A-Za-z0-9][A-Za-z0-9._-]*(\\|[A-Za-z0-9][A-Za-z0-9._-]*)*$",
|
|
88
|
+
"maxLength": 128
|
|
89
|
+
},
|
|
90
|
+
"cell": {
|
|
91
|
+
"type": "object",
|
|
92
|
+
"required": ["n", "positives", "p", "ci"],
|
|
93
|
+
"properties": {
|
|
94
|
+
"n": { "type": "integer", "minimum": 0 },
|
|
95
|
+
"positives": { "type": "integer", "minimum": 0 },
|
|
96
|
+
"p": {
|
|
97
|
+
"description": "positives / n at the document's precision, or null when n = 0: a cell with no trials has no proportion, and 0 would be a fabricated one.",
|
|
98
|
+
"type": ["number", "null"],
|
|
99
|
+
"minimum": 0,
|
|
100
|
+
"maximum": 1
|
|
101
|
+
},
|
|
102
|
+
"ci": {
|
|
103
|
+
"type": "array",
|
|
104
|
+
"minItems": 2,
|
|
105
|
+
"maxItems": 2,
|
|
106
|
+
"items": { "type": "number", "minimum": 0, "maximum": 1 }
|
|
107
|
+
},
|
|
108
|
+
"breakdown": {
|
|
109
|
+
"description": "How n splits by outcome, when the producer can say (testguard: caught / blind / nocover). Values sum to n. Lets a consumer recover a narrower rate — misses among covered code, say — without the producer publishing a second, misleading headline.",
|
|
110
|
+
"type": "object",
|
|
111
|
+
"minProperties": 1,
|
|
112
|
+
"propertyNames": { "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]*$" },
|
|
113
|
+
"additionalProperties": { "type": "integer", "minimum": 0 }
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
"additionalProperties": false
|
|
117
|
+
},
|
|
118
|
+
"buckets": {
|
|
119
|
+
"type": "object",
|
|
120
|
+
"propertyNames": { "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]*(\\|[A-Za-z0-9][A-Za-z0-9._-]*)*$" },
|
|
121
|
+
"additionalProperties": { "$ref": "#/$defs/cell" }
|
|
122
|
+
}
|
|
123
|
+
}
|
|
54
124
|
}
|
|
@@ -1,27 +1,43 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
-
"$id": "urn:
|
|
4
|
-
"title": "
|
|
3
|
+
"$id": "urn:claimspec:v1:claims",
|
|
4
|
+
"title": "claimspec v1 \u2014 claims file",
|
|
5
5
|
"description": "What must be true, and how to try to falsify it. A claim is a statement the project makes; each fault is one concrete way that statement could be false, expressed as a deterministic source change. This file is code: its `replace` strings execute under the test runner. Treat it with the same review discipline as source.",
|
|
6
6
|
"type": "object",
|
|
7
|
-
"required": [
|
|
7
|
+
"required": [
|
|
8
|
+
"schemaVersion",
|
|
9
|
+
"claims"
|
|
10
|
+
],
|
|
8
11
|
"properties": {
|
|
9
|
-
"$schema": {
|
|
10
|
-
|
|
12
|
+
"$schema": {
|
|
13
|
+
"type": "string"
|
|
14
|
+
},
|
|
15
|
+
"schemaVersion": {
|
|
16
|
+
"const": 1
|
|
17
|
+
},
|
|
11
18
|
"claims": {
|
|
12
19
|
"type": "array",
|
|
13
|
-
"items": {
|
|
20
|
+
"items": {
|
|
21
|
+
"$ref": "#/$defs/claim"
|
|
22
|
+
}
|
|
14
23
|
}
|
|
15
24
|
},
|
|
16
25
|
"additionalProperties": false,
|
|
17
26
|
"$defs": {
|
|
18
27
|
"claim": {
|
|
19
28
|
"type": "object",
|
|
20
|
-
"required": [
|
|
29
|
+
"required": [
|
|
30
|
+
"id",
|
|
31
|
+
"statement",
|
|
32
|
+
"source",
|
|
33
|
+
"severity",
|
|
34
|
+
"producedBy",
|
|
35
|
+
"faults"
|
|
36
|
+
],
|
|
21
37
|
"properties": {
|
|
22
38
|
"id": {
|
|
23
39
|
"description": "Stable identifier. Renaming it changes every fingerprint derived from it.",
|
|
24
|
-
"$ref": "urn:
|
|
40
|
+
"$ref": "urn:claimspec:v1:common#/$defs/identifier"
|
|
25
41
|
},
|
|
26
42
|
"statement": {
|
|
27
43
|
"description": "The claim in plain language, as the project states it.",
|
|
@@ -32,46 +48,82 @@
|
|
|
32
48
|
"source": {
|
|
33
49
|
"description": "Where the claim comes from. Spec- and ADR-sourced claims are human-governed; annotation- and comment-sourced claims may have been written by the same agent that wrote the tests, and should be ranked accordingly.",
|
|
34
50
|
"type": "object",
|
|
35
|
-
"required": [
|
|
51
|
+
"required": [
|
|
52
|
+
"kind"
|
|
53
|
+
],
|
|
36
54
|
"properties": {
|
|
37
|
-
"kind": {
|
|
38
|
-
|
|
55
|
+
"kind": {
|
|
56
|
+
"enum": [
|
|
57
|
+
"spec",
|
|
58
|
+
"adr",
|
|
59
|
+
"annotation",
|
|
60
|
+
"comment",
|
|
61
|
+
"manual"
|
|
62
|
+
]
|
|
63
|
+
},
|
|
64
|
+
"ref": {
|
|
65
|
+
"type": "string",
|
|
66
|
+
"maxLength": 512
|
|
67
|
+
}
|
|
39
68
|
},
|
|
40
69
|
"additionalProperties": false
|
|
41
70
|
},
|
|
42
|
-
"severity": {
|
|
43
|
-
|
|
71
|
+
"severity": {
|
|
72
|
+
"$ref": "urn:claimspec:v1:common#/$defs/severity"
|
|
73
|
+
},
|
|
74
|
+
"producedBy": {
|
|
75
|
+
"$ref": "urn:claimspec:v1:common#/$defs/provenance"
|
|
76
|
+
},
|
|
44
77
|
"defendedBy": {
|
|
45
78
|
"description": "Globs (project-relative) for the test files that supposedly defend this claim. Empty or absent means: discover defenders as the test files that import each fault's target file (relative paths and resolved aliases). If nothing resolves, the verdict is `nocover`.",
|
|
46
79
|
"type": "array",
|
|
47
|
-
"items": {
|
|
80
|
+
"items": {
|
|
81
|
+
"type": "string",
|
|
82
|
+
"minLength": 1
|
|
83
|
+
},
|
|
48
84
|
"uniqueItems": true
|
|
49
85
|
},
|
|
50
86
|
"faults": {
|
|
51
87
|
"type": "array",
|
|
52
88
|
"minItems": 1,
|
|
53
|
-
"items": {
|
|
89
|
+
"items": {
|
|
90
|
+
"$ref": "#/$defs/fault"
|
|
91
|
+
}
|
|
54
92
|
},
|
|
55
93
|
"tags": {
|
|
56
94
|
"type": "array",
|
|
57
|
-
"items": {
|
|
95
|
+
"items": {
|
|
96
|
+
"$ref": "urn:claimspec:v1:common#/$defs/identifier"
|
|
97
|
+
},
|
|
58
98
|
"uniqueItems": true
|
|
59
99
|
}
|
|
60
100
|
},
|
|
61
101
|
"additionalProperties": false
|
|
62
102
|
},
|
|
63
103
|
"fault": {
|
|
64
|
-
"description": "One concrete way the claim could be false.
|
|
104
|
+
"description": "One concrete way the claim could be false, and how a tool would try to make it so. `method` says which; it defaults to `fault-injection`, whose shape is an exact-string substitution and is unchanged. Producer-agnostic: a human, an operator, an agent or a derivation may all emit this shape, and every artifact says which.",
|
|
65
105
|
"type": "object",
|
|
66
|
-
"required": [
|
|
106
|
+
"required": [
|
|
107
|
+
"id",
|
|
108
|
+
"description",
|
|
109
|
+
"producedBy"
|
|
110
|
+
],
|
|
67
111
|
"properties": {
|
|
68
112
|
"id": {
|
|
69
113
|
"description": "Unique within the claim.",
|
|
70
|
-
"$ref": "urn:
|
|
114
|
+
"$ref": "urn:claimspec:v1:common#/$defs/identifier"
|
|
115
|
+
},
|
|
116
|
+
"description": {
|
|
117
|
+
"type": "string",
|
|
118
|
+
"minLength": 1,
|
|
119
|
+
"maxLength": 2000
|
|
120
|
+
},
|
|
121
|
+
"faultClass": {
|
|
122
|
+
"$ref": "urn:claimspec:v1:common#/$defs/faultClass"
|
|
123
|
+
},
|
|
124
|
+
"file": {
|
|
125
|
+
"$ref": "urn:claimspec:v1:common#/$defs/repoPath"
|
|
71
126
|
},
|
|
72
|
-
"description": { "type": "string", "minLength": 1, "maxLength": 2000 },
|
|
73
|
-
"faultClass": { "$ref": "urn:guard-spec:v1:common#/$defs/faultClass" },
|
|
74
|
-
"file": { "$ref": "urn:guard-spec:v1:common#/$defs/repoPath" },
|
|
75
127
|
"find": {
|
|
76
128
|
"description": "Exact text to locate in `file`. Must occur exactly `expectHits` times or the fault is `unverifiable`.",
|
|
77
129
|
"type": "string",
|
|
@@ -90,14 +142,66 @@
|
|
|
90
142
|
"default": 1
|
|
91
143
|
},
|
|
92
144
|
"expectHits": {
|
|
93
|
-
"description": "How many times `find` must occur in `file`. Any other count means the anchor has rotted or is ambiguous
|
|
145
|
+
"description": "How many times `find` must occur in `file`. Any other count means the anchor has rotted or is ambiguous \u2192 `unverifiable`, never a silent skip.",
|
|
94
146
|
"type": "integer",
|
|
95
147
|
"minimum": 1,
|
|
96
148
|
"default": 1
|
|
97
149
|
},
|
|
98
|
-
"producedBy": {
|
|
150
|
+
"producedBy": {
|
|
151
|
+
"$ref": "urn:claimspec:v1:common#/$defs/provenance"
|
|
152
|
+
},
|
|
153
|
+
"method": {
|
|
154
|
+
"$ref": "urn:claimspec:v1:common#/$defs/method"
|
|
155
|
+
},
|
|
156
|
+
"methodDetail": {
|
|
157
|
+
"description": "Data belonging to a method the spec has not specified yet. `assertion` and `scan` are reserved names with no required fields, so a tool adopting one needs somewhere to put the rule it checked and the place it checked \u2014 without which it can declare that a probe exists and nothing about it. Deliberately unconstrained, deliberately namespaced, and **forbidden under `fault-injection`**, whose shape IS specified and must not acquire a junk drawer. What the first real consumer puts here is the evidence for what the specified shape should become; it is a staging area, not a permanent home.",
|
|
158
|
+
"type": "object",
|
|
159
|
+
"minProperties": 1,
|
|
160
|
+
"additionalProperties": true
|
|
161
|
+
}
|
|
99
162
|
},
|
|
100
|
-
"additionalProperties": false
|
|
163
|
+
"additionalProperties": false,
|
|
164
|
+
"allOf": [
|
|
165
|
+
{
|
|
166
|
+
"if": {
|
|
167
|
+
"anyOf": [
|
|
168
|
+
{
|
|
169
|
+
"properties": {
|
|
170
|
+
"method": {
|
|
171
|
+
"const": "fault-injection"
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
"required": [
|
|
175
|
+
"method"
|
|
176
|
+
]
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
"not": {
|
|
180
|
+
"required": [
|
|
181
|
+
"method"
|
|
182
|
+
]
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
]
|
|
186
|
+
},
|
|
187
|
+
"then": {
|
|
188
|
+
"$comment": "The default method. Absent `method` means fault-injection, so every claims file written before the field existed is held to exactly the same requirements as before. The property stubs are what ajv's strict mode needs to accept `required` here; the real definitions stay above.",
|
|
189
|
+
"properties": {
|
|
190
|
+
"faultClass": true,
|
|
191
|
+
"file": true,
|
|
192
|
+
"find": true,
|
|
193
|
+
"replace": true,
|
|
194
|
+
"methodDetail": false
|
|
195
|
+
},
|
|
196
|
+
"required": [
|
|
197
|
+
"faultClass",
|
|
198
|
+
"file",
|
|
199
|
+
"find",
|
|
200
|
+
"replace"
|
|
201
|
+
]
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
]
|
|
101
205
|
}
|
|
102
206
|
}
|
|
103
207
|
}
|