testguard-cli 0.16.0 → 0.17.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 +51 -0
- package/README.md +152 -13
- package/package.json +1 -1
- package/spec/GATE-SEMANTICS.md +295 -0
- package/spec/README.md +27 -2
- package/spec/lib/authoring-input.mjs +40 -0
- package/spec/lib/origins.mjs +56 -0
- package/spec/lib/validate.mjs +120 -1
- package/spec/schemas/annotations.schema.json +65 -0
- package/spec/schemas/authoring-input.schema.json +67 -0
- package/spec/schemas/brief.schema.json +2 -0
- package/spec/schemas/claims.schema.json +2 -8
- package/spec/schemas/common.schema.json +73 -0
- package/spec/schemas/evidence.schema.json +37 -8
- package/spec/schemas/status.schema.json +6 -2
- package/src/brief/brief.mjs +11 -4
- package/src/claims/annotation-placement.mjs +357 -0
- package/src/claims/annotations.mjs +61 -2
- package/src/cli.mjs +40 -5
- package/src/commands/admit.mjs +13 -0
- package/src/commands/annotations.mjs +67 -0
- package/src/commands/claims.mjs +8 -4
- package/src/commands/probe.mjs +50 -7
- package/src/commands/replay.mjs +7 -0
- package/src/commands/scaffold.mjs +49 -2
- package/src/commands/sweep.mjs +7 -0
- package/src/evidence/writer.mjs +12 -4
- package/src/git.mjs +4 -4
- package/src/init/templates/SKILL.md +35 -0
- package/src/probe/attribution.mjs +10 -0
- package/src/probe/contention.mjs +20 -2
- package/src/probe/cost.mjs +6 -1
- package/src/probe/inject.mjs +2 -6
- package/src/probe/origin-policy.mjs +69 -0
- package/src/probe/policy-freshness.mjs +74 -0
- package/src/probe/probe.mjs +48 -23
- package/src/probe/runners/discovery.mjs +2 -0
- package/src/probe/runners/jest.mjs +2 -2
- package/src/probe/runners/lifecycle.mjs +58 -0
- package/src/probe/runners/playwright.mjs +6 -2
- package/src/probe/runners/python.mjs +10 -4
- package/src/probe/runners/shared.mjs +12 -6
- package/src/probe/runners/vitest.mjs +3 -3
- package/src/probe/universe.mjs +47 -0
- package/src/probe/worktree.mjs +3 -1
- package/src/render.mjs +17 -1
- package/src/replay/replay.mjs +8 -4
- package/src/scaffold/admission.mjs +117 -0
- package/src/scaffold/append.mjs +41 -0
- package/src/scaffold/apply-append.mjs +71 -0
- package/src/scaffold/fix-input.mjs +119 -0
- package/src/scaffold/fix-paths.mjs +90 -0
- package/src/scaffold/intent.mjs +2 -0
- package/src/scaffold/open-append.mjs +44 -0
- package/src/scaffold/plan-append.mjs +37 -0
- package/src/scaffold/plan-intent-input.mjs +38 -0
- package/src/scaffold/prepare-append.mjs +46 -0
- package/src/scaffold/request.mjs +30 -0
- package/src/scaffold/scaffold.mjs +23 -10
- package/src/status/status.mjs +34 -9
- package/src/sweep/pool.mjs +33 -0
- package/src/sweep/sweep.mjs +9 -4
package/spec/README.md
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
# claimspec — the contract spine
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
The candidate optional `originPolicy` result is a closed declared-origin policy
|
|
4
|
+
audit on evidence/status/brief. It is not independent-origin authentication or
|
|
5
|
+
current freshness authority. `probe --require-origin` explicitly evaluates it
|
|
6
|
+
against current native/input bindings. Offline status recomputes declarations
|
|
7
|
+
without certifying freshness; brief preserves labeled recorded-run policy
|
|
8
|
+
before filtering/caps. See GATE-SEMANTICS.md.
|
|
9
|
+
|
|
10
|
+
Shared formats for declaring what must be true about a codebase, recording the
|
|
11
|
+
evidence that tried to falsify it, gating only what is new, and explicit authoring.
|
|
5
12
|
|
|
6
13
|
They are shared as *formats*, not as code. The first tools to adopt them are
|
|
7
14
|
[`docguard-cli`](https://www.npmjs.com/package/docguard-cli) (Node ESM) and
|
|
@@ -33,6 +40,8 @@ that carry claims, baselines, scoping and briefs do not care.
|
|
|
33
40
|
| `status` | [`schemas/status.schema.json`](schemas/status.schema.json) | Where the project is and what happens next — the single machine-readable truth every human rendering derives from. Surfaces faults whose content changed since they were probed, and changed files that carry no claim. |
|
|
34
41
|
| `replay` | [`schemas/replay.schema.json`](schemas/replay.schema.json) | Would this suite have caught the bugs that already escaped? Replays real fix commits: revert the source, remove the test the fix shipped, run what remains. A replayed bug is ground truth — a human already confirmed it was a defect — which is what a fault model is calibrated against. |
|
|
35
42
|
| `gate` | [`schemas/gate.schema.json`](schemas/gate.schema.json) | Claim coverage of one change: which changed files carry a claim, which are excused (and by which ignore entry), which are unclaimed. The delta gate for code that has no claim yet. |
|
|
43
|
+
| `annotations` | [`schemas/annotations.schema.json`](schemas/annotations.schema.json) | Explicit file-header authoring preview/apply, refusals and recoverable partial failures. Never verification, a probe verdict, or authentication of intent; internal source contents are excluded. |
|
|
44
|
+
| `authoring-input` | [`schemas/authoring-input.schema.json`](schemas/authoring-input.schema.json) | Candidate metadata-only document/fix input handoff. No source text, origin assignment, claims or verification; historical partitions retain excluded/deleted/unsupported counts. CLI exposure remains pending. |
|
|
36
45
|
|
|
37
46
|
Shared definitions (verdicts, fault classes, provenance, annotations) live in
|
|
38
47
|
[`schemas/common.schema.json`](schemas/common.schema.json). Gate behaviour —
|
|
@@ -48,6 +57,22 @@ rule is in `GATE-SEMANTICS.md`.
|
|
|
48
57
|
|
|
49
58
|
Schemas are JSON Schema 2020-12 and identified as `urn:claimspec:v1:<kind>`.
|
|
50
59
|
|
|
60
|
+
Claim sources share `common#/$defs/claimSourceKind`: `spec`, `adr`, `annotation`,
|
|
61
|
+
`comment`, `manual`, `doc`, `bug`, `incident`, `review`, `inferred`. They declare
|
|
62
|
+
intent origin, not authenticated independence or an evidence-strength ranking.
|
|
63
|
+
References remain opaque metadata; producer provenance is separate. Existing
|
|
64
|
+
documents remain readable, but old closed-schema readers reject the new kinds.
|
|
65
|
+
Optional `origins` contracts on status, evidence and brief separate distinct
|
|
66
|
+
claim counts from record counts. Evidence validation recomputes every kind and
|
|
67
|
+
mixed declaration from all records; status/brief can validate only arithmetic
|
|
68
|
+
and document totals because source projections are omitted. References are
|
|
69
|
+
never echoed in these summaries. Legacy documents without them remain valid;
|
|
70
|
+
older closed readers reject the additive field. Status emits current declared
|
|
71
|
+
origins; probe evidence and evidence-backed briefs emit recorded origins before
|
|
72
|
+
filtering/caps. Human renderings label these declarations as unauthenticated.
|
|
73
|
+
Opt-in origin policy is available on complete confirmed candidate probes;
|
|
74
|
+
offline projections remain audits, never current native freshness. See `GATE-SEMANTICS.md`.
|
|
75
|
+
|
|
51
76
|
## Conformance
|
|
52
77
|
|
|
53
78
|
`lib/validate.mjs` validates a document of any kind: JSON Schema first, then
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { extname } from 'node:path';
|
|
2
|
+
|
|
3
|
+
const PRIVATE = new Set(['.local', '.wolf', '.git', '.testguard', 'graphify-out']);
|
|
4
|
+
const SOURCE = new Set(['.js', '.mjs', '.cjs', '.ts', '.mts', '.cts', '.jsx', '.tsx', '.py']);
|
|
5
|
+
|
|
6
|
+
/** Shape validation runs first. These checks cannot authenticate the actual input. */
|
|
7
|
+
export function authoringInputErrors(doc) {
|
|
8
|
+
const errors = [];
|
|
9
|
+
const fail = (path, message) => errors.push({ path, message });
|
|
10
|
+
const checkPath = (file, path) => {
|
|
11
|
+
if (Buffer.byteLength(file) > 4096 || file.includes('\\') || /[\x00-\x1f\x7f-\x9f]/.test(file) || /^[A-Za-z]:/.test(file) || file.split('/').some(part => !part || part === '.' || part === '..' || PRIVATE.has(part))) fail(path, 'input path must be bounded, canonical and outside private/tooling namespaces');
|
|
12
|
+
};
|
|
13
|
+
if (Buffer.byteLength(JSON.stringify(doc)) > 1024 * 1024) fail('/', 'authoring input report exceeds byte limit');
|
|
14
|
+
const input = doc.input;
|
|
15
|
+
if (input.kind === 'document') {
|
|
16
|
+
checkPath(input.file, '/input/file');
|
|
17
|
+
if (!['.md', '.txt', '.rst', '.adoc'].includes(extname(input.file))) fail('/input/file', 'document format is unsupported');
|
|
18
|
+
return errors;
|
|
19
|
+
}
|
|
20
|
+
if (input.commit.length !== input.parent.length || input.commit === input.parent) fail('/input/parent', 'fix requires a distinct same-width parent identity');
|
|
21
|
+
if (Buffer.byteLength(input.subject) > 1024 || /[\x00-\x1f\x7f-\x9f]/.test(input.subject)) fail('/input/subject', 'commit subject must be bounded untrusted single-line text');
|
|
22
|
+
const { total, supported, deleted, unsupported, excluded } = input.counts;
|
|
23
|
+
if (total !== supported + deleted + unsupported + excluded || input.paths.length !== supported + deleted + unsupported) fail('/input/counts', 'complete path partition must retain excluded/deleted/unsupported denominators');
|
|
24
|
+
const seen = new Set(), actual = { supported: 0, deleted: 0, unsupported: 0 };
|
|
25
|
+
input.paths.forEach((row, i) => {
|
|
26
|
+
const path = `/input/paths/${i}`;
|
|
27
|
+
checkPath(row.file, `${path}/file`);
|
|
28
|
+
if (seen.has(row.file)) fail(`${path}/file`, 'visible path is duplicated');
|
|
29
|
+
seen.add(row.file);
|
|
30
|
+
if (row.oldId.length !== input.commit.length || row.newId.length !== input.commit.length) fail(path, 'path object identities must match commit width');
|
|
31
|
+
const oldZero = /^0+$/.test(row.oldId), newZero = /^0+$/.test(row.newId);
|
|
32
|
+
if ((row.oldMode === '000000') !== oldZero || (row.newMode === '000000') !== newZero ||
|
|
33
|
+
(row.status === 'A' ? !oldZero || newZero : row.status === 'D' ? oldZero || !newZero : oldZero || newZero)) fail(path, 'mode, status and zero identity must agree');
|
|
34
|
+
const disposition = row.status === 'D' ? 'deleted' : !['100644', '100755'].includes(row.newMode) || !SOURCE.has(extname(row.file)) ? 'unsupported' : 'supported';
|
|
35
|
+
if (row.disposition !== disposition) fail(`${path}/disposition`, 'disposition must derive from actual status, mode and supported extension');
|
|
36
|
+
actual[row.disposition]++;
|
|
37
|
+
});
|
|
38
|
+
if (actual.supported !== supported || actual.deleted !== deleted || actual.unsupported !== unsupported) fail('/input/counts', 'visible disposition counts must reproduce every path');
|
|
39
|
+
return errors;
|
|
40
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
3
|
+
|
|
4
|
+
// One vocabulary for admission and reporting; never an evidence-strength order.
|
|
5
|
+
const common = JSON.parse(readFileSync(new URL('../schemas/common.schema.json', import.meta.url), 'utf8'));
|
|
6
|
+
export const CLAIM_SOURCE_KINDS = Object.freeze([...common.$defs.claimSourceKind.enum]);
|
|
7
|
+
const kindSet = new Set(CLAIM_SOURCE_KINDS);
|
|
8
|
+
const counts = () => Object.fromEntries(CLAIM_SOURCE_KINDS.map((kind) => [kind, 0]));
|
|
9
|
+
|
|
10
|
+
// Consumers validate their full document first. These narrow guards prevent
|
|
11
|
+
// malformed direct calls from silently manufacturing a meaningful summary.
|
|
12
|
+
function declaration(claim) {
|
|
13
|
+
const source = claim?.source;
|
|
14
|
+
if (typeof claim?.id !== 'string' || !claim.id || !kindSet.has(source?.kind)
|
|
15
|
+
|| Object.keys(source).some((key) => key !== 'kind' && key !== 'ref')
|
|
16
|
+
|| (Object.hasOwn(source, 'ref') && (typeof source.ref !== 'string' || source.ref.length > 512))) {
|
|
17
|
+
throw new TypeError('origin summary needs valid claim identities and declared sources');
|
|
18
|
+
}
|
|
19
|
+
return { kind: source.kind, ...(Object.hasOwn(source, 'ref') ? { ref: source.ref } : {}) };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Current declarations only: fault multiplicity cannot inflate claim counts. */
|
|
23
|
+
export function declaredOriginSummary(claims) {
|
|
24
|
+
const byKind = counts();
|
|
25
|
+
const seen = new Set();
|
|
26
|
+
for (const claim of claims) {
|
|
27
|
+
const source = declaration(claim);
|
|
28
|
+
if (seen.has(claim.id)) throw new TypeError('duplicate declared claim identity');
|
|
29
|
+
seen.add(claim.id);
|
|
30
|
+
byKind[source.kind]++;
|
|
31
|
+
}
|
|
32
|
+
return { basis: 'declared', claims: { total: seen.size, byKind, mixed: 0 } };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Actual recorded projections only; no current-claims relabeling or filtering. */
|
|
36
|
+
export function recordedOriginSummary(records) {
|
|
37
|
+
const recordKinds = counts();
|
|
38
|
+
const claims = new Map();
|
|
39
|
+
for (const record of records) {
|
|
40
|
+
const source = declaration(record.claim);
|
|
41
|
+
recordKinds[source.kind]++;
|
|
42
|
+
const previous = claims.get(record.claim.id);
|
|
43
|
+
if (!previous) claims.set(record.claim.id, { source, mixed: false });
|
|
44
|
+
else if (!isDeepStrictEqual(previous.source, source)) previous.mixed = true;
|
|
45
|
+
}
|
|
46
|
+
const byKind = counts();
|
|
47
|
+
let mixed = 0;
|
|
48
|
+
for (const claim of claims.values()) {
|
|
49
|
+
if (claim.mixed) mixed++;
|
|
50
|
+
else byKind[claim.source.kind]++;
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
basis: 'recorded', claims: { total: claims.size, byKind, mixed },
|
|
54
|
+
records: { total: records.length, byKind: recordKinds },
|
|
55
|
+
};
|
|
56
|
+
}
|
package/spec/lib/validate.mjs
CHANGED
|
@@ -4,10 +4,13 @@ import { fileURLToPath } from 'node:url';
|
|
|
4
4
|
import { dirname, join } from 'node:path';
|
|
5
5
|
import { fingerprint } from './fingerprint.mjs';
|
|
6
6
|
import { reproduces, decimals, MAX_PLACES } from './wilson.mjs';
|
|
7
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
8
|
+
import { recordedOriginSummary } from './origins.mjs';
|
|
9
|
+
import { authoringInputErrors } from './authoring-input.mjs';
|
|
7
10
|
|
|
8
11
|
const schemaDir = join(dirname(fileURLToPath(import.meta.url)), '..', 'schemas');
|
|
9
12
|
|
|
10
|
-
export const KINDS = Object.freeze(['claims', 'evidence', 'baseline', 'ignore', 'calibration', 'brief', 'status', 'gate', 'replay', 'sweep', 'concerns']);
|
|
13
|
+
export const KINDS = Object.freeze(['claims', 'evidence', 'baseline', 'ignore', 'calibration', 'brief', 'status', 'gate', 'replay', 'sweep', 'concerns', 'annotations', 'authoring-input']);
|
|
11
14
|
/**
|
|
12
15
|
* How a document says it tried to falsify its claims. Absent means
|
|
13
16
|
* `fault-injection`, so every document written before the field existed is
|
|
@@ -46,8 +49,113 @@ const nestedErrors = (nested = [], prefix = '/nested', occupied = []) => {
|
|
|
46
49
|
return errors;
|
|
47
50
|
};
|
|
48
51
|
|
|
52
|
+
// A projected summary is not source authority. Only evidence retains the
|
|
53
|
+
// complete claim projections needed to reproduce each kind and mixed count.
|
|
54
|
+
function originPolicyErrors(kind, doc) {
|
|
55
|
+
const errors = [];
|
|
56
|
+
const policies = [[doc.originPolicy, '/originPolicy'], [kind === 'status' ? doc.run?.originPolicy : undefined, '/run/originPolicy']];
|
|
57
|
+
const identities = (rows) => rows.map((r) => JSON.stringify([r.claimId, r.faultId])).sort();
|
|
58
|
+
for (const [p, path] of policies) {
|
|
59
|
+
if (!p) continue;
|
|
60
|
+
const fail = (message) => errors.push({ path, message });
|
|
61
|
+
for (const field of ['eligibleKinds', 'ineligibleClaims', 'unavailableReasons']) {
|
|
62
|
+
if (!isDeepStrictEqual(p[field], [...p[field]].sort())) fail(`${field} must be normalized in sorted order`);
|
|
63
|
+
}
|
|
64
|
+
const failures = p.ineligibleClaims.length + p.nonKilledFaults.length;
|
|
65
|
+
const expectedState = p.unavailableReasons.length ? 'unavailable' : failures ? 'failed' : 'passed';
|
|
66
|
+
if (p.state !== expectedState) fail('policy state must match refusal reasons and unsuppressed failures');
|
|
67
|
+
if (p.state !== 'unavailable' && (!p.claims || !p.faults)) fail('an evaluated policy requires a nonempty claim and fault universe');
|
|
68
|
+
if (p.ineligibleClaims.length > p.claims) fail('ineligible claims exceed the current claim denominator');
|
|
69
|
+
if (p.state !== 'unavailable' && p.nonKilledFaults.length > p.faults) fail('non-killed faults exceed the complete fault denominator');
|
|
70
|
+
if (kind === 'evidence') {
|
|
71
|
+
const actual = [...new Set(doc.records.filter((r) => r.verdict !== 'killed').map((r) => JSON.stringify([r.claim.id, r.subject.id])))].sort();
|
|
72
|
+
if (!isDeepStrictEqual(identities(p.nonKilledFaults), actual)) fail('policy must retain every actual non-killed fault identity');
|
|
73
|
+
if (p.state !== 'unavailable') {
|
|
74
|
+
const claims = new Set(doc.records.map((r) => r.claim.id));
|
|
75
|
+
const faults = new Set(doc.records.map((r) => JSON.stringify([r.claim.id, r.subject.id])));
|
|
76
|
+
if (p.claims !== claims.size || p.faults !== faults.size || faults.size !== doc.records.length) fail('evaluated policy denominators require complete unique recorded identities');
|
|
77
|
+
const ineligible = [...new Set(doc.records.filter((r) => !p.eligibleKinds.includes(r.claim.source.kind)).map((r) => r.claim.id))].sort();
|
|
78
|
+
if (!isDeepStrictEqual(p.ineligibleClaims, ineligible)) fail('policy eligibility must match recorded declarations');
|
|
79
|
+
if (!isInjection(doc.run) || doc.run.provisional || doc.run.confirmRuns < 3) fail('evaluated policy requires confirmed fault-injection evidence');
|
|
80
|
+
if (recordedOriginSummary(doc.records).claims.mixed) fail('evaluated policy cannot use mixed recorded origins');
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (kind === 'status' && (p.claims !== doc.counts.claims || p.faults !== doc.counts.faults)) fail('policy denominators must match current status claims and faults');
|
|
84
|
+
if (kind === 'brief' && p.state !== 'unavailable' && (p.claims !== doc.summary.claims || p.faults !== Object.values(doc.summary.byVerdict).reduce((a, b) => a + b, 0))) fail('recorded policy denominators must match the uncapped brief summary');
|
|
85
|
+
}
|
|
86
|
+
if (kind === 'status' && doc.run) {
|
|
87
|
+
const p = doc.run.originPolicy;
|
|
88
|
+
const expected = p?.state === 'unavailable' ? 2 : p?.state === 'failed' || doc.run.newSinceBaseline > 0 ? 1 : 0;
|
|
89
|
+
if (p && doc.run.exitCode !== expected) errors.push({ path: '/run/exitCode', message: 'exit code must preserve policy refusal/failure before baseline findings' });
|
|
90
|
+
if (!p && doc.run.exitCode === 2) errors.push({ path: '/run/exitCode', message: 'completed probe exit 2 requires an unavailable origin policy' });
|
|
91
|
+
if (p && (doc.run.scope || doc.run.provisional)) errors.push({ path: '/run/originPolicy', message: 'origin policy cannot certify a partial or provisional invocation' });
|
|
92
|
+
}
|
|
93
|
+
return errors;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function originErrors(kind, doc) {
|
|
97
|
+
if (!doc.origins) return [];
|
|
98
|
+
const errors = [], o = doc.origins;
|
|
99
|
+
const fail = (message) => errors.push({ path: '/origins', message });
|
|
100
|
+
const sum = (counts) => Object.values(counts).reduce((a, b) => a + b, 0);
|
|
101
|
+
if (sum(o.claims.byKind) + o.claims.mixed !== o.claims.total) fail('claim origin buckets plus mixed must equal the distinct claim total');
|
|
102
|
+
if (o.records && sum(o.records.byKind) !== o.records.total) fail('record origin buckets must equal the record total');
|
|
103
|
+
if (kind === 'status') {
|
|
104
|
+
if (o.basis !== 'declared' || o.records || o.claims.mixed !== 0) fail('status origins must describe unmixed current declarations without recorded counts');
|
|
105
|
+
if (o.claims.total !== doc.counts.claims) fail('declared origin total must equal current claim count');
|
|
106
|
+
} else {
|
|
107
|
+
if (o.basis !== 'recorded' || !o.records) fail('evidence and brief origins require recorded basis and record counts');
|
|
108
|
+
if (kind === 'evidence' && !isDeepStrictEqual(o, recordedOriginSummary(doc.records))) fail('recorded origins must reproduce every actual record and distinct claim declaration');
|
|
109
|
+
if (kind === 'brief' && (o.claims.total !== doc.summary.claims || o.records?.total !== sum(doc.summary.byVerdict))) fail('brief origin totals must match the complete summary, not capped items');
|
|
110
|
+
}
|
|
111
|
+
return errors;
|
|
112
|
+
}
|
|
113
|
+
|
|
49
114
|
// Rules JSON Schema cannot express. Each returns an array of {path, message}.
|
|
50
115
|
const semantic = {
|
|
116
|
+
'authoring-input': authoringInputErrors,
|
|
117
|
+
annotations(doc) {
|
|
118
|
+
const errors = [];
|
|
119
|
+
const fail = (path, message) => errors.push({ path, message });
|
|
120
|
+
const selected = new Set(doc.selected), seenFiles = new Set(), seenIds = new Set();
|
|
121
|
+
for (const [bucket, entries] of [['targets', doc.targets], ['refused', doc.refused]]) {
|
|
122
|
+
for (const [i, entry] of entries.entries()) {
|
|
123
|
+
if (seenFiles.has(entry.file)) fail(`/${bucket}/${i}/file`, 'target must occur in exactly one admission bucket');
|
|
124
|
+
if (bucket === 'targets' && (entry.file.includes('\\') || /^[A-Za-z]:/.test(entry.file) || entry.file.split('/').some((part) => !part || part === '.' || part === '..'))) fail(`/${bucket}/${i}/file`, 'admitted target must be a canonical relative path');
|
|
125
|
+
seenFiles.add(entry.file);
|
|
126
|
+
for (const id of entry.claimIds) {
|
|
127
|
+
if (!selected.has(id)) fail(`/${bucket}/${i}/claimIds`, 'target claim must be selected');
|
|
128
|
+
seenIds.add(id);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (doc.selected.some((id) => !seenIds.has(id))) fail('/selected', 'every selected claim must have a target or explicit refusal');
|
|
133
|
+
if (doc.mode === 'preview') {
|
|
134
|
+
if (doc.outcome) fail('/outcome', 'preview never has a write outcome');
|
|
135
|
+
if (doc.state !== (doc.refused.length ? 'refused' : 'preview')) fail('/state', 'preview state must reflect all admission refusals');
|
|
136
|
+
return errors;
|
|
137
|
+
}
|
|
138
|
+
if (!doc.outcome) { fail('/outcome', 'apply requires a write outcome'); return errors; }
|
|
139
|
+
if (doc.refused.length && doc.state !== 'refused') fail('/state', 'any admission refusal blocks all writes');
|
|
140
|
+
const o = doc.outcome;
|
|
141
|
+
const adds = doc.targets.filter((t) => t.action === 'add').map((t) => t.file);
|
|
142
|
+
const unchanged = doc.targets.filter((t) => t.action === 'unchanged').map((t) => t.file);
|
|
143
|
+
const same = (a, b) => a.length === b.length && a.every((x) => b.includes(x));
|
|
144
|
+
if (o.touched.some((file) => !adds.includes(file))) fail('/outcome/touched', 'only admitted add targets can be touched');
|
|
145
|
+
if (o.changed.some((file) => !o.touched.includes(file))) fail('/outcome/changed', 'verified changes must be touched');
|
|
146
|
+
if (!same(o.unchanged, unchanged)) fail('/outcome/unchanged', 'unchanged files must match unchanged targets');
|
|
147
|
+
for (const [i, failure] of o.failed.entries()) {
|
|
148
|
+
if (failure.file !== null && !doc.targets.some((t) => t.file === failure.file)) fail(`/outcome/failed/${i}/file`, 'failed file must be an admitted target or null');
|
|
149
|
+
}
|
|
150
|
+
if (doc.state === 'applied') {
|
|
151
|
+
if (doc.refused.length || o.failed.length || !same(o.changed, adds) || !same(o.touched, adds) || !o.recoveryDir || o.lockRelease?.released !== true) fail('/state', 'applied requires every admitted target verified, no refusals/failures, recovery and released owned lock');
|
|
152
|
+
} else if (doc.state === 'refused') {
|
|
153
|
+
if (o.touched.length || o.changed.length || (!doc.refused.length && !o.failed.length)) fail('/state', 'refused requires no touched files and an explicit refusal/failure');
|
|
154
|
+
} else if (doc.state === 'partial') {
|
|
155
|
+
if (!o.touched.length || !o.failed.length || !o.recoveryDir) fail('/state', 'partial requires touched files, explicit failure and retained recovery');
|
|
156
|
+
} else fail('/state', 'apply cannot be a preview');
|
|
157
|
+
return errors;
|
|
158
|
+
},
|
|
51
159
|
claims(doc) {
|
|
52
160
|
const errors = [];
|
|
53
161
|
const seenClaim = new Set();
|
|
@@ -75,6 +183,11 @@ const semantic = {
|
|
|
75
183
|
|
|
76
184
|
evidence(doc) {
|
|
77
185
|
const errors = [];
|
|
186
|
+
if (doc.run.measurements) {
|
|
187
|
+
const m = doc.run.measurements;
|
|
188
|
+
if (Math.abs(m.elapsedMs - m.runnerMs - m.overheadMs) > 0.001) errors.push({ path: '/run/measurements', message: 'elapsedMs must equal runnerMs plus overheadMs' });
|
|
189
|
+
}
|
|
190
|
+
if (doc.run.serial && doc.run.workers !== undefined && doc.run.workers !== 1) errors.push({ path: '/run/workers', message: 'serial execution requires one worker' });
|
|
78
191
|
const injection = isInjection(doc.run);
|
|
79
192
|
const n = doc.run.confirmRuns;
|
|
80
193
|
// N-run agreement is what fault injection means by "confirmed". A method
|
|
@@ -270,6 +383,10 @@ const semantic = {
|
|
|
270
383
|
|
|
271
384
|
status(doc) {
|
|
272
385
|
const errors = [];
|
|
386
|
+
if (doc.cost?.measurements) {
|
|
387
|
+
const m = doc.cost.measurements;
|
|
388
|
+
if (Math.abs(m.elapsedMs - m.runnerMs - m.overheadMs) > 0.001) errors.push({ path: '/cost/measurements', message: 'elapsedMs must equal runnerMs plus overheadMs' });
|
|
389
|
+
}
|
|
273
390
|
if (doc.changes?.nested) {
|
|
274
391
|
errors.push(...nestedErrors(doc.changes.nested, '/changes/nested', doc.changes.uncovered.map((file) => file.file)));
|
|
275
392
|
const nestedCount = doc.changes.nested.reduce((count, entry) => count + entry.files.length, 0);
|
|
@@ -472,5 +589,7 @@ export function validate(kind, doc) {
|
|
|
472
589
|
};
|
|
473
590
|
}
|
|
474
591
|
const errors = semantic[kind] ? semantic[kind](doc) : [];
|
|
592
|
+
errors.push(...originErrors(kind, doc));
|
|
593
|
+
errors.push(...originPolicyErrors(kind, doc));
|
|
475
594
|
return { ok: errors.length === 0, errors };
|
|
476
595
|
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$id": "urn:claimspec:v1:annotations",
|
|
3
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
4
|
+
"title": "claimspec v1 — annotation authoring",
|
|
5
|
+
"description": "File-level authoring only. Not verification or intent authentication. No source contents or reusable approval token.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": ["schemaVersion", "tool", "mode", "state", "selected", "targets", "refused"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"schemaVersion": { "const": 1 },
|
|
11
|
+
"tool": { "$ref": "urn:claimspec:v1:common#/$defs/tool" },
|
|
12
|
+
"mode": { "enum": ["preview", "apply"] },
|
|
13
|
+
"state": { "enum": ["preview", "applied", "refused", "partial"] },
|
|
14
|
+
"selected": { "$ref": "#/$defs/ids" },
|
|
15
|
+
"targets": {
|
|
16
|
+
"type": "array",
|
|
17
|
+
"items": {
|
|
18
|
+
"type": "object", "additionalProperties": false, "required": ["file", "claimIds", "action"],
|
|
19
|
+
"properties": {
|
|
20
|
+
"file": { "$ref": "urn:claimspec:v1:common#/$defs/repoPath" },
|
|
21
|
+
"claimIds": { "type": "array", "$ref": "#/$defs/ids", "minItems": 1 },
|
|
22
|
+
"action": { "enum": ["add", "unchanged"] }
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"refused": {
|
|
27
|
+
"type": "array",
|
|
28
|
+
"items": {
|
|
29
|
+
"type": "object", "additionalProperties": false, "required": ["file", "claimIds", "reason"],
|
|
30
|
+
"properties": {
|
|
31
|
+
"file": { "type": "string", "minLength": 1 },
|
|
32
|
+
"claimIds": { "type": "array", "$ref": "#/$defs/ids", "minItems": 1 },
|
|
33
|
+
"reason": { "enum": ["unsupported-claim-id", "unsupported-method", "unsafe-path", "excluded-target", "unsupported-source", "symlink-target", "not-regular-file", "nested-project", "source-too-large", "target-missing", "target-unreadable", "target-changed", "invalid-encoding", "mixed-newlines", "plan-source-budget", "proposed-source-too-large", "anchor-missing", "anchor-ambiguous"] }
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"outcome": {
|
|
38
|
+
"type": "object", "additionalProperties": false, "required": ["changed", "touched", "unchanged", "failed"],
|
|
39
|
+
"properties": {
|
|
40
|
+
"changed": { "$ref": "#/$defs/files" },
|
|
41
|
+
"touched": { "$ref": "#/$defs/files" },
|
|
42
|
+
"unchanged": { "$ref": "#/$defs/files" },
|
|
43
|
+
"failed": {
|
|
44
|
+
"type": "array", "items": {
|
|
45
|
+
"type": "object", "additionalProperties": false, "required": ["file", "stage", "reason"],
|
|
46
|
+
"properties": {
|
|
47
|
+
"file": { "type": ["string", "null"] },
|
|
48
|
+
"stage": { "enum": ["preparation", "all-target-preflight", "started-journal", "target-preflight", "source-write", "write-verification", "completion-journal", "lock-release", "result-journal"] },
|
|
49
|
+
"reason": { "type": "string", "minLength": 1 }
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
"recoveryDir": { "type": "string", "minLength": 1 },
|
|
54
|
+
"lockRelease": {
|
|
55
|
+
"type": "object", "additionalProperties": false, "required": ["released"],
|
|
56
|
+
"properties": { "released": { "type": "boolean" }, "reason": { "type": "string", "minLength": 1 } }
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"$defs": {
|
|
62
|
+
"ids": { "type": "array", "uniqueItems": true, "items": { "$ref": "urn:claimspec:v1:common#/$defs/identifier" } },
|
|
63
|
+
"files": { "type": "array", "uniqueItems": true, "items": { "$ref": "urn:claimspec:v1:common#/$defs/repoPath" } }
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$id": "urn:claimspec:v1:authoring-input",
|
|
3
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
4
|
+
"title": "claimspec v1 — read-only authoring input",
|
|
5
|
+
"description": "Metadata-only input handoff. Not claims, verification, freshness authority or authenticated intent. No source text or absolute roots.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": ["schemaVersion", "tool", "purpose", "verification", "next", "input"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"schemaVersion": { "const": 1 },
|
|
11
|
+
"tool": { "$ref": "urn:claimspec:v1:common#/$defs/tool" },
|
|
12
|
+
"purpose": { "const": "authoring-input" },
|
|
13
|
+
"verification": { "const": "not-performed" },
|
|
14
|
+
"next": { "const": "supply-independent-intent" },
|
|
15
|
+
"input": { "oneOf": [{ "$ref": "#/$defs/document" }, { "$ref": "#/$defs/fix" }] }
|
|
16
|
+
},
|
|
17
|
+
"$defs": {
|
|
18
|
+
"objectId": { "type": "string", "pattern": "^(?:[a-f0-9]{40}|[a-f0-9]{64})$" },
|
|
19
|
+
"document": {
|
|
20
|
+
"type": "object", "additionalProperties": false,
|
|
21
|
+
"required": ["kind", "file", "hash", "bytes"],
|
|
22
|
+
"properties": {
|
|
23
|
+
"kind": { "const": "document" },
|
|
24
|
+
"file": { "$ref": "urn:claimspec:v1:common#/$defs/repoPath" },
|
|
25
|
+
"hash": { "$ref": "urn:claimspec:v1:common#/$defs/sha256" },
|
|
26
|
+
"bytes": { "type": "integer", "minimum": 0, "maximum": 2097152 }
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"fix": {
|
|
30
|
+
"type": "object", "additionalProperties": false,
|
|
31
|
+
"required": ["kind", "commit", "parent", "subject", "scope", "paths", "counts"],
|
|
32
|
+
"properties": {
|
|
33
|
+
"kind": { "const": "fix" },
|
|
34
|
+
"commit": { "$ref": "#/$defs/objectId" },
|
|
35
|
+
"parent": { "$ref": "#/$defs/objectId" },
|
|
36
|
+
"subject": { "type": "string", "maxLength": 1024 },
|
|
37
|
+
"scope": { "enum": ["project-root", "selected-nested-project"] },
|
|
38
|
+
"paths": { "type": "array", "maxItems": 256, "items": { "$ref": "#/$defs/path" } },
|
|
39
|
+
"counts": {
|
|
40
|
+
"type": "object", "additionalProperties": false,
|
|
41
|
+
"required": ["total", "supported", "deleted", "unsupported", "excluded"],
|
|
42
|
+
"properties": {
|
|
43
|
+
"total": { "$ref": "#/$defs/count" },
|
|
44
|
+
"supported": { "$ref": "#/$defs/count" },
|
|
45
|
+
"deleted": { "$ref": "#/$defs/count" },
|
|
46
|
+
"unsupported": { "$ref": "#/$defs/count" },
|
|
47
|
+
"excluded": { "$ref": "#/$defs/count" }
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"count": { "type": "integer", "minimum": 0, "maximum": 256 },
|
|
53
|
+
"path": {
|
|
54
|
+
"type": "object", "additionalProperties": false,
|
|
55
|
+
"required": ["file", "status", "oldMode", "newMode", "oldId", "newId", "disposition"],
|
|
56
|
+
"properties": {
|
|
57
|
+
"file": { "$ref": "urn:claimspec:v1:common#/$defs/repoPath" },
|
|
58
|
+
"status": { "enum": ["A", "M", "D", "T"] },
|
|
59
|
+
"oldMode": { "type": "string", "pattern": "^[0-7]{6}$" },
|
|
60
|
+
"newMode": { "type": "string", "pattern": "^[0-7]{6}$" },
|
|
61
|
+
"oldId": { "$ref": "#/$defs/objectId" },
|
|
62
|
+
"newId": { "$ref": "#/$defs/objectId" },
|
|
63
|
+
"disposition": { "enum": ["supported", "deleted", "unsupported"] }
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
"type": "object",
|
|
7
7
|
"required": ["schemaVersion", "tool", "generatedAt", "heading", "summary", "items", "text"],
|
|
8
8
|
"properties": {
|
|
9
|
+
"originPolicy": { "$ref": "urn:claimspec:v1:common#/$defs/originPolicy" },
|
|
10
|
+
"origins": { "$ref": "urn:claimspec:v1:common#/$defs/originSummary" },
|
|
9
11
|
"$schema": { "type": "string" },
|
|
10
12
|
"schemaVersion": { "const": 1 },
|
|
11
13
|
"tool": { "$ref": "urn:claimspec:v1:common#/$defs/tool" },
|
|
@@ -46,20 +46,14 @@
|
|
|
46
46
|
"maxLength": 2000
|
|
47
47
|
},
|
|
48
48
|
"source": {
|
|
49
|
-
"description": "
|
|
49
|
+
"description": "Declared origin of claim intent. A source label or opaque reference does not authenticate independence, chronology or correctness. Producer provenance and measured test independence are separate.",
|
|
50
50
|
"type": "object",
|
|
51
51
|
"required": [
|
|
52
52
|
"kind"
|
|
53
53
|
],
|
|
54
54
|
"properties": {
|
|
55
55
|
"kind": {
|
|
56
|
-
"
|
|
57
|
-
"spec",
|
|
58
|
-
"adr",
|
|
59
|
-
"annotation",
|
|
60
|
-
"comment",
|
|
61
|
-
"manual"
|
|
62
|
-
]
|
|
56
|
+
"$ref": "urn:claimspec:v1:common#/$defs/claimSourceKind"
|
|
63
57
|
},
|
|
64
58
|
"ref": {
|
|
65
59
|
"type": "string",
|
|
@@ -4,6 +4,79 @@
|
|
|
4
4
|
"title": "claimspec v1 \u2014 shared definitions",
|
|
5
5
|
"description": "Definitions shared by every Guard-spec format. Referenced as urn:claimspec:v1:common#/$defs/<name>.",
|
|
6
6
|
"$defs": {
|
|
7
|
+
"originPolicy": {
|
|
8
|
+
"description": "An explicitly selected declared-origin policy, not authenticated independence. Stored results describe their run, not current freshness authority.",
|
|
9
|
+
"type": "object",
|
|
10
|
+
"required": ["state", "eligibleKinds", "claims", "faults", "ineligibleClaims", "nonKilledFaults", "unavailableReasons"],
|
|
11
|
+
"properties": {
|
|
12
|
+
"state": { "enum": ["passed", "failed", "unavailable"] },
|
|
13
|
+
"eligibleKinds": { "type": "array", "minItems": 1, "maxItems": 10, "uniqueItems": true, "items": { "$ref": "#/$defs/claimSourceKind" } },
|
|
14
|
+
"claims": { "$ref": "#/$defs/originCount" },
|
|
15
|
+
"faults": { "$ref": "#/$defs/originCount" },
|
|
16
|
+
"ineligibleClaims": { "type": "array", "uniqueItems": true, "items": { "$ref": "#/$defs/identifier" } },
|
|
17
|
+
"nonKilledFaults": {
|
|
18
|
+
"type": "array", "uniqueItems": true,
|
|
19
|
+
"items": {
|
|
20
|
+
"type": "object", "required": ["claimId", "faultId"],
|
|
21
|
+
"properties": { "claimId": { "$ref": "#/$defs/identifier" }, "faultId": { "$ref": "#/$defs/identifier" } },
|
|
22
|
+
"additionalProperties": false
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"unavailableReasons": {
|
|
26
|
+
"type": "array", "uniqueItems": true,
|
|
27
|
+
"items": { "enum": ["invalid-claims", "invalid-evidence", "empty-universe", "stale-inputs", "provisional-evidence", "incompatible-method", "missing-record", "extra-record", "duplicate-record", "claim-metadata-mismatch", "fault-mismatch"] }
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"additionalProperties": false
|
|
31
|
+
},
|
|
32
|
+
"originCount": { "type": "integer", "minimum": 0, "maximum": 9007199254740991 },
|
|
33
|
+
"originCounts": {
|
|
34
|
+
"type": "object",
|
|
35
|
+
"required": ["spec", "adr", "annotation", "comment", "manual", "doc", "bug", "incident", "review", "inferred"],
|
|
36
|
+
"properties": {
|
|
37
|
+
"spec": { "$ref": "#/$defs/originCount" },
|
|
38
|
+
"adr": { "$ref": "#/$defs/originCount" },
|
|
39
|
+
"annotation": { "$ref": "#/$defs/originCount" },
|
|
40
|
+
"comment": { "$ref": "#/$defs/originCount" },
|
|
41
|
+
"manual": { "$ref": "#/$defs/originCount" },
|
|
42
|
+
"doc": { "$ref": "#/$defs/originCount" },
|
|
43
|
+
"bug": { "$ref": "#/$defs/originCount" },
|
|
44
|
+
"incident": { "$ref": "#/$defs/originCount" },
|
|
45
|
+
"review": { "$ref": "#/$defs/originCount" },
|
|
46
|
+
"inferred": { "$ref": "#/$defs/originCount" }
|
|
47
|
+
},
|
|
48
|
+
"additionalProperties": false
|
|
49
|
+
},
|
|
50
|
+
"originSummary": {
|
|
51
|
+
"description": "Declared origins, not authenticated independence. Distinct claim identities and actual recorded projections have separate denominators. Evidence validation recomputes this from every record; projected status/brief documents can only bind arithmetic and totals.",
|
|
52
|
+
"type": "object",
|
|
53
|
+
"required": ["basis", "claims"],
|
|
54
|
+
"properties": {
|
|
55
|
+
"basis": { "enum": ["declared", "recorded"] },
|
|
56
|
+
"claims": {
|
|
57
|
+
"type": "object", "required": ["total", "byKind", "mixed"],
|
|
58
|
+
"properties": {
|
|
59
|
+
"total": { "$ref": "#/$defs/originCount" },
|
|
60
|
+
"byKind": { "$ref": "#/$defs/originCounts" },
|
|
61
|
+
"mixed": { "$ref": "#/$defs/originCount" }
|
|
62
|
+
},
|
|
63
|
+
"additionalProperties": false
|
|
64
|
+
},
|
|
65
|
+
"records": {
|
|
66
|
+
"type": "object", "required": ["total", "byKind"],
|
|
67
|
+
"properties": {
|
|
68
|
+
"total": { "$ref": "#/$defs/originCount" },
|
|
69
|
+
"byKind": { "$ref": "#/$defs/originCounts" }
|
|
70
|
+
},
|
|
71
|
+
"additionalProperties": false
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
"additionalProperties": false
|
|
75
|
+
},
|
|
76
|
+
"claimSourceKind": {
|
|
77
|
+
"description": "Declared origin of claim intent, not authenticated independence or an evidence-strength order. References remain opaque; producer provenance and measured test independence are separate.",
|
|
78
|
+
"enum": ["spec", "adr", "annotation", "comment", "manual", "doc", "bug", "incident", "review", "inferred"]
|
|
79
|
+
},
|
|
7
80
|
"nestedProject": {
|
|
8
81
|
"description": "Changed files delegated to a separately validated descendant TestGuard project. This is not coverage or evidence; run that project's gate separately.",
|
|
9
82
|
"type": "object",
|
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
"records"
|
|
12
12
|
],
|
|
13
13
|
"properties": {
|
|
14
|
+
"originPolicy": { "$ref": "urn:claimspec:v1:common#/$defs/originPolicy" },
|
|
15
|
+
"origins": { "$ref": "urn:claimspec:v1:common#/$defs/originSummary" },
|
|
14
16
|
"$schema": {
|
|
15
17
|
"type": "string"
|
|
16
18
|
},
|
|
@@ -168,6 +170,39 @@
|
|
|
168
170
|
},
|
|
169
171
|
"method": {
|
|
170
172
|
"$ref": "urn:claimspec:v1:common#/$defs/method"
|
|
173
|
+
},
|
|
174
|
+
"measurements": {
|
|
175
|
+
"type": "object",
|
|
176
|
+
"required": [
|
|
177
|
+
"elapsedMs",
|
|
178
|
+
"runnerMs",
|
|
179
|
+
"overheadMs",
|
|
180
|
+
"runnerInvocations"
|
|
181
|
+
],
|
|
182
|
+
"properties": {
|
|
183
|
+
"elapsedMs": {
|
|
184
|
+
"type": "number",
|
|
185
|
+
"minimum": 0
|
|
186
|
+
},
|
|
187
|
+
"runnerMs": {
|
|
188
|
+
"type": "number",
|
|
189
|
+
"minimum": 0
|
|
190
|
+
},
|
|
191
|
+
"overheadMs": {
|
|
192
|
+
"type": "number",
|
|
193
|
+
"minimum": 0
|
|
194
|
+
},
|
|
195
|
+
"runnerInvocations": {
|
|
196
|
+
"type": "integer",
|
|
197
|
+
"minimum": 0
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
"additionalProperties": false
|
|
201
|
+
},
|
|
202
|
+
"workers": {
|
|
203
|
+
"type": "integer",
|
|
204
|
+
"minimum": 1,
|
|
205
|
+
"description": "Requested built-in runner worker ceiling; Python is serial. Absent for custom commands, whose concurrency is user-managed."
|
|
171
206
|
}
|
|
172
207
|
},
|
|
173
208
|
"additionalProperties": false,
|
|
@@ -309,13 +344,7 @@
|
|
|
309
344
|
],
|
|
310
345
|
"properties": {
|
|
311
346
|
"kind": {
|
|
312
|
-
"
|
|
313
|
-
"spec",
|
|
314
|
-
"adr",
|
|
315
|
-
"annotation",
|
|
316
|
-
"comment",
|
|
317
|
-
"manual"
|
|
318
|
-
]
|
|
347
|
+
"$ref": "urn:claimspec:v1:common#/$defs/claimSourceKind"
|
|
319
348
|
},
|
|
320
349
|
"ref": {
|
|
321
350
|
"type": "string",
|
|
@@ -664,7 +693,7 @@
|
|
|
664
693
|
"additionalProperties": false
|
|
665
694
|
},
|
|
666
695
|
"reusedFrom": {
|
|
667
|
-
"description": "Run id whose verdict was reused because
|
|
696
|
+
"description": "Run id whose verdict was reused because fault, defender and recorded claim metadata inputs were unchanged. A changed claim statement, severity, source or producer requires a fresh probe.",
|
|
668
697
|
"$ref": "urn:claimspec:v1:common#/$defs/identifier"
|
|
669
698
|
},
|
|
670
699
|
"rank": {
|