diff --git a/CHANGELOG.md b/CHANGELOG.md index 39b6d41..b698206 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.3.0] — 2026-07-17 + ### Added - **Scoped cache acquisitions** — `CacheSet.acquire()` now performs a bounded, @@ -29,6 +31,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 conflicts fail closed because Git 2.43 cannot atomically prove that a missing direct ref is not an enumerator-invisible dangling symbolic ref. +### Fixed + +- **Pre-push Git environment isolation** — repository-local `GIT_*` variables + exported by Git hooks are cleared before lint and tests run, so child + processes can create and inspect independent temporary repositories. The + hook fails closed if Git cannot enumerate the local variables. + ### Documentation - **v6.2.0 publication evidence** — records the signed tag identity, successful diff --git a/STATUS.md b/STATUS.md index de697ae..f04d44c 100644 --- a/STATUS.md +++ b/STATUS.md @@ -1,8 +1,8 @@ # STATUS **Last tagged release:** `v6.2.0` (`2026-07-13`) -**Current release state:** `v6.2.0` is published to npm with provenance and to GitHub Releases; JSR validation is healthy, but JSR publication is not part of the release workflow. -**Latest verification:** `pnpm run release:verify` passed 13/13 steps with 6,124 observed tests on merged `main` at `432c5d9`; release run `29280878104` then passed tag validation, lint, unit tests, Node/Bun/Deno integration, npm trusted publication, and GitHub Release creation. +**Current release state:** `v6.3.0` release candidate; npm and GitHub Release publication remain pending the reviewed tag workflow. +**Latest verification:** `pnpm run release:verify` passed 14/14 steps with 6,325 observed tests against version `6.3.0`, including Node/Bun/Deno unit and integration suites, public type compatibility, npm package inspection, and JSR dry-run; no publication claim exists yet. **Playback truth:** `main` **Runtimes:** Node.js 22.x, Bun, Deno **Current planning method:** [WORKFLOW.md](./WORKFLOW.md) @@ -18,10 +18,11 @@ - The machine-facing `git cas agent` surface exists and now supports OS-keychain passphrase sources for vault-derived key flows, but parity and portability are still partial. -- **v6.2.0 artifact posture** — signed tag `v6.2.0` resolves to reviewed merge - `432c5d9`; npm reports `@git-stunts/git-cas@6.2.0` as `latest` with SLSA - provenance, and the final GitHub Release is published. JSR dry-run validation - is healthy, but JSR publication is not part of this release workflow. +- **v6.3.0 artifact posture** — the reviewed scoped-acquisition implementation + is merged and the versioned release candidate is verified locally. + npm trusted publication and GitHub Release creation remain pending the + `v6.3.0` tag workflow. JSR dry-run validation remains a pre-publication gate, + but JSR publication is not part of the release workflow. - **v6.0.0 encryption scheme simplification** — `whole-v1`/`whole-v2` collapsed to `whole`, `framed-v1`/`framed-v2` collapsed to `framed`, `convergent-v1` collapsed to `convergent`. AAD is now always on. Legacy scheme strings in @@ -71,6 +72,10 @@ - `ContentAddressableStore.caches.open()` owns TTL/capacity cache indexes and their Git reachability, while streaming scans keep residency independent of cache cardinality. +- `CacheSet.acquire()` performs a bounded reference-only lookup and anchors the + exact observed cache generation for an explicit caller lifetime. Release is + idempotent and generation-checked; doctor exposes bounded acquisition count, + age, truncation, malformed-ref, and clock-skew evidence. - `ContentAddressableStore.expiringSets.open()` owns atomic replay-marker retention under `refs/cas/expiring/*`. It persists only domain-separated key digests, survives process restart, and can release a marker only after its @@ -94,13 +99,12 @@ - GitHub Issues are canonical. If this section and GitHub disagree, GitHub wins and this section should be corrected. -- Latest completed release goalpost: - [#50 v6.2.0: Application Storage and Cache Ownership Boundary](https://github.com/git-stunts/git-cas/issues/50) +- Current release goalpost: + [#69 v6.3.0: Bounded scoped cache acquisitions](https://github.com/git-stunts/git-cas/issues/69) under the - [`v6.2.0` milestone](https://github.com/git-stunts/git-cas/milestone/3). -- No later release goalpost or design is selected in this snapshot. The latest - landed local design record is - [0047-application-storage-cache-boundary](./docs/design/0047-application-storage-cache-boundary/application-storage-cache-boundary.md). + [`v6.3.0` milestone](https://github.com/git-stunts/git-cas/milestone/7). +- The latest landed design record is + [0048-scoped-cache-acquisitions](./docs/design/0048-scoped-cache-acquisitions/scoped-cache-acquisitions.md). ## Read Next diff --git a/UPGRADING.md b/UPGRADING.md index 3ea6285..0eeb557 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -2,6 +2,38 @@ v6.0.0 is a major release that simplifies the encryption model, hardens security defaults, and cleans up the architecture. This guide covers every breaking change and what you need to do. +## v6.2.0 To v6.3.0 + +v6.3.0 is API-additive and does not require stored-data migration. It adds +scoped cache acquisitions for consumers that must keep a cache hit reachable +between lookup and use. + +`CacheSet.get()` retains its existing complete-validation semantics, but a +returned hit is only an observation. Use `acquire()` when consumption may +overlap replacement, expiry, eviction, removal, or destructive Git cleanup: + +```javascript +const acquisition = await cache.acquire(materializationKey); +if (acquisition) { + try { + await consume(acquisition.hit.handle); + } finally { + await acquisition.release(); + } +} +``` + +An acquisition anchors the exact observed cache generation until explicit, +idempotent release. It intentionally has no automatic TTL: age is diagnostic +evidence, not proof that a consumer is dead. Recovery tooling can use +`cache.inspectAcquisitions({ limit })` and generation-checked +`cache.releaseAcquisition(...)`; `cas.diagnostics.doctor()` reports bounded +count, age, truncation, malformed-ref, and clock-skew evidence. + +See [v6.3.0 Release Notes](./docs/releases/v6.3.0.md) and +[Acquire And Release](./docs/API.md#acquire-and-release) for the complete +lifetime and recovery contract. + ## v6.1.0 To v6.2.0 v6.2.0 is API-additive and does not require stored-data migration. It adds diff --git a/docs/design/0048-scoped-cache-acquisitions/witness/release-candidate.md b/docs/design/0048-scoped-cache-acquisitions/witness/release-candidate.md new file mode 100644 index 0000000..74e0496 --- /dev/null +++ b/docs/design/0048-scoped-cache-acquisitions/witness/release-candidate.md @@ -0,0 +1,83 @@ +# PERF-0048 v6.3.0 Release Candidate Witness + +Date: 2026-07-17 + +Issues: #69, #70 + +## Scope + +This witness records the pre-publication release candidate for bounded scoped +cache acquisitions. It does not claim that a `v6.3.0` tag, npm artifact, or +GitHub Release exists yet. + +The candidate: + +- sets npm, JSR, and the runtime `PACKAGE_VERSION` export to `6.3.0`; +- moves scoped cache acquisitions from `Unreleased` to `6.3.0`; +- ships and links `docs/releases/v6.3.0.md`; +- adds the v6.2.0-to-v6.3.0 adoption guidance to `UPGRADING.md`; +- records an honest release-candidate posture in `STATUS.md`; +- keeps the previous v6.2.0 publication witness immutable; and +- verifies that the npm package contains the public release documentation. + +## Implementation Provenance + +| Capability | Pull request | Merge commit | +| --- | --- | --- | +| Pre-push Git environment isolation | #71 | `eac13afe74f927c739c601b9f1c3688bf4022511` | +| Scoped cache acquisitions | #72 | `7b15ec1819a1c2500c459818785fcd7ec6cf7676` | + +## Verification + +The authoritative local command was: + +```bash +pnpm run release:verify +``` + +It passed 14/14 steps and observed 6,325 tests: + +| Step | Result | Tests | +| --- | --- | ---: | +| ESLint | pass | - | +| Node unit | pass | 1,928 | +| Bun unit | pass | 1,927 | +| Deno unit | pass | 1,918 | +| Public type compatibility | pass | - | +| Node integration | pass | 184 | +| Bun integration | pass | 184 | +| Deno integration | pass | 184 | +| Example: store-and-restore | pass | - | +| Example: encrypted-workflow | pass | - | +| Example: progress-tracking | pass | - | +| Build metadata stamp | pass | - | +| npm pack dry-run | pass | - | +| JSR publish dry-run | pass | - | + +The final npm dry-run reported 242 files, 747,220 packed bytes, and 2,054,933 +unpacked bytes. Exact compressed package identity is intentionally not embedded +in a packaged document because the build stamp is regenerated during pack and +publication. + +The first release-prep verifier correctly stopped because a point-in-time +documentation test required `STATUS.md` to remain frozen at the v6.2.0 +publication state. The test now enforces the actual invariant: v6.2.0 remains +the last tagged release with immutable publication evidence, while v6.3.0 is an +explicitly unpublished candidate. The complete 14-step result above comes from +a fresh rerun after that repair. + +## Publication Gate + +Publication remains blocked until all of the following are true: + +1. the release PR passes GitHub CI, self-review, Code Lawyer review, and the + agreed CodeRabbit posture; +2. the release PR is merged without unresolved findings; +3. a signed annotated `v6.3.0` tag points at the reviewed merge commit; +4. the release workflow passes version validation and runtime tests; +5. npm reports `@git-stunts/git-cas@6.3.0` with provenance; and +6. GitHub reports the final non-draft `v6.3.0` Release. + +Issue #69 stays open until publication evidence and the downstream git-warp +registry handoff are attached. git-warp must consume the registry artifact, not +a local path override. diff --git a/docs/design/0048-scoped-cache-acquisitions/witness/verification.md b/docs/design/0048-scoped-cache-acquisitions/witness/verification.md index 65278df..008f1b2 100644 --- a/docs/design/0048-scoped-cache-acquisitions/witness/verification.md +++ b/docs/design/0048-scoped-cache-acquisitions/witness/verification.md @@ -169,8 +169,8 @@ contains `docs/releases/v6.3.0.md` and reports: ```text files: 242 -packed size: 746,789 bytes -unpacked size: 2,053,392 bytes +packed size: 747,220 bytes +unpacked size: 2,054,933 bytes ``` The first package-doc run correctly failed because the public release note @@ -183,30 +183,29 @@ published artifact. Command: ```bash -pnpm run release:verify -- --skip-jsr +pnpm run release:verify ``` -Post-remediation result: +Pre-publication release-candidate result: ```text -Version: 6.2.0 -Steps passed: 13/13 -Total tests observed: 6319 -Skipped steps: JSR publish dry-run - -Unit Tests (Node) PASS 1926 -Unit Tests (Bun) PASS 1925 -Unit Tests (Deno) PASS 1916 +Version: 6.3.0 +Steps passed: 14/14 +Total tests observed: 6325 + +Unit Tests (Node) PASS 1928 +Unit Tests (Bun) PASS 1927 +Unit Tests (Deno) PASS 1918 Public type compatibility PASS - Integration Tests (Node) PASS 184 Integration Tests (Bun) PASS 184 Integration Tests (Deno) PASS 184 +JSR publish dry-run PASS - ``` -These are the final local pre-review totals. The package remains `6.2.0` in the -implementation PR; the release workflow creates v6.3.0 after merge. JSR remains -the only deliberately skipped step locally because registry publication is a -post-merge release action. +These are the final local pre-publication totals after the implementation PR +merged and all npm, JSR, runtime, type, example, and integration version paths +were promoted to `6.3.0`. Registry publication remains a post-tag action. A direct host integration invocation was also attempted and correctly refused because this repository requires `GIT_STUNTS_DOCKER=1` for integration tests. @@ -227,12 +226,12 @@ refusal is an environment guard, not a hidden test failure. ## Pending Repository Gates -- [ ] Implementation commit and non-draft pull request linked to issue #69. +- [x] Implementation commit and non-draft pull request linked to issue #69. - [x] Self-review has no unresolved findings after final remediation. - [x] Independent Code Lawyer review has no unresolved findings. -- [ ] GitHub Actions CI is green. -- [ ] Code Rabbit is clean, rate limited, or out of credits. -- [ ] Pull request is merged normally without amend, rebase, or force. +- [x] GitHub Actions CI is green. +- [x] Code Rabbit is clean, rate limited, or out of credits. +- [x] Pull request is merged normally without amend, rebase, or force. - [ ] v6.3.0 is published and verified from the npm registry. ## Bounded Claims diff --git a/docs/releases/v6.3.0.md b/docs/releases/v6.3.0.md index 05a9d2f..6afecfc 100644 --- a/docs/releases/v6.3.0.md +++ b/docs/releases/v6.3.0.md @@ -84,8 +84,8 @@ Focused unit and real-Git integration coverage proves bounded lookup, generation-race retry, explicit and checked release, doctor inventory, and survival across `git prune --expire=now`, namespace isolation, symbolic-ref authority safety, work-bounded inventory, and legacy adapter type compatibility. -The post-remediation release verifier passed 13/13 local gates and observed -6,319 tests across Node, Bun, and Deno. Its npm dry run contained 242 files; +The release-candidate verifier passed 14/14 local gates and observed 6,325 +tests across Node, Bun, and Deno. Its npm dry run contained 242 files; the non-packaged verification witness records the exact byte receipt without making the package describe its own compressed size. CI, self-review, Code Rabbit, and independent Code Lawyer remain mandatory gates before tagging. diff --git a/jsr.json b/jsr.json index 43673e9..2b616a2 100644 --- a/jsr.json +++ b/jsr.json @@ -1,6 +1,6 @@ { "name": "@git-stunts/git-cas", - "version": "6.2.0", + "version": "6.3.0", "exports": { ".": "./index.js", "./service": "./src/domain/services/CasService.js", diff --git a/package.json b/package.json index 2263d45..f57b996 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@git-stunts/git-cas", - "version": "6.2.0", + "version": "6.3.0", "description": "Content-addressed storage backed by Git's object database, with optional encryption and pluggable codecs", "type": "module", "main": "index.js", diff --git a/src/package-version.js b/src/package-version.js index f567706..c322b4f 100644 --- a/src/package-version.js +++ b/src/package-version.js @@ -1 +1 @@ -export const PACKAGE_VERSION = '6.2.0'; +export const PACKAGE_VERSION = '6.3.0'; diff --git a/test/unit/docs/release-state.test.js b/test/unit/docs/release-state.test.js index 5116859..502382e 100644 --- a/test/unit/docs/release-state.test.js +++ b/test/unit/docs/release-state.test.js @@ -1,35 +1,120 @@ import { describe, expect, it } from 'vitest'; -import { readFileSync } from 'node:fs'; +import { existsSync, readFileSync } from 'node:fs'; import path from 'node:path'; const repoRoot = process.cwd(); +const v630CandidateMarker = '**Current release state:** `v6.3.0` release candidate'; +const v630PublishedMarker = '**Current release state:** `v6.3.0` is published'; +const v630PublicationPath = + 'docs/design/0048-scoped-cache-acquisitions/witness/release-publication.md'; function read(relPath) { return readFileSync(path.join(repoRoot, relPath), 'utf8'); } +function readOptional(relPath) { + return existsSync(path.join(repoRoot, relPath)) ? read(relPath) : null; +} + function v6Heading(changelog) { return changelog.match(/^## \[6\.0\.0\] — (.+)$/m)?.[1]; } +function expectNoV630PublicationEvidence(...documents) { + const forbiddenMarkers = [ + '**Last tagged release:** `v6.3.0`', + '**Current release state:** `v6.3.0` is published', + '- Signed annotated tag: `v6.3.0`', + 'https://github.com/git-stunts/git-cas/releases/tag/v6.3.0', + '## npm Registry Evidence', + '| Package | `@git-stunts/git-cas@6.3.0` |', + '| Dist-tag | `latest` -> `6.3.0` |', + 'attestations/@git-stunts%2fgit-cas@6.3.0' + ]; + + for (const document of documents) { + for (const marker of forbiddenMarkers) { + expect(document).not.toContain(marker); + } + } +} + +function expectV630CandidateState(status, candidate, publication) { + expect(status).toContain('**Last tagged release:** `v6.2.0` (`2026-07-13`)'); + expect(status).toContain(v630CandidateMarker); + expect(status).toContain('publication remain pending the reviewed tag workflow'); + expect(candidate).toContain('It passed 14/14 steps and observed 6,325 tests'); + expect(candidate).toContain('242 files, 747,220 packed bytes, and 2,054,933'); + expect(candidate).toContain('7b15ec1819a1c2500c459818785fcd7ec6cf7676'); + expect(candidate).toContain('explicitly unpublished candidate'); + expect(publication).toBeNull(); + expectNoV630PublicationEvidence(status, candidate); +} + +function expectV630PublishedState(status, publication) { + expect(status).toMatch(/\*\*Last tagged release:\*\* `v6\.3\.0` \(`\d{4}-\d{2}-\d{2}`\)/); + expect(status).toContain(v630PublishedMarker); + expect(publication).toContain('# PERF-0048 v6.3.0 Publication Witness'); + expect(publication).toContain('- Signed annotated tag: `v6.3.0`'); + expect(publication).toContain('https://github.com/git-stunts/git-cas/releases/tag/v6.3.0'); + expect(publication).toContain('| Package | `@git-stunts/git-cas@6.3.0` |'); + expect(publication).toContain('| Dist-tag | `latest` -> `6.3.0` |'); + expect(publication).toContain('attestations/@git-stunts%2fgit-cas@6.3.0'); +} + +function expectV630Lifecycle(status, candidate, publication) { + const isCandidate = status.includes(v630CandidateMarker); + const isPublished = status.includes(v630PublishedMarker); + + expect(Number(isCandidate) + Number(isPublished)).toBe(1); + if (isCandidate) { + expectV630CandidateState(status, candidate, publication); + return; + } + expect(publication).not.toBeNull(); + expectV630PublishedState(status, publication); +} + +function expectFutureV630PublicationState() { + const status = [ + '**Last tagged release:** `v6.3.0` (`2026-07-17`)', + `${v630PublishedMarker} to npm with provenance and to GitHub Releases.` + ].join('\n'); + const publication = [ + '# PERF-0048 v6.3.0 Publication Witness', + '- Signed annotated tag: `v6.3.0`', + 'https://github.com/git-stunts/git-cas/releases/tag/v6.3.0', + '| Package | `@git-stunts/git-cas@6.3.0` |', + '| Dist-tag | `latest` -> `6.3.0` |', + 'attestations/@git-stunts%2fgit-cas@6.3.0' + ].join('\n'); + + expectV630Lifecycle(status, '', publication); +} + describe('release state docs', () => { - it('records the published v6.2.0 artifact and provenance state', () => { + it('enforces the v6.3.0 candidate-to-publication lifecycle', () => { const status = read('STATUS.md'); + const candidate = read( + 'docs/design/0048-scoped-cache-acquisitions/witness/release-candidate.md' + ); + const publication = readOptional(v630PublicationPath); const witness = read( 'docs/design/0047-application-storage-cache-boundary/witness/release-publication.md' ); - expect(status).toContain('**Last tagged release:** `v6.2.0` (`2026-07-13`)'); - expect(status).toContain('`v6.2.0` is published to npm with provenance'); - expect(status).toContain('release run `29280878104`'); - expect(status).toContain('Latest completed release goalpost:'); - expect(status).not.toContain('Current selected release goalpost:'); + expectV630Lifecycle(status, candidate, publication); + expectFutureV630PublicationState(); + expect(status).toContain('Current release goalpost:'); + expect(status).toContain('#69 v6.3.0: Bounded scoped cache acquisitions'); expect(witness).toContain('432c5d9effb12c9f66536f1386791bb4421f3cea'); expect(witness).toContain('sha512-m8+ZzgNhKU6pVS9pjqJlwAnwYI/s+NMEnINC+Q0g3h6T6mNPdH8U0jb4nEoxU9N1TF+Ut5bjtRMRRaYT75dlew=='); expect(witness).toContain('https://slsa.dev/provenance/v1'); expect(witness).toContain('https://github.com/git-stunts/git-cas/releases/tag/v6.2.0'); }); +}); +describe('historical v6 release evidence', () => { it('keeps v6.0.0 marked released once the tag workflow has published', () => { const changelogHeading = v6Heading(read('CHANGELOG.md')); const status = read('STATUS.md');