vdelta 0.1.0 → 0.2.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.
@@ -5,19 +5,31 @@
5
5
  * excluding committed-gitignore'd paths, computed against a dedicated
6
6
  * throwaway index seeded from HEAD, with host-dependent git settings pinned.
7
7
  * Rendered as a bare 40-hex OID (documented deviation from sha256:).
8
+ *
9
+ * treeDigest() is read-only with respect to the observed repo: all git
10
+ * object writes (blobs from `add -A`, the tree from `write-tree`) are
11
+ * redirected to a throwaway GIT_OBJECT_DIRECTORY under tmpdir(), while the
12
+ * repo's real objects (resolved via `--git-path objects`, which also
13
+ * handles linked worktrees whose objects live in the common dir) are made
14
+ * available for reads through GIT_ALTERNATE_OBJECT_DIRECTORIES. The
15
+ * throwaway directory is removed again once the digest has been computed.
8
16
  */
9
17
  import { execFile } from 'node:child_process';
10
- import { promisify } from 'node:util';
11
18
  import { randomUUID } from 'node:crypto';
12
- import { rm } from 'node:fs/promises';
19
+ import { mkdir, rm } from 'node:fs/promises';
13
20
  import { tmpdir } from 'node:os';
14
- import { join } from 'node:path';
21
+ import { delimiter, join } from 'node:path';
22
+ import { promisify } from 'node:util';
15
23
  const execFileP = promisify(execFile);
16
24
  const PIN = [
17
- '-c', 'core.autocrlf=false',
18
- '-c', 'core.eol=lf',
19
- '-c', 'core.excludesFile=/dev/null',
20
- '-c', 'advice.addEmbeddedRepo=false',
25
+ '-c',
26
+ 'core.autocrlf=false',
27
+ '-c',
28
+ 'core.eol=lf',
29
+ '-c',
30
+ 'core.excludesFile=/dev/null',
31
+ '-c',
32
+ 'advice.addEmbeddedRepo=false',
21
33
  ];
