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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [6.4.0] — 2026-07-17

### Added

- **Scoped staging workspaces** — `cas.workspaces.open()` now provides a
Expand All @@ -24,6 +26,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
witnesses reuse the existing `root-set` kind, and workspace-detail truncation
uses a dedicated code without widening closed public discriminant unions.

### Fixed

- **RootSet structured lock retries** — exact managed `update-ref --no-deref`
lock races surfaced as `GIT_REPOSITORY_LOCKED` now normalize to
`ROOT_SET_CONFLICT`, allowing the existing bounded retry policy to run.
Structured lock failures for other refs remain terminal.

## [6.3.0] — 2026-07-17

### Added
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,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.3.0 Release Notes](./docs/releases/v6.3.0.md).
[v6.4.0 Release Notes](./docs/releases/v6.4.0.md).

### 1. CLI Usage

Expand Down Expand Up @@ -200,6 +200,8 @@ All three runtimes are tested in CI on every push. The hexagonal architecture is
repository diagnostics.
- **[v6.3.0 Release Notes](./docs/releases/v6.3.0.md)**: Lifetime-safe,
reference-only cache acquisitions with explicit release and doctor evidence.
- **[v6.4.0 Release Notes](./docs/releases/v6.4.0.md)**: Scoped staging
workspaces for temporary multi-object reachability, promotion, and cleanup.
- **[Upgrading](./UPGRADING.md)**: Migration guide for v5 → v6.
- **[Changelog](./CHANGELOG.md)**: Version history and migration notes.

