vdelta 0.1.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/LICENSE +21 -0
- package/README.md +171 -0
- package/dist/adapters/vitest/capture.d.ts +50 -0
- package/dist/adapters/vitest/capture.js +7 -0
- package/dist/adapters/vitest/capture.js.map +1 -0
- package/dist/adapters/vitest/recorder.d.ts +31 -0
- package/dist/adapters/vitest/recorder.js +214 -0
- package/dist/adapters/vitest/recorder.js.map +1 -0
- package/dist/adapters/vitest/reporter.d.ts +15 -0
- package/dist/adapters/vitest/reporter.js +97 -0
- package/dist/adapters/vitest/reporter.js.map +1 -0
- package/dist/canonical.d.ts +9 -0
- package/dist/canonical.js +45 -0
- package/dist/canonical.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +264 -0
- package/dist/cli.js.map +1 -0
- package/dist/compare.d.ts +37 -0
- package/dist/compare.js +476 -0
- package/dist/compare.js.map +1 -0
- package/dist/digest.d.ts +5 -0
- package/dist/digest.js +14 -0
- package/dist/digest.js.map +1 -0
- package/dist/gate.d.ts +11 -0
- package/dist/gate.js +135 -0
- package/dist/gate.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/redact.d.ts +10 -0
- package/dist/redact.js +49 -0
- package/dist/redact.js.map +1 -0
- package/dist/render.d.ts +6 -0
- package/dist/render.js +53 -0
- package/dist/render.js.map +1 -0
- package/dist/run.d.ts +19 -0
- package/dist/run.js +158 -0
- package/dist/run.js.map +1 -0
- package/dist/schema.d.ts +236 -0
- package/dist/schema.js +395 -0
- package/dist/schema.js.map +1 -0
- package/dist/store.d.ts +38 -0
- package/dist/store.js +161 -0
- package/dist/store.js.map +1 -0
- package/dist/tree-digest.d.ts +11 -0
- package/dist/tree-digest.js +103 -0
- package/dist/tree-digest.js.map +1 -0
- package/package.json +53 -0
- package/spec/veridelta-1.md +1093 -0
|
@@ -0,0 +1,1093 @@
|
|
|
1
|
+
# veridelta/1 — Verification Delta Protocol
|
|
2
|
+
|
|
3
|
+
| | |
|
|
4
|
+
|---|---|
|
|
5
|
+
| **Status** | Draft |
|
|
6
|
+
| **Schema identifier** | `veridelta/1` |
|
|
7
|
+
| **Spec revision** | 0.3.0 — 2026-07-16 |
|
|
8
|
+
| **License** | MIT |
|
|
9
|
+
| **Reference implementation** | `vdelta` (this repository; in development) |
|
|
10
|
+
|
|
11
|
+
## Abstract
|
|
12
|
+
|
|
13
|
+
veridelta is a protocol for **proof-carrying verification deltas** in coding-agent
|
|
14
|
+
development loops. Given two comparable test runs, a conforming implementation
|
|
15
|
+
reports — deterministically and with evidence — whether a change improved the
|
|
16
|
+
outcome *while maintaining the same verification surface*, whether pre-existing
|
|
17
|
+
failures mutated into different failures, and whether red results disappeared
|
|
18
|
+
because they were fixed or because the verification surface shrank
|
|
19
|
+
(fail→skip, deleted tests, narrowed selectors).
|
|
20
|
+
|
|
21
|
+
veridelta is not a log-compression tool. It is a **trust layer**: a deterministic
|
|
22
|
+
state-transition report between two immutable, content-addressed runs, with an
|
|
23
|
+
explicit comparability judgment. When comparability does not hold, a conforming
|
|
24
|
+
implementation **abstains** rather than guessing. Token savings for agents are a
|
|
25
|
+
side effect; the product is that an agent (or a CI gate reviewing an agent's PR)
|
|
26
|
+
can proceed to the next step safely.
|
|
27
|
+
|
|
28
|
+
## 1. Introduction
|
|
29
|
+
|
|
30
|
+
### 1.1. Motivation
|
|
31
|
+
|
|
32
|
+
In an agent's test→fix→test loop, most of a rerun's output is identical to the
|
|
33
|
+
previous run. Tools that summarize only the *current* run can answer "what
|
|
34
|
+
failed now?" but not "what did this change fix, and what did it newly break?" —
|
|
35
|
+
the agent must reconstruct that from history it does not reliably retain.
|
|
36
|
+
|
|
37
|
+
Three failure modes motivate this protocol:
|
|
38
|
+
|
|
39
|
+
1. **Pre-existing failures.** In repositories with standing red, the child
|
|
40
|
+
process exits `1` every time. The exit code cannot express *improved*,
|
|
41
|
+
*regressed*, or *unchanged*.
|
|
42
|
+
2. **Red→red mutation.** A test that fails before and after a change may be
|
|
43
|
+
failing *differently*. Status-only diffs bury this, and the new failure mode
|
|
44
|
+
goes uninvestigated.
|
|
45
|
+
3. **Verification-surface reduction.** A red result can disappear without a fix:
|
|
46
|
+
fail→skip, fail→xfail, test deletion, selector narrowing, or a test rewritten
|
|
47
|
+
to pass. Outcome-only comparison reports these as improvements. In the
|
|
48
|
+
context of agent-generated changes this is precisely the cheating vector
|
|
49
|
+
(reward hacking), so it must be a first-class, separately reported axis.
|
|
50
|
+
|
|
51
|
+
### 1.2. Position
|
|
52
|
+
|
|
53
|
+
Three separations of concern define the protocol:
|
|
54
|
+
|
|
55
|
+
- The **child process's exit code** and the **semantic judgment "did anything
|
|
56
|
+
regress since the baseline?"** are different concepts and are never encoded in
|
|
57
|
+
the same channel (§10).
|
|
58
|
+
- **Test-ID identity** and **failure-finding identity** are different concepts:
|
|
59
|
+
the same test failing with different evidence is a state transition (§7.3).
|
|
60
|
+
- **Outcome improvement** and **verification-surface maintenance** are different
|
|
61
|
+
axes and are always reported separately (§7.4).
|
|
62
|
+
|
|
63
|
+
### 1.3. Scope
|
|
64
|
+
|
|
65
|
+
In scope:
|
|
66
|
+
|
|
67
|
+
- Comparing two recorded runs of a test command over the same repository,
|
|
68
|
+
including dirty working trees.
|
|
69
|
+
- Gating agent-generated changes (e.g., pull requests) on regression and
|
|
70
|
+
verification-surface reduction relative to a baseline ref.
|
|
71
|
+
- The canonical data model, run store semantics, baseline selection,
|
|
72
|
+
comparability rules, delta taxonomy, report contract, gate semantics, and
|
|
73
|
+
conformance requirements.
|
|
74
|
+
|
|
75
|
+
Out of scope (non-goals):
|
|
76
|
+
|
|
77
|
+
- General-purpose log compression or summarization.
|
|
78
|
+
- Test selection or execution optimization (e.g., `--lf`-style reruns).
|
|
79
|
+
- Semantic diffing of arbitrary text, or any LLM-based summarization in the
|
|
80
|
+
trust path.
|
|
81
|
+
- Attribution of *cause* ("the agent cheated", "your change broke this").
|
|
82
|
+
Conforming implementations report observations; intent and blame are the
|
|
83
|
+
consumer's judgment (§7.4).
|
|
84
|
+
|
|
85
|
+
### 1.4. Roles
|
|
86
|
+
|
|
87
|
+
| Role | Responsibility |
|
|
88
|
+
|---|---|
|
|
89
|
+
| **Adapter** | Translates one runner's native output/hooks into the canonical data model, under a declared capability set (§3.4, §12). |
|
|
90
|
+
| **Recorder** | Captures runs into the store. May be a runner plugin, a harness hook, or a CLI wrapper (§4.2). |
|
|
91
|
+
| **Run store** | Immutable, content-addressed persistence of runs (§4). |
|
|
92
|
+
| **Comparator** | Selects a baseline, judges comparability, computes the delta report (§5–§9). |
|
|
93
|
+
| **Gate** | Turns a delta report into a CI/agent-loop verdict under a policy, with integrity and staleness checks (§11). |
|
|
94
|
+
| **Consumer** | Anything that parses `veridelta/1` reports: agents, harnesses, CI, humans via secondary rendering (§9.4). |
|
|
95
|
+
|
|
96
|
+
A single binary may implement several roles. Conformance is claimed per role
|
|
97
|
+
(§13).
|
|
98
|
+
|
|
99
|
+
## 2. Conventions and terminology
|
|
100
|
+
|
|
101
|
+
The key words **MUST**, **MUST NOT**, **REQUIRED**, **SHALL**, **SHALL NOT**,
|
|
102
|
+
**SHOULD**, **SHOULD NOT**, **RECOMMENDED**, **MAY**, and **OPTIONAL** are to be
|
|
103
|
+
interpreted as described in RFC 2119 and RFC 8174 when, and only when, they
|
|
104
|
+
appear in all capitals.
|
|
105
|
+
|
|
106
|
+
- **Run**: one recorded execution of a test command (§3.1).
|
|
107
|
+
- **Stream**: the equivalence class of runs that are candidates for implicit
|
|
108
|
+
baseline selection (§5.1).
|
|
109
|
+
- **Baseline**: the run against which the current run is compared.
|
|
110
|
+
- **Verdict**: the canonical per-test outcome assigned by the runner (§7.1).
|
|
111
|
+
- **Red**: a verdict in the failing set (§7.1).
|
|
112
|
+
- **Failure finding**: the evidence attached to a red observation (§3.3).
|
|
113
|
+
- **Verification surface**: the set of checks a run actually performed —
|
|
114
|
+
inventory, selectors, suppression state, test/config sources, adapter
|
|
115
|
+
capabilities (§7.4).
|
|
116
|
+
- **Abstain**: report `inconclusive`/`none` with a reason instead of guessing.
|
|
117
|
+
- **Closed enum**: a field whose value set is fixed by this spec revision;
|
|
118
|
+
consumers MUST treat unknown values as a hard error (§9.4, §14).
|
|
119
|
+
|
|
120
|
+
## 3. Data model
|
|
121
|
+
|
|
122
|
+
All persisted records and reports are JSON. Field names are `snake_case`.
|
|
123
|
+
Digests are lowercase-hex SHA-256 rendered as `sha256:<hex>` unless an adapter
|
|
124
|
+
documents otherwise.
|
|
125
|
+
|
|
126
|
+
### 3.1. Run
|
|
127
|
+
|
|
128
|
+
A Run is the immutable record of one execution. Its identity is
|
|
129
|
+
content-addressed: `run_id` is derived from the canonical serialization of the
|
|
130
|
+
record (stable key order, no volatile fields), so identical runs collide and
|
|
131
|
+
mutation is detectable.
|
|
132
|
+
|
|
133
|
+
| Field group | Fields | Notes |
|
|
134
|
+
|---|---|---|
|
|
135
|
+
| `schema_version` | `"veridelta/1"` | REQUIRED. |
|
|
136
|
+
| `repo` | repo identity, worktree path, branch lineage, `cwd` | REQUIRED. |
|
|
137
|
+
| `invocation` | canonicalized command, test selector | REQUIRED. Selector is recorded as normalized by the adapter. |
|
|
138
|
+
| `instrument` | adapter name, adapter version, runner config digest | REQUIRED. Together these identify the *measuring instrument* (§6.2). The runner config digest MUST cover the effective configuration that alters evidence quality or structure — including assertion-introspection mode, traceback style, and diff/message truncation settings — however that configuration is supplied (command line, configuration files, plugins, or environment). |
|
|
139
|
+
| `environment` | runner, runtime, OS, fingerprint of adapter-declared comparison-relevant env vars | REQUIRED. Secret **values** MUST NOT be stored; fingerprints only (§15). |
|
|
140
|
+
| `provenance` | `head` (VCS revision), `dirty_diff_digest`, `tree_digest` | REQUIRED. `tree_digest` MUST identify the exact source content the run executed against, computed per §3.5. Provenance is **evidence of what was compared, never a stream-matching key** (§5.1). |
|
|
141
|
+
| `surface` | observed test inventory digest; digests of test source and test-relevant config files; suppression metadata | REQUIRED to the extent the adapter's declared capabilities allow. |
|
|
142
|
+
| `completeness` | `status`: `complete` \| `partial` \| `crashed`; `child_exit_code` | REQUIRED. |
|
|
143
|
+
| `observations` | array of TestObservation (§3.2) | REQUIRED. |
|
|
144
|
+
| `recording` | recorder kind, timestamps, raw-record references | Timestamps are permitted here and only here; they MUST NOT influence report content (§7.8), only duplicate matching (§4.3). |
|
|
145
|
+
|
|
146
|
+
### 3.2. TestObservation
|
|
147
|
+
|
|
148
|
+
| Field | Req | Description |
|
|
149
|
+
|---|---|---|
|
|
150
|
+
| `test_id` | MUST | Canonical item ID as provided by the adapter (e.g., pytest nodeid). The protocol does not accept user-supplied key regexes. |
|
|
151
|
+
| `verdict` | MUST | Canonical verdict (§7.1), derived **only** from the runner's verdict channel (INV-3). |
|
|
152
|
+
| `phase` | SHOULD | e.g., `collection` \| `setup` \| `call` \| `teardown`, if the capability is declared. |
|
|
153
|
+
| `suppression` | SHOULD | skip/xfail markers and reasons, if observable. |
|
|
154
|
+
| `source_ref` | MAY | file/line of the test definition, if the capability is declared. |
|
|
155
|
+
| `finding` | MUST when red | FailureFinding (§3.3). |
|
|
156
|
+
| `detail` | MAY | non-trust detail (e.g., duration). Never used for status derivation. |
|
|
157
|
+
|
|
158
|
+
### 3.3. FailureFinding
|
|
159
|
+
|
|
160
|
+
Each finding carries two fingerprints with strictly different trust roles:
|
|
161
|
+
|
|
162
|
+
| Field | Role |
|
|
163
|
+
|---|---|
|
|
164
|
+
| `evidence_digest` | **Trust path.** Lossless digest of the canonical failure evidence (§3.6) after deterministic secret redaction and canonical encoding, and after nothing else. Any change to canonical evidence changes this digest. Change detection MUST use this and only this. |
|
|
165
|
+
| `structural_fingerprint` | **Auxiliary.** Phase, exception type, assertion location, top project stack frames. Used for clustering and drill-down presentation only. It MUST NOT be used to suppress or merge a change that `evidence_digest` detects (INV-8). |
|
|
166
|
+
| `evidence` | Stored redacted raw evidence, addressable via anchors (§9.3). |
|
|
167
|
+
|
|
168
|
+
### 3.4. Adapter capability declaration
|
|
169
|
+
|
|
170
|
+
Adapters MUST declare, in schema form, which observables they can produce:
|
|
171
|
+
verdicts, phases, source locations, suppression metadata, failure evidence,
|
|
172
|
+
inventory, rename proof, coverage, etc. A missing capability means *unknown*,
|
|
173
|
+
never *unchanged*: comparators MUST NOT interpret absent data as absence of
|
|
174
|
+
change (§12). Capability values follow the three-valued convention
|
|
175
|
+
`pass`/`fail`/`unsupported` where applicable — "the runner cannot express this"
|
|
176
|
+
and "this broke" are never conflated.
|
|
177
|
+
|
|
178
|
+
### 3.5. Identifiers and digests
|
|
179
|
+
|
|
180
|
+
- All digest inputs MUST be canonicalized deterministically (encoding, ordering
|
|
181
|
+
of multi-part evidence) and the canonicalization MUST be documented by the
|
|
182
|
+
adapter.
|
|
183
|
+
- Redaction (§15) runs before digesting and MUST itself be deterministic, so
|
|
184
|
+
digests remain stable for identical inputs.
|
|
185
|
+
|
|
186
|
+
**Run identity.** `run_id` is content-derived and immutable: `run_` followed by
|
|
187
|
+
the lowercase-hex SHA-256 of the canonical serialization of the Run record
|
|
188
|
+
excluding the `recording` group. Canonical serialization is JSON with
|
|
189
|
+
lexicographically sorted keys, UTF-8 encoding, no insignificant whitespace, and
|
|
190
|
+
all numbers restricted to integers (durations in microseconds) — sidestepping
|
|
191
|
+
floating-point serialization divergence between implementations. Because
|
|
192
|
+
`recording` is excluded, two recorders that observe the same physical run
|
|
193
|
+
identically produce the *same* `run_id`: content addressing itself collapses
|
|
194
|
+
exact duplicates, and §4.3 matching is needed only for records that differ.
|
|
195
|
+
Tools MAY accept unambiguous `run_id` prefixes as input; identity is always the
|
|
196
|
+
full digest.
|
|
197
|
+
|
|
198
|
+
**Tree identity.** The `tree_digest` of a git worktree MUST be computed as a
|
|
199
|
+
git tree object id over the union of tracked, staged, unstaged, and untracked
|
|
200
|
+
files, excluding paths ignored by committed `.gitignore`/`.gitattributes`
|
|
201
|
+
rules. Implementations MUST compute it against a dedicated, throwaway index
|
|
202
|
+
(`GIT_INDEX_FILE`) seeded from `HEAD` (`git read-tree HEAD`, or `read-tree
|
|
203
|
+
--empty` for an unborn `HEAD`), followed by `git add -A` and `git write-tree`,
|
|
204
|
+
so that the operation never mutates the repository's real index or working
|
|
205
|
+
tree. To keep the digest deterministic and independent of host and time, the
|
|
206
|
+
computation MUST pin `core.autocrlf=false`, `core.eol=lf`, and
|
|
207
|
+
`core.excludesFile=/dev/null`, and MUST NOT depend on file mtimes. For a clean
|
|
208
|
+
checkout the resulting id is identical to `git rev-parse HEAD^{tree}`, which a
|
|
209
|
+
verifier MAY use directly as the canonical form (subject to the conditions of
|
|
210
|
+
§11.3). The digest records submodules solely by their gitlink commit id; it
|
|
211
|
+
does NOT capture uncommitted changes inside a submodule working tree, and it
|
|
212
|
+
cannot represent empty directories — both are explicit, documented limitations
|
|
213
|
+
(see §11.3 for gate-side handling).
|
|
214
|
+
|
|
215
|
+
### 3.6. Canonical failure evidence
|
|
216
|
+
|
|
217
|
+
The input to `evidence_digest` is the **canonical failure evidence**: a
|
|
218
|
+
deterministic projection of the runner's structured failure representation,
|
|
219
|
+
declared per adapter as a versioned **composition** and satisfying:
|
|
220
|
+
|
|
221
|
+
- **CE-1 (signal completeness)**: it MUST include the exception type, the
|
|
222
|
+
runner's failure message with asserted values intact, the failing source
|
|
223
|
+
region text, and the traceback entry structure, as provided by the runner's
|
|
224
|
+
structured channel.
|
|
225
|
+
- **CE-2 (rerun stability)**: it MUST NOT include durations, wall-clock or
|
|
226
|
+
monotonic time, process- or host-specific values (memory addresses, PIDs,
|
|
227
|
+
absolute filesystem paths), captured program output streams, or any field
|
|
228
|
+
capable of carrying values supplied by run-scoped resources (e.g.,
|
|
229
|
+
function-argument representations, which can embed per-run temporary paths).
|
|
230
|
+
Exclusion under this rule operates on whole declared fields (CE-5): a field
|
|
231
|
+
is excluded for what it *can carry*, never conditionally on what a particular
|
|
232
|
+
value contains.
|
|
233
|
+
- **CE-3 (position stability)**: source position MUST be encoded in a
|
|
234
|
+
line-shift-stable form (enclosing symbol plus symbol-relative offset, and/or
|
|
235
|
+
source line text). Absolute line numbers MUST NOT enter the digest.
|
|
236
|
+
- **CE-4 (structured fields only)**: the digest MUST be computed from
|
|
237
|
+
structured fields, never from rendered display strings that interleave
|
|
238
|
+
position, style, and message.
|
|
239
|
+
- **CE-5 (whole-field granularity)**: composition includes or excludes whole
|
|
240
|
+
declared fields only. Value-level rewriting of evidence content is
|
|
241
|
+
prohibited; the sole exception is deterministic secret redaction (§15).
|
|
242
|
+
Redaction MUST NOT be used to normalize non-secret volatile values.
|
|
243
|
+
|
|
244
|
+
Where the runner's structured channel does not provide a CE-1 component
|
|
245
|
+
(e.g., vitest provides no source region text), the adapter MUST either
|
|
246
|
+
(a) declare the corresponding capability `unsupported` (§3.4), surfacing it
|
|
247
|
+
through `degraded_capabilities` (§9.1), or (b) reconstruct the component
|
|
248
|
+
deterministically from the run's recorded tree at the location named by the
|
|
249
|
+
structured channel, declaring the reconstruction in its composition. A
|
|
250
|
+
composition MUST distinguish channel-provided from tree-reconstructed
|
|
251
|
+
components; silent omission is non-conforming.
|
|
252
|
+
|
|
253
|
+
Material excluded by CE-2/CE-3 MUST be stored in the finding's annex,
|
|
254
|
+
addressable via anchors (§9.3); exclusion from the digest is never exclusion
|
|
255
|
+
from the record (INV-2). An adapter that stores captured-output annex material
|
|
256
|
+
MUST compute a `context_digest` over it; when it differs between compared runs,
|
|
257
|
+
affected `still_fail_unchanged` entries MUST carry `context_changed: true` with
|
|
258
|
+
an anchor. `context_digest` MUST NOT influence transition classification
|
|
259
|
+
(§7.3).
|
|
260
|
+
|
|
261
|
+
The declared composition is part of the measuring instrument: any composition
|
|
262
|
+
change requires an adapter version change (§6.2).
|
|
263
|
+
|
|
264
|
+
**Known limitation (normative acknowledgment):** a failure message that embeds
|
|
265
|
+
values varying across executions at an identical tree (times, run-scoped
|
|
266
|
+
paths) yields differing digests on rerun. This is honest `updated_fail`
|
|
267
|
+
reporting of genuinely differing evidence, not noise to be normalized; such
|
|
268
|
+
transitions carry `failure_mode_changed: false` when the structural
|
|
269
|
+
fingerprint is stable, and gates MAY narrow blocking accordingly (§11.1,
|
|
270
|
+
subject to the blocking-set floor of §11.5) without narrowing reporting.
|
|
271
|
+
|
|
272
|
+
## 4. Recording and the run store
|
|
273
|
+
|
|
274
|
+
### 4.1. Immutability and content addressing
|
|
275
|
+
|
|
276
|
+
Stored runs are immutable. Implementations MUST NOT rewrite a stored run;
|
|
277
|
+
corrections are new records. The store is repo-local, MUST be excluded from
|
|
278
|
+
version control (e.g., enforced gitignore), and SHOULD be bounded (LRU or
|
|
279
|
+
equivalent).
|
|
280
|
+
|
|
281
|
+
### 4.2. Ambient recording
|
|
282
|
+
|
|
283
|
+
Recording is **ambient-first**: the RECOMMENDED deployment records every run
|
|
284
|
+
via runner plugins or harness hooks, with a CLI wrapper (`vdelta run --`) as
|
|
285
|
+
just one recorder implementation. Rationale: a wrapper-only design depends on
|
|
286
|
+
agent discipline, and one forgotten wrap severs the stream — contradicting the
|
|
287
|
+
premise that agents are stateless. **Record always; compare on demand.**
|
|
288
|
+
|
|
289
|
+
Continuous recording also enables the highest-value inner-loop query: comparing
|
|
290
|
+
the current subset run against the widest available proven-superset run over
|
|
291
|
+
their common IDs, under the `subset` comparability rules of §6.1 and the
|
|
292
|
+
`previous-superset` baseline mode of §5.2.
|
|
293
|
+
|
|
294
|
+
### 4.3. Duplicate-record normalization
|
|
295
|
+
|
|
296
|
+
When multiple recorders coexist (plugin + hook + wrapper), the same physical
|
|
297
|
+
test run may be recorded more than once. Stores MUST normalize duplicates
|
|
298
|
+
deterministically:
|
|
299
|
+
|
|
300
|
+
- Group candidate records by stream key (§5.1) and match by timestamp
|
|
301
|
+
proximity with a fixed, documented window.
|
|
302
|
+
- Matching MUST be deterministic given the same set of records.
|
|
303
|
+
- Joined records and unjoinable residuals MUST both be explicit; raw and
|
|
304
|
+
normalized forms are both retained. Residuals that cannot be normalized MUST
|
|
305
|
+
remain observable — normalization never silently discards a record.
|
|
306
|
+
|
|
307
|
+
### 4.4. Store hygiene
|
|
308
|
+
|
|
309
|
+
- Writes MUST be atomic.
|
|
310
|
+
- Locks are advisory and **fail-open**: lock contention degrades to raw
|
|
311
|
+
passthrough (INV-5), never to blocking or corruption.
|
|
312
|
+
- Known secret shapes MUST be redacted before persistence (§15).
|
|
313
|
+
|
|
314
|
+
### 4.5. Execution-cache coherence
|
|
315
|
+
|
|
316
|
+
A recorder MUST ensure the evidence a run reports was produced from the source
|
|
317
|
+
content identified by `provenance.tree_digest`. Runner- or runtime-level
|
|
318
|
+
caches that can serve compiled or rewritten artifacts from a prior source
|
|
319
|
+
state MUST be neutralized for recorded runs; the mechanism MUST be declared by
|
|
320
|
+
the adapter and MUST pass the stale-cache collision fixture (§13.2). For the
|
|
321
|
+
pytest adapter: purge in-scope `__pycache__` directories, or point
|
|
322
|
+
`PYTHONPYCACHEPREFIX` at a run-scoped empty directory.
|
|
323
|
+
`PYTHONDONTWRITEBYTECODE` alone is insufficient: it suppresses writes but not
|
|
324
|
+
reads of pre-existing cached bytecode. (pytest's `-p no:cacheprovider` does
|
|
325
|
+
not address this cache.)
|
|
326
|
+
|
|
327
|
+
Not every runner has such a cache: empirical probing found no stale-source
|
|
328
|
+
path for vitest in run mode (its persistent caches carry no evidence), so its
|
|
329
|
+
adapter declares that no neutralization is required — the §13.2 fixture
|
|
330
|
+
remains mandatory as the arbiter of that declaration.
|
|
331
|
+
|
|
332
|
+
## 5. Streams and baseline selection
|
|
333
|
+
|
|
334
|
+
Baseline selection is a public protocol, not an internal heuristic. Every
|
|
335
|
+
report MUST state which baseline was chosen and why (`selection_reason`).
|
|
336
|
+
|
|
337
|
+
### 5.1. Stream key
|
|
338
|
+
|
|
339
|
+
The default comparison stream is:
|
|
340
|
+
|
|
341
|
+
```
|
|
342
|
+
repo + worktree + branch + cwd + canonical command + selector + instrument
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
Branch switches, command changes, and selector changes start a new stream.
|
|
346
|
+
`tree_digest`/`head` are provenance, **not** part of the stream key — the tree
|
|
347
|
+
changes on every edit; that is the thing being iterated, not the thing that
|
|
348
|
+
identifies the series.
|
|
349
|
+
|
|
350
|
+
The **series key** is the stream key minus the selector: `repo + worktree +
|
|
351
|
+
branch + cwd + canonical command + instrument`. The canonical command
|
|
352
|
+
component excludes the selector, which §3.1 records as a separate field; two
|
|
353
|
+
invocations differing only in selector share a series. The series key exists
|
|
354
|
+
solely to scope `previous-superset` selection (§5.2); it never widens
|
|
355
|
+
`previous-comparable` matching.
|
|
356
|
+
|
|
357
|
+
### 5.2. Baseline modes
|
|
358
|
+
|
|
359
|
+
| Mode | Intended use | Meaning |
|
|
360
|
+
|---|---|---|
|
|
361
|
+
| `previous-comparable` | dirty-tree inner loop (default) | Most recent **complete** run in the same stream. |
|
|
362
|
+
| `git-ref` | PR / regression gate | Complete run whose provenance matches the given ref. |
|
|
363
|
+
| `explicit-run-id` | agent/harness control | Caller names an immutable run. |
|
|
364
|
+
| `previous-superset` | inner loop, subset runs | Candidates are **complete** runs in the same series whose selector the adapter proves to be a **proper superset** of the current run's selector via the `selector-relation` capability (§6.4). Among candidates, only those **maximal under the proven-containment partial order** (no other candidate's selector is a proven proper superset of theirs) are eligible; a narrower run MUST never be selected over a wider one. Select the most recent maximal candidate; recency ties break by lexicographic `run_id`. When more than one maximal candidate exists, the report MUST disclose `baseline.superset_candidates` (count) and the baseline's selector. An equal selector belongs to the same stream and is handled by `previous-comparable`, never by this mode. Abstention reasons are distinct (§6.3): when candidate relations are decided but no proper-superset candidate exists, this mode abstains with `baseline-missing`; when `unknown` relations prevent candidacy from being determined, it abstains with `selector-relation-unknown`. An `unknown` relation is absence of proof, never containment; this mode MUST NOT fall back to a weaker match (§5.3). |
|
|
365
|
+
|
|
366
|
+
An implicit "the previous invocation" baseline (ordinal, order-dependent) is
|
|
367
|
+
prohibited: it is contaminated by branch switches and partial runs. Selection
|
|
368
|
+
MUST be content-addressed and explainable.
|
|
369
|
+
|
|
370
|
+
### 5.3. Selection transparency
|
|
371
|
+
|
|
372
|
+
`baseline.selection_reason` is REQUIRED in every comparison report and MUST be
|
|
373
|
+
sufficient for the consumer to verify the choice (e.g.,
|
|
374
|
+
`same-worktree-command-config-scope`). If no acceptable baseline exists, the
|
|
375
|
+
comparator MUST abstain with comparability `none` (§6.3), never fall back
|
|
376
|
+
silently to a weaker match.
|
|
377
|
+
|
|
378
|
+
## 6. Comparability
|
|
379
|
+
|
|
380
|
+
### 6.1. Levels and permitted claims
|
|
381
|
+
|
|
382
|
+
| `comparability` | Condition | Permitted claims |
|
|
383
|
+
|---|---|---|
|
|
384
|
+
| `exact` | Same scope and inventory; both runs `complete`; same instrument (§6.2) | All status transitions, including `unchanged`. |
|
|
385
|
+
| `scope_changed` | Same selector, but tests were added/removed/renamed | Transitions over common IDs, plus added/removed. A removed test MUST NOT be counted as repaired. |
|
|
386
|
+
| `subset` | Baseline selector is a proven proper superset of the current selector; both runs `complete`; same instrument | Transitions over common IDs, plus `test-added` for current-only IDs. Baseline IDs matching the current selector but unobserved are `removed`; baseline IDs outside it are `out_of_scope` — never `repaired`, never conflated with `removed` or `not_observed`. Red `out_of_scope` IDs MUST be listed. Every `subset` comparison MUST emit a `selector-subset` event (§7.4), which carries both selectors. All claims are bounded to the baseline's scope; claims about any wider suite MUST NOT be asserted. |
|
|
387
|
+
| `partial` | Declared scope not fully observed | Only facts observed in the current run. `repaired`, `missing`, and `unchanged` MUST NOT be asserted. |
|
|
388
|
+
| `none` | No comparable baseline | Structured current-run results only. |
|
|
389
|
+
|
|
390
|
+
When the conditions of more than one level hold, the comparator MUST assign
|
|
391
|
+
the highest level in the order `exact` > `scope_changed` > `subset` >
|
|
392
|
+
`partial`.
|
|
393
|
+
|
|
394
|
+
A comparator MUST NOT claim more than its comparability level permits (INV-7).
|
|
395
|
+
`comparability` and its reason appear in every report.
|
|
396
|
+
|
|
397
|
+
### 6.2. Same-instrument rule
|
|
398
|
+
|
|
399
|
+
If adapter name, adapter version, or runner config digest differ between the
|
|
400
|
+
two runs, the measuring instrument itself changed. The comparator MUST NOT
|
|
401
|
+
claim `exact`; it MUST report comparability `none` with reason
|
|
402
|
+
`instrument-changed`. (Evidence digests are not comparable across instrument
|
|
403
|
+
changes — formatting drift would surface as false `updated` findings.)
|
|
404
|
+
|
|
405
|
+
### 6.3. Reasons for `none` (closed enum)
|
|
406
|
+
|
|
407
|
+
"We determined the runs are incomparable" and "the tool did not run properly"
|
|
408
|
+
are different statements and get different vocabulary. `comparability_detail`
|
|
409
|
+
is REQUIRED whenever `comparability` is `none`:
|
|
410
|
+
|
|
411
|
+
```json
|
|
412
|
+
{ "reason": "baseline-missing", "kind": "determined" }
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
| `reason` | `kind` |
|
|
416
|
+
|---|---|
|
|
417
|
+
| `baseline-missing` | `determined` |
|
|
418
|
+
| `stream-mismatch` | `determined` |
|
|
419
|
+
| `instrument-changed` | `determined` |
|
|
420
|
+
| `selector-relation-unknown` | `determined` |
|
|
421
|
+
| `store-corrupt` | `failed` |
|
|
422
|
+
| `adapter-crashed` | `failed` |
|
|
423
|
+
| `record-integrity-failed` | `failed` |
|
|
424
|
+
|
|
425
|
+
`kind: failed` reasons additionally trigger the fail-open behavior of INV-5
|
|
426
|
+
where applicable. This enum is closed (§14).
|
|
427
|
+
|
|
428
|
+
### 6.4. Selector semantics and containment proof
|
|
429
|
+
|
|
430
|
+
A recorded selector denotes the invocation's **inclusion intent**. Exclusion
|
|
431
|
+
mechanisms (e.g., pytest `--deselect`, `--ignore`, `collect_ignore`,
|
|
432
|
+
deselection hooks) MUST NOT narrow the recorded selector: an ID matching the
|
|
433
|
+
inclusion selector but not observed is in-scope non-observation (`removed` or
|
|
434
|
+
`not_observed`), never `out_of_scope`.
|
|
435
|
+
|
|
436
|
+
Cross-selector comparability (`subset`) requires a deterministic containment
|
|
437
|
+
proof. Adapters MAY declare the `selector-relation` capability, providing
|
|
438
|
+
pure, documented functions
|
|
439
|
+
`selector_relation(a, b) → equal | subset | superset | disjoint | unknown` and
|
|
440
|
+
`selector_matches(selector, test_id) → yes | no | unknown`, evaluated over
|
|
441
|
+
inclusion intent only and covered by conformance fixtures. Where the relation
|
|
442
|
+
is undecidable (e.g., pytest `-k`/`-m` expressions, vitest
|
|
443
|
+
`--testNamePattern` or `--changed`), the adapter MUST return `unknown`;
|
|
444
|
+
comparators MUST treat `unknown` as absence of proof, never as containment.
|
|
445
|
+
|
|
446
|
+
## 7. Delta taxonomy
|
|
447
|
+
|
|
448
|
+
Deltas are reported on three axes, never collapsed into one array:
|
|
449
|
+
**outcome delta** (§7.2), **failure-mode delta** (§7.3), and
|
|
450
|
+
**verification-surface delta** (§7.4).
|
|
451
|
+
|
|
452
|
+
### 7.1. Canonical verdicts
|
|
453
|
+
|
|
454
|
+
`pass | fail | error | skip | xfail | xpass | not_run`
|
|
455
|
+
|
|
456
|
+
The **red set** is `{fail, error}`. Adapters MUST map any runner outcome the
|
|
457
|
+
runner itself treats as failing (e.g., strict-mode `xpass` in pytest) into the
|
|
458
|
+
red set. Verdicts derive exclusively from the runner's verdict channel, never
|
|
459
|
+
from output text (INV-3).
|
|
460
|
+
|
|
461
|
+
### 7.2. Outcome verdict
|
|
462
|
+
|
|
463
|
+
`outcome_verdict`: `regressed | improved | unchanged | inconclusive`
|
|
464
|
+
|
|
465
|
+
If even one new or updated failure exists, `regressed` takes precedence over
|
|
466
|
+
any concurrent repairs; the breakdown is preserved in `transitions`. No blended
|
|
467
|
+
scores, no confidence values.
|
|
468
|
+
|
|
469
|
+
### 7.3. Failure-mode delta
|
|
470
|
+
|
|
471
|
+
Test-ID identity and failure identity are separated. For tests red in either
|
|
472
|
+
run:
|
|
473
|
+
|
|
474
|
+
| Transition | Meaning |
|
|
475
|
+
|---|---|
|
|
476
|
+
| `new_fail` | Not red at baseline (or newly added and red), red now. |
|
|
477
|
+
| `still_fail_unchanged` | Red in both; `evidence_digest` identical. Listed by ID; detail suppressed but never omitted (INV-1, INV-2). |
|
|
478
|
+
| `updated_fail` | Red in both; `evidence_digest` changed. Carries `evidence_digest_before/after` and `failure_mode_changed: true` when the structural fingerprint also changed. |
|
|
479
|
+
| `repaired_*` | Red at baseline, not red now — decomposed per §7.5. |
|
|
480
|
+
|
|
481
|
+
If the structural fingerprint matches but raw evidence changed, the transition
|
|
482
|
+
is still `updated_fail`. Rounding "similar failures" together is never in the
|
|
483
|
+
trust path (INV-8).
|
|
484
|
+
|
|
485
|
+
### 7.4. Verification-surface delta
|
|
486
|
+
|
|
487
|
+
"Red disappeared" and "the same check passed" are separated. Deterministically
|
|
488
|
+
observable surface changes are reported as `verification_surface.events`:
|
|
489
|
+
|
|
490
|
+
- inventory `test-added` / `test-removed` / `test-renamed` (rename only when
|
|
491
|
+
the adapter can prove it uniquely)
|
|
492
|
+
- verdict-class moves into suppression: `fail-to-skip`, `fail-to-xfail`
|
|
493
|
+
- `selector-changed`, `runner-config-changed`, `adapter-capability-changed`
|
|
494
|
+
- `selector-subset` — the current selector is a proven proper subset of the
|
|
495
|
+
baseline selector; carries both selectors and the proving capability.
|
|
496
|
+
Distinct from `selector-changed` (unproven or non-containment change)
|
|
497
|
+
- `test-source-changed`, `config-source-changed` (digest changes of test/config
|
|
498
|
+
sources)
|
|
499
|
+
|
|
500
|
+
`verification_surface.status`: `intact | changed | reduced | inconclusive`.
|
|
501
|
+
`changed` covers source/config digest drift alone; `reduced` is REQUIRED
|
|
502
|
+
whenever the observed scope demonstrably shrank *within the compared scope*
|
|
503
|
+
(fail→skip/xfail, deletions, lost observation). Proven selector narrowing is
|
|
504
|
+
carried by the `subset` comparability level and its mandatory
|
|
505
|
+
`selector-subset` event (§6.1, §11.1), never by `status`: under `subset`
|
|
506
|
+
comparability, `verification_surface.status` is computed over the current
|
|
507
|
+
selector's scope, so `reduced` retains its alarm value for in-scope reduction
|
|
508
|
+
rather than firing on every deliberate subset run. An unproven selector change
|
|
509
|
+
precludes cross-selector comparability entirely (§5.2, §6.3). Coverage-based
|
|
510
|
+
surface evidence is handled only by adapters that declare it; otherwise
|
|
511
|
+
`inconclusive` (Appendix A).
|
|
512
|
+
|
|
513
|
+
Implementations MUST NOT claim intent or causation ("test was weakened to
|
|
514
|
+
cheat", "your change caused this"). The observation is the deliverable; the
|
|
515
|
+
judgment belongs to the consumer.
|
|
516
|
+
|
|
517
|
+
### 7.5. Repaired decomposition
|
|
518
|
+
|
|
519
|
+
`repaired` is never a single bucket:
|
|
520
|
+
|
|
521
|
+
| Class | Meaning |
|
|
522
|
+
|---|---|
|
|
523
|
+
| `repaired_same_surface` | Same test, same scope, fail→pass, test/config digests unchanged. |
|
|
524
|
+
| `repaired_with_test_change` | fail→pass, but test or config source changed. |
|
|
525
|
+
| `fail_to_skip` / `fail_to_xfail` | Transition into suppression. **Not repaired.** |
|
|
526
|
+
| `removed` / `not_observed` | Deleted or unobserved. **Not repaired.** |
|
|
527
|
+
| `out_of_scope` | Red at baseline, provably outside the current run's inclusion selector (§6.4). Occurs **only** under `subset` comparability (§6.1). **Not repaired. Not removed. Not `not_observed`.** Red IDs listed individually; non-red out-of-scope items MAY be reported as counts in `observation_coverage`. |
|
|
528
|
+
| `verification_inconclusive` | Adapter capability or provenance insufficient to classify. |
|
|
529
|
+
|
|
530
|
+
### 7.6. Normalization and masking constraints
|
|
531
|
+
|
|
532
|
+
- Free-form, caller-supplied masking/keying/watching expressions MUST NOT
|
|
533
|
+
exist in the trust path. (Design history: Appendix B.)
|
|
534
|
+
- Curated maskers, if implemented at all, MUST NOT touch status, test IDs, or
|
|
535
|
+
assertion regions; they are OPTIONAL, default-off, and restricted to detail
|
|
536
|
+
deduplication (e.g., durations).
|
|
537
|
+
- Any suppression a masker performs MUST be loudly accounted: count and at
|
|
538
|
+
least one concrete example per application, in the report.
|
|
539
|
+
|
|
540
|
+
### 7.7. Flakiness
|
|
541
|
+
|
|
542
|
+
Implementations MUST NOT infer flakiness from a two-run alternation. A
|
|
543
|
+
`flaky`-class label is permitted only from the runner's own retry verdict, or
|
|
544
|
+
from three or more comparable historical runs (`observed-flaky`, post-1.0 —
|
|
545
|
+
Appendix A). Flaky annotations never suppress new/updated failure reporting.
|
|
546
|
+
|
|
547
|
+
### 7.8. Determinism
|
|
548
|
+
|
|
549
|
+
Same input runs + same configuration → byte-identical report. Reports contain
|
|
550
|
+
no timestamps and use stable sort orders. Runners that execute tests in
|
|
551
|
+
parallel workers deliver results in nondeterministic order; recorders MUST
|
|
552
|
+
order observations canonically (by test ID) so that run records and reports
|
|
553
|
+
are independent of arrival order. This is proven by conformance fixtures over
|
|
554
|
+
adversarial inputs: reordering, partial execution, flakiness, secret-bearing
|
|
555
|
+
output, branch crossing (§13).
|
|
556
|
+
|
|
557
|
+
## 8. Trust invariants (normative core)
|
|
558
|
+
|
|
559
|
+
Safety takes precedence over budget: if the mandatory failure IDs and
|
|
560
|
+
observation coverage exceed a requested `--budget`, the implementation MUST NOT
|
|
561
|
+
omit them; it exceeds the budget and sets `budget_exceeded_for_safety: true`.
|
|
562
|
+
|
|
563
|
+
Each invariant carries a **justification class** (general rule: any distrust
|
|
564
|
+
mechanism must declare one, plus a sunset path if capability-bound):
|
|
565
|
+
|
|
566
|
+
- **incentive-structural** — guards against actors whose incentive to game the
|
|
567
|
+
mechanism *grows* with capability. Never sunset.
|
|
568
|
+
- **blast-radius** — guards against failure modes whose cost is unbounded
|
|
569
|
+
regardless of actor intent. Permanent.
|
|
570
|
+
- **capability-bound** — compensates for current model/tooling limitations;
|
|
571
|
+
MUST declare a re-evaluation trigger. *(None of INV-1..11 is in this class;
|
|
572
|
+
the class exists so future mechanisms must self-classify.)*
|
|
573
|
+
|
|
574
|
+
| # | Invariant | Class |
|
|
575
|
+
|---|---|---|
|
|
576
|
+
| **INV-1** | Never report green where red exists: failing items are never omitted and never masked into `unchanged`. | blast-radius |
|
|
577
|
+
| **INV-2** | Never omit without accounting: suppressed items' existence and status always appear in observation coverage. Only the *detail* of unchanged/passing items may be suppressed. | blast-radius |
|
|
578
|
+
| **INV-3** | Status derives from the runner's verdict channel, never from any text a masker could touch. Green→red concealment is structurally impossible, not policy-forbidden. | blast-radius |
|
|
579
|
+
| **INV-4** | Completeness gate: incomplete/partial/crashed runs never yield `repaired`/`missing` as fact; they yield "N items unobserved". | blast-radius |
|
|
580
|
+
| **INV-5** | Fail-open / crash-safe: config errors, cache faults, lock contention, and internal errors all degrade to transparent passthrough of the child's raw output and exit code. **veridelta is never worse than its absence.** | blast-radius |
|
|
581
|
+
| **INV-6** | Determinism: same input + same config → same output, proven against adversarial conformance fixtures. | blast-radius |
|
|
582
|
+
| **INV-7** | Claims never exceed comparability: baseline mismatch → abstain from all transitions; scope change → common IDs only; incomplete runs → no `repaired`/`missing`/`unchanged`. Comparability with reason is always returned. | blast-radius |
|
|
583
|
+
| **INV-8** | Red→red mutation is never buried in `still-fail/unchanged`: if the lossless evidence digest changed, the transition is `updated`. Structural fingerprints cluster; they never conceal. | incentive-structural |
|
|
584
|
+
| **INV-9** | Red that vanished by shrinking the verification surface is never called repaired: fail→skip/xfail, deletion, selector narrowing, non-observation, and capability loss are reported as surface changes, separate from `repaired`. | incentive-structural |
|
|
585
|
+
| **INV-10** | The threat model includes the **deceiving** agent, not just the confused one. The agent can shell into the local store, so local outputs are advisory; gate verdicts require tamper-evident records (content addressing + signature verification) or re-verification in a trusted environment (CI). Every report states which threat model each claim holds under. | incentive-structural |
|
|
586
|
+
| **INV-11** | "Honest but stale" records are never gated on (TOCTOU): the gate verifies exact content-hash equality between the run's `tree_digest` and the judged target before judging. Mismatch is `inconclusive` — no proximity or ratio rescue. Staleness is a distinct threat from tampering; both are closed independently. | incentive-structural |
|
|
587
|
+
|
|
588
|
+
## 9. Report contract
|
|
589
|
+
|
|
590
|
+
### 9.1. Comparison report
|
|
591
|
+
|
|
592
|
+
JSON is the primary interface. Human-readable text MUST be a secondary
|
|
593
|
+
rendering of this schema with no independent logic.
|
|
594
|
+
|
|
595
|
+
```json
|
|
596
|
+
{
|
|
597
|
+
"schema_version": "veridelta/1",
|
|
598
|
+
"outcome_verdict": "regressed",
|
|
599
|
+
"comparability": "exact",
|
|
600
|
+
"baseline": {
|
|
601
|
+
"run_id": "run_abc123",
|
|
602
|
+
"mode": "previous-comparable",
|
|
603
|
+
"selection_reason": "same-worktree-command-config-scope"
|
|
604
|
+
},
|
|
605
|
+
"current": {
|
|
606
|
+
"run_id": "run_def456",
|
|
607
|
+
"complete": true,
|
|
608
|
+
"child_exit_code": 1
|
|
609
|
+
},
|
|
610
|
+
"observation_coverage": {
|
|
611
|
+
"baseline": "842/842",
|
|
612
|
+
"current": "842/842"
|
|
613
|
+
},
|
|
614
|
+
"verification_surface": {
|
|
615
|
+
"status": "changed",
|
|
616
|
+
"events": [
|
|
617
|
+
{
|
|
618
|
+
"kind": "test-source-changed",
|
|
619
|
+
"test_id": "tests/api/test_user.py::test_update_user"
|
|
620
|
+
}
|
|
621
|
+
]
|
|
622
|
+
},
|
|
623
|
+
"transitions": {
|
|
624
|
+
"new_fail": ["tests/api/test_user.py::test_create_user"],
|
|
625
|
+
"still_fail_unchanged": ["tests/legacy/test_import.py::test_v2"],
|
|
626
|
+
"updated_fail": [
|
|
627
|
+
{
|
|
628
|
+
"test_id": "tests/legacy/test_import.py::test_v1",
|
|
629
|
+
"evidence_digest_before": "sha256:111",
|
|
630
|
+
"evidence_digest_after": "sha256:222",
|
|
631
|
+
"failure_mode_changed": true
|
|
632
|
+
}
|
|
633
|
+
],
|
|
634
|
+
"repaired_same_surface": [],
|
|
635
|
+
"repaired_with_test_change": ["tests/api/test_user.py::test_update_user"],
|
|
636
|
+
"fail_to_skip": [],
|
|
637
|
+
"fail_to_xfail": [],
|
|
638
|
+
"removed": [],
|
|
639
|
+
"not_observed": []
|
|
640
|
+
},
|
|
641
|
+
"failure_evidence": {
|
|
642
|
+
"composition_id": "pytest-native/1",
|
|
643
|
+
"degraded_capabilities": []
|
|
644
|
+
},
|
|
645
|
+
"trust": {
|
|
646
|
+
"record_integrity": "advisory"
|
|
647
|
+
},
|
|
648
|
+
"anchors": {
|
|
649
|
+
"new_fail:tests/api/test_user.py::test_create_user":
|
|
650
|
+
"vdelta show run_def456 --test tests/api/test_user.py::test_create_user",
|
|
651
|
+
"raw": "vdelta show run_def456 --raw"
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
Additional REQUIRED-when-applicable fields:
|
|
657
|
+
|
|
658
|
+
- `comparability_detail` when `comparability` is `none` (§6.3).
|
|
659
|
+
- `budget_exceeded_for_safety: true` when §8's budget rule fires.
|
|
660
|
+
- `masking_applied` with count and example when §7.6 dedup fired.
|
|
661
|
+
- `trust.record_integrity`: `advisory | tamper-evident | trusted-environment`
|
|
662
|
+
(INV-10).
|
|
663
|
+
|
|
664
|
+
Reports MUST include `failure_evidence.composition_id` (the adapter's declared
|
|
665
|
+
canonical-evidence composition and version, §3.6) and
|
|
666
|
+
`failure_evidence.degraded_capabilities` (signal-bearing evidence capabilities
|
|
667
|
+
declared `unsupported`, §3.4; empty list otherwise). When
|
|
668
|
+
`degraded_capabilities` is non-empty, every `still_fail_unchanged` and
|
|
669
|
+
`updated_fail` claim MUST carry it, consumers MUST relay it with the claim,
|
|
670
|
+
and the gate report MUST surface it under every policy. Gates MAY treat
|
|
671
|
+
degraded `still_fail_unchanged` as gate-relevant; the default gate-relevant
|
|
672
|
+
set is unchanged.
|
|
673
|
+
|
|
674
|
+
Entries in `still_fail_unchanged` are test-ID strings, or objects
|
|
675
|
+
`{"test_id": ..., "context_changed": true}` when §3.6 requires the context
|
|
676
|
+
flag; the corresponding drill-down anchor appears under `anchors`.
|
|
677
|
+
|
|
678
|
+
### 9.2. Budget
|
|
679
|
+
|
|
680
|
+
`--budget N` (tokens) is a first-class parameter: the implementation returns
|
|
681
|
+
the most informative representation within N tokens, deterministically. Budget
|
|
682
|
+
never overrides safety (§8).
|
|
683
|
+
|
|
684
|
+
### 9.3. Anchors
|
|
685
|
+
|
|
686
|
+
Every omission leaves a drill-down anchor: a concrete command (or address) that
|
|
687
|
+
retrieves the elided detail (`vdelta show <run> --test <id>`, `--raw`).
|
|
688
|
+
Progressive disclosure is mandatory — a consumer can always reach raw evidence.
|
|
689
|
+
|
|
690
|
+
### 9.4. Consumer requirements
|
|
691
|
+
|
|
692
|
+
- Consumers MUST treat an unknown value in any closed enum as a hard error
|
|
693
|
+
(throw), never as a default or a silent skip. Backward-compatibility
|
|
694
|
+
scaffolding around unknown enum values is non-conforming.
|
|
695
|
+
- Consumers MUST NOT reinterpret unknown fields; unknown fields SHOULD be
|
|
696
|
+
rejected (§14).
|
|
697
|
+
- Consumers MUST honor `comparability` limits when relaying claims onward.
|
|
698
|
+
|
|
699
|
+
## 10. Command interface (reference CLI)
|
|
700
|
+
|
|
701
|
+
Command names are illustrative of the reference implementation; the division of
|
|
702
|
+
responsibilities and exit-code semantics are normative for any CLI claiming
|
|
703
|
+
conformance.
|
|
704
|
+
|
|
705
|
+
| Command | Responsibility | Exit code |
|
|
706
|
+
|---|---|---|
|
|
707
|
+
| `vdelta run -- <cmd>` | Execute, record, report | **Transparent child exit.** Internal errors degrade to raw passthrough (INV-5). |
|
|
708
|
+
| `vdelta compare <run-a> <run-b>` | Compare immutable runs | Success of the comparison operation itself (an `inconclusive` *result* is a successful comparison). |
|
|
709
|
+
| `vdelta show <run-id> [--test <id>\|--raw]` | Drill-down retrieval | Retrieval success. |
|
|
710
|
+
| `vdelta gate` | Policy verdict for CI/agent loops | `0` no gate-relevant transitions; `1` regression or surface reduction; `2` inconclusive or error (§11). |
|
|
711
|
+
| `vdelta doctor` *(OPTIONAL)* | Store-health diagnostics: comparable-run density, inconclusive rate, passthrough rate | Diagnostic success. Small samples MUST abstain (`insufficient_data`), not extrapolate. |
|
|
712
|
+
|
|
713
|
+
The child's "tests failed" and "something regressed since baseline" are never
|
|
714
|
+
encoded in the same exit code: `run` owns transparency, `gate` owns semantics.
|
|
715
|
+
|
|
716
|
+
**`run` I/O contract.** In the normal path, `vdelta run` captures the child's
|
|
717
|
+
output into the run record and emits the delta report to stdout (`--report
|
|
718
|
+
json` for the primary JSON contract; the secondary human rendering otherwise)
|
|
719
|
+
— replacing raw output with the report is the point of the tool, and the raw
|
|
720
|
+
stream stays reachable via anchors (`show --raw`). The child's exit code is
|
|
721
|
+
passed through unchanged, including signal-death conventions. In the degraded
|
|
722
|
+
path (INV-5), the child's raw stdout/stderr are passed through verbatim to
|
|
723
|
+
their respective streams and no report is emitted. veridelta's own
|
|
724
|
+
diagnostics go to stderr and MUST NOT interleave with the report on stdout.
|
|
725
|
+
Implementations MAY offer opt-in live passthrough for long-running suites;
|
|
726
|
+
recording and the report are unaffected by it.
|
|
727
|
+
|
|
728
|
+
## 11. Gate
|
|
729
|
+
|
|
730
|
+
### 11.1. Policies and the reporting floor
|
|
731
|
+
|
|
732
|
+
`policy` is a closed enum — there is no free-form selection of which findings
|
|
733
|
+
exist:
|
|
734
|
+
|
|
735
|
+
| Policy | Exit behavior | Intended stage |
|
|
736
|
+
|---|---|---|
|
|
737
|
+
| `report-only` | Always `0` (report published, e.g., PR comment); `2` only if the gate itself cannot produce a report | First deployment; builds trust without blocking. |
|
|
738
|
+
| `advisory` | Exit codes as `blocking`, but the report marks `policy: advisory` so harnesses surface it as a warning | Transition stage. |
|
|
739
|
+
| `blocking` | `1` on gate-relevant transitions, `2` on inconclusive | Required check. |
|
|
740
|
+
|
|
741
|
+
**Ungameable floor:** under every policy, `new_fail` findings and
|
|
742
|
+
`verification_surface` reduction events MUST appear in the output, and no flag,
|
|
743
|
+
configuration, or policy choice may remove them. Implementations MAY allow
|
|
744
|
+
narrowing which transitions *block*, but never which are *reported*; a
|
|
745
|
+
`blocking`-policy gate is further constrained by the blocking-set floor of
|
|
746
|
+
§11.5.
|
|
747
|
+
|
|
748
|
+
The default gate-relevant set is `{new_fail, updated_fail,
|
|
749
|
+
verification-surface reduced}`.
|
|
750
|
+
|
|
751
|
+
When comparing against a `git-ref` baseline, the gate MUST treat a
|
|
752
|
+
`selector-subset` event as gate-relevant: proven selector narrowing relative
|
|
753
|
+
to the baseline yields gate verdict `fail`, with common-ID transitions still
|
|
754
|
+
reported. An unproven selector change (`selector-relation-unknown`) yields
|
|
755
|
+
gate verdict `inconclusive`. Exit behavior follows the policy table: under
|
|
756
|
+
`blocking`, `1` and `2` respectively. Neither outcome ever maps to verdict
|
|
757
|
+
`pass` under any policy. `subset` comparability never relaxes §11.2 or §11.3.
|
|
758
|
+
|
|
759
|
+
### 11.2. Record integrity (INV-10)
|
|
760
|
+
|
|
761
|
+
The agent under audit can execute arbitrary commands, including edits to a
|
|
762
|
+
local run store. Therefore:
|
|
763
|
+
|
|
764
|
+
- Verdicts computed from an unverified local store are `advisory` and MUST be
|
|
765
|
+
labeled as such.
|
|
766
|
+
- A `blocking` or `advisory` gate verdict MUST be based on records whose
|
|
767
|
+
content addresses (and, where configured, signatures) verify, or on
|
|
768
|
+
re-execution in an environment the agent cannot write to (CI).
|
|
769
|
+
- Verification failure is `comparability_detail: {reason:
|
|
770
|
+
"record-integrity-failed", kind: "failed"}` → exit `2`.
|
|
771
|
+
|
|
772
|
+
### 11.3. Staleness check (INV-11)
|
|
773
|
+
|
|
774
|
+
Before judging, the gate MUST verify that the run's `provenance.tree_digest`
|
|
775
|
+
equals the content digest of the judged target (e.g., PR HEAD) **exactly**.
|
|
776
|
+
Binary equality only: no ratio, proximity, or "close enough" rescue. Mismatch →
|
|
777
|
+
`inconclusive`, exit `2`. Tampering (§11.2) and staleness are independent
|
|
778
|
+
checks; passing one never waives the other.
|
|
779
|
+
|
|
780
|
+
The gate MUST recompute the target's `tree_digest` from the workspace in which
|
|
781
|
+
the head-side run executed, immediately before judging. `git rev-parse
|
|
782
|
+
HEAD^{tree}` MAY be used as the canonical equivalent only when `git status
|
|
783
|
+
--porcelain --ignore-submodules=none` reports a clean workspace AND the judged
|
|
784
|
+
tree contains no gitlink entries. When submodules are present, the gate MUST
|
|
785
|
+
verify each initialized submodule worktree recursively: the submodule
|
|
786
|
+
worktree's `tree_digest` MUST equal the tree of the commit named by the
|
|
787
|
+
superproject's gitlink; any mismatch is `inconclusive`, exit 2. Submodules
|
|
788
|
+
without an initialized worktree cannot be content-verified: the gate report
|
|
789
|
+
MUST list them under `staleness.unverified_submodules`. The superproject
|
|
790
|
+
digest alone does not witness submodule content.
|
|
791
|
+
|
|
792
|
+
### 11.4. Gate report
|
|
793
|
+
|
|
794
|
+
The gate report extends the comparison report (§9.1) with:
|
|
795
|
+
|
|
796
|
+
```json
|
|
797
|
+
{
|
|
798
|
+
"gate": {
|
|
799
|
+
"policy": "report-only",
|
|
800
|
+
"verdict": "fail",
|
|
801
|
+
"triggered": ["new_fail", "verification_surface_reduced"],
|
|
802
|
+
"target": {
|
|
803
|
+
"kind": "merge",
|
|
804
|
+
"head_sha": "1a2b3c...",
|
|
805
|
+
"base_sha": "4d5e6f...",
|
|
806
|
+
"merge_sha": "7a8b9c..."
|
|
807
|
+
},
|
|
808
|
+
"staleness": {
|
|
809
|
+
"run_tree_digest": "sha256:aaa",
|
|
810
|
+
"target_tree_digest": "sha256:aaa",
|
|
811
|
+
"match": true,
|
|
812
|
+
"unverified_submodules": []
|
|
813
|
+
},
|
|
814
|
+
"record_integrity": "tamper-evident"
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
```
|
|
818
|
+
|
|
819
|
+
`gate.verdict`: `pass | fail | inconclusive` — reported identically under all
|
|
820
|
+
policies; policy affects only exit behavior.
|
|
821
|
+
|
|
822
|
+
### 11.5. Gate invocation contract (CI)
|
|
823
|
+
|
|
824
|
+
**Baseline supply.** A gate producing `blocking` or `advisory` verdicts MUST
|
|
825
|
+
support `same-job` mode: the gate job checks out the base tree and the target
|
|
826
|
+
tree and executes and records both runs itself. A gate MAY additionally
|
|
827
|
+
support `trusted-store` mode, substituting a stored base-branch run for
|
|
828
|
+
base-side execution only if all of the following verify: (1) record integrity
|
|
829
|
+
per §11.2; (2) exact `tree_digest` equality with the base tree; (3) exact
|
|
830
|
+
instrument identity per §6.2 and this section. On any failure the gate MUST
|
|
831
|
+
fall back to same-job base execution or return `inconclusive` (exit 2); it
|
|
832
|
+
MUST NOT select a weaker baseline (§5.3). Records produced inside the audited
|
|
833
|
+
agent's writable environment MUST NOT feed `blocking` or `advisory` verdicts
|
|
834
|
+
(INV-10).
|
|
835
|
+
|
|
836
|
+
**Isolation and sealing (INV-10).** Same-job execution qualifies as
|
|
837
|
+
`trusted-environment` only under this ordering contract: (1) the base-side run
|
|
838
|
+
MUST complete before any code from the audited change (tests,
|
|
839
|
+
conftest/build/install hooks, dependency resolution of the head tree) executes
|
|
840
|
+
in the job; (2) before any such code executes, the gate MUST seal the base
|
|
841
|
+
record's content address and a digest of the gate toolchain (gate executable,
|
|
842
|
+
runtime, configuration) by exporting them to an append-only channel outside
|
|
843
|
+
the job workspace (e.g., the CI step log or a check annotation); where the
|
|
844
|
+
channel permits later appends, verifiers MUST bind to the earliest seal entry
|
|
845
|
+
for the evaluation; (3) immediately before judging, the gate MUST re-verify
|
|
846
|
+
the base record and its own toolchain against the sealed values — any mismatch
|
|
847
|
+
is `record-integrity-failed` (§6.3), exit 2; (4) because step (3) executes
|
|
848
|
+
after the audited change's code has run in the job, a `blocking` verdict
|
|
849
|
+
additionally REQUIRES that the judgment's integrity is established outside the
|
|
850
|
+
audited change's write reach: either (a) the judgment runs in a separate job
|
|
851
|
+
or runner that receives only the sealed records and independently verifies
|
|
852
|
+
them against the sealed values, or (b) the party enforcing the verdict
|
|
853
|
+
independently verifies the published report and records against the sealed
|
|
854
|
+
values before acting on it. A gate that judges in-job without (a) or (b) MUST
|
|
855
|
+
cap `trust.record_integrity` at `tamper-evident` and its verdict at
|
|
856
|
+
`advisory`: the seals make in-job tampering detectable by an external auditor,
|
|
857
|
+
but an in-job judgment cannot witness its own integrity (§15). If the gate's
|
|
858
|
+
invocation path (workflow definition, checkout procedure, or gate-binary
|
|
859
|
+
resolution) is modifiable by the change's author for the evaluation at hand,
|
|
860
|
+
the verdict MUST NOT be `blocking`.
|
|
861
|
+
|
|
862
|
+
**Target definition.** The judged target is a tree, not a ref. The head-side
|
|
863
|
+
run MUST execute against the tree being judged (INV-11); testing a synthetic
|
|
864
|
+
merge tree while judging the head commit tree, or vice versa, is
|
|
865
|
+
non-conforming. The gate report MUST bind the judged tree to its provenance:
|
|
866
|
+
`gate.target: {kind: "head" | "merge", head_sha, base_sha, merge_sha?}`. For
|
|
867
|
+
`kind: "merge"` the base tree is the first parent's tree; for `kind: "head"`
|
|
868
|
+
it is the tree of `merge-base(base_branch, head_sha)`. A `pass` certifies the
|
|
869
|
+
judged tree only; re-validation after base movement is the responsibility of
|
|
870
|
+
branch protection, and the report MUST expose `base_sha` to enable it.
|
|
871
|
+
|
|
872
|
+
**Instrument identity.** Same-job execution does not waive §6.2. Each run MUST
|
|
873
|
+
execute in a dependency environment resolved from its own tree, and the run's
|
|
874
|
+
`environment` fingerprint MUST include the resolved runner plugin set and
|
|
875
|
+
adapter-declared runner-affecting environment variables (for pytest: including
|
|
876
|
+
`PYTEST_ADDOPTS`). Implementations SHOULD resolve both environments from
|
|
877
|
+
committed lockfiles where available, so that registry drift between the two
|
|
878
|
+
in-job resolutions cannot manufacture an instrument mismatch. Any instrument
|
|
879
|
+
mismatch between the two runs yields `none`/`instrument-changed`, exit 2, with
|
|
880
|
+
the corresponding `verification_surface` events (§7.4) in the report.
|
|
881
|
+
|
|
882
|
+
**Bytecode-cache hygiene.** Before each run, the recorder MUST ensure the
|
|
883
|
+
interpreter cannot load compilation or assertion-rewrite caches not derived
|
|
884
|
+
from that run's tree (§4.5). For CPython the RECOMMENDED mechanism is a fresh
|
|
885
|
+
`PYTHONPYCACHEPREFIX` per run, which neutralizes both stale and committed
|
|
886
|
+
`__pycache__` content without mutating the worktree; purging caches is
|
|
887
|
+
acceptable only for untracked files (deleting tracked files diverges the
|
|
888
|
+
workspace from the judged tree). `PYTHONDONTWRITEBYTECODE` alone is NOT
|
|
889
|
+
sufficient: it does not prevent reading pre-existing caches. Adapters SHOULD
|
|
890
|
+
report tracked bytecode files in the judged tree as a `verification_surface`
|
|
891
|
+
event.
|
|
892
|
+
|
|
893
|
+
**Blocking-set floor.** For `policy: blocking`, the gate-relevant set MUST
|
|
894
|
+
include `new_fail`, `updated_fail`, and `verification_surface` reduction. The
|
|
895
|
+
narrowing permitted by §11.1 MUST NOT remove `updated_fail` from a blocking
|
|
896
|
+
gate's set; this clause takes precedence over §11.1.
|
|
897
|
+
|
|
898
|
+
**Claim scope.** Gate claims are facts about the two recorded runs: `new_fail`
|
|
899
|
+
asserts "red in the head run and not red in the base run", nothing stronger.
|
|
900
|
+
The gate MUST NOT rerun tests to derive flakiness (§7.7). A base run that is
|
|
901
|
+
not `complete` is not an acceptable baseline (§5.2) and yields `inconclusive`,
|
|
902
|
+
exit 2 — never `pass`.
|
|
903
|
+
|
|
904
|
+
## 12. Adapters
|
|
905
|
+
|
|
906
|
+
- Adapters are **capability-declared, structured-first** (§3.4). They consume
|
|
907
|
+
runner-native structured channels (plugins, machine-readable reporters), not
|
|
908
|
+
scraped human-oriented text.
|
|
909
|
+
- Fail-closed on ambiguity: duplicate IDs, unknown outcomes, count mismatches,
|
|
910
|
+
and undeclared capabilities yield `inconclusive` (or `kind: failed`
|
|
911
|
+
abstention), never a guess.
|
|
912
|
+
- Unsupported formats pass through raw (INV-5). Line-level fallback parsing is
|
|
913
|
+
excluded from the trust core.
|
|
914
|
+
- Lossy interchange formats (e.g., JUnit XML dialects that drop xfail, phase,
|
|
915
|
+
source, markers) require an explicit supported-subset declaration and a
|
|
916
|
+
capability matrix; anything outside the subset is `unsupported`, not
|
|
917
|
+
approximated.
|
|
918
|
+
|
|
919
|
+
Adapter roadmap for the reference implementation (informative): `vitest`
|
|
920
|
+
native reporter first — agent-authored code skews heavily toward TS/JS, the
|
|
921
|
+
maintainer's own repositories (the cheapest dogfood subjects) are TypeScript,
|
|
922
|
+
and empirical probing confirmed the channel satisfies §3.6 with no
|
|
923
|
+
stale-cache path in run mode. `pytest` native second (its richer structured
|
|
924
|
+
channel is already probed and its composition pre-designed); JUnit XML third,
|
|
925
|
+
for CI breadth, under the constraints above.
|
|
926
|
+
|
|
927
|
+
## 13. Conformance
|
|
928
|
+
|
|
929
|
+
### 13.1. Conformance classes
|
|
930
|
+
|
|
931
|
+
Implementations claim conformance per role: **Recorder**, **Store**,
|
|
932
|
+
**Comparator**, **Gate**, **Adapter**, **Consumer**. The badge
|
|
933
|
+
`veridelta/1 compliant` requires passing the published conformance suite for
|
|
934
|
+
every claimed role.
|
|
935
|
+
|
|
936
|
+
### 13.2. Required fixture classes
|
|
937
|
+
|
|
938
|
+
The conformance suite MUST include, at minimum:
|
|
939
|
+
|
|
940
|
+
1. **Invariant fixtures** — one or more per INV-1..11 (INV-10 may be limited
|
|
941
|
+
to the gate path in early revisions).
|
|
942
|
+
2. **Adversarial input fixtures** — reordering, partial execution, flaky
|
|
943
|
+
sequences, secret-bearing output, branch crossing (§7.8) — including:
|
|
944
|
+
(a) rerun stability — two executions of a deterministically failing test at
|
|
945
|
+
an identical `tree_digest` MUST yield identical `evidence_digest`s;
|
|
946
|
+
(b) stale-cache collision — a same-size source revert under coarse mtime
|
|
947
|
+
resolution MUST NOT reproduce evidence from the prior source state (§4.5);
|
|
948
|
+
(c) degraded-capability marking — a run pair recorded with assertion
|
|
949
|
+
introspection disabled MUST emit non-empty `degraded_capabilities` on
|
|
950
|
+
red-in-both claims.
|
|
951
|
+
3. **Operational pitfall fixtures** — the four classes that recur in practice:
|
|
952
|
+
gate wired to the wrong target (wiring position), instrument drift
|
|
953
|
+
undetected (§6.2), duplicate records double-counted (§4.3), and
|
|
954
|
+
fail-open/fail-closed confusion (§6.3 vs INV-5).
|
|
955
|
+
4. **Consumer fixtures** — unknown enum value MUST throw; comparability limits
|
|
956
|
+
MUST be honored.
|
|
957
|
+
5. **Verification-surface recall fixtures** — labeled cheating corpus
|
|
958
|
+
(fail→skip/xfail, test deletion, repaired-with-test-change, selector
|
|
959
|
+
narrowing, and collection-modifier inputs — `--deselect`, `--ignore`,
|
|
960
|
+
`collect_ignore`-style exclusion — that MUST classify as in-scope
|
|
961
|
+
non-observation, never `out_of_scope`): detection recall MUST be 100% on
|
|
962
|
+
comparable runs.
|
|
963
|
+
|
|
964
|
+
### 13.3. Determinism proof
|
|
965
|
+
|
|
966
|
+
Fixture outputs are byte-compared. Any nondeterminism is a conformance
|
|
967
|
+
failure, not a warning.
|
|
968
|
+
|
|
969
|
+
## 14. Versioning and extensibility
|
|
970
|
+
|
|
971
|
+
- `schema_version` `"veridelta/1"` names this contract. Enums defined here are
|
|
972
|
+
**closed** for `/1`; new enum values require a new schema version. During
|
|
973
|
+
the draft phase (0.x spec revisions), enum sets may still change between
|
|
974
|
+
revisions; closure binds from the first published revision of `veridelta/1`.
|
|
975
|
+
- Field additions within `/1` occur only through published spec revisions;
|
|
976
|
+
consumers SHOULD reject unknown fields and MUST NOT silently reinterpret
|
|
977
|
+
them.
|
|
978
|
+
- Producers MUST emit exactly one schema version per report.
|
|
979
|
+
- The spec is licensed MIT and intended for independent implementation;
|
|
980
|
+
absorbing the schema into other tools (with or without the reference CLI) is
|
|
981
|
+
the intended success mode.
|
|
982
|
+
|
|
983
|
+
## 15. Security considerations
|
|
984
|
+
|
|
985
|
+
- **Secrets**: secret values are never persisted. Environment comparison uses
|
|
986
|
+
fingerprints of adapter-declared variables; failure evidence is redacted for
|
|
987
|
+
known secret shapes before storage and digesting, deterministically (§3.5).
|
|
988
|
+
- **Threat model**: three distinct adversarial conditions are handled
|
|
989
|
+
independently — the *confused* agent (wrong baseline, partial runs; INV-1..9),
|
|
990
|
+
the *deceiving* agent (store tampering; INV-10, §11.2), and the *stale honest
|
|
991
|
+
record* (TOCTOU; INV-11, §11.3). A mechanism addressing one MUST NOT be
|
|
992
|
+
presented as addressing another.
|
|
993
|
+
- **Store**: repo-local, ignored by VCS, ephemeral, bounded; atomic writes;
|
|
994
|
+
advisory fail-open locks (§4.4).
|
|
995
|
+
- The gate MUST run outside the audited agent's blast radius (separate
|
|
996
|
+
process, CI environment) for any non-advisory claim.
|
|
997
|
+
|
|
998
|
+
## Appendix A (informative): Post-1.0 roadmap
|
|
999
|
+
|
|
1000
|
+
| # | Feature | Constraint carried from this spec |
|
|
1001
|
+
|---|---|---|
|
|
1002
|
+
| A.1 | Failure clusters | Cluster by structural fingerprint; keep all IDs, counts, representative evidence, raw anchors; never assert same root cause. |
|
|
1003
|
+
| A.2 | Changed-hunk correlation | `direct / transitive / none / unavailable` intersection of failure locations and changed hunks — prioritization evidence, not causation. |
|
|
1004
|
+
| A.3 | Observed-flaky history | Only from ≥3 comparable runs at the same code state; never suppresses new/updated findings; gate policy stays with the caller. |
|
|
1005
|
+
| A.4 | Coverage surface delta | Per-changed-line/branch execution comparison where a coverage adapter exists; aggregate coverage % alone never proves surface equality. |
|
|
1006
|
+
| A.5 | Causal proof | Opt-in red/green re-verification: stash implementation-only changes → confirm red → restore → confirm green. Proves the test constrains the implementation (no vacuous pass) — a strictly stronger claim than `repaired_same_surface`, kept as a separate proof level. Ambiguous cases (mixed test/impl changes, stash failure, already-green base) stay `inconclusive`. |
|
|
1007
|
+
|
|
1008
|
+
## Appendix B (informative): Rejected designs
|
|
1009
|
+
|
|
1010
|
+
These were removed during adversarial design review and MUST NOT be
|
|
1011
|
+
reintroduced without revisiting the arguments:
|
|
1012
|
+
|
|
1013
|
+
| Rejected | Why |
|
|
1014
|
+
|---|---|
|
|
1015
|
+
| Caller-supplied `--mask` regex in the trust path | `--mask '\d+'` collapses `expected 200 got 500` into `expected N got N` — a silently hidden regression that no diagnostic layer can recover. |
|
|
1016
|
+
| `suggest` (inducing masks from run history) | "Environmental noise" and "values my fix moved" are indistinguishable as run-to-run diffs; suggesting masks from them is a self-propagating blind spot. |
|
|
1017
|
+
| Caller-supplied `--key` regex | Replaced by adapter-provided canonical test IDs; also fixes parametrized-test merge accidents. |
|
|
1018
|
+
| Implicit "previous invocation" baseline | Order-dependent; contaminated by branch switches and partial runs. Replaced by content addressing + explicit selection (§5). |
|
|
1019
|
+
| Agent-interpreted diagnostic meta-loop | Making the agent reason about the tool's health spends the context the tool exists to save. Replaced by self-check → automatic fail-open (INV-5). |
|
|
1020
|
+
|
|
1021
|
+
Two counterarguments that were withdrawn, preserved for future reviewers:
|
|
1022
|
+
|
|
1023
|
+
- "`pytest --lf` suffices" — `--lf` changes *what runs* and can hide
|
|
1024
|
+
regressions in previously-passing tests; veridelta keeps the full suite
|
|
1025
|
+
semantics and changes only *what is looked at*. Orthogonal axes.
|
|
1026
|
+
- "Let the agent diff the outputs" — both full outputs must enter context
|
|
1027
|
+
(defeating the purpose), and model summaries hallucinate and drop items;
|
|
1028
|
+
a deterministic diff structurally cannot.
|
|
1029
|
+
|
|
1030
|
+
## Appendix C (informative): Known residual risks
|
|
1031
|
+
|
|
1032
|
+
Accepted limitations surfaced during adversarial review of revision 0.2.0.
|
|
1033
|
+
None violates an invariant; each is disclosed rather than papered over.
|
|
1034
|
+
|
|
1035
|
+
- **Degraded-evidence repositories.** Where both compared runs were recorded
|
|
1036
|
+
with assertion introspection disabled, a genuine red→red value mutation can
|
|
1037
|
+
yield digest-identical `still_fail_unchanged`. The mandatory
|
|
1038
|
+
`degraded_capabilities` surfacing (§9.1) makes the condition visible;
|
|
1039
|
+
escalating it to gate-relevant remains a MAY.
|
|
1040
|
+
- **Flood valve vs. value signal.** Narrowing blocking to
|
|
1041
|
+
`failure_mode_changed: true` (to absorb honest flapping from
|
|
1042
|
+
volatile-in-message values, §3.6) would also exclude genuine value-only
|
|
1043
|
+
changes, whose structural fingerprint is likewise stable. Hence the
|
|
1044
|
+
blocking-set floor (§11.5); below `blocking`, the tradeoff is the
|
|
1045
|
+
operator's.
|
|
1046
|
+
- **Multiple maximal superset baselines.** When maximal `previous-superset`
|
|
1047
|
+
candidates are mutually non-comparable, recency decides which out-of-scope
|
|
1048
|
+
ledger a report shows. Disclosed via `baseline.superset_candidates`; full
|
|
1049
|
+
closure (a per-series red ledger) is post-1.0.
|
|
1050
|
+
- **Undecidable selectors.** For `-k`/`-m`-style expressions,
|
|
1051
|
+
`selector_relation` returns `unknown` and `previous-superset` abstains — a
|
|
1052
|
+
functional limitation, not a trust hole.
|
|
1053
|
+
- **Cross-stream suppression blind spot (inner loop).** A fail→skip between
|
|
1054
|
+
two superset runs with different selectors appears in no local comparison;
|
|
1055
|
+
the final defense is the gate's full-width `git-ref` comparison.
|
|
1056
|
+
- **Seal channel strength is platform-dependent.** The spec can require only
|
|
1057
|
+
append-only export and earliest-entry binding (§11.5); platforms where the
|
|
1058
|
+
audited job's token can rewrite annotations weaken the evidence.
|
|
1059
|
+
- **Registry drift without lockfiles.** Per-tree dependency resolution twice
|
|
1060
|
+
in one job can hit registry drift, producing PR-unrelated
|
|
1061
|
+
`instrument-changed` abstentions; lockfile resolution is SHOULD-level.
|
|
1062
|
+
- **Uninitialized submodules.** Content verification is impossible; the gate
|
|
1063
|
+
can only disclose them (`staleness.unverified_submodules`).
|
|
1064
|
+
- **Two-run factualism.** A flaky base red whose evidence is byte-identical to
|
|
1065
|
+
the head's deterministic failure classifies as `still_fail_unchanged`;
|
|
1066
|
+
reporting floors keep it visible, but 2-run facts cannot distinguish it.
|
|
1067
|
+
- **Head-run self-forgery is out of contract scope.** A hostile change that
|
|
1068
|
+
makes its own tests report `pass` is common to every architecture that
|
|
1069
|
+
executes the author's code; defenses are the surface events (§7.4) and the
|
|
1070
|
+
non-goal boundary (§1.3) — intent attribution stays with the consumer.
|
|
1071
|
+
|
|
1072
|
+
## Revision history
|
|
1073
|
+
|
|
1074
|
+
- **0.3.0 (2026-07-16)** — First adapter target flipped to vitest after an
|
|
1075
|
+
empirical probe confirmed §3.6 satisfiability (core digest stable across
|
|
1076
|
+
rerun/line-shift/unrelated-edit, sensitive only to genuine signal; no
|
|
1077
|
+
stale-cache path in run mode). Added: channel-absent CE-1 component rule
|
|
1078
|
+
(declare `unsupported` or reconstruct deterministically from the recorded
|
|
1079
|
+
tree, §3.6); runner-cache asymmetry note (§4.5); truncation settings in the
|
|
1080
|
+
instrument digest (§3.1); canonical observation ordering under parallel
|
|
1081
|
+
workers (§7.8); vitest selector examples (§6.4); adapter roadmap reorder
|
|
1082
|
+
(§12); the `run` command I/O contract (§10).
|
|
1083
|
+
- **0.2.0 (2026-07-16)** — Resolved the open implementation questions via
|
|
1084
|
+
empirical probes (git tree-digest behavior; pytest failure-evidence
|
|
1085
|
+
volatility) and a two-round adversarial design review. Added: canonical
|
|
1086
|
+
failure evidence (§3.6), run/tree identity algorithms (§3.5),
|
|
1087
|
+
execution-cache coherence (§4.5), series key and `previous-superset`
|
|
1088
|
+
baseline mode (§5), `subset` comparability and selector semantics (§6.1,
|
|
1089
|
+
§6.4), `out_of_scope` transition and `selector-subset` event (§7),
|
|
1090
|
+
evidence-composition transparency in reports (§9.1), the gate CI invocation
|
|
1091
|
+
contract (§11.5), conformance fixtures for all of the above (§13.2), and
|
|
1092
|
+
this appendix.
|
|
1093
|
+
- **0.1.0 (2026-07-16)** — Initial draft from the design document.
|