Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 20 additions & 0 deletions docs/design/BADCODE_full-suite-timeout-nondeterminism.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand All @@ -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`
2 changes: 2 additions & 0 deletions docs/method/releases/v0.8.0/release.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
36 changes: 31 additions & 5 deletions docs/method/releases/v0.8.0/verification.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
# 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

- Repository type: JS/TS package (pnpm)
- 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: 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

Expand All @@ -28,6 +30,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:
Expand Down
14 changes: 14 additions & 0 deletions scripts/isolated-test-args.ts
Original file line number Diff line number Diff line change
@@ -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];
}
7 changes: 5 additions & 2 deletions scripts/isolated-test-runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion test/unit/release/docker-test-isolation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
Expand Down
21 changes: 21 additions & 0 deletions test/unit/release/v080-witness.test.ts
Original file line number Diff line number Diff line change
@@ -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");
});
});
32 changes: 31 additions & 1 deletion test/unit/scripts/isolated-test-args.test.ts
Original file line number Diff line number Diff line change
@@ -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", () => {
Expand All @@ -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%",
]);
});
});
Loading