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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 playpark
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# veridelta / vdelta
|
|
2
|
+
|
|
3
|
+
**Proof-carrying verification deltas for coding-agent development loops.**
|
|
4
|
+
|
|
5
|
+
`vdelta` is the reference implementation of the
|
|
6
|
+
[`veridelta/1` protocol](spec/veridelta-1.md): given two comparable test
|
|
7
|
+
runs, it reports — deterministically and with evidence — whether a change
|
|
8
|
+
*improved* the outcome while *maintaining the same verification surface*,
|
|
9
|
+
whether pre-existing failures mutated into different failures, and whether
|
|
10
|
+
red results disappeared because they were fixed or because the verification
|
|
11
|
+
surface shrank (fail→skip, deleted tests, weakened assertions).
|
|
12
|
+
|
|
13
|
+
It is not a log summarizer. It is a trust layer: an exit code cannot tell an
|
|
14
|
+
agent (or a CI gate reviewing an agent's PR) "you fixed one thing and broke
|
|
15
|
+
another" — a verification delta can, and when two runs are not comparable it
|
|
16
|
+
**abstains instead of guessing**.
|
|
17
|
+
|
|
18
|
+
- Runner support (MVP): **vitest v4** (native reporter, structured channel only)
|
|
19
|
+
- Zero runtime dependencies; Node ≥ 20
|
|
20
|
+
- Machine-verified against the [conformance suite](conformance/) —
|
|
21
|
+
38 fixtures covering the spec's invariants, adversarial inputs, and a
|
|
22
|
+
10-mutation cheating corpus with 100% detection recall
|
|
23
|
+
|
|
24
|
+
## Quickstart (5 minutes)
|
|
25
|
+
|
|
26
|
+
Requires Node 20+ inside a git repository.
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm i -D vitest vdelta
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Create a failing test:
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
// src/status.ts
|
|
36
|
+
export const getStatus = () => 500
|
|
37
|
+
|
|
38
|
+
// tests/status.test.ts
|
|
39
|
+
import { test, expect } from 'vitest'
|
|
40
|
+
import { getStatus } from '../src/status.js'
|
|
41
|
+
|
|
42
|
+
test('status is ok', () => {
|
|
43
|
+
expect(getStatus()).toBe(200)
|
|
44
|
+
})
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Record the red run (vdelta wraps your normal test command — it injects its
|
|
48
|
+
reporter, captures everything, and passes the child's exit code through):
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npx vdelta run -- npx vitest run
|
|
52
|
+
# veridelta/1 inconclusive (comparability: none)
|
|
53
|
+
# reason: baseline-missing (determined) ← first run: nothing to compare yet
|
|
54
|
+
# red now (1):
|
|
55
|
+
# ✗ tests/status.test.ts::status is ok
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Fix the bug (`500` → `200`), then run again:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
npx vdelta run -- npx vitest run
|
|
62
|
+
# veridelta/1 improved (comparability: exact)
|
|
63
|
+
# repaired: 1 same-surface, 0 with-test-change ← a real fix, not a hidden skip
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Had you "fixed" it with `test.skip` instead, the same command reports
|
|
67
|
+
`fail_to_skip` and `surface: reduced` — never `repaired`.
|
|
68
|
+
|
|
69
|
+
Everything is also available as machine-readable JSON (the primary
|
|
70
|
+
interface), with drill-down anchors for every omitted detail:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
npx vdelta run --report json -- npx vitest run # report on stdout, exit = vitest's
|
|
74
|
+
npx vdelta compare --report json # re-compare the last two runs
|
|
75
|
+
npx vdelta show run_ab12cd34 --test 'tests/status.test.ts::status is ok'
|
|
76
|
+
npx vdelta show run_ab12cd34 --raw # the captured vitest output
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Gate a change against a baseline ref (report-only policy — builds trust
|
|
80
|
+
before it ever blocks):
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
npx vdelta gate --ref origin/main --policy report-only --report json
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The gate verifies record integrity (content-addressed run ids) and staleness
|
|
87
|
+
(the recorded tree must equal the judged working tree, byte-exact) before it
|
|
88
|
+
judges anything.
|
|
89
|
+
|
|
90
|
+
## Commands
|
|
91
|
+
|
|
92
|
+
| Command | Purpose | Exit code |
|
|
93
|
+
|---|---|---|
|
|
94
|
+
| `vdelta run [--report json\|text] -- <cmd>` | Execute, record, report | The child's exit code, unchanged. Internal errors degrade to raw passthrough — vdelta is never worse than its absence. |
|
|
95
|
+
| `vdelta compare [<baseline> <current>] [--ref <git-ref>]` | Compare recorded runs (explicit ids, a git ref, or the previous comparable run) | 0 when the comparison ran (an `inconclusive` result is a successful comparison); 1 on operation failure |
|
|
96
|
+
| `vdelta show <run-id> [--test <id>\|--raw]` | Drill down into a run record | Retrieval success |
|
|
97
|
+
| `vdelta gate --ref <git-ref> [--policy report-only]` | Policy verdict for CI/agent loops | report-only: 0 when a report was produced; 2 otherwise |
|
|
98
|
+
|
|
99
|
+
Run ids may be abbreviated to any unambiguous prefix.
|
|
100
|
+
|
|
101
|
+
## What the report separates (and why)
|
|
102
|
+
|
|
103
|
+
Three axes, never collapsed into one:
|
|
104
|
+
|
|
105
|
+
1. **Outcome verdict** — `regressed | improved | unchanged | inconclusive`.
|
|
106
|
+
One new or updated failure outweighs any number of repairs.
|
|
107
|
+
2. **Failure-mode delta** — the same test failing *differently* is
|
|
108
|
+
`updated_fail` (evidence digests differ), never buried in
|
|
109
|
+
"still failing". Test identity ≠ failure identity.
|
|
110
|
+
3. **Verification-surface delta** — red that vanished via `fail→skip`,
|
|
111
|
+
deletion, `.only` narrowing, config excludes, or a rewritten assertion is
|
|
112
|
+
reported as surface reduction / `repaired_with_test_change`,
|
|
113
|
+
**never** as `repaired_same_surface`. Cheating is a first-class,
|
|
114
|
+
separately-reported axis.
|
|
115
|
+
|
|
116
|
+
Comparability is judged first (`exact | scope_changed | partial | none`),
|
|
117
|
+
and every claim is bounded by it: instrument changes (e.g. a different
|
|
118
|
+
`chaiConfig.truncateThreshold`) abstain with `instrument-changed`; a missing
|
|
119
|
+
baseline abstains with `baseline-missing`. Reasons are closed enums —
|
|
120
|
+
consumers must treat unknown values as hard errors.
|
|
121
|
+
|
|
122
|
+
## vitest adapter notes
|
|
123
|
+
|
|
124
|
+
- The recorder injects `--includeTaskLocation` and its reporter into the
|
|
125
|
+
vitest invocation; your config needs no changes. Evidence-affecting
|
|
126
|
+
settings you *do* set (like `chaiConfig.truncateThreshold`) become part of
|
|
127
|
+
the measuring-instrument identity.
|
|
128
|
+
- Positional arguments after the vitest token are recorded as the run's
|
|
129
|
+
selector. If you pass vitest flags that take a value, use the
|
|
130
|
+
`--flag=value` form (a separate value token would be read as a selector
|
|
131
|
+
filter — comparisons then abstain; nothing false-greens).
|
|
132
|
+
- Evidence digests are built from vitest's structured channel only
|
|
133
|
+
(exception type, message, structured expected/actual, operator, and
|
|
134
|
+
line-shift-stable relative positions). Durations, absolute paths/lines,
|
|
135
|
+
raw stacks, rendered diffs, and console output are stored as annex
|
|
136
|
+
material, reachable via `vdelta show`, never digested.
|
|
137
|
+
- vitest's channel provides no failing-source-region text, so the adapter
|
|
138
|
+
declares that capability `unsupported`; every red-in-both claim carries
|
|
139
|
+
`degraded_capabilities: ["source-region-text"]`.
|
|
140
|
+
- Known secret shapes (AWS keys, GitHub/Slack tokens, JWTs, private keys,
|
|
141
|
+
bearer tokens) are redacted deterministically before anything is stored
|
|
142
|
+
or digested.
|
|
143
|
+
- The run store lives in `.veridelta/` (repo-local, self-gitignored,
|
|
144
|
+
content-addressed, immutable).
|
|
145
|
+
|
|
146
|
+
## Protocol
|
|
147
|
+
|
|
148
|
+
The spec is the product: [`spec/veridelta-1.md`](spec/veridelta-1.md)
|
|
149
|
+
defines the `veridelta/1` schema, trust invariants (INV-1..11), gate
|
|
150
|
+
semantics, and conformance requirements. Independent implementations are the
|
|
151
|
+
intended success mode.
|
|
152
|
+
|
|
153
|
+
## Conformance
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
npm test # unit + full conformance suite
|
|
157
|
+
npm run test:conformance # the 38-fixture suite only
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
The suite is authored independently of this implementation (the fixture
|
|
161
|
+
author reads only the spec and the harness contract in
|
|
162
|
+
[docs/conformance-harness.md](docs/conformance-harness.md), never `src/`)
|
|
163
|
+
and mechanically verifies, among the spec's §13.2 classes: byte-identical
|
|
164
|
+
determinism of re-executed comparisons, 100% detection recall on the
|
|
165
|
+
cheating corpus, zero false green, fail-open degradation (INV-5),
|
|
166
|
+
record-integrity tampering detection (INV-10), and exact tree-digest
|
|
167
|
+
staleness (INV-11).
|
|
168
|
+
|
|
169
|
+
## License
|
|
170
|
+
|
|
171
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Capture interchange format between the in-process vitest reporter and the
|
|
3
|
+
* out-of-process recorder (`vdelta run`). Raw structured-channel data only;
|
|
4
|
+
* canonicalization/redaction/digesting happen in the recorder.
|
|
5
|
+
*/
|
|
6
|
+
export declare const CAPTURE_VERSION = 1;
|
|
7
|
+
export interface CapturedError {
|
|
8
|
+
name: string;
|
|
9
|
+
message: string;
|
|
10
|
+
expected?: string;
|
|
11
|
+
actual?: string;
|
|
12
|
+
operator?: string;
|
|
13
|
+
frames: {
|
|
14
|
+
file: string;
|
|
15
|
+
line: number;
|
|
16
|
+
column: number;
|
|
17
|
+
}[];
|
|
18
|
+
}
|
|
19
|
+
export interface CapturedTest {
|
|
20
|
+
rel: string;
|
|
21
|
+
module_id: string;
|
|
22
|
+
full_name: string;
|
|
23
|
+
state: 'passed' | 'failed' | 'skipped' | 'pending';
|
|
24
|
+
mode: 'run' | 'only' | 'skip' | 'todo';
|
|
25
|
+
fails: boolean;
|
|
26
|
+
note?: string;
|
|
27
|
+
location_line: number | null;
|
|
28
|
+
errors: CapturedError[];
|
|
29
|
+
console: {
|
|
30
|
+
type: string;
|
|
31
|
+
content: string;
|
|
32
|
+
}[];
|
|
33
|
+
duration_us: number | null;
|
|
34
|
+
}
|
|
35
|
+
export interface Capture {
|
|
36
|
+
capture_version: number;
|
|
37
|
+
runner: 'vitest';
|
|
38
|
+
runner_version: string;
|
|
39
|
+
reason: 'passed' | 'failed' | 'interrupted';
|
|
40
|
+
unhandled_errors: number;
|
|
41
|
+
config: {
|
|
42
|
+
include_task_location: boolean;
|
|
43
|
+
truncate_threshold: number | null;
|
|
44
|
+
};
|
|
45
|
+
tests: CapturedTest[];
|
|
46
|
+
module_errors: {
|
|
47
|
+
rel: string;
|
|
48
|
+
messages: string[];
|
|
49
|
+
}[];
|
|
50
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Capture interchange format between the in-process vitest reporter and the
|
|
3
|
+
* out-of-process recorder (`vdelta run`). Raw structured-channel data only;
|
|
4
|
+
* canonicalization/redaction/digesting happen in the recorder.
|
|
5
|
+
*/
|
|
6
|
+
export const CAPTURE_VERSION = 1;
|
|
7
|
+
//# sourceMappingURL=capture.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capture.js","sourceRoot":"","sources":["../../../src/adapters/vitest/capture.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,CAAA"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type RunRecord } from '../../schema.js';
|
|
2
|
+
import type { Capture, CapturedTest } from './capture.js';
|
|
3
|
+
export declare const ADAPTER_NAME = "vitest";
|
|
4
|
+
export declare const COMPOSITION_ID = "vitest-native/1";
|
|
5
|
+
/** CE-1: vitest's structured channel has no failing-source-region text (expC Q1). */
|
|
6
|
+
export declare const DEGRADED_CAPABILITIES: string[];
|
|
7
|
+
/** Env vars whose values (fingerprinted, never stored) are comparison-relevant. */
|
|
8
|
+
export declare const DECLARED_ENV_VARS: readonly ["CI", "NODE_ENV", "TZ", "LANG"];
|
|
9
|
+
export declare class RecorderError extends Error {
|
|
10
|
+
constructor(message: string);
|
|
11
|
+
}
|
|
12
|
+
export interface RecordContext {
|
|
13
|
+
worktree: string;
|
|
14
|
+
repoIdentity: string;
|
|
15
|
+
branch: string;
|
|
16
|
+
cwdRel: string;
|
|
17
|
+
command: string[];
|
|
18
|
+
selector: string[];
|
|
19
|
+
head: string | null;
|
|
20
|
+
treeDigest: string;
|
|
21
|
+
dirtyDiffDigest: string;
|
|
22
|
+
childExitCode: number;
|
|
23
|
+
rawStdout: string;
|
|
24
|
+
rawStderr: string;
|
|
25
|
+
adapterVersion: string;
|
|
26
|
+
recordedAtMs: number;
|
|
27
|
+
}
|
|
28
|
+
export declare function buildRunRecord(capture: Capture, ctx: RecordContext): RunRecord;
|
|
29
|
+
/** The effective evidence-affecting configuration (§3.1, contract §5.4). */
|
|
30
|
+
export declare function instrumentConfigDigest(capture: Capture): string;
|
|
31
|
+
export declare function testId(t: CapturedTest): string;
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vitest adapter, recorder side: turns a Capture dump plus execution context
|
|
3
|
+
* into a canonical RunRecord (§3). Composition `vitest-native/1` (expC):
|
|
4
|
+
* digest core = exception type + message + structured expected/actual +
|
|
5
|
+
* operator + line-shift-stable relOffsets; source-region-text is declared
|
|
6
|
+
* unsupported (degraded capability); absolute positions, raw stacks, console
|
|
7
|
+
* output and durations are annex/recording material, never digested.
|
|
8
|
+
* Execution-cache coherence (§4.5): empirical probing found no stale-source
|
|
9
|
+
* path in vitest run mode, so this adapter declares that no cache
|
|
10
|
+
* neutralization is required — the §13.2(b) fixture arbitrates that claim.
|
|
11
|
+
*/
|
|
12
|
+
import { readFileSync } from 'node:fs';
|
|
13
|
+
import { join } from 'node:path';
|
|
14
|
+
import { canonicalDigest } from '../../digest.js';
|
|
15
|
+
import { redactText, redactValue } from '../../redact.js';
|
|
16
|
+
import { SCHEMA_VERSION, } from '../../schema.js';
|
|
17
|
+
export const ADAPTER_NAME = 'vitest';
|
|
18
|
+
export const COMPOSITION_ID = 'vitest-native/1';
|
|
19
|
+
/** CE-1: vitest's structured channel has no failing-source-region text (expC Q1). */
|
|
20
|
+
export const DEGRADED_CAPABILITIES = ['source-region-text'];
|
|
21
|
+
/** Env vars whose values (fingerprinted, never stored) are comparison-relevant. */
|
|
22
|
+
export const DECLARED_ENV_VARS = ['CI', 'NODE_ENV', 'TZ', 'LANG'];
|
|
23
|
+
const CONFIG_SOURCE_CANDIDATES = [
|
|
24
|
+
'vitest.config.ts',
|
|
25
|
+
'vitest.config.mts',
|
|
26
|
+
'vitest.config.js',
|
|
27
|
+
'vitest.config.mjs',
|
|
28
|
+
'vite.config.ts',
|
|
29
|
+
'vite.config.js',
|
|
30
|
+
];
|
|
31
|
+
export class RecorderError extends Error {
|
|
32
|
+
constructor(message) {
|
|
33
|
+
super(message);
|
|
34
|
+
this.name = 'RecorderError';
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export function buildRunRecord(capture, ctx) {
|
|
38
|
+
if (capture.capture_version !== 1) {
|
|
39
|
+
throw new RecorderError(`unsupported capture version ${capture.capture_version}`);
|
|
40
|
+
}
|
|
41
|
+
const observations = [];
|
|
42
|
+
const durations = {};
|
|
43
|
+
const seenIds = new Set();
|
|
44
|
+
const suppressed = [];
|
|
45
|
+
const sorted = [...capture.tests].sort((a, b) => testId(a) < testId(b) ? -1 : testId(a) > testId(b) ? 1 : 0);
|
|
46
|
+
for (const t of sorted) {
|
|
47
|
+
const id = testId(t);
|
|
48
|
+
if (seenIds.has(id)) {
|
|
49
|
+
// Fail-closed on ambiguity (§12): duplicate canonical IDs are not guessable.
|
|
50
|
+
throw new RecorderError(`duplicate test id: ${id}`);
|
|
51
|
+
}
|
|
52
|
+
seenIds.add(id);
|
|
53
|
+
const obs = toObservation(t, id);
|
|
54
|
+
observations.push(obs);
|
|
55
|
+
if (t.duration_us !== null)
|
|
56
|
+
durations[id] = t.duration_us;
|
|
57
|
+
if (obs.verdict === 'skip' || obs.verdict === 'xfail')
|
|
58
|
+
suppressed.push(id);
|
|
59
|
+
}
|
|
60
|
+
const testSources = {};
|
|
61
|
+
for (const rel of [...new Set(capture.tests.map((t) => t.rel))].sort()) {
|
|
62
|
+
const digest = fileDigest(join(ctx.worktree, rel));
|
|
63
|
+
if (digest !== null)
|
|
64
|
+
testSources[rel] = digest;
|
|
65
|
+
}
|
|
66
|
+
const configSources = {};
|
|
67
|
+
for (const rel of CONFIG_SOURCE_CANDIDATES) {
|
|
68
|
+
const digest = fileDigest(join(ctx.worktree, rel));
|
|
69
|
+
if (digest !== null)
|
|
70
|
+
configSources[rel] = digest;
|
|
71
|
+
}
|
|
72
|
+
const notRun = observations.filter((o) => o.verdict === 'not_run').length;
|
|
73
|
+
let status = 'complete';
|
|
74
|
+
if (capture.unhandled_errors > 0 || capture.module_errors.length > 0)
|
|
75
|
+
status = 'crashed';
|
|
76
|
+
else if (capture.reason === 'interrupted' || notRun > 0)
|
|
77
|
+
status = 'partial';
|
|
78
|
+
return {
|
|
79
|
+
schema_version: SCHEMA_VERSION,
|
|
80
|
+
repo: {
|
|
81
|
+
identity: ctx.repoIdentity,
|
|
82
|
+
worktree: ctx.worktree,
|
|
83
|
+
branch: ctx.branch,
|
|
84
|
+
cwd: ctx.cwdRel,
|
|
85
|
+
},
|
|
86
|
+
invocation: { command: ctx.command, selector: [...ctx.selector].sort() },
|
|
87
|
+
instrument: {
|
|
88
|
+
adapter: ADAPTER_NAME,
|
|
89
|
+
adapter_version: ctx.adapterVersion,
|
|
90
|
+
composition_id: COMPOSITION_ID,
|
|
91
|
+
config_digest: instrumentConfigDigest(capture),
|
|
92
|
+
},
|
|
93
|
+
environment: {
|
|
94
|
+
runner: capture.runner,
|
|
95
|
+
runner_version: capture.runner_version,
|
|
96
|
+
runtime: `node ${process.version}`,
|
|
97
|
+
os: process.platform,
|
|
98
|
+
env_fingerprint: canonicalDigest(Object.fromEntries(DECLARED_ENV_VARS.map((k) => [k, process.env[k] ?? null]))),
|
|
99
|
+
},
|
|
100
|
+
provenance: {
|
|
101
|
+
head: ctx.head,
|
|
102
|
+
dirty_diff_digest: ctx.dirtyDiffDigest,
|
|
103
|
+
tree_digest: ctx.treeDigest,
|
|
104
|
+
},
|
|
105
|
+
surface: {
|
|
106
|
+
inventory_digest: canonicalDigest([...seenIds].sort()),
|
|
107
|
+
test_sources: testSources,
|
|
108
|
+
config_sources: configSources,
|
|
109
|
+
suppressed,
|
|
110
|
+
},
|
|
111
|
+
completeness: { status, child_exit_code: ctx.childExitCode },
|
|
112
|
+
observations,
|
|
113
|
+
recording: {
|
|
114
|
+
recorder: 'vdelta-run',
|
|
115
|
+
recorded_at_ms: ctx.recordedAtMs,
|
|
116
|
+
durations_us: durations,
|
|
117
|
+
raw_stdout: redactText(ctx.rawStdout),
|
|
118
|
+
raw_stderr: redactText(ctx.rawStderr),
|
|
119
|
+
capture_reason: capture.reason,
|
|
120
|
+
unhandled_errors: capture.unhandled_errors,
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
/** The effective evidence-affecting configuration (§3.1, contract §5.4). */
|
|
125
|
+
export function instrumentConfigDigest(capture) {
|
|
126
|
+
return canonicalDigest({
|
|
127
|
+
include_task_location: capture.config.include_task_location,
|
|
128
|
+
truncate_threshold: capture.config.truncate_threshold,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
export function testId(t) {
|
|
132
|
+
return `${t.rel}::${t.full_name}`;
|
|
133
|
+
}
|
|
134
|
+
function toObservation(t, id) {
|
|
135
|
+
const { verdict, suppression } = mapVerdict(t);
|
|
136
|
+
const obs = { test_id: id, verdict };
|
|
137
|
+
if (suppression)
|
|
138
|
+
obs.suppression = suppression;
|
|
139
|
+
if (t.location_line !== null)
|
|
140
|
+
obs.source_ref = { file: t.rel, line: t.location_line };
|
|
141
|
+
if (verdict === 'fail' || verdict === 'error')
|
|
142
|
+
obs.finding = buildFinding(t);
|
|
143
|
+
return obs;
|
|
144
|
+
}
|
|
145
|
+
/** Contract §5.2 verdict mapping — verdict channel first (INV-3), refined only by structured markers. */
|
|
146
|
+
function mapVerdict(t) {
|
|
147
|
+
switch (t.state) {
|
|
148
|
+
case 'passed':
|
|
149
|
+
return t.fails
|
|
150
|
+
? { verdict: 'xfail', suppression: { marker: 'fails' } }
|
|
151
|
+
: { verdict: 'pass' };
|
|
152
|
+
case 'failed':
|
|
153
|
+
return t.fails
|
|
154
|
+
? { verdict: 'fail', suppression: { marker: 'fails' } }
|
|
155
|
+
: { verdict: 'fail' };
|
|
156
|
+
case 'skipped':
|
|
157
|
+
if (t.mode === 'skip')
|
|
158
|
+
return { verdict: 'skip', suppression: { marker: 'skip' } };
|
|
159
|
+
if (t.mode === 'todo')
|
|
160
|
+
return { verdict: 'skip', suppression: { marker: 'todo' } };
|
|
161
|
+
return {
|
|
162
|
+
verdict: 'skip',
|
|
163
|
+
suppression: { marker: 'runtime', ...(t.note !== undefined ? { note: t.note } : {}) },
|
|
164
|
+
};
|
|
165
|
+
case 'pending':
|
|
166
|
+
return { verdict: 'not_run' };
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
function buildFinding(t) {
|
|
170
|
+
const errors = t.errors.map((e) => ({
|
|
171
|
+
exception_type: e.name,
|
|
172
|
+
message: redactText(e.message),
|
|
173
|
+
...(e.expected !== undefined ? { expected: redactText(e.expected) } : {}),
|
|
174
|
+
...(e.actual !== undefined ? { actual: redactText(e.actual) } : {}),
|
|
175
|
+
...(e.operator !== undefined ? { operator: e.operator } : {}),
|
|
176
|
+
rel_offsets: relOffsets(t, e.frames),
|
|
177
|
+
}));
|
|
178
|
+
const consoleEntries = t.console.map((c) => ({ type: c.type, content: redactText(c.content) }));
|
|
179
|
+
return {
|
|
180
|
+
evidence_digest: canonicalDigest({ errors }),
|
|
181
|
+
structural_fingerprint: canonicalDigest({
|
|
182
|
+
module: t.rel,
|
|
183
|
+
exception_types: errors.map((e) => e.exception_type),
|
|
184
|
+
operators: errors.map((e) => e.operator ?? null),
|
|
185
|
+
rel_offsets: errors.map((e) => e.rel_offsets),
|
|
186
|
+
}),
|
|
187
|
+
evidence: { errors },
|
|
188
|
+
context_digest: canonicalDigest(consoleEntries),
|
|
189
|
+
annex: redactValue({
|
|
190
|
+
frames: t.errors.flatMap((e) => e.frames),
|
|
191
|
+
console: consoleEntries,
|
|
192
|
+
location_line: t.location_line,
|
|
193
|
+
}),
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* CE-3 position stability: per-frame line offsets relative to the test's own
|
|
198
|
+
* declaration line, for frames inside the test module only. Absolute lines
|
|
199
|
+
* never enter the digest (expC §2).
|
|
200
|
+
*/
|
|
201
|
+
function relOffsets(t, frames) {
|
|
202
|
+
if (t.location_line === null)
|
|
203
|
+
return [];
|
|
204
|
+
return frames.filter((f) => f.file === t.module_id).map((f) => f.line - t.location_line);
|
|
205
|
+
}
|
|
206
|
+
function fileDigest(path) {
|
|
207
|
+
try {
|
|
208
|
+
return canonicalDigest(readFileSync(path, 'utf8'));
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
//# sourceMappingURL=recorder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recorder.js","sourceRoot":"","sources":["../../../src/adapters/vitest/recorder.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Reporter, TestModule, TestSpecification, Vitest } from 'vitest/node';
|
|
2
|
+
export default class VdeltaReporter implements Reporter {
|
|
3
|
+
private ctx;
|
|
4
|
+
private consoleByTask;
|
|
5
|
+
onInit(ctx: Vitest): void;
|
|
6
|
+
onTestRunStart(_specifications: readonly TestSpecification[]): void;
|
|
7
|
+
onUserConsoleLog(log: {
|
|
8
|
+
taskId?: string;
|
|
9
|
+
type: string;
|
|
10
|
+
content: string;
|
|
11
|
+
}): void;
|
|
12
|
+
onTestRunEnd(testModules: readonly TestModule[], unhandledErrors: readonly unknown[], reason: 'passed' | 'interrupted' | 'failed'): void;
|
|
13
|
+
private captureTest;
|
|
14
|
+
}
|
|
15
|
+
export { VdeltaReporter };
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vitest v4 Reporter (Reported Tasks API) — the vdelta capture side of the
|
|
3
|
+
* adapter. Structured-first (§12): consumes only the runner's structured
|
|
4
|
+
* channel (TestCase results, options, locations, console callbacks), never
|
|
5
|
+
* rendered output. Writes a Capture dump to $VDELTA_CAPTURE_FILE at run end;
|
|
6
|
+
* without that env var it is inert, so it can stay permanently configured in
|
|
7
|
+
* a project's vitest config (ambient recording, §4.2).
|
|
8
|
+
*/
|
|
9
|
+
import { writeFileSync } from 'node:fs';
|
|
10
|
+
import { CAPTURE_VERSION } from './capture.js';
|
|
11
|
+
export default class VdeltaReporter {
|
|
12
|
+
ctx;
|
|
13
|
+
consoleByTask = new Map();
|
|
14
|
+
onInit(ctx) {
|
|
15
|
+
this.ctx = ctx;
|
|
16
|
+
}
|
|
17
|
+
onTestRunStart(_specifications) {
|
|
18
|
+
this.consoleByTask.clear();
|
|
19
|
+
}
|
|
20
|
+
onUserConsoleLog(log) {
|
|
21
|
+
if (log.taskId === undefined)
|
|
22
|
+
return;
|
|
23
|
+
const entries = this.consoleByTask.get(log.taskId) ?? [];
|
|
24
|
+
entries.push({ type: log.type, content: log.content });
|
|
25
|
+
this.consoleByTask.set(log.taskId, entries);
|
|
26
|
+
}
|
|
27
|
+
onTestRunEnd(testModules, unhandledErrors, reason) {
|
|
28
|
+
const outFile = process.env.VDELTA_CAPTURE_FILE;
|
|
29
|
+
if (outFile === undefined || outFile === '')
|
|
30
|
+
return;
|
|
31
|
+
const tests = [];
|
|
32
|
+
const moduleErrors = [];
|
|
33
|
+
for (const mod of testModules) {
|
|
34
|
+
const modErrors = mod.errors();
|
|
35
|
+
if (modErrors.length > 0) {
|
|
36
|
+
moduleErrors.push({
|
|
37
|
+
rel: mod.relativeModuleId,
|
|
38
|
+
messages: modErrors.map((e) => String(e.message ?? '')),
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
for (const tc of mod.children.allTests()) {
|
|
42
|
+
tests.push(this.captureTest(tc));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const config = this.ctx?.config;
|
|
46
|
+
const chaiConfig = config
|
|
47
|
+
?.chaiConfig;
|
|
48
|
+
const capture = {
|
|
49
|
+
capture_version: CAPTURE_VERSION,
|
|
50
|
+
runner: 'vitest',
|
|
51
|
+
runner_version: this.ctx?.version ?? 'unknown',
|
|
52
|
+
reason,
|
|
53
|
+
unhandled_errors: unhandledErrors.length,
|
|
54
|
+
config: {
|
|
55
|
+
include_task_location: config?.includeTaskLocation === true,
|
|
56
|
+
truncate_threshold: chaiConfig?.truncateThreshold ?? null,
|
|
57
|
+
},
|
|
58
|
+
tests,
|
|
59
|
+
module_errors: moduleErrors,
|
|
60
|
+
};
|
|
61
|
+
writeFileSync(outFile, JSON.stringify(capture));
|
|
62
|
+
}
|
|
63
|
+
captureTest(tc) {
|
|
64
|
+
const result = tc.result();
|
|
65
|
+
const diagnostic = tc.diagnostic();
|
|
66
|
+
const errors = (result.errors ?? []).map((e) => {
|
|
67
|
+
const err = e;
|
|
68
|
+
return {
|
|
69
|
+
name: typeof err.name === 'string' ? err.name : 'Error',
|
|
70
|
+
message: typeof err.message === 'string' ? err.message : String(err.message ?? ''),
|
|
71
|
+
...(typeof err.expected === 'string' ? { expected: err.expected } : {}),
|
|
72
|
+
...(typeof err.actual === 'string' ? { actual: err.actual } : {}),
|
|
73
|
+
...(typeof err.operator === 'string' ? { operator: err.operator } : {}),
|
|
74
|
+
frames: (err.stacks ?? [])
|
|
75
|
+
.filter((f) => typeof f.file === 'string' && typeof f.line === 'number' && typeof f.column === 'number')
|
|
76
|
+
.map((f) => ({ file: f.file, line: f.line, column: f.column })),
|
|
77
|
+
};
|
|
78
|
+
});
|
|
79
|
+
return {
|
|
80
|
+
rel: tc.module.relativeModuleId,
|
|
81
|
+
module_id: tc.module.moduleId,
|
|
82
|
+
full_name: tc.fullName,
|
|
83
|
+
state: result.state,
|
|
84
|
+
mode: tc.options.mode,
|
|
85
|
+
fails: tc.options.fails === true,
|
|
86
|
+
...(result.state === 'skipped' && result.note !== undefined ? { note: result.note } : {}),
|
|
87
|
+
location_line: tc.location?.line ?? null,
|
|
88
|
+
errors,
|
|
89
|
+
console: this.consoleByTask.get(tc.id) ?? [],
|
|
90
|
+
duration_us: result.state === 'passed' || result.state === 'failed'
|
|
91
|
+
? Math.round((diagnostic?.duration ?? 0) * 1000)
|
|
92
|
+
: null,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
export { VdeltaReporter };
|
|
97
|
+
//# sourceMappingURL=reporter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reporter.js","sourceRoot":"","sources":["../../../src/adapters/vitest/reporter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical JSON serialization (spec §3.5): lexicographically sorted keys,
|
|
3
|
+
* UTF-8, no insignificant whitespace, numbers restricted to integers.
|
|
4
|
+
* Used for run_id derivation and every digest input.
|
|
5
|
+
*/
|
|
6
|
+
export declare class NonCanonicalValueError extends Error {
|
|
7
|
+
constructor(message: string);
|
|
8
|
+
}
|
|
9
|
+
export declare function canonicalJson(value: unknown): string;
|