From 626df112a24aa66eceaca92a9b0d92be90093fd8 Mon Sep 17 00:00:00 2001 From: James Ross Date: Fri, 2 Oct 2026 03:24:44 -0700 Subject: [PATCH 1/8] docs: design published mktree recovery adoption --- .../mktree-transport-recovery.md | 175 ++++++++++++++++++ 1 file changed, 175 insertions(+) create mode 100644 docs/design/0062-mktree-transport-recovery/mktree-transport-recovery.md diff --git a/docs/design/0062-mktree-transport-recovery/mktree-transport-recovery.md b/docs/design/0062-mktree-transport-recovery/mktree-transport-recovery.md new file mode 100644 index 0000000..a164eb4 --- /dev/null +++ b/docs/design/0062-mktree-transport-recovery/mktree-transport-recovery.md @@ -0,0 +1,175 @@ +--- +title: "PROTO-0062 - Mktree transport recovery" +cycle: "0062" +task_id: "mktree-transport-recovery" +legend: "PROTO" +release_home: "v6.5.11" +issue: "https://github.com/git-stunts/git-cas/issues/131" +goalpost_issue: "none" +tracker_source: "github" +status: "active" +base_commit: "c02c87e" +owners: ["@git-stunts"] +sponsors: + human: "James" + agent: "Codex" +blocking_issues: ["https://github.com/git-stunts/plumbing/pull/20"] +supersedes: [] +superseded_by: null +created: "2026-10-02" +updated: "2026-10-02" +--- + +# Mktree transport recovery + +## Linked Issue + +https://github.com/git-stunts/git-cas/issues/131 + +## Linked Tracker + +GitHub milestone [v6.5.11](https://github.com/git-stunts/git-cas/milestone/21) owns release membership; issue #131 owns workflow state. + +## Design Type + +Dependency adoption and patch release with executable storage recovery proof. + +## Decision Summary + +Require published Plumbing 3.3.2, preserving the existing one-retry immutable-object session policy. Verify public protocol classes and packed registry consumers through COPY-based Docker. + +## Sponsored Human + +James Ross. + +## Sponsored Agent + +Codex. + +## Hill + +A consumer can build single and batched trees after external Git repacking closes a stale mktree transport, without an unbounded retry or altered producer error. + +## Current Truth + +`package.json` currently requires Plumbing ^3.3.0. `GitObjectSessionPool.#attempt` invalidates a failed session and retries only GitProtocolError once. Plumbing PR #20 classifies transport EPIPE and SESSION_INPUT_CLOSED accordingly; registry publication is a prerequisite, not evidence already obtained. + +## Problem + +Git-warp attachment checkpoint/GC checks exposed raw EPIPE escaping bounded recovery. Its patch-package repair only covers development checkouts and does not deploy the repair to npm consumers. + +## Scope + +Adopt the repaired registry dependency and lockfile; add transport recovery regressions; verify all runtimes, public types, examples and packages; publish 6.5.11 with integrity and consumer receipts. Route encountered CI/release unit tests and examples through COPY-based Docker. + +## Non-Goals + +No new retry, mutable-ref recovery, asset format, recursive ownership, git-warp attachment API, or performance claim. + +## Runtime / API Contract + +Single and batch tree operations retry a transport failure once through a fresh process. Two failed attempts terminate with GitProtocolError. EACCES and producer EPIPE preserve their identity and do not retry. Stored object identities and publication semantics remain unchanged. + +## Accessibility Posture + +Evidence and errors are plain linear text and machine-readable results; no visual-only interaction is introduced. + +## User-Facing Text / Directionality + +No localization or new UI strings. Release notes name the recovery and its bounded retry contract. + +## Agent Inspectability / Explainability Posture + +Fault-injected public CommandSession/GitMktreeSession instances expose attempt counts and exact OIDs. Registry version, integrity and installed dependency versions are recorded separately from checkout validation. + +## Linked Invariants + +Immutable-object retry remains bounded; producer errors are not transport errors; mutable refs are not retried by this repair; test execution never mounts host Git repositories. + +## Design Alternatives Considered + +A downstream local patch does not reach registry consumers. Broadly retrying all errors would obscure producer failures and operational refusals. Published protocol classification uses the existing narrowly typed retry. + +## Decision + +Adopt Plumbing rather than duplicate its transport classification in git-cas. + +## Proof Surface + +The first RED uses the actual protocol and persistence adapter with transport failure injection against unchanged Plumbing. The GREEN uses the published dependency; all runtime and package acceptance remains separate from design/documentation assertions. + +## Implementation Slices + +Design and prerequisite record; deterministic fault regression and published dependency adoption; isolated release gates; current-head independent review; merge and full release verification; signed tag and registry publication; downstream consumer proof. + +## Tests To Write First + +Single and batch EPIPE recovery; already-closed input; two-attempt bound; unrelated transport error; producer error preservation. CI/release configuration checks must reject host unit-test and example execution. + +## Acceptance Criteria + +The six recovery checks and all required runtime suites pass in Docker. Lint, examples, public type and package/JSR validation pass. 6.5.11 metadata agrees and the published registry artifact resolves Plumbing >=3.3.2. The issue stays open until publication/consumer evidence exists. + +## Validation Plan + +Use COPY-based Docker Compose services for Node/Bun/Deno unit and serialized integration suites, Deno public types, and Node examples; run lint and package/JSR dry runs with the release candidate. No host runtime tests or repository mounts. + +## Playback / Witness + +Human: does the installed package survive a dead mktree process? Agent: do canonical OIDs, exactly two process attempts, original failures and artifact integrity prove the claim? Store pinned RED/GREEN and publication receipts under this cycle witness directory. + +## Risks + +A missing upstream registry version blocks adoption. A successful checkout patch is not consumer evidence. A GitHub approval gate or publication environment may delay the train; neither permits bypassing protections. + +## Follow-On Debt + +None currently discovered beyond tracked git-warp #923 attachment adoption. + +## Tracker Disposition + +Issue #131 remains open through release publication; Plumbing PR #20 is the upstream prerequisite; git-warp #923 remains open through downstream adoption. + +## Done Does Not Mean + +This dependency repair does not restore git-warp Runtime/Lane node/edge attachment operations or complete its bounded stream contract. + +## Retrospective + +Record after merge and publication, with PR and release receipts. + +## Data / State Model + +No stored data or ref schema changes. + +## Architecture / Anti-SLUDGE Posture + +Transport classification remains owned by Plumbing. git-cas retains its typed session retry and constructor-injected plumbing boundary. + +## Cost / Residency Posture + +At most one replacement immutable-object process; no new collector or buffer. + +## Determinism / Replay / Causality + +Canonical Git tree IDs are unchanged; deterministic injected faults establish retry behavior without timing assertions. + +## Git Substrate Impact + +Existing trees/blobs remain unchanged. Release creates a new immutable signed version tag after merged-main verification. + +## Compatibility / Migration Posture + +Patch dependency minimum only; no consumer source or stored-data migration. + +## Error Contract + +Transport EPIPE and SESSION_INPUT_CLOSED become GitProtocolError with original cause. Other errors retain identity. + +## Security / Trust / Redaction Posture + +Receipts contain repository-relative paths, public commits and package integrity; no credentials or machine-local paths. + +## Lower Modes + +Plain logs and JSON receipts provide complete evidence without color or rendered UI. From d7717d3a3d02d030f6a98c01ee8f664b92e9cf99 Mon Sep 17 00:00:00 2001 From: James Ross Date: Fri, 2 Oct 2026 03:31:57 -0700 Subject: [PATCH 2/8] Fix: isolate git-cas release verification in Docker --- .github/workflows/ci.yml | 2 +- .github/workflows/release.yml | 2 +- CHANGELOG.md | 4 +++ .../mktree-transport-recovery.md | 2 +- docs/design/README.md | 2 ++ docs/method/release.md | 2 +- package.json | 9 +++--- pnpm-lock.yaml | 9 ++++++ scripts/release/verify.js | 16 +++++------ scripts/testing/DockerExecutionBoundary.js | 11 ++++++++ test/CONVENTIONS.md | 6 ++-- test/docker-setup.js | 3 ++ .../scripts/docker-execution-boundary.test.js | 28 +++++++++++++++++++ test/unit/scripts/release-verify.test.js | 13 +++++++-- test/unit/scripts/release-workflow.test.js | 12 ++++++++ vitest.config.js | 1 + 16 files changed, 100 insertions(+), 22 deletions(-) create mode 100644 scripts/testing/DockerExecutionBoundary.js create mode 100644 test/docker-setup.js create mode 100644 test/unit/scripts/docker-execution-boundary.test.js diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index feb8f78..1219201 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,7 +33,7 @@ jobs: node-version: 22 cache: pnpm - run: pnpm install --frozen-lockfile - - run: pnpm test + - run: docker compose run --build --rm test-node test-docker: runs-on: ubuntu-latest diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6f705ab..878ff6c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -62,7 +62,7 @@ jobs: cache: pnpm - run: pnpm install --frozen-lockfile - run: pnpm run lint - - run: pnpm test + - run: docker compose run --build --rm test-node - name: Integration tests (Node) run: docker compose run --build --rm test-node pnpm vitest run test/integration --no-file-parallelism diff --git a/CHANGELOG.md b/CHANGELOG.md index b15a78c..5615ed7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- CI and release Node unit checks and examples run through COPY-based Docker. Tests and benchmarks refuse host execution before loading test modules, including forged Docker and CI environment flags. + ## [6.5.10] — 2026-08-24 ### Added diff --git a/docs/design/0062-mktree-transport-recovery/mktree-transport-recovery.md b/docs/design/0062-mktree-transport-recovery/mktree-transport-recovery.md index a164eb4..5b73325 100644 --- a/docs/design/0062-mktree-transport-recovery/mktree-transport-recovery.md +++ b/docs/design/0062-mktree-transport-recovery/mktree-transport-recovery.md @@ -8,7 +8,7 @@ issue: "https://github.com/git-stunts/git-cas/issues/131" goalpost_issue: "none" tracker_source: "github" status: "active" -base_commit: "c02c87e" +base_commit: "c02c87ee0d7a72b0371762e5239223adb3ac4781" owners: ["@git-stunts"] sponsors: human: "James" diff --git a/docs/design/README.md b/docs/design/README.md index 3f1c7d2..ed55fc4 100644 --- a/docs/design/README.md +++ b/docs/design/README.md @@ -11,6 +11,8 @@ process in [docs/method/process.md](../method/process.md). ## Active METHOD Cycles +- [0062-mktree-transport-recovery - mktree-transport-recovery](./0062-mktree-transport-recovery/mktree-transport-recovery.md) + - [0054-batched-page-retention - batched-page-retention](./0054-batched-page-retention/batched-page-retention.md) - [0050-lazy-bundle-reference-reads - lazy-bundle-reference-reads](./0050-lazy-bundle-reference-reads/lazy-bundle-reference-reads.md) - [0049-scoped-staging-workspaces — scoped-staging-workspaces](./0049-scoped-staging-workspaces/scoped-staging-workspaces.md) diff --git a/docs/method/release.md b/docs/method/release.md index ad15d82..6d0712d 100644 --- a/docs/method/release.md +++ b/docs/method/release.md @@ -10,7 +10,7 @@ All of the following must pass on the release candidate. Prefer `npm run release:verify` so the release record comes from one command. 1. `npx eslint .` -2. `npm test` +2. `docker compose run --build --rm test-node` 3. `docker compose run --build --rm test-node npx vitest run test/integration` 4. `docker compose run --build --rm test-bun bunx vitest run test/unit` 5. `docker compose run --build --rm test-bun bunx vitest run test/integration` diff --git a/package.json b/package.json index 6c1de04..9677564 100644 --- a/package.json +++ b/package.json @@ -86,17 +86,17 @@ "provenance": true }, "scripts": { - "test": "vitest run test/unit", + "test": "docker compose run --build --rm test-node", "test:local": "vitest run test/unit", "test:node": "docker compose run --build --rm test-node", "test:bun": "docker compose run --build --rm test-bun", "test:deno": "docker compose run --build --rm test-deno", - "test:integration": "vitest run test/integration --no-file-parallelism", + "test:integration": "docker compose run --build --rm test-node pnpm vitest run test/integration --no-file-parallelism", "test:integration:node": "docker compose run --build --rm test-node npx vitest run test/integration --no-file-parallelism", "test:integration:bun": "docker compose run --build --rm test-bun bunx vitest run test/integration --no-file-parallelism", "test:integration:deno": "docker compose run --build --rm test-deno deno run -A npm:vitest run test/integration --no-file-parallelism", "test:platforms": "bats --jobs 3 test/platform/runtimes.bats", - "benchmark": "vitest bench test/benchmark", + "benchmark": "docker compose run --build --rm test-node pnpm vitest bench test/benchmark", "benchmark:local": "vitest bench test/benchmark", "release:verify": "node scripts/release/verify.js", "stamp": "node scripts/stamp-build.js", @@ -131,6 +131,7 @@ "fast-check": "^4.6.0", "jsr": "^0.14.2", "prettier": "^3.4.2", - "vitest": "^2.1.8" + "vitest": "^2.1.8", + "@git-stunts/docker-guard": "^0.1.0" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 71471fc..895d3ef 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -42,6 +42,9 @@ importers: '@eslint/js': specifier: ^9.17.0 version: 9.39.2 + '@git-stunts/docker-guard': + specifier: ^0.1.0 + version: 0.1.0 '@types/node': specifier: ^25.3.2 version: 25.3.2 @@ -297,6 +300,9 @@ packages: resolution: {integrity: sha512-0DPhJdKhYTcsPuoOnYIIyvlwaIM7yIx4fQM4Q48abe/VDLTfZef1ubeT1pYio+ZTp1lKXtSG663973ewBbi/yw==} engines: {node: '>=20.0.0'} + '@git-stunts/docker-guard@0.1.0': + resolution: {integrity: sha512-9h2kzMlidbWeoj62VybBzwEMeMySqN/p3vP03rg5enklElkde68KhwfHB3pfaSR/Cx50tnUT27Vfcb7RMcdZkA==} + '@git-stunts/plumbing@3.3.0': resolution: {integrity: sha512-v/AT3hKgmFKSQ3M+n7n9VgC5Ri7C+NDtZS11Bj1JmT0Xv523hNdjCIaRHSXjAaCpiuXuDKFlj+E8PcBI1+FxbA==} engines: {bun: '>=1.3.5', deno: '>=2.0.0', node: '>=20.0.0'} @@ -626,6 +632,7 @@ packages: eslint@9.39.2: resolution: {integrity: sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. hasBin: true peerDependencies: jiti: '*' @@ -1161,6 +1168,8 @@ snapshots: '@git-stunts/alfred@0.10.0': {} + '@git-stunts/docker-guard@0.1.0': {} + '@git-stunts/plumbing@3.3.0': dependencies: zod: 3.25.76 diff --git a/scripts/release/verify.js b/scripts/release/verify.js index a8d7091..655e340 100644 --- a/scripts/release/verify.js +++ b/scripts/release/verify.js @@ -37,27 +37,27 @@ export const RELEASE_STEPS = [ { id: 'unit-node', label: 'Unit Tests (Node)', - command: 'pnpm', - args: ['test'], + command: 'docker', + args: ['compose', 'run', '--build', '--rm', 'test-node'], testCount: true, }, { id: 'example-store-and-restore', label: 'Example: store-and-restore', - command: 'node', - args: ['examples/store-and-restore.js'], + command: 'docker', + args: ['compose', 'run', '--build', '--rm', 'test-node', 'node', 'examples/store-and-restore.js'], }, { id: 'example-encrypted-workflow', label: 'Example: encrypted-workflow', - command: 'node', - args: ['examples/encrypted-workflow.js'], + command: 'docker', + args: ['compose', 'run', '--build', '--rm', 'test-node', 'node', 'examples/encrypted-workflow.js'], }, { id: 'example-progress-tracking', label: 'Example: progress-tracking', - command: 'node', - args: ['examples/progress-tracking.js'], + command: 'docker', + args: ['compose', 'run', '--build', '--rm', 'test-node', 'node', 'examples/progress-tracking.js'], }, { id: 'unit-bun', diff --git a/scripts/testing/DockerExecutionBoundary.js b/scripts/testing/DockerExecutionBoundary.js new file mode 100644 index 0000000..7b558ae --- /dev/null +++ b/scripts/testing/DockerExecutionBoundary.js @@ -0,0 +1,11 @@ +import { existsSync } from 'node:fs'; +import { ensureDocker } from '@git-stunts/docker-guard'; + +/** Require a physical Docker marker; environment flags cannot authorize host tests. */ +export function requireDockerExecution({ readMarker = existsSync, exit, logger } = {}) { + ensureDocker({ + env: readMarker('/.dockerenv') ? { GIT_STUNTS_DOCKER: '1' } : {}, + exit, + logger, + }); +} diff --git a/test/CONVENTIONS.md b/test/CONVENTIONS.md index e9efb59..0e8309c 100644 --- a/test/CONVENTIONS.md +++ b/test/CONVENTIONS.md @@ -66,11 +66,9 @@ sorted output semantics instead of assuming first-seen insertion order. If insertion order matters, assert it at the lower-level boundary that builds the tree entries before they are handed to Git. -## Integration Runtime Policy +## Test Runtime Policy -**Integration suites are Docker-only.** The integration tests intentionally -refuse to run on the host and require `GIT_STUNTS_DOCKER=1` so Git, Bun, and -Deno run in a consistent environment. +**All tests and benchmarks are Docker-only.** Vitest setup uses `@git-stunts/docker-guard` with a physical Docker marker before test modules load. Environment flags do not authorize host execution. Use COPY-based Docker Compose services without repository or Git-directory mounts. `npm test` routes to the Node service; `test:local` is an in-container command. **Integration files run with `fileParallelism: false`.** These tests spawn real Git and CLI subprocesses, so the integration workspace is intentionally kept to diff --git a/test/docker-setup.js b/test/docker-setup.js new file mode 100644 index 0000000..26e1774 --- /dev/null +++ b/test/docker-setup.js @@ -0,0 +1,3 @@ +import { requireDockerExecution } from '../scripts/testing/DockerExecutionBoundary.js'; + +requireDockerExecution(); diff --git a/test/unit/scripts/docker-execution-boundary.test.js b/test/unit/scripts/docker-execution-boundary.test.js new file mode 100644 index 0000000..52ab2bd --- /dev/null +++ b/test/unit/scripts/docker-execution-boundary.test.js @@ -0,0 +1,28 @@ +import { describe, expect, it, vi } from 'vitest'; +import { requireDockerExecution } from '../../../scripts/testing/DockerExecutionBoundary.js'; + +describe('physical Docker test boundary', () => { + it('rejects an absent marker even when ambient environment claims Docker or CI', () => { + vi.stubEnv('GIT_STUNTS_DOCKER', '1'); + vi.stubEnv('GITHUB_ACTIONS', 'true'); + const exit = vi.fn(); + const logger = vi.fn(); + try { + requireDockerExecution({ readMarker: () => false, exit, logger }); + expect(exit).toHaveBeenCalledWith(1); + expect(logger).toHaveBeenCalledWith(expect.stringContaining('HOST EXECUTION PROHIBITED')); + } finally { + vi.unstubAllEnvs(); + } + }); + + it('accepts a physical marker without relying on ambient flags', () => { + const readMarker = vi.fn().mockReturnValue(true); + const exit = vi.fn(); + const logger = vi.fn(); + requireDockerExecution({ readMarker, exit, logger }); + expect(readMarker).toHaveBeenCalledWith('/.dockerenv'); + expect(exit).not.toHaveBeenCalled(); + expect(logger).not.toHaveBeenCalled(); + }); +}); diff --git a/test/unit/scripts/release-verify.test.js b/test/unit/scripts/release-verify.test.js index 2da9a38..45262b9 100644 --- a/test/unit/scripts/release-verify.test.js +++ b/test/unit/scripts/release-verify.test.js @@ -188,10 +188,10 @@ describe('release verify step definitions', () => { .map((file) => path.posix.join('examples', file)) .sort(); const exampleSteps = RELEASE_STEPS.filter((step) => step.id.startsWith('example-')); - const examplePaths = exampleSteps.map((step) => step.args[0]).sort(); + const examplePaths = exampleSteps.map((step) => step.args.at(-1)).sort(); expect(examplePaths).toEqual(expectedExamplePaths); - expect(exampleSteps.every((step) => step.command === 'node')).toBe(true); + expect(exampleSteps.every((step) => step.command === 'docker' && step.args.includes('test-node') && step.args.includes('node'))).toBe(true); }); it('serializes Bun unit files to avoid CPU-heavy test starvation', () => { @@ -300,3 +300,12 @@ describe('release verify package assertions', () => { }); }); }); + + +describe('release verification isolation', () => { + it('runs the Node unit gate in a copied Docker image', () => { + const step = RELEASE_STEPS.find((entry) => entry.id === 'unit-node'); + expect(step).toMatchObject({ command: 'docker' }); + expect(step.args).toEqual(expect.arrayContaining(['compose', 'run', '--build', '--rm', 'test-node'])); + }); +}); diff --git a/test/unit/scripts/release-workflow.test.js b/test/unit/scripts/release-workflow.test.js index 1459fb7..d289f65 100644 --- a/test/unit/scripts/release-workflow.test.js +++ b/test/unit/scripts/release-workflow.test.js @@ -53,3 +53,15 @@ describe('release workflow publishing', () => { } }); }); + + +describe('release test isolation', () => { + it('runs the Node unit gate through COPY-based Docker in CI and publication', () => { + for (const file of ['.github/workflows/ci.yml', '.github/workflows/release.yml']) { + const source = read(file); + expect(source).not.toMatch(/^\s+- run: pnpm test$/mu); + expect(source).toContain('docker compose run --build --rm test-node'); + } + expect(read('docker-compose.yml')).not.toMatch(/^\s+volumes:/mu); + }); +}); diff --git a/vitest.config.js b/vitest.config.js index 10db157..b86bc04 100644 --- a/vitest.config.js +++ b/vitest.config.js @@ -1,4 +1,5 @@ import { defineConfig } from 'vitest/config'; export default defineConfig({ + test: { setupFiles: ['./test/docker-setup.js'] }, }); From 0f2567c18eceb1884fa8eb152cc2444e5d5a2ebd Mon Sep 17 00:00:00 2001 From: James Ross Date: Fri, 2 Oct 2026 03:37:30 -0700 Subject: [PATCH 3/8] docs: record isolated dependency recovery preparation --- .../witness/preparation-evidence.txt | 55 +++++++++++++++++++ .../witness/preparation.md | 34 ++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 docs/design/0062-mktree-transport-recovery/witness/preparation-evidence.txt create mode 100644 docs/design/0062-mktree-transport-recovery/witness/preparation.md diff --git a/docs/design/0062-mktree-transport-recovery/witness/preparation-evidence.txt b/docs/design/0062-mktree-transport-recovery/witness/preparation-evidence.txt new file mode 100644 index 0000000..693e553 --- /dev/null +++ b/docs/design/0062-mktree-transport-recovery/witness/preparation-evidence.txt @@ -0,0 +1,55 @@ +Release isolation RED + FAIL |unit| test/unit/scripts/release-verify.test.js > release verify step definitions > executes maintained examples as release-gate steps +AssertionError: expected false to be true // Object.is equality + FAIL |unit| test/unit/scripts/release-verify.test.js > release verification isolation > runs the Node unit gate in a copied Docker image +AssertionError: expected { id: 'unit-node', …(4) } to match object { command: 'docker' } + FAIL |unit| test/unit/scripts/release-workflow.test.js > release test isolation > runs the Node unit gate through COPY-based Docker in CI and publication +AssertionError: expected 'name: CI\n\non:\n push:\n branche…' not to match /^\s+- run: pnpm test$/mu + Test Files 2 failed (2) + Tests 3 failed | 17 passed (20) + Start at 10:27:23 + Duration 235ms (transform 52ms, setup 0ms, collect 88ms, tests 20ms, environment 0ms, prepare 80ms) + +Recovery RED against locked Plumbing 3.3.0 + FAIL |unit| test/unit/infrastructure/adapters/GitPersistenceAdapter.mktree-recovery.test.js > mktree transport recovery > reopens the process once for a broken pipe writing one tree + FAIL |unit| test/unit/infrastructure/adapters/GitPersistenceAdapter.mktree-recovery.test.js > mktree transport recovery > reopens the process once for a broken pipe writing tree batch + FAIL |unit| test/unit/infrastructure/adapters/GitPersistenceAdapter.mktree-recovery.test.js > mktree transport recovery > also recovers when process completion wins the race with the next write +AssertionError: promise rejected "GitPlumbingError: input closed { …(2) }" instead of resolving + FAIL |unit| test/unit/infrastructure/adapters/GitPersistenceAdapter.mktree-recovery.test.js > mktree transport recovery > reports a typed failure after two broken processes, without an unbounded retry +AssertionError: expected Error: broken pipe { code: 'EPIPE' } to be an instance of GitProtocolError + Test Files 1 failed (1) + Tests 4 failed | 2 passed (6) + Start at 10:26:44 + Duration 420ms (transform 171ms, setup 0ms, collect 299ms, tests 7ms, environment 0ms, prepare 35ms) + +Recovery GREEN against packed 3.3.2 candidate + Test Files 1 passed (1) + Tests 6 passed (6) + Start at 10:34:34 + Duration 450ms (transform 185ms, setup 6ms, collect 317ms, tests 7ms, environment 0ms, prepare 36ms) + +Node preparation unit and lint + Test Files 233 passed | 1 skipped (234) + Tests 2197 passed | 3 skipped (2200) + Start at 10:31:15 + Duration 6.35s (transform 3.39s, setup 2.36s, collect 25.61s, tests 21.50s, environment 20ms, prepare 8.07s) +> @git-stunts/git-cas@6.5.10 lint +> eslint . + +Bun preparation unit + Test Files 233 passed | 1 skipped (234) + Tests 2197 passed | 3 skipped (2200) + Start at 10:34:05 + Duration 37.11s (transform 625ms, setup 476ms, collect 4.88s, tests 16.66s, environment 14ms, prepare 4.46s) + +Deno preparation unit + Test Files 233 passed | 1 skipped (234) + Tests 2188 passed | 12 skipped (2200) + Start at 10:34:43 + Duration 4.77s (transform 1.98s, setup 3.66s, collect 35.97s, tests 18.37s, environment 23ms, prepare 5.01s) + +Node candidate integration + Test Files 14 passed (14) + Tests 207 passed (207) + Start at 10:35:10 + Duration 22.71s (transform 233ms, setup 32ms, collect 1.35s, tests 20.38s, environment 1ms, prepare 277ms) diff --git a/docs/design/0062-mktree-transport-recovery/witness/preparation.md b/docs/design/0062-mktree-transport-recovery/witness/preparation.md new file mode 100644 index 0000000..310dc2b --- /dev/null +++ b/docs/design/0062-mktree-transport-recovery/witness/preparation.md @@ -0,0 +1,34 @@ +# Preparatory playback evidence + +These are preparatory results for issue [#131](https://github.com/git-stunts/git-cas/issues/131), not release or registry-publication evidence. + +## Coordinates + +Git-cas preparation source: `d7717d3`; base: `c02c87ee0d7a72b0371762e5239223adb3ac4781`. Upstream candidate source: `d728cbf56ac85ccef9b276422e6168a68a9e6ad0`. The baseline Docker dependency reports Plumbing `3.3.0`; the packed-candidate Docker dependency reports `3.3.2`. + +Packed Plumbing candidate SHA-512 (hex): `c86840045f5efa8f867366fc2fc2e45c9804d000ab248d0c6f2b7147285970728b25f645bfd2c0b3c44dc6fe0ab485cbc501e3d92b655bb4f5e300e4d1b5d63f`. The artifact was produced with `npm pack` inside COPY-based Docker; it was installed only in a preparation image. It is not a registry artifact, and no file/tarball dependency is added to the tracked package or lockfile. + +## Executed checks + +| Surface | Outcome | Qualification | +| --- | --- | --- | +| CI/release isolation regression | 3 failed, 17 passed before fix | Detected host unit and example paths | +| Public mktree/persistence recovery regression | 4 failed, 2 passed before upstream adoption | Actual protocol instances against locked 3.3.0 | +| Same recovery regression with candidate | 6 passed | Packed 3.3.2, not yet npm | +| Node unit preparation | 2,197 passed, 3 skipped | Existing skips retained | +| Bun unit preparation | 2,197 passed, 3 skipped | Existing skips retained | +| Deno unit preparation | 2,188 passed, 12 skipped | Existing runtime skips retained | +| Node integration with candidate | 207 passed | 14 integration files, serialized | +| ESLint | Passed | Entire preparation checkout | + +Selected raw summaries are in [preparation-evidence.txt](./preparation-evidence.txt). Complete hosted CI for `d7717d3`, including all three integration runtimes and public Deno types, is [run 36995996685](https://github.com/git-stunts/git-cas/actions/runs/36995996685). + +All executions used COPY-based Docker with no host repository or Git-directory mounts. Environment flags did not authorize a host execution. The new guard checks the physical Docker marker before test modules load. + +## Playback + +Human: the packed candidate can recover the stale-transport fault and the 207 Git-backed integration checks pass. Agent: deterministic fault cases show canonical OIDs, two attempts at most, and unchanged unrelated/producer failure identities. Those are separate claims from actual external GC and registry consumer delivery. + +## Remaining release evidence + +Merge and publish Plumbing 3.3.2; update the registry dependency minimum and lockfile; rerun complete release verification against that immutable registry version; review/merge git-cas; verify synced main; publish 6.5.11; record registry integrity and fresh consumer proof; adopt it in git-warp. No tagged or published 6.5.11 is claimed here. From 05ff73672cb8ac6a67f98c4f1d9865cd80704352 Mon Sep 17 00:00:00 2001 From: James Ross Date: Fri, 2 Oct 2026 05:05:35 -0700 Subject: [PATCH 4/8] Fix: adopt published Plumbing mktree transport recovery --- CHANGELOG.md | 2 + package.json | 2 +- pnpm-lock.yaml | 10 +- ...PersistenceAdapter.mktree-recovery.test.js | 107 ++++++++++++++++++ 4 files changed, 115 insertions(+), 6 deletions(-) create mode 100644 test/unit/infrastructure/adapters/GitPersistenceAdapter.mktree-recovery.test.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 5615ed7..c58fff8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- Require published Plumbing 3.3.2 so stale mktree transports after external Git GC enter the existing single fresh-process retry for single and batch tree writes. Producer and unrelated transport errors retain their identity; retry remains bounded at two attempts. + - CI and release Node unit checks and examples run through COPY-based Docker. Tests and benchmarks refuse host execution before loading test modules, including forged Docker and CI environment flags. ## [6.5.10] — 2026-08-24 diff --git a/package.json b/package.json index 9677564..2e06632 100644 --- a/package.json +++ b/package.json @@ -112,7 +112,7 @@ "@flyingrobots/bijou-tui": "^7.2.0", "@flyingrobots/bijou-tui-app": "^7.2.0", "@git-stunts/alfred": "^0.10.0", - "@git-stunts/plumbing": "^3.3.0", + "@git-stunts/plumbing": "^3.3.2", "@git-stunts/vault": "^1.0.1", "cbor-x": "^1.6.0", "commander": "14.0.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 895d3ef..4befee5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -24,8 +24,8 @@ importers: specifier: ^0.10.0 version: 0.10.0 '@git-stunts/plumbing': - specifier: ^3.3.0 - version: 3.3.0 + specifier: ^3.3.2 + version: 3.3.2 '@git-stunts/vault': specifier: ^1.0.1 version: 1.0.1 @@ -303,8 +303,8 @@ packages: '@git-stunts/docker-guard@0.1.0': resolution: {integrity: sha512-9h2kzMlidbWeoj62VybBzwEMeMySqN/p3vP03rg5enklElkde68KhwfHB3pfaSR/Cx50tnUT27Vfcb7RMcdZkA==} - '@git-stunts/plumbing@3.3.0': - resolution: {integrity: sha512-v/AT3hKgmFKSQ3M+n7n9VgC5Ri7C+NDtZS11Bj1JmT0Xv523hNdjCIaRHSXjAaCpiuXuDKFlj+E8PcBI1+FxbA==} + '@git-stunts/plumbing@3.3.2': + resolution: {integrity: sha512-yGhABF9e+o+Gc2b8L8LkXJgE0ACrJI0MbytxRyhZcHKLJfZFv9LAs8RNxv4KtIXLxQHj2StlW7T14wDk0bXWPw==} engines: {bun: '>=1.3.5', deno: '>=2.0.0', node: '>=20.0.0'} '@git-stunts/vault@1.0.1': @@ -1170,7 +1170,7 @@ snapshots: '@git-stunts/docker-guard@0.1.0': {} - '@git-stunts/plumbing@3.3.0': + '@git-stunts/plumbing@3.3.2': dependencies: zod: 3.25.76 diff --git a/test/unit/infrastructure/adapters/GitPersistenceAdapter.mktree-recovery.test.js b/test/unit/infrastructure/adapters/GitPersistenceAdapter.mktree-recovery.test.js new file mode 100644 index 0000000..9ca59c6 --- /dev/null +++ b/test/unit/infrastructure/adapters/GitPersistenceAdapter.mktree-recovery.test.js @@ -0,0 +1,107 @@ +import { describe, expect, it } from 'vitest'; +import { GitPersistenceAdapter } from '../../../../index.js'; +import { CommandSession, GitMktreeSession, GitPlumbingError, GitProtocolError } from '@git-stunts/plumbing'; +import { Readable } from 'node:stream'; +import { TextEncoder } from 'node:util'; + +const OID = 'a'.repeat(40); +const TREE = [`100644 blob ${'b'.repeat(40)}\tcontent`]; + +function command(failure) { + let finish; + const finished = new Promise((resolve) => { finish = resolve; }); + function settle({ code, terminated }) { + finish({ code, error: null, signal: null, stderr: '', terminated, timedOut: false }); + } + return new CommandSession({ + stdoutStream: Readable.from([new TextEncoder().encode(`${OID}\n`)]), + finished, + write: async () => { if (failure !== null) { throw failure; } }, + closeInput: async () => { settle({ code: 0, terminated: false }); }, + terminate: () => { settle({ code: 1, terminated: true }); }, + }); +} + +function fixture(failures) { + let openings = 0; + const persistence = new GitPersistenceAdapter({ + plumbing: { + openMktreeSession: async () => { + const failure = failures[openings] ?? null; + openings += 1; + return new GitMktreeSession(command(failure)); + }, + }, + policy: { execute: (operation) => operation() }, + sessionIdleTimeoutMs: 60_000, + }); + return { persistence, openings: () => openings }; +} + +function brokenPipe() { + return Object.assign(new Error('broken pipe'), { code: 'EPIPE' }); +} + +describe('mktree transport recovery', () => { + it.each(['one tree', 'tree batch'])('reopens the process once for a broken pipe writing %s', async (mode) => { + const { persistence, openings } = fixture([brokenPipe()]); + try { + const result = mode === 'one tree' + ? await persistence.writeTree(TREE) + : await persistence.writeTrees([TREE]); + expect(result).toEqual(mode === 'one tree' ? OID : [OID]); + expect(openings()).toBe(2); + } finally { + await persistence.close(); + } + }); + + it('also recovers when process completion wins the race with the next write', async () => { + const failure = new GitPlumbingError('input closed', 'write', { code: 'SESSION_INPUT_CLOSED' }); + const { persistence, openings } = fixture([failure]); + try { + await expect(persistence.writeTree(TREE)).resolves.toBe(OID); + expect(openings()).toBe(2); + } finally { + await persistence.close(); + } + }); + +}); + +describe('mktree recovery error preservation', () => { + it('reports a typed failure after two broken processes, without an unbounded retry', async () => { + const { persistence, openings } = fixture([brokenPipe(), brokenPipe()]); + try { + await expect(persistence.writeTree(TREE)).rejects.toBeInstanceOf(GitProtocolError); + expect(openings()).toBe(2); + } finally { + await persistence.close(); + } + }); + + it('preserves other write failures and does not retry them', async () => { + const failure = Object.assign(new Error('permission denied'), { code: 'EACCES' }); + const { persistence, openings } = fixture([failure]); + try { + await expect(persistence.writeTree(TREE)).rejects.toBe(failure); + expect(openings()).toBe(1); + } finally { + await persistence.close(); + } + }); + + it('preserves a producer failure even when it has the same error code', async () => { + const failure = brokenPipe(); + const session = new GitMktreeSession(command(null)); + async function* entries() { + yield { mode: '100644', type: 'blob', oid: 'b'.repeat(40), name: 'content' }; + throw failure; + } + try { + await expect(session.write(entries())).rejects.toBe(failure); + } finally { + await session.terminate(); + } + }); +}); From e7124767dfbeccf49fcdad892edb5da5c93bab77 Mon Sep 17 00:00:00 2001 From: James Ross Date: Fri, 2 Oct 2026 05:07:58 -0700 Subject: [PATCH 5/8] Release: prepare git-cas 6.5.11 recovery candidate --- CHANGELOG.md | 2 ++ README.md | 2 ++ UPGRADING.md | 4 ++++ docs/releases/v6.5.11.md | 15 +++++++++++++++ jsr.json | 2 +- package.json | 5 +++-- 6 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 docs/releases/v6.5.11.md diff --git a/CHANGELOG.md b/CHANGELOG.md index c58fff8..819ea63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [6.5.11] — 2026-10-02 + ### Fixed - Require published Plumbing 3.3.2 so stale mktree transports after external Git GC enter the existing single fresh-process retry for single and batch tree writes. Producer and unrelated transport errors retain their identity; retry remains bounded at two attempts. diff --git a/README.md b/README.md index 6f6890f..74d3b65 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,8 @@ Existing v5 users should read [UPGRADING.md](./UPGRADING.md) and run For the release overview, see the [v6.5.10 Release Notes](./docs/releases/v6.5.10.md). +The v6.5.11 candidate adopts published Plumbing 3.3.2 for bounded recovery from closed mktree transports after external Git GC. It preserves producer error identity and existing object formats; see [v6.5.11 Release Notes](./docs/releases/v6.5.11.md). Registry publication is pending release verification and merge. + ### 1. CLI Usage Initialize a vault and store your first asset. diff --git a/UPGRADING.md b/UPGRADING.md index 98914d8..c91988f 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -725,3 +725,7 @@ The manifest blob was corrupted or tampered with after storage. The original dat ### Constructor throws "chunker is required" You're using `CasService` directly. Either switch to the `ContentAddressableStore` facade (recommended) or inject a `chunker` and `compressionAdapter` manually. See [API Changes](#casservice-constructor-library-users). + +## v6.5.10 To v6.5.11 + +Update the package normally. The Plumbing minimum becomes 3.3.2 to recover closed mktree transports with the existing one-retry bound. No stored-data or application migration is required; unrelated and producer errors remain unchanged. diff --git a/docs/releases/v6.5.11.md b/docs/releases/v6.5.11.md new file mode 100644 index 0000000..6572540 --- /dev/null +++ b/docs/releases/v6.5.11.md @@ -0,0 +1,15 @@ +# git-cas v6.5.11 Release Notes + +This patch release adopts published Plumbing 3.3.2 so a stale mktree process closed by external Git repacking enters the existing bounded immutable-object recovery. Single-tree and batched-tree writes reopen a failed process once. Two failed attempts stop with GitProtocolError; unrelated transport errors and producer errors retain their original identity. + +## Compatibility + +No API, handle, stored object, descriptor, ref namespace, encryption, or reader format changes. No application or stored-data migration is required. Mutable-ref retry and publication rules remain unchanged. + +## Test Isolation + +All tests and benchmarks require a physical Docker marker before test modules load. Environment flags do not authorize host execution. CI/release Node unit gates and maintained examples use COPY-based Docker without repository or Git-directory mounts. + +## Evidence And Delivery + +Issue [#131](https://github.com/git-stunts/git-cas/issues/131) and PR [#132](https://github.com/git-stunts/git-cas/pull/132) own delivery. [Preparatory evidence](../design/0062-mktree-transport-recovery/witness/preparation.md) pins candidate behavior separately from registry delivery. The six recovery checks fail in four cases against Plumbing 3.3.0 and pass against published Plumbing 3.3.2. Full release verification, merge, signed tag, npm publication and registry consumer evidence remain required; this candidate document claims no published 6.5.11 artifact. diff --git a/jsr.json b/jsr.json index 9886be8..159f64c 100644 --- a/jsr.json +++ b/jsr.json @@ -1,6 +1,6 @@ { "name": "@git-stunts/git-cas", - "version": "6.5.10", + "version": "6.5.11", "exports": { ".": "./index.js", "./service": "./src/domain/services/CasService.js", diff --git a/package.json b/package.json index 2e06632..49f259d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@git-stunts/git-cas", - "version": "6.5.10", + "version": "6.5.11", "description": "Content-addressed storage backed by Git's object database, with optional encryption and pluggable codecs", "type": "module", "main": "index.js", @@ -55,7 +55,8 @@ "test/CONVENTIONS.md", "test/cycles/README.md", "LICENSE", - "CHANGELOG.md" + "CHANGELOG.md", + "docs/releases/v6.5.11.md" ], "engines": { "node": ">=22.0.0" From 1dfd7d6e4d3344e561d70a791c5bd68eb7a28ee9 Mon Sep 17 00:00:00 2001 From: James Ross Date: Fri, 2 Oct 2026 05:20:27 -0700 Subject: [PATCH 6/8] Fix: synchronize CLI version with 6.5.11 candidate --- src/package-version.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/package-version.js b/src/package-version.js index 655d8c8..abe205e 100644 --- a/src/package-version.js +++ b/src/package-version.js @@ -1 +1 @@ -export const PACKAGE_VERSION = '6.5.10'; +export const PACKAGE_VERSION = '6.5.11'; From 4d9fcc361ab8dcaac88d5ca72bd87faa6daa2f43 Mon Sep 17 00:00:00 2001 From: James Ross Date: Fri, 2 Oct 2026 05:26:01 -0700 Subject: [PATCH 7/8] docs: record published-dependency release candidate witness --- .../witness/candidate-verification.json | 261 ++++++++++++++++++ .../witness/candidate.md | 19 ++ 2 files changed, 280 insertions(+) create mode 100644 docs/design/0062-mktree-transport-recovery/witness/candidate-verification.json create mode 100644 docs/design/0062-mktree-transport-recovery/witness/candidate.md diff --git a/docs/design/0062-mktree-transport-recovery/witness/candidate-verification.json b/docs/design/0062-mktree-transport-recovery/witness/candidate-verification.json new file mode 100644 index 0000000..9ecc03c --- /dev/null +++ b/docs/design/0062-mktree-transport-recovery/witness/candidate-verification.json @@ -0,0 +1,261 @@ +{ + "version": "6.5.11", + "results": [ + { + "id": "lint", + "label": "Lint", + "command": "pnpm", + "args": [ + "run", + "lint" + ], + "code": 0, + "signal": null, + "passed": true, + "tests": null, + "errorMessage": null + }, + { + "id": "unit-node", + "label": "Unit Tests (Node)", + "command": "docker", + "args": [ + "compose", + "run", + "--build", + "--rm", + "test-node" + ], + "testCount": true, + "code": 0, + "signal": null, + "passed": true, + "tests": 2203, + "errorMessage": null + }, + { + "id": "example-store-and-restore", + "label": "Example: store-and-restore", + "command": "docker", + "args": [ + "compose", + "run", + "--build", + "--rm", + "test-node", + "node", + "examples/store-and-restore.js" + ], + "code": 0, + "signal": null, + "passed": true, + "tests": null, + "errorMessage": null + }, + { + "id": "example-encrypted-workflow", + "label": "Example: encrypted-workflow", + "command": "docker", + "args": [ + "compose", + "run", + "--build", + "--rm", + "test-node", + "node", + "examples/encrypted-workflow.js" + ], + "code": 0, + "signal": null, + "passed": true, + "tests": null, + "errorMessage": null + }, + { + "id": "example-progress-tracking", + "label": "Example: progress-tracking", + "command": "docker", + "args": [ + "compose", + "run", + "--build", + "--rm", + "test-node", + "node", + "examples/progress-tracking.js" + ], + "code": 0, + "signal": null, + "passed": true, + "tests": null, + "errorMessage": null + }, + { + "id": "unit-bun", + "label": "Unit Tests (Bun)", + "command": "docker", + "args": [ + "compose", + "run", + "--build", + "--rm", + "test-bun", + "bunx", + "vitest", + "run", + "test/unit", + "--no-file-parallelism" + ], + "testCount": true, + "code": 0, + "signal": null, + "passed": true, + "tests": 2203, + "errorMessage": null + }, + { + "id": "unit-deno", + "label": "Unit Tests (Deno)", + "command": "docker", + "args": [ + "compose", + "run", + "--build", + "--rm", + "test-deno", + "deno", + "run", + "-A", + "npm:vitest", + "run", + "test/unit" + ], + "testCount": true, + "code": 0, + "signal": null, + "passed": true, + "tests": 2194, + "errorMessage": null + }, + { + "id": "types-public", + "label": "Public type compatibility", + "command": "docker", + "args": [ + "compose", + "run", + "--rm", + "test-deno", + "deno", + "check", + "--config", + "test/types/deno.json", + "test/types/public-api-compatibility.ts" + ], + "code": 0, + "signal": null, + "passed": true, + "tests": null, + "errorMessage": null + }, + { + "id": "integration-node", + "label": "Integration Tests (Node)", + "command": "pnpm", + "args": [ + "run", + "test:integration:node" + ], + "testCount": true, + "code": 0, + "signal": null, + "passed": true, + "tests": 207, + "errorMessage": null + }, + { + "id": "integration-bun", + "label": "Integration Tests (Bun)", + "command": "pnpm", + "args": [ + "run", + "test:integration:bun" + ], + "testCount": true, + "code": 0, + "signal": null, + "passed": true, + "tests": 207, + "errorMessage": null + }, + { + "id": "integration-deno", + "label": "Integration Tests (Deno)", + "command": "pnpm", + "args": [ + "run", + "test:integration:deno" + ], + "testCount": true, + "code": 0, + "signal": null, + "passed": true, + "tests": 207, + "errorMessage": null + }, + { + "id": "stamp-build", + "label": "Build metadata stamp", + "command": "pnpm", + "args": [ + "run", + "stamp" + ], + "code": 0, + "signal": null, + "passed": true, + "tests": null, + "errorMessage": null + }, + { + "id": "npm-pack", + "label": "npm pack dry-run", + "command": "npm", + "args": [ + "pack", + "--dry-run", + "--json" + ], + "requiredFiles": [ + "build-info.json" + ], + "code": 0, + "signal": null, + "passed": true, + "tests": null, + "errorMessage": null + }, + { + "id": "jsr-publish", + "label": "JSR publish dry-run", + "command": "npx", + "args": [ + "jsr", + "publish", + "--dry-run", + "--allow-dirty" + ], + "code": 0, + "signal": null, + "passed": true, + "tests": null, + "errorMessage": null + } + ], + "totalTests": 7221, + "skippedSteps": [], + "summary": "## Release Verification Summary\n\n- Version: `6.5.11`\n- Steps passed: 14/14\n- Total tests observed: 7221\n\n| Step | Status | Tests |\n| --- | --- | ---: |\n| Lint | PASS | \u2014 |\n| Unit Tests (Node) | PASS | 2203 |\n| Example: store-and-restore | PASS | \u2014 |\n| Example: encrypted-workflow | PASS | \u2014 |\n| Example: progress-tracking | PASS | \u2014 |\n| Unit Tests (Bun) | PASS | 2203 |\n| Unit Tests (Deno) | PASS | 2194 |\n| Public type compatibility | PASS | \u2014 |\n| Integration Tests (Node) | PASS | 207 |\n| Integration Tests (Bun) | PASS | 207 |\n| Integration Tests (Deno) | PASS | 207 |\n| Build metadata stamp | PASS | \u2014 |\n| npm pack dry-run | PASS | \u2014 |\n| JSR publish dry-run | PASS | \u2014 |\n", + "sourceCommit": "1dfd7d6e4d3344e561d70a791c5bd68eb7a28ee9", + "execution": "All executable checks in COPY-based Docker; no host repository or Git mounts. Canonical verifier used an isolation runner: Compose steps use a clean git archive; other commands use a persistent copied container to retain stamped build metadata.", + "rawLogSha256": "2daffd082f58371e106ee2a1726ae8063c3053674bd37f6e89346512b4e45ebd" +} diff --git a/docs/design/0062-mktree-transport-recovery/witness/candidate.md b/docs/design/0062-mktree-transport-recovery/witness/candidate.md new file mode 100644 index 0000000..9726b73 --- /dev/null +++ b/docs/design/0062-mktree-transport-recovery/witness/candidate.md @@ -0,0 +1,19 @@ +# Published-dependency candidate witness + +Source: `1dfd7d6e4d3344e561d70a791c5bd68eb7a28ee9`; version 6.5.11 candidate. Plumbing 3.3.2 is the published npm dependency, with upstream merge `8811356d4f2277a2a10cd496e5e6f1b94b89f480`. This evidence does not claim git-cas 6.5.11 publication. + +## Playback answers + +The actual Plumbing session and CAS persistence adapter recover after injected closed-input and broken-pipe transport failures. Six deterministic checks cover single and batched trees, already-closed input, the two-attempt limit, unrelated transport errors, and producer error identity. Against Plumbing 3.3.0, four fail and two pass; against published 3.3.2, all six pass. This injection establishes transport recovery, not an independent real-GC causal witness. + +The first full release run caught a stale CLI version export (6.5.10 versus metadata 6.5.11). Commit 1dfd7d6 synchronizes it; three CLI checks pass, followed by all 14 canonical release gates. The attached JSON records the exact checked source and raw-log digest. + +## Execution and bounds + +All checks ran in COPY-based Docker without host repository or Git mounts. The canonical release verifier orchestrated copied Node/Bun/Deno Compose services. Lint, stamping, npm packing and JSR dry-run used a persistent copied validation container; stamped metadata survived into packaging. Source-only Git metadata identifies the checked commit; graph refs and host configuration were excluded. + +Node and Bun unit suites each passed 2,203 with three existing skips. Deno passed 2,194 with twelve existing skips. Each runtime passed 207 integration tests. These are per-runtime observations, not a claim of identical execution coverage. Three maintained examples, public types, lint, stamp, npm pack and JSR dry-run all passed. No release-verification stage was skipped. + +## Remaining delivery + +Current-head independent review, green hosted CI, merge, verification of merged main, signed tag, registry publication and downstream consumer proof remain required. Keep issue #131 open until delivery. The dependency fix does not implement git-warp Runtime/Lane attachment APIs or complete #818. From 243592ce1b72865ce4f1fd552f504e9488780246 Mon Sep 17 00:00:00 2001 From: James Ross Date: Fri, 2 Oct 2026 05:32:36 -0700 Subject: [PATCH 8/8] Fix: remove host BATS from platform validation --- CHANGELOG.md | 2 ++ package.json | 2 +- test/platform/runtimes.bats | 14 -------------- test/unit/scripts/release-workflow.test.js | 13 +++++++++++++ 4 files changed, 16 insertions(+), 15 deletions(-) delete mode 100644 test/platform/runtimes.bats diff --git a/CHANGELOG.md b/CHANGELOG.md index 819ea63..efe8860 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - CI and release Node unit checks and examples run through COPY-based Docker. Tests and benchmarks refuse host execution before loading test modules, including forged Docker and CI environment flags. +- Replace the host BATS platform launcher with sequential COPY-based Docker orchestration; a failed runtime stops the platform command. + ## [6.5.10] — 2026-08-24 ### Added diff --git a/package.json b/package.json index 49f259d..4ba14a9 100644 --- a/package.json +++ b/package.json @@ -96,7 +96,7 @@ "test:integration:node": "docker compose run --build --rm test-node npx vitest run test/integration --no-file-parallelism", "test:integration:bun": "docker compose run --build --rm test-bun bunx vitest run test/integration --no-file-parallelism", "test:integration:deno": "docker compose run --build --rm test-deno deno run -A npm:vitest run test/integration --no-file-parallelism", - "test:platforms": "bats --jobs 3 test/platform/runtimes.bats", + "test:platforms": "docker compose run --build --rm test-node && docker compose run --build --rm test-bun && docker compose run --build --rm test-deno", "benchmark": "docker compose run --build --rm test-node pnpm vitest bench test/benchmark", "benchmark:local": "vitest bench test/benchmark", "release:verify": "node scripts/release/verify.js", diff --git a/test/platform/runtimes.bats b/test/platform/runtimes.bats deleted file mode 100644 index dba6db4..0000000 --- a/test/platform/runtimes.bats +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env bats -# Run: bats --jobs 3 test/platform/runtimes.bats - -@test "Node.js: full test suite passes" { - docker compose run --rm test-node -} - -@test "Bun: full test suite passes" { - docker compose run --rm test-bun -} - -@test "Deno: full test suite passes" { - docker compose run --rm test-deno -} diff --git a/test/unit/scripts/release-workflow.test.js b/test/unit/scripts/release-workflow.test.js index d289f65..1ae73f9 100644 --- a/test/unit/scripts/release-workflow.test.js +++ b/test/unit/scripts/release-workflow.test.js @@ -65,3 +65,16 @@ describe('release test isolation', () => { expect(read('docker-compose.yml')).not.toMatch(/^\s+volumes:/mu); }); }); + + +describe('platform test isolation', () => { + it('dispatches platform checks directly to copied containers without a host test runner', () => { + const command = JSON.parse(read('package.json')).scripts['test:platforms']; + expect(command).not.toMatch(/\bbats\b/u); + const commands = command.split(' && '); + expect(commands).toHaveLength(3); + for (const runtime of ['node', 'bun', 'deno']) { + expect(commands).toContain(`docker compose run --build --rm test-${runtime}`); + } + }); +});