Skip to content

Commit 7b15ec1

Browse files
authored
Merge pull request #72 from git-stunts/fix/scoped-cache-acquisitions
feat: add scoped cache acquisitions
2 parents eac13af + 5b3cc5c commit 7b15ec1

40 files changed

Lines changed: 3807 additions & 67 deletions

.github/workflows/ci.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,5 +55,11 @@ jobs:
5555
run: docker compose build test-${{ matrix.runtime }}
5656
- name: Unit tests (${{ matrix.runtime }})
5757
run: docker compose run --rm test-${{ matrix.runtime }}
58+
- name: Public type compatibility (Deno)
59+
if: matrix.runtime == 'deno'
60+
run: >-
61+
docker compose run --rm test-deno
62+
deno check --config test/types/deno.json
63+
test/types/public-api-compatibility.ts
5864
- name: Integration tests (${{ matrix.runtime }})
5965
run: docker compose run --rm test-${{ matrix.runtime }} ${{ matrix.integration_cmd }}

ARCHITECTURE.md

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,10 @@ The facade is orchestration glue. It is not the storage engine itself.
187187
- **`CacheSetRegistry` and `CacheSet`** — own cache index generations,
188188
compare-and-swap replacement, TTL, entry and logical-byte limits,
189189
approximate-LRU sweeps, coalesced touches, doctor, and authoritative repair
190-
under `refs/cas/caches/*`.
190+
under `refs/cas/caches/*`. `CacheAcquisitionRegistry` atomically verifies a
191+
selected cache generation and creates a scoped anchor under
192+
`refs/cas/cache-acquisitions/*`; acquisition release is explicit,
193+
idempotent, and generation-checked.
191194

192195
- **`ExpiringSetRegistry` and `ExpiringSet`** — own digest-only replay markers
193196
under `refs/cas/expiring/*`. Admission is atomic, membership reads are
@@ -447,21 +450,26 @@ make objects Git-reachable, but they encode different lifecycle contracts:
447450
| --- | --- | --- |
448451
| RootSet | `refs/cas/rootsets/*` | Caller-managed current generation |
449452
| CacheSet | `refs/cas/caches/*` | TTL/capacity/approximate-LRU managed cache |
453+
| Cache acquisition | `refs/cas/cache-acquisitions/*` | Explicit caller scope over one cache generation |
450454
| ExpiringSet | `refs/cas/expiring/*` | Expiry-only replay protection |
451455
| Publication | Caller-allowlisted ref | Application-controlled causal history |
452456
| Vault | `refs/cas/vault` | History-preserving named assets |
453457

454-
High-level retention, CacheSet, ExpiringSet, and application publication
455-
operations return generation-scoped retention evidence. Replacing a parentless
456-
current-generation head releases old edges when no other ref or reflog reaches
457-
them; Git's normal grace and pruning policy still controls physical deletion.
458+
High-level retention, CacheSet, cache acquisition, ExpiringSet, and application
459+
publication operations return generation-scoped retention evidence. Replacing
460+
a parentless current-generation head releases old edges when no other ref or
461+
reflog reaches them; Git's normal grace and pruning policy still controls
462+
physical deletion. A cache acquisition temporarily retains the complete
463+
selected generation, not only the requested target, and must be released when
464+
consumption ends.
458465

459466
### Repository Diagnostics
460467

461468
`cas.diagnostics.doctor()` reports total, anchored, orphaned, volatile, and
462-
unreachable object evidence plus managed-storage summaries. Ref and reflog
463-
reachability defines anchored objects; volatile objects come only from safe
464-
prune dry-run output for the selected expiry cutoff.
469+
unreachable object evidence plus managed-storage summaries, including active
470+
cache-acquisition count and age. Ref and reflog reachability defines anchored
471+
objects; volatile objects come only from safe prune dry-run output for the
472+
selected expiry cutoff.
465473

466474
The report names limits Git cannot prove exactly, including per-owner
467475
deduplicated physical bytes and packed-object age. The inspection surface has

CHANGELOG.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
12+
- **Scoped cache acquisitions**`CacheSet.acquire()` now performs a bounded,
13+
reference-only lookup and atomically anchors the selected cache generation
14+
for an explicit caller lifetime. Acquisitions carry separate pinned
15+
retention evidence, release idempotently with a generation check, expose
16+
bounded inspection and operational cleanup, and appear in repository doctor
17+
with count and age evidence. Clock skew leaves structurally valid retention
18+
healthy while reporting an unknown age and explicit diagnostic issue.
19+
Real-Git coverage proves active acquisitions
20+
survive aggressive prune without making lookup reads scale with target graph
21+
size. Canonical one-segment namespace refs, symbolic-ref preflight,
22+
no-dereference mutations across ordinary managed refs and acquisition refs,
23+
post-conflict ref-type checks, and hard Git inventory limits preserve
24+
ownership boundaries under hostile or malformed refs. Unknown ref-type
25+
evidence fails doctor health closed. Acquisition capabilities remain optional
26+
on the base Git ref port, the existing retention-kind and diagnostic-kind
27+
unions remain unchanged, and the new doctor group is type-optional for
28+
compatibility with existing consumers. Production Git checked-delete
29+
conflicts fail closed because Git 2.43 cannot atomically prove that a missing
30+
direct ref is not an enumerator-invisible dangling symbolic ref.
31+
1032
### Documentation
1133

