From 62aecf57d3f7c1c24a09d92a5ffb3f85ec8e27a3 Mon Sep 17 00:00:00 2001 From: James Ross Date: Tue, 12 May 2026 20:26:44 -0700 Subject: [PATCH 1/2] test: stabilize isolated release gate --- CHANGELOG.md | 4 ++ ...DCODE_full-suite-timeout-nondeterminism.md | 20 ++++++++++ docs/method/releases/v0.8.0/release.md | 2 + docs/method/releases/v0.8.0/verification.md | 37 ++++++++++++++++--- scripts/isolated-test-args.ts | 14 +++++++ scripts/isolated-test-runner.ts | 7 +++- .../release/docker-test-isolation.test.ts | 2 +- test/unit/scripts/isolated-test-args.test.ts | 32 +++++++++++++++- 8 files changed, 109 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d55a36b..f85e08a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -132,6 +132,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/). WARP structural path filters are directory-boundary safe, the MCP server passes injected Git clients through `ToolContext`, and playback tests avoid Node 21-only `import.meta.dirname`. +- **Release gate stability**: the Docker-isolated test runner now bounds + default Vitest worker concurrency unless an explicit `--maxWorkers` + argument is supplied, keeping full-suite release validation stable under + constrained Docker resources. - **Parser readiness follow-up**: root library hosts can now call `ensureParserReady()` before synchronous structured-buffer use, while pre-warm sync projection bundles return explicit partial parser diff --git a/docs/design/BADCODE_full-suite-timeout-nondeterminism.md b/docs/design/BADCODE_full-suite-timeout-nondeterminism.md index 9038ced..09c47d4 100644 --- a/docs/design/BADCODE_full-suite-timeout-nondeterminism.md +++ b/docs/design/BADCODE_full-suite-timeout-nondeterminism.md @@ -35,6 +35,22 @@ not a valid release signal. 3. Isolated rerun of the affected files passed. 4. Full `pnpm test` rerun passed. +## v0.8.0 Release Recurrence + +During final v0.8.0 release validation on merged `main`, unbounded +Docker-isolated Vitest worker concurrency reproduced the same class of +failure at larger scale: + +- `pnpm release:check` failed inside `pnpm test` with 20 timeout + failures across 15 files. +- The failing files passed in a focused host-side rerun: 15 files, 153 + tests. +- The full Docker-isolated suite passed when invoked with + `--maxWorkers 2`: 216 files, 1592 tests. + +This confirmed the release blocker as suite-wide resource contention, +not a deterministic product assertion failure in the affected tests. + ## Risk The failure pattern suggests timing pressure, hidden coupling, leaked @@ -70,6 +86,8 @@ runtime budget: - run daemon integration tests with one real child-process worker and no unrelated persisted-history graph writes - update the causal status schemas to match observed nullable repo concurrency +- bound the release-grade Docker-isolated Vitest worker count by default + while preserving explicit `--maxWorkers` overrides for local diagnosis ## Acceptance @@ -91,3 +109,5 @@ runtime budget: - `pnpm typecheck` - `pnpm lint` - `env -u GIT_DIR -u GIT_WORK_TREE -u GIT_WARP_HOME pnpm test` +- `pnpm test -- --maxWorkers 2` +- `pnpm release:check` diff --git a/docs/method/releases/v0.8.0/release.md b/docs/method/releases/v0.8.0/release.md index 3c6ea56..d6a52f3 100644 --- a/docs/method/releases/v0.8.0/release.md +++ b/docs/method/releases/v0.8.0/release.md @@ -14,6 +14,8 @@ - Add lazy parser readiness through `ensureParserReady()`. - Add Docker Desktop auto-start assistance for the isolated test runner on macOS. +- Bound Docker-isolated release test worker concurrency by default while + preserving explicit `--maxWorkers` overrides. - Harden MCP invocation/server construction, governed read parity, WARP symbol-id handling, Git version checks, and projection-bundle docs. diff --git a/docs/method/releases/v0.8.0/verification.md b/docs/method/releases/v0.8.0/verification.md index b90a431..fcdddf4 100644 --- a/docs/method/releases/v0.8.0/verification.md +++ b/docs/method/releases/v0.8.0/verification.md @@ -1,8 +1,9 @@ # Release Witness: v0.8.0 -This witness records release-branch preflight. Tagging and publish -verification are still pending until the release branch is merged to -`main` and the `v0.8.0` tag is pushed from the merged main commit. +This witness records release-branch preflight and release-blocker +follow-up validation. Tagging and publish verification are still pending +until the release-blocker branch is merged to `main` and the `v0.8.0` +tag is pushed from the merged main commit. ## Discovery @@ -10,8 +11,10 @@ verification are still pending until the release branch is merged to - Previous package version: `0.7.1` - Planned version: `0.8.0` - Branch: `release/v0.8.0` -- Release branch synced with origin: pending push after witness - finalization +- Merged main baseline: `3394d6e Merge pull request #49 from flyingrobots/release/v0.8.0` +- Release blocker branch: `release/v0.8.0-blockers` +- Release blocker branch synced with origin: pending push after witness + finalization and commit - `main` release guard: pending; final release runbook requires main to be exactly synced with `origin/main` before tag/publish @@ -28,6 +31,30 @@ verification are still pending until the release branch is merged to | `pnpm release:check` | pass, 2026-05-06 11:58 PDT | | `git diff --check` | pass, 2026-05-06 12:00 PDT | +## Release Gate Stabilization + +Merged-`main` validation on 2026-05-12 reproduced full-suite timeout +nondeterminism before tagging: + +| Step | Result | +|------|--------| +| `pnpm release:check` on `main` at `3394d6e` | fail in Docker-isolated `pnpm test`: 15 failed files / 20 timed-out tests, 2026-05-12 | +| Focused rerun of the failed files | pass, 15 files / 153 tests, 2026-05-12 | +| `pnpm test -- --maxWorkers 2` | pass, Docker isolated, 216 files / 1592 tests, 2026-05-12 | + +The release blocker was suite-wide resource pressure from unbounded +Vitest worker concurrency in the Docker-isolated release runner, not a +deterministic assertion failure in the affected product paths. + +The release-blocker branch now applies bounded Docker-isolated Vitest +worker concurrency by default while preserving explicit `--maxWorkers` +overrides for diagnosis. + +| Step | Result | +|------|--------| +| `pnpm test:local --run test/unit/scripts/isolated-test-args.test.ts test/unit/release/docker-test-isolation.test.ts tests/playback/CORE_test-runner-docker-daemon-hard-failure.test.ts` | pass, 3 files / 19 tests, 2026-05-12 20:20 PDT | +| `pnpm release:check` | pass, includes Docker-isolated test suite at 216 files / 1594 tests plus security and pack checks, 2026-05-12 20:23 PDT | + ## Security Disposition Initial release prep found one moderate advisory: diff --git a/scripts/isolated-test-args.ts b/scripts/isolated-test-args.ts index ae50054..0a8a068 100644 --- a/scripts/isolated-test-args.ts +++ b/scripts/isolated-test-args.ts @@ -1,6 +1,20 @@ +export const DEFAULT_ISOLATED_VITEST_MAX_WORKERS = "2"; + export function normalizeVitestArgs(args: string[]): string[] { if (args[0] === "--") { return args.slice(1); } return args; } + +function hasExplicitMaxWorkers(args: string[]): boolean { + return args.some((arg) => arg === "--maxWorkers" || arg.startsWith("--maxWorkers=")); +} + +export function applyIsolatedVitestDefaults(args: string[]): string[] { + if (hasExplicitMaxWorkers(args)) { + return args; + } + + return [...args, "--maxWorkers", DEFAULT_ISOLATED_VITEST_MAX_WORKERS]; +} diff --git a/scripts/isolated-test-runner.ts b/scripts/isolated-test-runner.ts index c299337..6df516b 100644 --- a/scripts/isolated-test-runner.ts +++ b/scripts/isolated-test-runner.ts @@ -5,7 +5,10 @@ import { type DockerAvailability, } from "./docker-availability.js"; import { ensureDockerAvailability } from "./docker-autostart.js"; -import { normalizeVitestArgs } from "./isolated-test-args.js"; +import { + applyIsolatedVitestDefaults, + normalizeVitestArgs, +} from "./isolated-test-args.js"; const CONTAINER_ENV = "GRAFT_TEST_CONTAINER"; const DEFAULT_IMAGE = "graft-test:local"; @@ -107,7 +110,7 @@ export async function runIsolatedTests(options: IsolatedTestRunnerOptions): Prom argv: options.argv, env: options.env, }; - const testArgs = normalizeVitestArgs(runnerOptions.argv); + const testArgs = applyIsolatedVitestDefaults(normalizeVitestArgs(runnerOptions.argv)); if (runnerOptions.env[CONTAINER_ENV] === "1") { run("pnpm", ["exec", "vitest", "run", ...testArgs], runnerOptions); diff --git a/test/unit/release/docker-test-isolation.test.ts b/test/unit/release/docker-test-isolation.test.ts index 4243990..0d93af2 100644 --- a/test/unit/release/docker-test-isolation.test.ts +++ b/test/unit/release/docker-test-isolation.test.ts @@ -89,7 +89,7 @@ describe("Docker-isolated test validation", () => { [ "docker run --rm --network none", "-e GRAFT_TEST_CONTAINER=1", - "graft-test:local pnpm exec vitest run", + "graft-test:local pnpm exec vitest run --maxWorkers 2", ].join(" "), ]); expect(exits).toEqual([0]); diff --git a/test/unit/scripts/isolated-test-args.test.ts b/test/unit/scripts/isolated-test-args.test.ts index 2c68fb2..b2aed6a 100644 --- a/test/unit/scripts/isolated-test-args.test.ts +++ b/test/unit/scripts/isolated-test-args.test.ts @@ -1,5 +1,8 @@ import { describe, expect, it } from "vitest"; -import { normalizeVitestArgs } from "../../../scripts/isolated-test-args.js"; +import { + applyIsolatedVitestDefaults, + normalizeVitestArgs, +} from "../../../scripts/isolated-test-args.js"; describe("isolated test argument normalization", () => { it("strips the pnpm argument separator before forwarding to Vitest", () => { @@ -14,4 +17,31 @@ describe("isolated test argument normalization", () => { "--runInBand", ]); }); + + it("bounds release-grade isolated Vitest worker concurrency by default", () => { + expect(applyIsolatedVitestDefaults(["test/unit/example.test.ts"])).toEqual([ + "test/unit/example.test.ts", + "--maxWorkers", + "2", + ]); + }); + + it("does not override explicit isolated Vitest worker concurrency", () => { + expect(applyIsolatedVitestDefaults([ + "test/unit/example.test.ts", + "--maxWorkers", + "4", + ])).toEqual([ + "test/unit/example.test.ts", + "--maxWorkers", + "4", + ]); + expect(applyIsolatedVitestDefaults([ + "test/unit/example.test.ts", + "--maxWorkers=50%", + ])).toEqual([ + "test/unit/example.test.ts", + "--maxWorkers=50%", + ]); + }); }); From 6413d556af160f75732545c1279b31745b0d0eb3 Mon Sep 17 00:00:00 2001 From: James Ross Date: Wed, 13 May 2026 06:00:20 -0700 Subject: [PATCH 2/2] Fix: refresh v0.8.0 witness branch sync --- docs/method/releases/v0.8.0/verification.md | 3 +-- test/unit/release/v080-witness.test.ts | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 test/unit/release/v080-witness.test.ts diff --git a/docs/method/releases/v0.8.0/verification.md b/docs/method/releases/v0.8.0/verification.md index fcdddf4..0c3a67d 100644 --- a/docs/method/releases/v0.8.0/verification.md +++ b/docs/method/releases/v0.8.0/verification.md @@ -13,8 +13,7 @@ tag is pushed from the merged main commit. - Branch: `release/v0.8.0` - Merged main baseline: `3394d6e Merge pull request #49 from flyingrobots/release/v0.8.0` - Release blocker branch: `release/v0.8.0-blockers` -- Release blocker branch synced with origin: pending push after witness - finalization and commit +- Release blocker branch synced with origin: yes, `origin/release/v0.8.0-blockers` - `main` release guard: pending; final release runbook requires main to be exactly synced with `origin/main` before tag/publish diff --git a/test/unit/release/v080-witness.test.ts b/test/unit/release/v080-witness.test.ts new file mode 100644 index 0000000..b4c3b9b --- /dev/null +++ b/test/unit/release/v080-witness.test.ts @@ -0,0 +1,21 @@ +import * as fs from "node:fs"; +import * as path from "node:path"; +import { fileURLToPath } from "node:url"; +import { describe, expect, it } from "vitest"; + +const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../.."); + +function readReleaseWitness(): string { + return fs.readFileSync(path.join(ROOT, "docs/method/releases/v0.8.0/verification.md"), "utf8"); +} + +describe("v0.8.0 release witness", () => { + it("records the pushed release blocker branch instead of stale pending sync text", () => { + const witness = readReleaseWitness(); + + expect(witness).toContain( + "- Release blocker branch synced with origin: yes, `origin/release/v0.8.0-blockers`", + ); + expect(witness).not.toContain("Release blocker branch synced with origin: pending"); + }); +});