Add expiry-safe replay sets#65
Conversation
Self-reviewVerdict: PASS. No unresolved findings. I reviewed the complete Key conclusions:
Validation is green locally: lint; 1,851 unit tests with 2 expected skips; 173 real-Git integration tests; TypeScript declaration compilation; citation validation; and The full source-pinned review and residual constraints are recorded in |
Code Lawyer ReviewVerdict: PASS after nine adversarial findings were resolved.
Source and regression evidence for every finding is pinned under the |
📝 WalkthroughSummary by CodeRabbit
WalkthroughAdds a Git-backed ChangesExpiring Set Lifecycle
Estimated code review effort: 4 (Complex) | ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/domain/helpers/isCanonicalCollectionKey.js`:
- Around line 21-35: Confirm the project’s engines or minimum Node target
supports String.prototype.isWellFormed() (Node 20+), then replace the manual
isWellFormed loop with the native String.prototype.isWellFormed() call while
preserving the existing boolean validation behavior.
In `@src/domain/services/ExpiringSet.js`:
- Around line 308-338: Collapse the duplicate witness construction in
`#markerWitness` and `#indexWitness` into a single shared helper, preserving the
existing RetentionWitness fields and root metadata. Update both call sites to
use the shared helper while retaining their current argument semantics, so the
marker and index paths cannot diverge.
In `@src/domain/services/ExpiringSetMetadataCodec.js`:
- Around line 55-81: Update normalizeState to validate that state.updatedAt is
not earlier than state.createdAt, alongside the existing timestamp and
nextExpiry checks. Reject inconsistent states using the established invalid
validation path, while preserving all existing count and expiry invariants.
In `@src/domain/value-objects/ExpiringMarker.js`:
- Around line 30-38: In the ExpiringMarker construction flow, guard evidence for
null or undefined before the RetentionWitness normalization branch. Throw
invalid(...) using the existing EXPIRING_SET_MARKER_INVALID path, and only
instantiate RetentionWitness for non-nullish evidence while preserving existing
RetentionWitness instances.
In `@test/unit/domain/services/ExpiringSetMetadataCodec.test.js`:
- Around line 35-64: Add a direct assertion in the “ExpiringSet metadata
rejection” test that codec.encodeMarker rejects a marker whose
verificationDigest equals marker.keyDigest, expecting
EXPIRING_SET_MARKER_INVALID. Keep the existing non-canonical marker and
state-count assertions unchanged.
In `@test/unit/domain/value-objects/CacheCollectionValues.test.js`:
- Around line 42-44: Add a lone-surrogate rejection assertion alongside the
existing ExpiringSetKey combining-character test, using the same
EXPIRING_SET_KEY_INVALID error expectation. Keep the current valid-key assertion
and Unicode normalization coverage unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 962cf915-8ff4-41a3-8c90-5803b5898cd9
📒 Files selected for processing (23)
CHANGELOG.mdREADME.mdSTATUS.mddocs/API.mddocs/design/0047-application-storage-cache-boundary/witness/expiring-set.mdindex.d.tsindex.jssrc/domain/errors/Codes.jssrc/domain/helpers/isCanonicalCollectionKey.jssrc/domain/services/ExpiringSet.jssrc/domain/services/ExpiringSetIndex.jssrc/domain/services/ExpiringSetMetadataCodec.jssrc/domain/services/ExpiringSetRegistry.jssrc/domain/value-objects/CacheKey.jssrc/domain/value-objects/ExpiringMarker.jssrc/domain/value-objects/ExpiringSetKey.jssrc/domain/value-objects/ExpiringSetRef.jstest/integration/expiring-set.test.jstest/unit/domain/services/ExpiringSet.test.jstest/unit/domain/services/ExpiringSetMetadataCodec.test.jstest/unit/domain/value-objects/CacheCollectionValues.test.jstest/unit/facade/ContentAddressableStore.application-storage.test.jstest/unit/types/declaration-accuracy.test.js
📜 Review details
⏰ Context from checks skipped due to timeout. (3)
- GitHub Check: test-docker (node)
- GitHub Check: test-docker (bun)
- GitHub Check: test-docker (deno)
🧰 Additional context used
📓 Path-based instructions (4)
CHANGELOG.md
📄 CodeRabbit inference engine (AGENTS.md)
Use
CHANGELOG.mdto record the historical truth of merged behavior
Files:
CHANGELOG.md
STATUS.md
📄 CodeRabbit inference engine (AGENTS.md)
Use
STATUS.mdas a compact snapshot of release and runtime truth
Files:
STATUS.md
README.md
📄 CodeRabbit inference engine (AGENTS.md)
Use
README.mdas the public front door, core value prop, and quick start documentation
Files:
README.md
docs/design/**
📄 CodeRabbit inference engine (AGENTS.md)
Use
docs/design/directory for durable design contracts and proof plans
Files:
docs/design/0047-application-storage-cache-boundary/witness/expiring-set.md
🧠 Learnings (2)
📚 Learning: 2026-02-28T19:21:13.982Z
Learnt from: flyingrobots
Repo: git-stunts/git-cas PR: 15
File: test/unit/domain/services/CasService.envelope.test.js:326-330
Timestamp: 2026-02-28T19:21:13.982Z
Learning: In fuzz tests for cryptographic operations, use a seeded PRNG (e.g., xorshift32) for control-flow variables such as plaintext size selection and recipient index selection to ensure reproducibility. Continue to use randomBytes() for cryptographic keys and nonces to preserve realistic randomness and avoid security anti-patterns. This guideline applies to test files that perform fuzz testing of crypto logic; implement a consistent seed setup (e.g., fixed seed in test initialization) and document the rationale to enable deterministic replays across runs.
Applied to files:
test/unit/types/declaration-accuracy.test.jstest/unit/domain/value-objects/CacheCollectionValues.test.jstest/unit/domain/services/ExpiringSetMetadataCodec.test.jstest/unit/facade/ContentAddressableStore.application-storage.test.jstest/integration/expiring-set.test.jstest/unit/domain/services/ExpiringSet.test.js
📚 Learning: 2026-03-30T19:53:48.000Z
Learnt from: flyingrobots
Repo: git-stunts/git-cas PR: 29
File: docs/design/TR-010-planning-index-consistency-review.md:121-131
Timestamp: 2026-03-30T19:53:48.000Z
Learning: In this repo, CHANGELOG.md entries should be user-facing and descriptive (bullet points) rather than cycle-ID headings (e.g., use a phrase like “Planning-index consistency review” instead of a “TR-010 — …” heading). When searching the changelog, don’t rely on grepping for cycle IDs (e.g., “TR-010”) because it may cause false negatives; search for the descriptive keywords/phrases from the bullet entries instead.
Applied to files:
CHANGELOG.md
🪛 ast-grep (0.44.1)
test/integration/expiring-set.test.js
[warning] 4-4: Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { spawnSync } from 'node:child_process';
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').
(detect-child-process)
🔇 Additional comments (39)
CHANGELOG.md (1)
50-55: LGTM!README.md (1)
30-33: LGTM!Also applies to: 106-106, 128-130
STATUS.md (1)
71-77: LGTM!docs/API.md (6)
1562-1601: LGTM!
1602-1622: LGTM!
1623-1639: LGTM!
1640-1655: LGTM!
1675-1683: LGTM!
1663-1667: 🎯 Functional Correctness
inspect()cap matches the implementation
inspect()is capped at 1,000 records per call.docs/design/0047-application-storage-cache-boundary/witness/expiring-set.md (4)
1-23: LGTM!
24-101: LGTM!
222-224: 📐 Maintainability & Code Quality | ⚡ Quick winCitation end line is off by one.
docs/API.md#1562-1682should bedocs/API.md#1562-1683— the added "Expiring Sets" section runs through line 1683 (the Clock Trust closing sentence), which this citation currently omits.As per coding guidelines, `docs/design/**` is used "for durable design contracts and proof plans," so citation accuracy in this witness matters for its evidentiary purpose.📝 Proposed fix
-[cite: `docs/API.md#1562-1682@bf66f6e5247284975631ffdfaabdc15fdb0e25d4`] +[cite: `docs/API.md#1562-1683@bf66f6e5247284975631ffdfaabdc15fdb0e25d4`]Source: Coding guidelines
226-234: LGTM!test/integration/expiring-set.test.js (4)
1-48: LGTM!
50-91: LGTM!
93-107: LGTM!
110-131: LGTM!src/domain/errors/Codes.js (1)
25-28: LGTM!src/domain/helpers/isCanonicalCollectionKey.js (1)
1-12: LGTM!src/domain/value-objects/CacheKey.js (1)
1-3: LGTM!Also applies to: 25-27
src/domain/value-objects/ExpiringSetKey.js (1)
1-28: LGTM!src/domain/value-objects/ExpiringMarker.js (2)
12-29: LGTM!
65-78: LGTM!src/domain/value-objects/ExpiringSetRef.js (1)
12-19: 🎯 Functional CorrectnessNo ref-specific error code is expected here.
ExpiringSetReffollows the same pattern asCacheSetRef, and both useCOLLECTION_NAMESPACE_INVALIDfor prefix mismatches;RootSetRefis the separate outlier withROOT_SET_REF_INVALID.> Likely an incorrect or invalid review comment.src/domain/services/ExpiringSetMetadataCodec.js (3)
1-45: LGTM!
84-95: LGTM!
97-160: LGTM!index.d.ts (1)
360-386: LGTM!Also applies to: 851-914, 1144-1150, 1199-1199
test/unit/domain/value-objects/CacheCollectionValues.test.js (1)
6-7: LGTM!Also applies to: 17-17
test/unit/types/declaration-accuracy.test.js (1)
96-99: LGTM!src/domain/services/ExpiringSetIndex.js (3)
26-117: LGTM!
159-254: LGTM!
133-157: 🎯 Functional CorrectnessNo change needed:
iterateMemberReferences()yields canonical path order, andmarkerPath()uses fixed-length hex digests, so this merge logic stays in ascending digest order.test/unit/domain/services/ExpiringSet.test.js (1)
28-480: LGTM!src/domain/services/ExpiringSet.js (2)
34-306: LGTM!
340-520: LGTM!src/domain/services/ExpiringSetRegistry.js (1)
14-101: LGTM!index.js (1)
17-18: LGTM!Also applies to: 69-69, 96-96, 186-188, 232-233, 291-314, 373-378
test/unit/facade/ContentAddressableStore.application-storage.test.js (1)
8-9: LGTM!Also applies to: 41-48, 64-65, 78-87
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/domain/services/ExpiringSet.js (1)
394-409: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winValidate every injected dependency during construction.
The check only validates
rootSet.mutate,index.scan, andcrypto.sha256, while later methods callclock.now,rootSet.read/doctor, and index methods such asfromRootEntries,getMarker,stageMarker,rewrite,markers,getState, andtoRootEntry. A partially wired adapter therefore passes construction and fails later with a rawTypeErrorinstead ofINVALID_OPTIONS. Validate all required methods, including a custom clock, and add regression coverage.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/domain/services/ExpiringSet.js` around lines 394 - 409, Expand ExpiringSet.#assertDependencies to validate every method used by the service, including clock.now, rootSet.read/doctor, and index.fromRootEntries, getMarker, stageMarker, rewrite, markers, getState, and toRootEntry, while retaining the existing checks. Ensure any missing method is reported through the existing INVALID_OPTIONS error path during construction, and add regression coverage for partially wired dependencies.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@src/domain/services/ExpiringSet.js`:
- Around line 394-409: Expand ExpiringSet.#assertDependencies to validate every
method used by the service, including clock.now, rootSet.read/doctor, and
index.fromRootEntries, getMarker, stageMarker, rewrite, markers, getState, and
toRootEntry, while retaining the existing checks. Ensure any missing method is
reported through the existing INVALID_OPTIONS error path during construction,
and add regression coverage for partially wired dependencies.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: dde5bb83-f5cc-485a-b304-39993e8f4edb
📒 Files selected for processing (5)
docs/design/0047-application-storage-cache-boundary/witness/expiring-set.mdsrc/domain/services/ExpiringSet.jssrc/domain/value-objects/ExpiringMarker.jstest/unit/domain/value-objects/ApplicationStorageResults.test.jstest/unit/domain/value-objects/CacheCollectionValues.test.js
📜 Review details
🧰 Additional context used
📓 Path-based instructions (1)
docs/design/**
📄 CodeRabbit inference engine (AGENTS.md)
Use
docs/design/directory for durable design contracts and proof plans
Files:
docs/design/0047-application-storage-cache-boundary/witness/expiring-set.md
🧠 Learnings (2)
📚 Learning: 2026-02-28T19:21:13.982Z
Learnt from: flyingrobots
Repo: git-stunts/git-cas PR: 15
File: test/unit/domain/services/CasService.envelope.test.js:326-330
Timestamp: 2026-02-28T19:21:13.982Z
Learning: In fuzz tests for cryptographic operations, use a seeded PRNG (e.g., xorshift32) for control-flow variables such as plaintext size selection and recipient index selection to ensure reproducibility. Continue to use randomBytes() for cryptographic keys and nonces to preserve realistic randomness and avoid security anti-patterns. This guideline applies to test files that perform fuzz testing of crypto logic; implement a consistent seed setup (e.g., fixed seed in test initialization) and document the rationale to enable deterministic replays across runs.
Applied to files:
test/unit/domain/value-objects/CacheCollectionValues.test.jstest/unit/domain/value-objects/ApplicationStorageResults.test.js
📚 Learning: 2026-07-13T17:00:46.222Z
Learnt from: flyingrobots
Repo: git-stunts/git-cas PR: 65
File: src/domain/helpers/isCanonicalCollectionKey.js:21-35
Timestamp: 2026-07-13T17:00:46.222Z
Learning: In git-stunts/git-cas, the codebase is intended to run on multiple JavaScript runtimes (Node, Bun, and Deno), not just Node. During code review, don’t suggest replacing existing portable helper logic with newer Node-only built-ins (e.g., Node 20+ APIs like `String.prototype.isWellFormed()`) unless the change preserves Bun/Deno compatibility (via portable/polyfill implementations or safe runtime gating). If a file includes manual implementations (such as `src/domain/helpers/isCanonicalCollectionKey.js`), treat them as intentional to avoid requiring a higher host built-in baseline.
Applied to files:
src/domain/value-objects/ExpiringMarker.jssrc/domain/services/ExpiringSet.js
🔇 Additional comments (7)
src/domain/value-objects/ExpiringMarker.js (1)
11-62: LGTM!Also applies to: 64-66, 68-81
test/unit/domain/value-objects/CacheCollectionValues.test.js (1)
6-7: LGTM!Also applies to: 17-18, 42-46
test/unit/domain/value-objects/ApplicationStorageResults.test.js (1)
5-5: LGTM!Also applies to: 184-195
src/domain/services/ExpiringSet.js (1)
1-32: LGTM!Also applies to: 34-392, 413-496
docs/design/0047-application-storage-cache-boundary/witness/expiring-set.md (3)
15-15: LGTM!
205-232: LGTM!
253-253: LGTM!
Summary
cas.expiringSets.open()with atomicaddIfAbsent, targetedcontains, expiry-onlysweep, boundedinspect, and non-mutatingdoctorrefs/cas/expiring/*Why
Security-sensitive replay markers must survive process restart and Git garbage collection for their entire acceptance window. Application code should not manage CAS objects, trees, refs, or an independent cache to get that behavior.
ExpiringSet deliberately has no live
remove,repair, capacity, or LRU path. Release means publishing a parentless generation that no longer reaches expired support; this API never deletes objects or runs Git GC.Linked Issues
Closes #53
Parent goalpost: #50
Proof
docs/design/0047-application-storage-cache-boundary/witness/expiring-set.mdValidation
pnpm lintpnpm test(209 files; 1,851 passed; 2 expected skips)GIT_STUNTS_DOCKER=1 pnpm test:integration(9 files; 173 passed)pnpm exec tsc --noEmit --skipLibCheck --module NodeNext --moduleResolution NodeNext --target ES2022 index.d.tsgit diff --check