1234
- **v6.2.0 publication evidence** — records the signed tag identity, successful

README.md

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ Unlike traditional LFS which moves files to external servers, `git-cas` treats t
2525
without vault history retaining every prior generation.
2626
- **Managed Cache Sets**: Applications can store handles under
2727
`refs/cas/caches/*` with TTL, entry and logical-byte limits, approximate LRU
28-
eviction, immutable retention evidence, bounded inspection, and repair.
28+
eviction, immutable retention evidence, bounded inspection, repair, and
29+
scoped acquisitions that keep a selected generation reachable during use.
2930
Cache indexes and object reachability remain git-cas responsibilities.
3031
- **Expiry-Safe Replay Sets**: Security-sensitive markers can live under
3132
`refs/cas/expiring/*` with atomic add-if-absent, digest-only metadata, and
@@ -43,7 +44,7 @@ Unlike traditional LFS which moves files to external servers, `git-cas` treats t
4344
Existing v5 users should read [UPGRADING.md](./UPGRADING.md) and run
4445
`npm run upgrade` in dry-run mode before restoring old encrypted vault entries.
4546
For the release overview, see the
46-
[v6.2.0 Release Notes](./docs/releases/v6.2.0.md).
47+
[v6.3.0 Release Notes](./docs/releases/v6.3.0.md).
4748

4849
### 1. CLI Usage
4950

@@ -103,6 +104,7 @@ The README is the front door. Detailed mechanics live in the guide set:
103104
| Assets, pages, bundles, retention, and publication | [Application storage](./docs/API.md#application-storage) |
104105
| GC retention for caches and derived state | [Root Sets](./docs/API.md#root-sets) |
105106
| Managed TTL and capacity caches | [Cache Sets](./docs/API.md#cache-sets) |
107+
| Scoped protection while consuming a cache hit | [Cache acquisitions](./docs/API.md#acquire-and-release) |
106108
| Durable replay markers with expiry-only release | [Expiring Sets](./docs/API.md#expiring-sets) |
107109
| Repository reachability and git-cas usage evidence | [Repository Diagnostics](./docs/API.md#repository-diagnostics) |
108110
| v5 to v6 migration | [Upgrading](./UPGRADING.md) |
@@ -126,6 +128,10 @@ Core capabilities:
126128
compare-and-swap replacement, expiry, capacity eviction, retention
127129
witnesses, diagnostics, and repair. Reads do not write access metadata;
128130
callers opt into coalesced access updates with `touch()`.
131+
- **Scoped cache acquisitions**: `cache.acquire()` performs a reference-only
132+
lookup and atomically anchors the selected cache generation for the caller's
133+
explicit lifetime. `release()` is idempotent, while inspection and checked
134+
cleanup make abandoned acquisition refs observable and recoverable.
129135
- **Expiry-safe replay lifecycle**: `expiringSets.open()` atomically retains
130136
digest-only replay markers through their declared window. `contains()` is
131137
read-only, and `sweep()` can release only markers whose expiry has passed.
@@ -137,8 +143,9 @@ Core capabilities:
137143
avoid re-encrypting data blobs.
138144
- **Operational diagnostics**: `cas.diagnostics.doctor()` streams repository
139145
object/ref evidence, classifies anchored, orphaned, and volatile objects,
140-
and summarizes CacheSet, RootSet, ExpiringSet, and Vault usage without
141-
mutating Git. The `git-cas doctor` CLI continues to validate vault health.
146+
and summarizes cache acquisitions, CacheSet, RootSet, ExpiringSet, and Vault
147+
usage without mutating Git. The `git-cas doctor` CLI continues to validate
148+
vault health.
142149

143150
## Safety Snapshot
144151

@@ -186,6 +193,8 @@ All three runtimes are tested in CI on every push. The hexagonal architecture is
186193
- **[v6.2.0 Release Notes](./docs/releases/v6.2.0.md)**: Opaque application
187194
storage, managed cache and replay lifecycles, retention evidence, and
188195
repository diagnostics.
196+
- **[v6.3.0 Release Notes](./docs/releases/v6.3.0.md)**: Lifetime-safe,
197+
reference-only cache acquisitions with explicit release and doctor evidence.
189198
- **[Upgrading](./UPGRADING.md)**: Migration guide for v5 → v6.
190199
- **[Changelog](./CHANGELOG.md)**: Version history and migration notes.
191200

0 commit comments

Comments
 (0)