22
34
  async function git(worktree, args, env) {
23
35
  const { stdout } = await execFileP('git', [...PIN, '-C', worktree, ...args], {
@@ -28,8 +40,24 @@ async function git(worktree, args, env) {
28
40
  }
29
41
  export async function treeDigest(worktree) {
30
42
  const idx = join(tmpdir(), `vd-idx-${randomUUID()}`);
31
- const idxEnv = { GIT_INDEX_FILE: idx };
43
+ const objDir = join(tmpdir(), `vd-obj-${randomUUID()}`);
32
44
  try {
45
+ await mkdir(objDir, { recursive: true });
46
+ const realObjects = await git(worktree, [
47
+ 'rev-parse',
48
+ '--path-format=absolute',
49
+ '--git-path',
50
+ 'objects',
51
+ ]);
52
+ const existingAlternates = process.env.GIT_ALTERNATE_OBJECT_DIRECTORIES;
53
+ const alternates = existingAlternates
54
+ ? `${realObjects}${delimiter}${existingAlternates}`
55
+ : realObjects;
56
+ const env = {
57
+ GIT_INDEX_FILE: idx,
58
+ GIT_OBJECT_DIRECTORY: objDir,
59
+ GIT_ALTERNATE_OBJECT_DIRECTORIES: alternates,
60
+ };
33
61
  let hasHead = true;
34
62
  try {
35
63
  await git(worktree, ['rev-parse', '--verify', '-q', 'HEAD']);
@@ -38,16 +66,17 @@ export async function treeDigest(worktree) {
38
66
  hasHead = false;
39
67
  }
40
68
  if (hasHead) {
41
- await git(worktree, ['read-tree', 'HEAD'], idxEnv);
69
+ await git(worktree, ['read-tree', 'HEAD'], env);
42
70
  }
43
71
  else {
44
- await git(worktree, ['read-tree', '--empty'], idxEnv);
72
+ await git(worktree, ['read-tree', '--empty'], env);
45
73
  }
46
- await git(worktree, ['add', '-A'], idxEnv);
47
- return await git(worktree, ['write-tree'], idxEnv);
74
+ await git(worktree, ['add', '-A'], env);
75
+ return await git(worktree, ['write-tree'], env);
48
76
  }
49
77
  finally {
50
78
  await rm(idx, { force: true });
79
+ await rm(objDir, { recursive: true, force: true });
51
80
  }
52
81
  }
53
82
  export async function gitHead(worktree) {
@@ -78,7 +107,11 @@ export async function gitRepoRoot(dir) {
78
107
  /** Resolve a ref to {commitSha, treeOid}; null when unresolvable. */
79
108
  export async function resolveRef(worktree, ref) {
80
109
  try {
81
- const commit = await git(worktree, ['rev-parse', '--verify', `${ref}^{commit}`]);
110
+ const commit = await git(worktree, [
111
+ 'rev-parse',
112
+ '--verify',
113
+ `${ref}^{commit}`,
114
+ ]);
82
115
  const tree = await git(worktree, ['rev-parse', '--verify', `${ref}^{tree}`]);
83
116
  return { commit, tree };
84
117
  }
@@ -89,7 +122,11 @@ export async function resolveRef(worktree, ref) {
89
122
  /** Deterministic digest material for the dirty state (status + diff vs HEAD). */
90
123
  export async function dirtyDiffMaterial(worktree) {
91
124
  try {
92
- const status = await git(worktree, ['status', '--porcelain=v1', '--untracked-files=all']);
125
+ const status = await git(worktree, [
126
+ 'status',
127
+ '--porcelain=v1',
128
+ '--untracked-files=all',
129
+ ]);
93
130
  let diff = '';
94
131
  if ((await gitHead(worktree)) !== null) {
95
132
  diff = await git(worktree, ['diff', 'HEAD', '--no-color']);
@@ -1 +1 @@
1
- {"version":3,"file":"tree-digest.js","sourceRoot":"","sources":["../src/tree-digest.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tree-digest.js","sourceRoot":"","sources":["../src/tree-digest.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vdelta",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Reference implementation of veridelta/1 — proof-carrying verification deltas for coding-agent development loops",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -35,17 +35,20 @@
35
35
  "spec"
36
36
  ],
37
37
  "engines": {
38
- "node": ">=20"
38
+ "node": ">=22"
39
39
  },
40
40
  "scripts": {
41
41
  "build": "tsc -p tsconfig.build.json",
42
- "prepublishOnly": "npm run build && npm run test",
42
+ "lint": "biome check .",
43
+ "lint:fix": "biome check --write .",
44
+ "prepublishOnly": "npm run lint && npm run build && npm run test",
43
45
  "test": "vitest run",
44
46
  "test:unit": "vitest run --project unit",
45
47
  "test:conformance": "vitest run --project conformance",
46
48
  "typecheck": "tsc --noEmit"
47
49
  },
48
50
  "devDependencies": {
51
+ "@biomejs/biome": "^2.5.4",
49
52
  "@types/node": "^24.0.0",
50
53
  "typescript": "^5.9.0",
51
54
  "vitest": "^4.0.0"
@@ -4,7 +4,7 @@
4
4
  |---|---|
5
5
  | **Status** | Draft |
6
6
  | **Schema identifier** | `veridelta/1` |
7
- | **Spec revision** | 0.3.0 — 2026-07-16 |
7
+ | **Spec revision** | 0.3.1 — 2026-07-16 |
8
8
  | **License** | MIT |
9
9
  | **Reference implementation** | `vdelta` (this repository; in development) |
10
10
 
@@ -375,6 +375,37 @@ sufficient for the consumer to verify the choice (e.g.,
375
375
  comparator MUST abstain with comparability `none` (§6.3), never fall back
376
376
  silently to a weaker match.
377
377
 
378
+ ### 5.4. Near-miss disclosure
379
+
380
+ When `previous-comparable` selection abstains with `baseline-missing` (§6.3)
381
+ and the store contains at least one complete run other than the current run,
382
+ the report MUST enrich `comparability_detail` with a `near_miss` disclosure
383
+ identifying the nearest stored complete run and the stream-key component(s)
384
+ (§5.1) on which it differs from the current run.
385
+
386
+ Selection rule (normative, deterministic): candidates are all stored complete
387
+ runs excluding the current run, in store insertion order; each candidate is
388
+ scored by the number of mismatching components among the nine stream-key
389
+ components in the canonical order `repo.identity, repo.worktree, repo.branch,
390
+ repo.cwd, invocation.command, invocation.selector, instrument.adapter,
391
+ instrument.adapter_version, instrument.config_digest`; the candidate with the
392
+ fewest mismatches is selected; ties break by recency, where recency is store
393
+ insertion order, never timestamps (§7.8).
394
+
395
+ `mismatches` lists each differing component in the canonical order above with
396
+ the candidate's recorded value and the current run's value; array-valued
397
+ components (`invocation.command`, `invocation.selector`) are rendered as their
398
+ elements joined by a single space. Values are drawn from stored run records,
399
+ which are redacted before persistence (§15), so the disclosure never widens
400
+ the exposure surface.
401
+
402
+ The disclosure is informative context only: it MUST NOT alter baseline
403
+ selection, the abstention reason, or comparability — the report still
404
+ abstains with `baseline-missing`/`determined`, and this section does not
405
+ weaken the stream key. Other baseline modes (`git-ref`, `explicit-run-id`,
406
+ `previous-superset`) MUST NOT emit `near_miss`. The component-name vocabulary
407
+ is a closed enum (§14).
408
+
378
409
  ## 6. Comparability
379
410
 
380
411
  ### 6.1. Levels and permitted claims
@@ -412,6 +443,9 @@ is REQUIRED whenever `comparability` is `none`:
412
443
  { "reason": "baseline-missing", "kind": "determined" }
413
444
  ```
414
445
 
446
+ For `previous-comparable` `baseline-missing`, `comparability_detail`
447
+ additionally carries the `near_miss` disclosure when §5.4 requires it.
448
+
415
449
  | `reason` | `kind` |
416
450
  |---|---|
417
451
  | `baseline-missing` | `determined` |
@@ -656,6 +690,10 @@ rendering of this schema with no independent logic.
656
690
  Additional REQUIRED-when-applicable fields:
657
691
 
658
692
  - `comparability_detail` when `comparability` is `none` (§6.3).
693
+ - `comparability_detail.near_miss` when §5.4 applies — an object
694
+ `{"run_id": ..., "mismatches": [{"field": ..., "recorded": ..., "current": ...}]}`
695
+ where `field` is one of the nine stream-key component names (closed enum)
696
+ and `mismatches` is non-empty.
659
697
  - `budget_exceeded_for_safety: true` when §8's budget rule fires.
660
698
  - `masking_applied` with count and example when §7.6 dedup fired.
661
699
  - `trust.record_integrity`: `advisory | tamper-evident | trusted-environment`
@@ -920,9 +958,17 @@ Adapter roadmap for the reference implementation (informative): `vitest`
920
958
  native reporter first — agent-authored code skews heavily toward TS/JS, the
921
959
  maintainer's own repositories (the cheapest dogfood subjects) are TypeScript,
922
960
  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.
961
+ stale-cache path in run mode. Playwright native reporter second, by the same
962
+ dogfood-first criterion: maintained end-to-end suites are available as
963
+ dogfood subjects, e2e suites are where flakiness attribution and fail-closed
964
+ delta gating pay most, and first-class retries, per-project repetition of
965
+ the same title, and worker parallelism exercise adapter-interface edges
966
+ vitest does not. Commitment is contingent on an empirical probe confirming
967
+ the structured reporter channel satisfies §3.6. `pytest` native is
968
+ demand-driven rather than scheduled — its structured channel is already
969
+ probed and its composition pre-designed, so the banked design does not decay
970
+ while parked. JUnit XML is likewise demand-driven, for CI breadth, under the
971
+ constraints above.
926
972
 
927
973
  ## 13. Conformance
928
974
 
@@ -1071,6 +1117,14 @@ None violates an invariant; each is disclosed rather than papered over.
1071
1117
 
1072
1118
  ## Revision history
1073
1119
 
1120
+ - **0.3.1 (2026-07-16)** — Adapter roadmap reordered (§12, informative):
1121
+ Playwright second, by the same dogfood-first criterion that flipped the
1122
+ first target to vitest in 0.3.0 — maintained e2e suites are available as
1123
+ dogfood subjects, flakiness attribution and fail-closed gating pay most in
1124
+ e2e, and retries/projects/worker parallelism exercise adapter-interface
1125
+ edges vitest lacks — contingent on a §3.6 probe of its structured reporter
1126
+ channel. `pytest` and JUnit XML moved to demand-driven; pytest's probe
1127
+ results and pre-designed composition remain banked. No normative changes.
1074
1128
  - **0.3.0 (2026-07-16)** — First adapter target flipped to vitest after an
1075
1129
  empirical probe confirmed §3.6 satisfiability (core digest stable across
1076
1130
  rerun/line-shift/unrelated-edit, sensitive only to genuine signal; no