Expand Down
14 changes: 8 additions & 6 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,22 +32,24 @@ GitHub wins and this file should be corrected.
| Milestone | Theme | Tracker |
| ------------------------------------------------------------- | ---------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| [`v6.0.1`](https://github.com/git-stunts/git-cas/milestone/1) | Patch closeout and planning truth | [#37](https://github.com/git-stunts/git-cas/issues/37) |
| [`v6.1.0`](https://github.com/git-stunts/git-cas/milestone/2) | Bounded residency and scale hardening | [#38](https://github.com/git-stunts/git-cas/issues/38) |
| [`v6.1.0`](https://github.com/git-stunts/git-cas/milestone/2) | Bounded residency and scale hardening | [#43](https://github.com/git-stunts/git-cas/issues/43), [#44](https://github.com/git-stunts/git-cas/issues/44), [#45](https://github.com/git-stunts/git-cas/issues/45), [#48](https://github.com/git-stunts/git-cas/issues/48) |
| [`v6.2.0`](https://github.com/git-stunts/git-cas/milestone/3) | Emergency application storage and cache ownership boundary | [#50](https://github.com/git-stunts/git-cas/issues/50) |
| [`v6.3.0`](https://github.com/git-stunts/git-cas/milestone/4) | Operator TUI and agent automation follow-through | [#39](https://github.com/git-stunts/git-cas/issues/39), [#40](https://github.com/git-stunts/git-cas/issues/40) |
| [`v6.4.0`](https://github.com/git-stunts/git-cas/milestone/5) | Browser and edge read-path exploration | [#41](https://github.com/git-stunts/git-cas/issues/41) |
| [`v6.3.0`](https://github.com/git-stunts/git-cas/milestone/7) | Bounded scoped cache acquisitions | [#69](https://github.com/git-stunts/git-cas/issues/69), [#70](https://github.com/git-stunts/git-cas/issues/70) |
| [`v6.4.0`](https://github.com/git-stunts/git-cas/milestone/4) | Scoped staging workspaces | [#75](https://github.com/git-stunts/git-cas/issues/75), [#77](https://github.com/git-stunts/git-cas/issues/77) |
| [`v6.4.1`](https://github.com/git-stunts/git-cas/milestone/8) | Historical bounded-residency proof closeout | [#38](https://github.com/git-stunts/git-cas/issues/38), [#46](https://github.com/git-stunts/git-cas/issues/46) |
| [`v6.5.0`](https://github.com/git-stunts/git-cas/milestone/5) | Operator TUI and agent automation follow-through | [#39](https://github.com/git-stunts/git-cas/issues/39), [#40](https://github.com/git-stunts/git-cas/issues/40) |
| [`v6.6.0`](https://github.com/git-stunts/git-cas/milestone/9) | Browser and edge read-path exploration | [#41](https://github.com/git-stunts/git-cas/issues/41) |
| [`v7.0.0`](https://github.com/git-stunts/git-cas/milestone/6) | Protocol break only if audit requires it | [#42](https://github.com/git-stunts/git-cas/issues/42), only when justified |

## Latest Landed Design

The latest landed design record is:

- [0047-application-storage-cache-boundary](./docs/design/0047-application-storage-cache-boundary/application-storage-cache-boundary.md)
- [0049-scoped-staging-workspaces](./docs/design/0049-scoped-staging-workspaces/scoped-staging-workspaces.md)

Its GitHub goalpost issue,
[#50](https://github.com/git-stunts/git-cas/issues/50), owns the release
[#75](https://github.com/git-stunts/git-cas/issues/75), owns the release
evidence. The design doc is the durable contract; GitHub records completion.
No later design is selected in this file.

The `v6.2.0` slice record is:

Expand Down
15 changes: 10 additions & 5 deletions STATUS.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# STATUS

**Last tagged release:** `v6.3.0` (`2026-07-17`)
**Current release state:** `v6.3.0` is published to npm with provenance and to GitHub Releases; JSR publication is not claimed.
**Latest verification:** `pnpm run release:verify` passed 14/14 steps with 6,325 observed tests before tagging; release run `29566015478` then passed tag validation, lint, unit tests, Node/Bun/Deno integration, npm trusted publication, and final GitHub Release creation.
**Current release state:** `v6.4.0` release candidate; tag, npm publication, and GitHub Release remain pending the reviewed tag workflow.
**Latest verification:** the `v6.4.0` release verifier passed 14/14 steps with 6,538 observed tests across Node.js, Bun, and Deno; npm and JSR dry-runs passed.
**Playback truth:** `main`
**Runtimes:** Node.js 22.x, Bun, Deno
**Current planning method:** [WORKFLOW.md](./WORKFLOW.md)
Expand All @@ -18,6 +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.4.0 candidate posture** — scoped staging workspaces are merged through
reviewed commit `1ac2fc8`, and structured RootSet lock retries are corrected
by `8185dfb9`; npm, JSR, and runtime version metadata now identify `6.4.0`,
while the tag and registry artifacts remain deliberately absent until
release-candidate review completes.
- **v6.3.0 artifact posture** — signed tag `v6.3.0` resolves to reviewed merge
`33f4171`; npm reports `@git-stunts/git-cas@6.3.0` as `latest` with SLSA
provenance, and the final GitHub Release is published. JSR dry-run validation
Expand Down Expand Up @@ -99,11 +104,11 @@
- GitHub Issues are canonical. If this section and GitHub disagree, GitHub
wins and this section should be corrected.
- Current release goalpost:
[#69 v6.3.0: Bounded scoped cache acquisitions](https://github.com/git-stunts/git-cas/issues/69)
[#75 Scoped staging workspaces for multi-object promotion](https://github.com/git-stunts/git-cas/issues/75)
under the
[`v6.3.0` milestone](https://github.com/git-stunts/git-cas/milestone/7).
[`v6.4.0` milestone](https://github.com/git-stunts/git-cas/milestone/4).
- The latest landed design record is
[0048-scoped-cache-acquisitions](./docs/design/0048-scoped-cache-acquisitions/scoped-cache-acquisitions.md).
[0049-scoped-staging-workspaces](./docs/design/0049-scoped-staging-workspaces/scoped-staging-workspaces.md).

## Read Next

Expand Down
42 changes: 42 additions & 0 deletions UPGRADING.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,48 @@

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.3.0 To v6.4.0

v6.4.0 is API-additive and does not require stored-data migration. It adds
scoped staging workspaces for applications that construct one durable result
from many intermediate assets, pages, or bundles.

Do not use a CacheSet as temporary construction storage. Open one workspace,
stage the intermediate values through it, compact the retained roots after an
aggregate becomes transitively complete, and promote only the terminal handle:

```javascript
const workspace = await cas.workspaces.open({
namespace: 'my-application/materializations',
ttlMs: 2 * 60 * 60 * 1000,
});

try {
const page = await workspace.pages.put({ source: encodedPage });
const bundle = await workspace.bundles.putOrdered({
members: [['pages/result.cbor', page.handle]],
});
await workspace.checkpoint({ handles: [bundle.handle] });
await workspace.promoteToCache({ cache, key, handle: bundle.handle });
} finally {
await workspace.release();
}
```

Every returned staged value is anchored by an exact workspace generation.
Promotion requires matching destination retention evidence before source
release. Expiry is diagnostic posture, not automatic deletion; abandoned
workspaces remain reachable until a caller traverses bounded `inspect()` or
`sweep()` pages using `nextCursor`.

The interval between writing an object and publishing the workspace ref still
depends on Git's ordinary unreachable-object grace period. Concurrent
immediate-expiry pruning inside that interval is unsupported.

See [v6.4.0 Release Notes](./docs/releases/v6.4.0.md) and
[Scoped Staging Workspaces](./docs/API.md#scoped-staging-workspaces) for the
complete retention, promotion, and recovery contract.

## v6.2.0 To v6.3.0

v6.3.0 is API-additive and does not require stored-data migration. It adds
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -526,7 +526,8 @@ The work is done when:
- [x] Public declarations and docs match runtime behavior.
- [x] README, changelog, release notes, and witness are updated.
- [x] Issue and PR are linked correctly.
- [ ] CI, Code Rabbit, self-review, and independent Code Lawyer review are clean.
- [x] Implementation PR CI, Code Rabbit, self-review, and independent Code
Lawyer review are clean.
- [ ] v6.4.0 is published before git-warp consumes the API.

## Validation Plan
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# PERF-0049 v6.4.0 Release Candidate Witness

Date: 2026-07-17

Issues: #75, #77

## Scope

This witness records the pre-publication release candidate for scoped staging
workspaces. It does not claim that a `v6.4.0` tag, npm artifact, or GitHub
Release exists yet. This is an explicitly unpublished candidate.

The candidate:

- sets npm, JSR, and the runtime `PACKAGE_VERSION` export to `6.4.0`;
- moves scoped staging workspaces from `Unreleased` to `6.4.0`;
- packages and links `docs/releases/v6.4.0.md`;
- adds v6.3.0-to-v6.4.0 adoption guidance to `UPGRADING.md`;
- records an honest release-candidate posture in `STATUS.md`;
- preserves the v6.3.0 publication witness as immutable history; and
- aligns repository roadmap links with the explicitly retargeted GitHub
milestones.

## Implementation Provenance

| Capability | Review anchor | Commit |
| --- | --- | --- |
| Scoped staging workspaces | #76 | `1ac2fc85be857ca769c459b89c29bf4483b3f304` |
| Structured RootSet lock retry | #77 | `8185dfb9819909d9fbe0f0394de6ae31fc0a94a3` |
| Exact lock command validation | #78 | `d1e126952d70007313a2ecd39bc7cffda06a9edd` |

The retry correction recognizes a structured Git lock only when the captured
argument vector proves an exact `update-ref --no-deref` operation against the
managed RootSet ref. Locks for other refs remain non-conflict failures.
[cite: `src/domain/services/RootSetPersistence.js#13-36@d1e126952d70007313a2ecd39bc7cffda06a9edd`]
[cite: `src/domain/services/RootSetPersistence.js#275-295@d1e126952d70007313a2ecd39bc7cffda06a9edd`]
[cite: `test/unit/domain/services/RootSetPersistence.test.js#150-193@d1e126952d70007313a2ecd39bc7cffda06a9edd`]

## Verification

The release candidate passed the complete verifier:

```bash
pnpm run release:verify
```

| Step | Result | Tests |
| --- | --- | ---: |
| Lint | PASS | - |
| Unit Tests (Node) | PASS | 1,993 |
| Example: store-and-restore | PASS | - |
| Example: encrypted-workflow | PASS | - |
| Example: progress-tracking | PASS | - |
| Unit Tests (Bun) | PASS | 1,992 |
| Unit Tests (Deno) | PASS | 1,983 |
| Public type compatibility | PASS | - |
| Integration Tests (Node) | PASS | 190 |
| Integration Tests (Bun) | PASS | 190 |
| Integration Tests (Deno) | PASS | 190 |
| Build metadata stamp | PASS | - |
| npm pack dry-run | PASS | - |
| JSR publish dry-run | PASS | - |

The verifier passed 14/14 steps with 6,538 observed tests. A final npm dry-run
after the release prose was frozen produced `git-stunts-git-cas-6.4.0.tgz`
with 247 files, 763,925 packed bytes, and 2,129,717 unpacked bytes.

An initial candidate run stopped during Node integration when a structured Git
lock escaped RootSet conflict normalization. Issue #77 converted that
nondeterministic observation into direct positive and negative unit coverage.
After correction, the focused RootSet suite passed 16/16, lint passed, twenty
repeated real-Git ExpiringSet concurrency runs passed, and the complete
verifier passed as recorded above.

## 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.4.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.4.0` with provenance; and
6. GitHub reports the final non-draft `v6.4.0` Release.

Issue #75 stays open until publication evidence is attached. The downstream
git-warp registry upgrade and unchanged performance gate remain separate
adoption evidence after publication.
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ Review corrected eight contract defects before this witness:
- The first Code Rabbit and independent Code Lawyer reviews completed. Their
findings are addressed locally; GitHub thread resolution and post-push CI are
pending.
- The package version and release milestone require an explicit release decision.
- The fresh `v6.4.0` candidate verifier passed 14/14 steps with 6,538 observed
tests, including npm and JSR dry-runs. Release-PR review and the tag-driven
publication workflow remain pending.
- Downstream git-warp adoption and its unchanged ten-second integration hook
remain the end-to-end performance and compatibility gate.
21 changes: 14 additions & 7 deletions docs/releases/v6.4.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,15 @@ Workspace expiry never revokes reachability automatically. Operators inspect
and sweep one bounded namespace explicitly:

```javascript
const page = await cas.workspaces.inspect({ namespace, limit: 100 });
let cursor = null;
const cleanup = await cas.workspaces.sweep({
namespace,
limit: 100,
cursor,
});
cursor = cleanup.nextCursor;
do {
const cleanup = await cas.workspaces.sweep({
namespace,
limit: 100,
cursor,
});
cursor = cleanup.nextCursor;
} while (cursor !== null);
```

Sweep removes only expired direct refs whose generation still matches the
Expand Down Expand Up @@ -111,6 +112,12 @@ inventory. Real-Git integration runs `git prune --expire=now` after returned
operations and proves survival through page, asset, and bundle composition,
promotion, expiry, sweep, and release.

The final release-candidate verifier passed 14/14 steps with 6,538 observed
tests: 1,993 Node unit tests, 1,992 Bun unit tests, 1,983 Deno unit tests, and
190 integration tests on each runtime. npm pack and JSR publish dry-runs also
passed. Twenty repeated real-Git ExpiringSet runs verify that concurrent
duplicate-writer behavior remains stable after the RootSet retry correction.

The unchanged downstream `git-warp` ten-second integration hook remains the
release adoption gate after package publication. CI, self-review, Code Rabbit,
and independent Code Lawyer review remain mandatory before tagging.
Expand Down
2 changes: 1 addition & 1 deletion jsr.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@git-stunts/git-cas",
"version": "6.3.0",
"version": "6.4.0",
"exports": {
".": "./index.js",
"./service": "./src/domain/services/CasService.js",
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@git-stunts/git-cas",
"version": "6.3.0",
"version": "6.4.0",
"description": "Content-addressed storage backed by Git's object database, with optional encryption and pluggable codecs",
"type": "module",
"main": "index.js",
Expand Down Expand Up @@ -35,6 +35,7 @@
"docs/releases/v6.1.0.md",
"docs/releases/v6.2.0.md",
"docs/releases/v6.3.0.md",
"docs/releases/v6.4.0.md",
"docs/THREAT_MODEL.md",
"docs/VAULT_INTERNALS.md",
"docs/WALKTHROUGH.md",
Expand Down
36 changes: 34 additions & 2 deletions src/domain/services/RootSetPersistence.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,30 @@ const UPDATE_REF_CONFLICT_MARKERS = Object.freeze({
cannotLockRef: 'cannot lock ref',
referenceAlreadyExists: 'reference already exists',
});
const GIT_REPOSITORY_LOCKED = 'GIT_REPOSITORY_LOCKED';

function classifyStructuredUpdateRefConflict(err, {
rootSetRef,
newCommit,
expectedHeadOid,
}) {
const details = err?.details && typeof err.details === 'object' ? err.details : {};
if (details.code !== GIT_REPOSITORY_LOCKED) {
return null;
}
if (!Array.isArray(details.args)) {
return false;
}
const expectedArgs = [
'update-ref',
'--no-deref',
rootSetRef,
newCommit,
expectedHeadOid ?? '0'.repeat(newCommit.length),
];
return details.args.length === expectedArgs.length &&
expectedArgs.every((arg, index) => details.args[index] === arg);
}

/**
* Stateless persistence boundary for one root-set ref and snapshot format.
Expand Down Expand Up @@ -214,7 +238,7 @@ export default class RootSetPersistence {
});
} catch (err) {
const meta = await this.#updateFailureMeta(err, commitOid, expectedHeadOid);
if (this.#isConflict(err)) {
if (this.#isConflict(err, commitOid, expectedHeadOid)) {
throw new CasError(
'Concurrent root-set update detected',
ErrorCodes.ROOT_SET_CONFLICT,
Expand Down Expand Up @@ -248,11 +272,19 @@ export default class RootSetPersistence {
};
}

#isConflict(err) {
#isConflict(err, newCommit, expectedHeadOid) {
const meta = err?.meta && typeof err.meta === 'object' ? err.meta : {};
if (Object.hasOwn(meta, 'expectedOldOid') && Object.hasOwn(meta, 'actualOldOid')) {
return true;
}
const structuredConflict = classifyStructuredUpdateRefConflict(err, {
rootSetRef: this.rootSetRef,
newCommit,
expectedHeadOid,
});
if (structuredConflict !== null) {
return structuredConflict;
}
const normalized = errorDetailsText(err).toLowerCase();
if (!normalized.includes(this.rootSetRef.toLowerCase())) {
return false;
Expand Down
2 changes: 1 addition & 1 deletion src/package-version.js
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export const PACKAGE_VERSION = '6.3.0';
export const PACKAGE_VERSION = '6.4.0';
Loading
Loading