From f0d30a1778773ed231b3907ad1818c86bd6d0d56 Mon Sep 17 00:00:00 2001 From: James Ross Date: Mon, 13 Jul 2026 12:44:16 -0700 Subject: [PATCH 1/2] release: prepare git-cas v6.2.0 --- ARCHITECTURE.md | 111 ++++++++++++++- CHANGELOG.md | 6 + README.md | 5 +- STATUS.md | 12 +- UPGRADING.md | 43 ++++++ .../application-storage-cache-boundary.md | 43 +++++- .../witness/release-candidate.md | 78 +++++++++++ docs/design/README.md | 2 +- docs/releases/v6.2.0.md | 131 ++++++++++++++++++ jsr.json | 2 +- package.json | 3 +- src/package-version.js | 2 +- test/unit/cli/version.test.js | 7 + test/unit/docs/package-docs.test.js | 4 + test/unit/docs/release-truth.test.js | 15 ++ 15 files changed, 448 insertions(+), 16 deletions(-) create mode 100644 docs/design/0047-application-storage-cache-boundary/witness/release-candidate.md create mode 100644 docs/releases/v6.2.0.md diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index bad7464c..7f13b022 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -11,12 +11,17 @@ metadata exposure, see [docs/THREAT_MODEL.md](./docs/THREAT_MODEL.md). `git-cas` uses Git as the storage substrate, not as a user-facing abstraction. -At a high level, the system does four things: +At a high level, the system does five things: 1. turns input bytes into chunk blobs stored in Git 2. records how to rebuild those bytes in a manifest -3. emits a Git tree that keeps the manifest and chunk blobs reachable -4. optionally indexes trees by slug through a GC-safe vault ref +3. exposes assets, pages, and bundles through opaque application handles +4. retains or publishes handle graphs through explicit lifecycle policies +5. inspects repository reachability and managed-storage health without mutation + +Low-level blob, manifest, and tree operations remain available. Application +code can use the higher-level capabilities without constructing Git trees, +persisting naked object IDs, or implementing its own cache ref protocol. The same core supports: @@ -81,6 +86,27 @@ credential resolution share `bin/credentials.js` so raw key files, passphrase-derived vault keys, and encrypted restore input requirements stay consistent across human and machine command surfaces. +Application-facing lifecycle capabilities sit above the same CAS and Git +ports: + +```mermaid +flowchart TD + APP[Application] + HANDLE[Assets / Pages / Bundles] + POLICY[Retention / Publication / Cache / Expiring Set] + CAS[CAS Pipeline] + GIT[Git Objects and Refs] + DOCTOR[Repository Diagnostics] + + APP --> HANDLE + HANDLE --> CAS + HANDLE --> POLICY + CAS --> GIT + POLICY --> GIT + DOCTOR --> GIT + APP --> DOCTOR +``` + ## Store Pipeline ``` @@ -117,6 +143,10 @@ The public entrypoint is [index.js](./index.js). observability adapters - exposes convenience methods like `storeFile()` and `restoreFile()` - exposes `rootSets.open()` for mutable current-generation Git reachability +- exposes opaque `assets`, `pages`, and `bundles` capability groups +- exposes `retention`, `publications`, `caches`, and `expiringSets` for explicit + reachability and lifecycle policy +- exposes read-only repository evidence through `diagnostics.doctor()` The facade is orchestration glue. It is not the storage engine itself. @@ -145,6 +175,28 @@ The facade is orchestration glue. It is not the storage engine itself. `RootSetPersistence`, `RootSetMetadataCodec`, and `RootSetTreeCodec` own the ref, metadata, and real Git reachability boundaries. +- **`AssetService`, `PageService`, and `BundleService`** — stage application + payloads behind immutable opaque handles. Pages enforce bounded blob size; + bundles build deterministic bounded-fanout trees and support targeted member + traversal without hydrating the complete structure. + +- **`RetentionService` and `PublicationService`** — validate complete handle + graphs, then either retain them in a RootSet with generation-scoped evidence + or publish them atomically under an allowlisted application ref. + +- **`CacheSetRegistry` and `CacheSet`** — own cache index generations, + compare-and-swap replacement, TTL, entry and logical-byte limits, + approximate-LRU sweeps, coalesced touches, doctor, and authoritative repair + under `refs/cas/caches/*`. + +- **`ExpiringSetRegistry` and `ExpiringSet`** — own digest-only replay markers + under `refs/cas/expiring/*`. Admission is atomic, membership reads are + side-effect free, and only expired entries can be released. + +- **`RepositoryDoctor`** — streams object, ref, reflog-reachability, and safe + prune-dry-run evidence to classify repository objects and summarize managed + CacheSet, RootSet, ExpiringSet, and Vault usage. + - **`KeyResolver`** — resolves key sources: passphrase-derived keys via KDF, envelope recipient DEK wrapping and unwrapping. `CasService` delegates all key material resolution through `this.keyResolver`. @@ -200,6 +252,14 @@ The facade is orchestration glue. It is not the storage engine itself. - **`Oid`** — immutable Git object identifier value with 40- or 64-hex validation. - **`RootSetRef`** — immutable validated ref below `refs/cas/rootsets/`. +- **`AssetHandle`, `PageHandle`, and `BundleHandle`** — canonical repository- + independent locators for validated application payload graphs. +- **`RetentionWitness`** — immutable evidence naming the generation and real + Git tree edge that retained or published a handle graph. +- **`CacheKey`, `CachePolicy`, `CacheHit`, and `CacheSetRef`** — validated cache + identity, policy, lookup evidence, and namespace boundaries. +- **`ExpiringMarker`, `ExpiringSetKey`, and `ExpiringSetRef`** — validated + replay-marker evidence, domain-separated key identity, and namespace refs. - **`Slug`** — immutable vault slug representation. It enforces vault slug limits and exposes `.toTreePath()` for the percent-encoded Git tree-entry name used by plain vault trees. @@ -249,6 +309,8 @@ with methods that throw "not implemented" by default. - **`CompressionPort`** — compress/decompress for both byte arrays and streams. - **`ObservabilityPort`** — metrics, logs, and spans without binding the domain to any runtime event API. +- **`RepositoryInspectionPort`** — non-mutating streams for objects, refs, + reachable IDs, safe prunable candidates, and reachable physical bytes. ### Infrastructure (`src/infrastructure/`) @@ -267,6 +329,9 @@ Git: `@git-stunts/plumbing` to shell out to the `git` CLI. - **`GitRefAdapter`** — `GitRefPort` implementation using `@git-stunts/plumbing`. +- **`GitRepositoryInspectionAdapter`** — `RepositoryInspectionPort` + implementation using streamed Git inventories and plumbing's non-mutating + prunable-object inspection. Compression: - **`NodeCompressionAdapter`** — `CompressionPort` backed by `node:zlib`. @@ -362,6 +427,46 @@ For Merkle assets the tree contains: Chunk blobs are deduplicated at the tree-entry level by digest. The manifest still remains authoritative for repeated-chunk order and multiplicity. +### Application Handles And Structured Materializations + +An opaque handle is a canonical content locator, not a durability claim. Asset +handles identify existing CAS trees. Page handles identify bounded immutable +blobs. Bundle handles identify deterministic descriptor trees whose leaves are +assets, pages, or nested bundles. + +Bundle traversal reads only the descriptor path and selected payload required +for a named member. Validation re-enforces persisted path, descriptor-byte, +fanout, and nesting limits before a graph is retained or published. + +### Retention And Publication + +RootSets, CacheSets, ExpiringSets, application publications, and the Vault all +make objects Git-reachable, but they encode different lifecycle contracts: + +| Surface | Ref namespace | Lifecycle | +| --- | --- | --- | +| RootSet | `refs/cas/rootsets/*` | Caller-managed current generation | +| CacheSet | `refs/cas/caches/*` | TTL/capacity/approximate-LRU managed cache | +| ExpiringSet | `refs/cas/expiring/*` | Expiry-only replay protection | +| Publication | Caller-allowlisted ref | Application-controlled causal history | +| Vault | `refs/cas/vault` | History-preserving named assets | + +High-level retention, CacheSet, ExpiringSet, and application publication +operations return generation-scoped retention evidence. Replacing a parentless +current-generation head releases old edges when no other ref or reflog reaches +them; Git's normal grace and pruning policy still controls physical deletion. + +### Repository Diagnostics + +`cas.diagnostics.doctor()` reports total, anchored, orphaned, volatile, and +unreachable object evidence plus managed-storage summaries. Ref and reflog +reachability defines anchored objects; volatile objects come only from safe +prune dry-run output for the selected expiry cutoff. + +The report names limits Git cannot prove exactly, including per-owner +deduplicated physical bytes and packed-object age. The inspection surface has +no object-write, ref-update, GC, or destructive prune operation. + ### Vault The vault is a GC-safe slug index rooted at `refs/cas/vault`. diff --git a/CHANGELOG.md b/CHANGELOG.md index a5c25ac5..6da73c1d 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.2.0] — 2026-07-13 + ### Added - **Application storage ownership contract** — cycle API-0047 defines opaque @@ -62,6 +64,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- **6.2.0 release verification** — `pnpm run release:verify` passed all 13 + steps with 6,124 observed tests across Node, Bun, Deno, and all three + real-Git integration runtimes. npm and JSR metadata now agree on `6.2.0`; + build stamping, npm package inspection, and the JSR publish dry-run passed. - **Safe prune inspection dependency** — repository diagnostics require `@git-stunts/plumbing` 3.1.0, whose public API fixes the exact non-mutating `git prune --dry-run --verbose --no-progress --expire=` contract. diff --git a/README.md b/README.md index c60d9b23..139a40eb 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ Unlike traditional LFS which moves files to external servers, `git-cas` treats t Existing v5 users should read [UPGRADING.md](./UPGRADING.md) and run `npm run upgrade` in dry-run mode before restoring old encrypted vault entries. For the release overview, see the -[v6.1.0 Release Notes](./docs/releases/v6.1.0.md). +[v6.2.0 Release Notes](./docs/releases/v6.2.0.md). ### 1. CLI Usage @@ -183,6 +183,9 @@ All three runtimes are tested in CI on every push. The hexagonal architecture is - **[v6.0.0 Release Notes](./docs/releases/v6.0.0.md)**: Major-release summary, upgrade call-to-action, and publication notes. - **[v6.1.0 Release Notes](./docs/releases/v6.1.0.md)**: Root-set retention, doctor/repair, and GC-policy guidance. +- **[v6.2.0 Release Notes](./docs/releases/v6.2.0.md)**: Opaque application + storage, managed cache and replay lifecycles, retention evidence, and + repository diagnostics. - **[Upgrading](./UPGRADING.md)**: Migration guide for v5 → v6. - **[Changelog](./CHANGELOG.md)**: Version history and migration notes. diff --git a/STATUS.md b/STATUS.md index be4c5df0..117e7094 100644 --- a/STATUS.md +++ b/STATUS.md @@ -1,8 +1,8 @@ # STATUS **Last tagged release:** `v6.1.0` (`2026-07-11`) -**Current release state:** `v6.1.0` is published to npm and GitHub Releases; JSR publication remains deferred behind the upstream toolchain gate. -**Latest verification:** `npm run release:verify -- --skip-jsr` passed 12/12 steps with 5,521 observed tests on `2026-07-11`; release run `29170112655` also passed lint, unit tests, Node/Bun/Deno integration, npm trusted publication, and GitHub Release creation. +**Current release state:** `v6.2.0` release candidate; npm and GitHub Release publication remain pending the reviewed tag workflow. +**Latest verification:** `pnpm run release:verify` passed 13/13 steps with 6,124 observed tests on `2026-07-13`, including Node/Bun/Deno unit and integration suites, npm package inspection, and a JSR publish dry-run. **Playback truth:** `main` **Runtimes:** Node.js 22.x, Bun, Deno **Current planning method:** [WORKFLOW.md](./WORKFLOW.md) @@ -18,10 +18,10 @@ - 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.1.0 artifact posture** — npm publication and the GitHub Release are - complete through tag `v6.1.0`. JSR publication is deferred because the current - `jsr`/Deno toolchain panics before package validation; it can return in a - later 6.x maintenance change once its dry-run is healthy. +- **v6.2.0 artifact posture** — the versioned release candidate and npm package + surface are verified locally. npm trusted publication and GitHub Release + creation remain pending the `v6.2.0` tag workflow. JSR dry-run validation is + healthy, but JSR publication is not part of this 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 diff --git a/UPGRADING.md b/UPGRADING.md index 3f9a87bc..3ea6285d 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -2,6 +2,49 @@ 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.1.0 To v6.2.0 + +v6.2.0 is API-additive and does not require stored-data migration. It adds +high-level application storage, managed cache and replay lifecycles, immutable +retention evidence, and repository-wide diagnostics. + +Applications should stop treating a naked Git object ID stored in JSON as a +durability guarantee. Stage content through `assets`, `pages`, or `bundles`, +then retain or publish the returned opaque handle through the policy surface +that matches its lifecycle: + +```javascript +const staged = await cas.assets.put({ + source, + slug: 'warp/materialization', + filename: 'materialization.bin', +}); + +const cache = await cas.caches.open({ + namespace: 'git-warp/materializations', + policy: { maxEntries: 10_000, maxBytes: 2 * 1024 * 1024 * 1024 }, +}); +const stored = await cache.put(cacheKey, staged.handle, { + retention: 'evictable', + expiresAt: new Date(Date.now() + 14 * 24 * 60 * 60 * 1000), +}); +``` + +Existing root sets and vault entries remain valid. Before running destructive +Git cleanup, applications migrating an existing object-ID index must first +enumerate every still-live object, validate that it still exists, and adopt it +into a RootSet or rebuild it through the appropriate high-level API. Neither +repair nor diagnostics can recover an object that Git already pruned. + +Use `cas.diagnostics.doctor()` before and after migration to inspect anchored, +orphaned, and grace-expired object counts plus managed CacheSet, RootSet, +ExpiringSet, and Vault usage. The doctor is read-only; it never runs Git GC or +prune. Repository diagnostics require `@git-stunts/plumbing` 3.1.0 or newer. + +See [v6.2.0 Release Notes](./docs/releases/v6.2.0.md) and +[Application Storage](./docs/API.md#application-storage) for the complete +capability and policy map. + ## v6.0.1 To v6.1.0 v6.1.0 is API-additive and does not require stored-data migration. It adds root diff --git a/docs/design/0047-application-storage-cache-boundary/application-storage-cache-boundary.md b/docs/design/0047-application-storage-cache-boundary/application-storage-cache-boundary.md index 741da68e..ec8ac682 100644 --- a/docs/design/0047-application-storage-cache-boundary/application-storage-cache-boundary.md +++ b/docs/design/0047-application-storage-cache-boundary/application-storage-cache-boundary.md @@ -7,7 +7,7 @@ release_home: 'v6.2.0' issue: 'https://github.com/git-stunts/git-cas/issues/58' goalpost_issue: 'https://github.com/git-stunts/git-cas/issues/50' tracker_source: 'github' -status: 'active' +status: 'landed' base_commit: '295f21fd5b4d73d353ee36647804b96686329157' owners: - '@git-stunts' @@ -800,10 +800,19 @@ recorded in [witness/asset-handles.md](./witness/asset-handles.md). Structured bundle and page implementation evidence is recorded in [witness/bundle-pages.md](./witness/bundle-pages.md). +Managed CacheSet lifecycle evidence is recorded in +[witness/cache-set.md](./witness/cache-set.md). + +Expiry-safe replay-set evidence is recorded in +[witness/expiring-set.md](./witness/expiring-set.md). + Repository-wide object, reachability, managed-storage, and non-mutation evidence is recorded in [witness/repository-diagnostics.md](./witness/repository-diagnostics.md). +Version, package, documentation, and local release-gate evidence is recorded in +[witness/release-candidate.md](./witness/release-candidate.md). + ## Risks - The facade could become a new monolith. Mitigation: capability groups delegate @@ -850,4 +859,34 @@ evidence is recorded in ## Retrospective -Pending implementation and release playback. +What changed from the design: + +- The shipped names stayed capability-oriented: `assets`, `pages`, `bundles`, + `retention`, `publications`, `caches`, `expiringSets`, and `diagnostics`. +- Cache metadata uses immutable parentless generations and a bounded + approximate-LRU policy rather than a mutable side database. +- Replay protection received a distinct ExpiringSet surface because capacity + or recency eviction would violate the minimum retention window. +- Repository diagnostics report exact evidence only where Git can prove it; + shared-byte attribution and packed-object age remain explicit unknowns. + +What the tests proved: + +- Application payloads can be staged and streamed through opaque handles, + retained with generation-scoped witnesses, or atomically published under an + allowlisted application ref. +- Bundles and pages support bounded targeted traversal without materializing a + complete structured value. +- Cache replacement preserves the newly published generation under concurrent + updates, and replay markers cannot be released before expiry. +- Repository diagnostics include refs and reflogs, use only dry-run prune + inspection, fail closed on inconsistent inventories, and do not mutate Git. + +What remains open: + +- Applications such as git-warp must migrate their own domain indexes and + causal publication rules onto these capabilities. +- A future major release may reorganize legacy root-level plumbing without + removing the deliberate low-level escape hatch. +- Very large repository diagnostics may later use commit-graph or bitmap + acceleration without changing the evidence contract. diff --git a/docs/design/0047-application-storage-cache-boundary/witness/release-candidate.md b/docs/design/0047-application-storage-cache-boundary/witness/release-candidate.md new file mode 100644 index 00000000..87c765aa --- /dev/null +++ b/docs/design/0047-application-storage-cache-boundary/witness/release-candidate.md @@ -0,0 +1,78 @@ +# API-0047 v6.2.0 Release Candidate Witness + +Date: 2026-07-13 + +Issues: #50, #60 + +## Scope + +This witness records the pre-publication release candidate for the application +storage and cache ownership boundary. It does not claim that a tag, npm +artifact, or GitHub Release exists yet. + +The candidate: + +- sets npm, JSR, and the runtime `PACKAGE_VERSION` export to `6.2.0` +- moves the completed feature record from `Unreleased` to `6.2.0` +- ships and links `docs/releases/v6.2.0.md` +- adds the v6.1.0-to-v6.2.0 adoption guidance to `UPGRADING.md` +- updates `ARCHITECTURE.md` for opaque handles, managed lifecycles, retention + evidence, and repository diagnostics +- marks API-0047 landed and links every implementation witness +- verifies that the npm package includes all public release documentation + +## Implementation Provenance + +| Capability | Pull request | Merge commit | +| --- | --- | --- | +| Ownership design | #61 | `ee1be5d494196a31f2ccfbf526da266fee58e83a` | +| Opaque assets, retention, publication | #62 | `863c692e9992686b1f74a48ea075f40b35f9cb2e` | +| Structured bundles and pages | #63 | `cf7685b3d25d7c2e4f58fd869c76360dc355797a` | +| Managed CacheSet lifecycle | #64 | `dbf7424eec3c8079fb73eb7106b47cc344e01170` | +| Expiry-safe replay sets | #65 | `1f10e5c7a551b3a7185019a3637700f4eaef6bdc` | +| Repository diagnostics | #66 | `6d222dd133b767c443b99dc55944ed4ded56e2eb` | + +## Verification + +The authoritative local command was: + +```bash +pnpm run release:verify +``` + +It passed 13/13 steps and observed 6,124 tests: + +| Step | Result | Tests | +| --- | --- | ---: | +| ESLint | pass | - | +| Node unit | pass | 1,871 | +| Bun unit | pass | 1,870 | +| Deno unit | pass | 1,861 | +| Node integration | pass | 174 | +| Bun integration | pass | 174 | +| Deno integration | pass | 174 | +| Three runnable examples | pass | - | +| Build metadata stamp | pass | - | +| npm pack dry-run | pass | - | +| JSR publish dry-run | pass | - | + +The first release-prep pass correctly failed because the CLI package-version +export still reported 6.1.0. A later successful pass exposed that the JSR +dry-run still simulated 6.0.0. Both distribution-version paths are now pinned +by `test/unit/cli/version.test.js`, and the table above comes from a complete +rerun after both repairs. + +## 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 + CodeRabbit review; +2. the release PR is merged without bypassing unresolved findings; +3. a signed `v6.2.0` tag points at the reviewed merge commit; +4. the release workflow passes its version check and runtime test jobs; +5. npm reports `@git-stunts/git-cas@6.2.0` with provenance; and +6. GitHub reports the final non-draft `v6.2.0` Release. + +Issue #60 stays open until publication evidence is attached. Downstream +git-warp work must consume the registry artifact, not a local path override. diff --git a/docs/design/README.md b/docs/design/README.md index 89b43c32..a7c970fc 100644 --- a/docs/design/README.md +++ b/docs/design/README.md @@ -37,11 +37,11 @@ process in [docs/method/process.md](../method/process.md). - [0043-vault-retry-abstraction — vault-retry-abstraction](./0043-vault-retry-abstraction/vault-retry-abstraction.md) - [0044-casservice-decomposition-plan — casservice-decomposition-plan](./0044-casservice-decomposition-plan/casservice-decomposition-plan.md) - [0045-v6-1-bounded-residency — bounded-residency](./0045-v6-1-bounded-residency/bounded-residency.md) -- [0047-application-storage-cache-boundary — application-storage-cache-boundary](./0047-application-storage-cache-boundary/application-storage-cache-boundary.md) ## Landed METHOD Cycles - [0046-gc-retention-root-sets — gc-retention-root-sets](./0046-gc-retention-root-sets/gc-retention-root-sets.md) +- [0047-application-storage-cache-boundary — application-storage-cache-boundary](./0047-application-storage-cache-boundary/application-storage-cache-boundary.md) ## Legacy Landed Cycle Docs diff --git a/docs/releases/v6.2.0.md b/docs/releases/v6.2.0.md new file mode 100644 index 00000000..a3beae7e --- /dev/null +++ b/docs/releases/v6.2.0.md @@ -0,0 +1,131 @@ +# git-cas v6.2.0 Release Notes + +`v6.2.0` makes `git-cas` the storage and cache lifecycle owner for application +packages. Applications can define domain keys, payload schemas, causal +publication rules, and retention intent without constructing Git trees, +persisting naked object IDs, or maintaining a second cache protocol. + +## The Contract + +> A handle is not a durability claim. + +`cas.assets`, `cas.pages`, and `cas.bundles` return immutable opaque handles. +The staged result explicitly reports that no root was created. A caller then +chooses how the handle graph should live: + +| Policy surface | Intended lifecycle | Git reachability behavior | +| --- | --- | --- | +| `cas.retention` / RootSet | Caller-managed current set | Anchored while present | +| `cas.caches` | TTL/capacity/approximate-LRU cache | Anchored until managed eviction | +| `cas.expiringSets` | Security replay window | Anchored until expiry; no early eviction | +| `cas.publications` | Application causal history | Anchored by the allowlisted application ref | +| Vault | History-preserving named assets | Anchored by `refs/cas/vault` history | + +Successful retention and publication operations return a `RetentionWitness` +that identifies the exact ref generation and tree edge that established +reachability. + +## Application Storage + +```javascript +const staged = await cas.assets.put({ + source, + slug: 'warp/materialization', + filename: 'materialization.bin', +}); + +const retained = await cas.retention.retain({ + handle: staged.handle, + root: { + ref: 'refs/cas/rootsets/git-warp/materializations', + name: materializationKey, + }, + policy: 'evictable', +}); + +console.log(retained.witness.root.generation); +``` + +- Asset writes stream through the existing chunking, deduplication, + compression, encryption, manifest, and tree pipeline. +- Pages store bounded immutable blobs and expose targeted streams. +- Bundles build deterministic bounded-fanout trees from named streams or + nested handles. +- `bundles.iterateMembers()` streams descriptors, while `getMember()` and + `openMember()` traverse only the requested descriptor path and payload. +- `cas.publications.commit()` validates an entire handle graph, ordered commit + parents, an explicit ref allowlist, and expected-head compare-and-swap state + before changing a ref. + +Handles contain no repository path and support canonical SHA-1 and SHA-256 +object IDs. Importing a handle validates that its graph exists and has the +expected Git object and codec shape. + +## Managed Cache And Replay Lifecycles + +`cas.caches.open()` owns immutable cache indexes under `refs/cas/caches/*`. +CacheSet handles TTL, entry and logical-byte limits, approximate-LRU sweeps, +coalesced touches, retention witnesses, bounded inspection, doctor, and repair. +A cache miss is read-only. + +`cas.expiringSets.open()` owns digest-only replay markers under +`refs/cas/expiring/*`. `addIfAbsent()` is atomic across writers, `contains()` is +read-only, and `sweep()` can release only entries whose expiry has passed. The +surface intentionally has no remove, repair, capacity, or recency eviction +operation that could shorten a security acceptance window. + +Both services publish parentless current-generation commits. Superseded +generations do not remain reachable through their own storage history. + +## Repository Diagnostics + +```javascript +const report = await cas.diagnostics.doctor({ + gracePeriodMs: 14 * 24 * 60 * 60 * 1000, +}); +``` + +The report streams Git object, ref, and reflog-reachability inventories; +classifies anchored, orphaned, grace-expired volatile, and total unreachable +objects; and summarizes CacheSet, RootSet, ExpiringSet, and Vault usage. It +fails closed if concurrent writes make derived arithmetic inconsistent. + +Diagnostics use plumbing's exact prune dry-run contract for volatile objects. +The doctor does not run `git gc` or destructive prune and exposes no object or +ref mutation operation. Shared deduplicated bytes, packed-object age, reflog +entry count, alternate-store attribution, and pack metadata overhead remain +explicitly reported limitations rather than guessed values. + +## Compatibility And Upgrade + +This release is API-additive. Existing CAS manifests, RootSets, Vaults, refs, +and low-level `createTree()` callers remain compatible; no stored-data +migration is required. Repository diagnostics require +`@git-stunts/plumbing` 3.1.0 or newer. + +Applications that currently store tree or blob IDs only in JSON should adopt +every still-live object into a RootSet or rebuild it through the matching +high-level capability before destructive Git cleanup. An object that Git +already pruned cannot be recovered by doctor or repair. + +See [UPGRADING.md](../../UPGRADING.md) for the adoption sequence, +[Application Storage](../API.md#application-storage) for API contracts, and +[Repository Diagnostics](../API.md#repository-diagnostics) for report fields +and limitations. + +## Verification + +`pnpm run release:verify` passed 13/13 steps with 6,124 observed tests: + +| Suite | Tests | +| --- | ---: | +| Node unit | 1,871 | +| Bun unit | 1,870 | +| Deno unit | 1,861 | +| Node integration | 174 | +| Bun integration | 174 | +| Deno integration | 174 | + +The same run passed ESLint, all three runnable examples, build metadata +stamping, npm package-content inspection, and a JSR publish dry-run. GitHub CI, +self-review, and Code Lawyer review remain mandatory before tagging. diff --git a/jsr.json b/jsr.json index 55b2e507..43673e98 100644 --- a/jsr.json +++ b/jsr.json @@ -1,6 +1,6 @@ { "name": "@git-stunts/git-cas", - "version": "6.0.0", + "version": "6.2.0", "exports": { ".": "./index.js", "./service": "./src/domain/services/CasService.js", diff --git a/package.json b/package.json index 29d5e5c5..ddb776b8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@git-stunts/git-cas", - "version": "6.1.0", + "version": "6.2.0", "description": "Content-addressed storage backed by Git's object database, with optional encryption and pluggable codecs", "type": "module", "main": "index.js", @@ -33,6 +33,7 @@ "docs/STORE_RESTORE_PIPELINE.md", "docs/releases/v6.0.0.md", "docs/releases/v6.1.0.md", + "docs/releases/v6.2.0.md", "docs/THREAT_MODEL.md", "docs/VAULT_INTERNALS.md", "docs/WALKTHROUGH.md", diff --git a/src/package-version.js b/src/package-version.js index 06e3a2f4..f5677066 100644 --- a/src/package-version.js +++ b/src/package-version.js @@ -1 +1 @@ -export const PACKAGE_VERSION = '6.1.0'; +export const PACKAGE_VERSION = '6.2.0'; diff --git a/test/unit/cli/version.test.js b/test/unit/cli/version.test.js index e2f8b28c..9ecffc61 100644 --- a/test/unit/cli/version.test.js +++ b/test/unit/cli/version.test.js @@ -8,6 +8,9 @@ import { PACKAGE_VERSION } from '../../../src/package-version.js'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const BIN = path.resolve(__dirname, '../../../bin/git-cas.js'); const { version } = JSON.parse(readFileSync(path.resolve(__dirname, '../../../package.json'), 'utf8')); +const { version: jsrVersion } = JSON.parse( + readFileSync(path.resolve(__dirname, '../../../jsr.json'), 'utf8') +); const RUNTIME_CMD = globalThis.Bun ? ['bun', 'run', BIN] @@ -20,6 +23,10 @@ describe('git-cas --version', () => { expect(PACKAGE_VERSION).toBe(version); }); + it('keeps npm and JSR package metadata on the same version', () => { + expect(jsrVersion).toBe(version); + }); + it('matches package metadata', () => { const result = spawnSync(RUNTIME_CMD[0], [...RUNTIME_CMD.slice(1), '--version'], { encoding: 'utf8', diff --git a/test/unit/docs/package-docs.test.js b/test/unit/docs/package-docs.test.js index 0dfaaf88..99a124d5 100644 --- a/test/unit/docs/package-docs.test.js +++ b/test/unit/docs/package-docs.test.js @@ -10,6 +10,8 @@ const requiredStandardDocs = [ 'docs/EXTENDING.md', 'docs/STORE_RESTORE_PIPELINE.md', 'docs/releases/v6.0.0.md', + 'docs/releases/v6.1.0.md', + 'docs/releases/v6.2.0.md', ]; const forbiddenPackagePrefixes = [ 'docs/audit/', @@ -83,6 +85,8 @@ function publicPackagedMarkdownFiles(files) { 'docs/EXTENDING.md', 'docs/STORE_RESTORE_PIPELINE.md', 'docs/releases/v6.0.0.md', + 'docs/releases/v6.1.0.md', + 'docs/releases/v6.2.0.md', 'docs/THREAT_MODEL.md', 'docs/WALKTHROUGH.md', ].filter((file) => files.has(file)); diff --git a/test/unit/docs/release-truth.test.js b/test/unit/docs/release-truth.test.js index c98816c9..45ae0c26 100644 --- a/test/unit/docs/release-truth.test.js +++ b/test/unit/docs/release-truth.test.js @@ -172,6 +172,21 @@ describe('root-set release documentation', () => { }); }); +describe('application storage release documentation', () => { + it('ships and links the v6.2.0 ownership and migration contract', () => { + const readme = read('README.md'); + const upgrading = read('UPGRADING.md'); + const releaseNotes = read('docs/releases/v6.2.0.md'); + + expect(readme).toContain('[v6.2.0 Release Notes](./docs/releases/v6.2.0.md)'); + expect(upgrading).toContain('Applications should stop treating a naked Git object ID'); + expect(upgrading).toContain('`cas.diagnostics.doctor()`'); + expect(releaseNotes).toContain('# git-cas v6.2.0 Release Notes'); + expect(releaseNotes).toContain('A handle is not a durability claim'); + expect(releaseNotes).toContain('does not run `git gc` or destructive prune'); + }); +}); + describe('advanced guide rendering', () => { it('keeps the table of contents rendered as Markdown links', () => { const advancedGuide = read('ADVANCED_GUIDE.md'); From c8f39d3395112f976a6a0c1d51e5bd7dc4dce9e7 Mon Sep 17 00:00:00 2001 From: James Ross Date: Mon, 13 Jul 2026 12:53:07 -0700 Subject: [PATCH 2/2] docs: enumerate release verification steps --- .../witness/release-candidate.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/design/0047-application-storage-cache-boundary/witness/release-candidate.md b/docs/design/0047-application-storage-cache-boundary/witness/release-candidate.md index 87c765aa..4f702856 100644 --- a/docs/design/0047-application-storage-cache-boundary/witness/release-candidate.md +++ b/docs/design/0047-application-storage-cache-boundary/witness/release-candidate.md @@ -51,7 +51,9 @@ It passed 13/13 steps and observed 6,124 tests: | Node integration | pass | 174 | | Bun integration | pass | 174 | | Deno integration | pass | 174 | -| Three runnable examples | pass | - | +| 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 | - |