All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
git cas agent recipient ...— added machine-facing recipient inspection and mutation commands so Relay can list recipients and perform add/remove flows through structured protocol data instead of human CLI text.git cas agent rotate— added a machine-facing rotation flow so Relay can rotate recipient keys by slug or detached tree OID and expose the resulting tree and vault side effects explicitly.git cas agent vault rotate— added a machine-facing vault passphrase rotation flow so Relay can rotate encrypted vault state with explicit commit, KDF, and rotated/skipped-entry results.git cas agent vault init|remove— added machine-facing vault lifecycle commands so Relay can initialize encrypted or plaintext vaults and remove entries without scraping human CLI output.- Docs maintainer checklist — added docs/DOCS_CHECKLIST.md as the short pre-review pass for doc-heavy branches, covering boundary clarity, canonical-source links, index hygiene, and empty-state wording discipline.
- Pre-PR doc cross-link audit — added a lightweight routing audit to docs/DOCS_CHECKLIST.md, WORKFLOW.md, and CONTRIBUTING.md so doc-heavy branches verify canonical adjacent links before review instead of discovering missing cross-links late in PR feedback.
- Planning-index consistency review — added an explicit planning-surface review to docs/DOCS_CHECKLIST.md and WORKFLOW.md, defining when to verify backlog, design, archive, and legend alignment.
- Benchmark baselines doc — added docs/BENCHMARKS.md with the first published chunking baseline, including fixed-size versus CDC throughput, dedupe reuse results, and refresh instructions.
- Threat model doc — added docs/THREAT_MODEL.md as the canonical statement of attacker models, trust boundaries, exposed metadata, and explicit non-goals.
- Workflow model — added WORKFLOW.md, explicit legends/backlog/invariants directories, and a cycle-first planning model for fresh work.
- Review automation baseline — added
.github/CODEOWNERSwith repo-wide ownership for@git-stunts. - Release runbook — added
docs/RELEASE.mdand linked it fromCONTRIBUTING.mdas the canonical patch-release workflow. pnpm release:verify— new maintainer-facing release helper runs the full release checklist, captures observed test counts, and prints a Markdown summary that can be pasted into release notes or changelog prep.git cas vault stats— new vault summary command reports logical size, chunk references, dedupe ratio, encryption coverage, compression usage, and chunking strategy breakdowns.git cas doctor— new diagnostics command scansrefs/cas/vault, validates every referenced manifest, and exits non-zero with structured issue output when it finds broken entries or a missing vault ref.- Deterministic property-based envelope coverage — added a
fast-check-backed property suite for envelope-encrypted store/restore round-trips and tamper rejection across empty, boundary-adjacent, and multi-chunk payload sizes.
- README rewritten — the front page now focuses on current product truth, clear quick starts, operational caveats, and the canonical doc map instead of mixing release history, marketing copy, and reference detail.
- Planning lifecycle clarified — live backlog items now exclude delivered work, archive directories now hold retired backlog history and reserved retired design space, landed cycle docs use explicit landed status, and the design/backlog indexes now reflect current truth instead of stale activity.
- Architecture map repaired — ARCHITECTURE.md now describes the shipped system instead of an older flat-manifest-only model, including Merkle manifests, the extracted
VaultServiceandKeyResolver, current ports/adapters, and the real storage layout for trees and the vault. - Architecture navigation clarified — ARCHITECTURE.md now distinguishes the public package boundary from internal domain helpers and links directly to docs/THREAT_MODEL.md as adjacent truth.
- Guide moved under docs and repaired — the long-form guide now lives at docs/GUIDE.md, links back to the canonical API/security docs, uses current
restore --oidsyntax, and no longer teaches stale EventEmitter-first or internal-import-heavy workflows for common use. - Markdown surface map added — docs/MARKDOWN_SURFACE.md now records a per-file
KEEP/CUT/MERGE/MOVErecommendation across the tracked Markdown surface, including which root docs still belong at the repo front door and which remaining artifacts are migration or local-only candidates. - Examples surface audited — examples/README.md now records the recommendation for each maintained example, and the store/restore example now uses the public
readManifest()helper instead of manual manifest decoding through service internals. - Security doc discoverability improved — README.md, CONTRIBUTING.md, WORKFLOW.md, ARCHITECTURE.md, docs/API.md, and docs/DOCS_CHECKLIST.md now link more directly to SECURITY.md and docs/THREAT_MODEL.md so maintainers and agents can find the canonical security guidance from the docs they read first.
- GitHub Actions runtime maintenance — CI and release workflows now run on
actions/checkout@v6andactions/setup-node@v6, clearing the Node 20 deprecation warnings from GitHub-hosted runners. - Ubuntu-based Docker test stages — the local/CI Node, Bun, and Deno test images now build on
ubuntu:24.04, copying runtime binaries from the official upstream images instead of inheriting Debian-based runtime images directly, and the final test commands now run as an unprivilegedgitstuntsuser. - Test conventions expanded —
test/CONVENTIONS.mdnow documents Git tree filename ordering, Docker-only integration policy, pinned integrationfileParallelism: false, and direct-argv subprocess helpers.
- CLI credential edge cases —
store --recipientnow ignores ambientGIT_CAS_PASSPHRASEstate when no explicit vault passphrase flag/file was provided, store/restore/init now reject ambiguous explicit credential combinations consistently,vault init --algorithmno longer silently falls back to plaintext without a passphrase source, andvault rotatenow rejects whitespace-only old/new passphrase inputs instead of treating them as valid credentials. - Bun blob writes in Git persistence —
GitPersistenceAdapter.writeBlob()now hashes temp files instead of piping large buffers throughgit hash-object --stdinunder Bun, avoiding unhandledEPIPEfailures during real Git-backed stores. - Release verification runner failures —
runReleaseVerify()now converts thrown step-runner errors into structured step failures with aReleaseVerifyErrorsummary instead of letting raw exceptions escape. - Machine-readable release verification —
pnpm release:verify --jsonnow emits structured JSON on both success and failure paths, making CI automation and release-note tooling consume the same verification source of truth. - Dashboard launch context normalization —
launchDashboard()now treats injected Bijou contexts without an explicitmodeas interactive, avoiding an incorrect static fallback, and the CLI mode tests now lock theBIJOU_ACCESSIBLEandTERM=dumbbranches.
- Vitest workspace split — unit, integration, and benchmark suites now live in explicit workspace projects so the integration suite always runs with
fileParallelism: false, regardless of the exact CLI invocation shape. - Status semantics —
STATUS.mdnow distinguishes the last released version (v5.3.1) from the current branch version (v5.3.2).
- CLI version drift —
bin/git-cas.jsnow reads the package version instead of carrying a stale hardcoded literal, sogit-cas --versiontracks the in-repo release line correctly.
- Repeated chunk tree emission —
createTree()and_createMerkleTree()now emit one chunk blob tree entry per unique digest, preserving first-seen order at write time while leaving the manifest unchanged as the authoritative ordered index of chunk occurrences. - Invalid Git trees for repetitive content — repetitive files no longer produce duplicate tree entry names, so emitted trees pass
git fsck --fullwithoutduplicateEntriesfailures. - Regression coverage for tree reachability — added unit tests for first-seen dedupe behavior and integration tests that store repetitive content, verify restore correctness, and assert clean
git fsckresults on a real Git repository.
- Vault rotate passphrase-file support —
vault rotatenow accepts--old-passphrase-fileand--new-passphrase-fileflags, bringing it to parity with the store/restore passphrase-file support. - CLI store flags —
--gzip,--strategy <fixed|cdc>,--chunk-size <n>,--concurrency <n>,--codec <json|cbor>,--merkle-threshold <n>,--target-chunk-size <n>,--min-chunk-size <n>,--max-chunk-size <n>. All library-level chunking, compression, codec, and concurrency options are now accessible from the CLI. - CLI restore flags —
--concurrency <n>,--max-restore-buffer <n>. Parallel I/O and restore buffer limit now configurable from CLI. .casrcconfig file — JSON config file at the repository root provides default values for CLI flags. CLI flags always take precedence. Supports:chunkSize,strategy,concurrency,codec,compression,merkleThreshold,maxRestoreBufferSize, andcdc.*sub-keys.- CODE-EVAL.md — Forensic architectural audit (zero-knowledge code extraction, critical assessment, roadmap reconciliation, prescriptive blueprint).
- M16 Capstone — New milestone in ROADMAP.md addressing all 9 audit flaws and 10 concerns (C1–C10). 13 task cards, ~698 LoC, ~21h estimated.
- Concerns C8–C10 — Three architectural concerns from the CODE-EVAL.md audit now documented: crypto adapter LSP violation (C8), FixedChunker quadratic allocation (C9), encrypt-then-chunk dedup loss (C10).
- CasError codes —
RESTORE_TOO_LARGEandENCRYPTION_BUFFER_EXCEEDEDregistered in canonical error code table. - 16.2 — Memory restore guard —
CasServiceacceptsmaxRestoreBufferSize(default 512 MiB)._restoreBufferedthrowsRESTORE_TOO_LARGEwith{ size, limit }meta when encrypted/compressed restore would exceed the limit. Unencrypted streaming restore is unaffected. - 16.3 — Web Crypto encryption buffer guard —
WebCryptoAdapteracceptsmaxEncryptionBufferSize(default 512 MiB). ThrowsENCRYPTION_BUFFER_EXCEEDEDwhen streaming encryption exceeds the limit, since Web Crypto AES-GCM is a one-shot API. NodeCryptoAdapter uses true streaming and is unaffected. - 16.5 — Encrypt-then-chunk dedup warning —
CasService.store()now logs a warning when encryption is combined with CDC chunking, since ciphertext is pseudorandom and content-defined boundaries provide no dedup benefit. - 16.10 — Orphaned blob tracking —
STREAM_ERRORnow includesmeta.orphanedBlobs— an array of OIDs for blobs successfully written before the stream failure. Error metric includesorphanedBlobscount for observability. - 16.11 — Passphrase input security — New
--vault-passphrase-file <path>CLI option reads passphrase from file (use-for stdin). Interactive TTY prompt added as fallback when no other passphrase source is available.resolvePassphraseis now async with priority: file → flag → env → TTY → undefined. Empty passphrases rejected. File permission warning on group/world-readable files. - 16.12 — KDF brute-force awareness —
CasServicenow emitsdecryption_failedmetric with slug context when decryption fails withINTEGRITY_ERRORduring encrypted restore. CLI adds a 1-second delay afterINTEGRITY_ERRORto slow brute-force attempts. Library API imposes no delay — callers manage their own rate-limiting policy. - 16.13 — GCM nonce collision docs + encryption counter —
SECURITY.mdmoved to project root with new sections: GCM nonce bound (2^32 NIST limit), key rotation frequency, KDF parameter guidance, and passphrase entropy recommendations. Vault metadata now tracksencryptionCount, incremented per encryptedaddToVault(). Observability warning emitted when count exceeds 2^31.VaultServiceaccepts optionalobservabilityport. - 16.7 — Lifecycle method naming — Added
inspectAsset()(replacesdeleteAsset()) andcollectReferencedChunks()(replacesfindOrphanedChunks()) as canonical names on bothCasServiceand the facade. Old names are preserved as deprecated aliases that emit observability warnings. Type definitions updated with@deprecatedJSDoc.
runActioninjectable delay —runAction()now accepts an optional{ delay }dependency, replacing the hardcodedsetTimeoutcall. Tests inject a spy instead of usingvi.useFakeTimers(), making INTEGRITY_ERROR rate-limit tests deterministic across Node, Bun, and Deno.- Test conventions — Added
test/CONVENTIONS.mddocumenting rules for deterministic, cross-runtime tests: inject time dependencies, usechmod()instead ofwriteFile({ mode }), avoid global state patching. - VaultService test observability wiring —
VaultService.test.jsnow passes amockObservability()port to all tests instead of relying on the silent no-op default.rotateVaultPassphrase.test.jsnow passesSilentObserverexplicitly. If observability wiring breaks, the test suite will catch it. NodeCryptoAdapter.encryptBufferJSDoc —@returnsannotation corrected toPromise<...>, matching the async implementation.maxRestoreBufferSizedocumented — constructor JSDoc and#configtype inContentAddressableStorenow include the parameter.- ROADMAP.md heading level — added
## Task Cardsheading between# M16and### 16.1to satisfy MD001 heading-increment rule. - 16.1 — Crypto adapter behavioral normalization —
NodeCryptoAdapter.encryptBuffernow returns a Promise (was sync), matching Bun/Web.decryptBuffervalidates key on all adapters.NodeCryptoAdapter.createEncryptionStreamguardsfinalize()withSTREAM_NOT_CONSUMED. New conformance test suite asserts identical contracts across all adapters. - 16.4 — FixedChunker pre-allocated buffer — Replaced
Buffer.concat()loop with a pre-allocatedBuffer.allocUnsafe(chunkSize)working buffer, eliminating O(n²) copies for many small input buffers. Matches the allocation strategy used byCdcChunker.
- Post-decompression size guard —
_restoreBufferednow enforcesmaxRestoreBufferSizeafter decompression, not just before. Compressed payloads that inflate beyond the configured limit now throwRESTORE_TOO_LARGEinstead of silently allocating unbounded memory. - CLI passphrase prompt deferral —
resolveEncryptionKeynow checks vault metadata before callingresolvePassphrase, avoiding unnecessary TTY prompts for unencrypted vaults. Store action recipient-conflict check inspects flags/env without consuming stdin. - CRLF passphrase normalization —
readPassphraseFilenow strips trailing\r\n(Windows line endings) in addition to\n, preventing passphrase mismatches from Windows-edited files. - Constructor validation —
CasService.maxRestoreBufferSize(integer >= 1024),CasService.chunkSize(integer >= 1024),WebCryptoAdapter.maxEncryptionBufferSize(finite, positive), andFixedChunker.chunkSize(positive integer) are now validated at construction time, preventing silent misconfiguration. - Error-path test hardening —
orphanedBlobs,restoreGuard,kdfBruteForce, andconformancetests now fail explicitly when expected errors are not thrown (previously silent pass-through). - Orphaned blob enrichment on CasError re-throw —
_chunkAndStorenow attachesorphanedBlobsmetadata to existingCasErrorinstances before re-throwing, instead of discarding the information. - VaultService metadata mutation on retry —
addToVaultnow shallow-copiesstate.metadatabefore mutation, preventingencryptionCountfrom being incremented multiple times across CAS retries. - 16.8 — CasError portability guard —
Error.captureStackTracenow guarded with a runtime check. CasError constructs correctly on runtimes wherecaptureStackTraceis unavailable (e.g. Firefox, older Deno). - 16.9 — Pre-commit hook + hooks directory —
scripts/git-hooks/renamed toscripts/hooks/per CLAUDE.md convention. Newpre-commithook runs lint gate.install-hooks.shupdated accordingly. - 16.6 — Chunk size upper bound — CasService, FixedChunker, and CdcChunker now reject chunk sizes exceeding 100 MiB. CasService logs a warning when chunk size exceeds 10 MiB.
- ROADMAP.md M16 summary — Corrected LoC/hours from
~430/~28hto~698/~21hto match the detailed task breakdown. - VaultService constructor type — Added missing
observability?: ObservabilityPortparameter toindex.d.tsdeclaration. - Nullish coalescing for config merging —
strategyandcodecinmergeConfig()now use??instead of||, so empty-string CLI values don't fall through to.casrcdefaults. - Empty passphrase rejection —
readPassphraseFilerejects files that yield an empty string after newline stripping.resolvePassphrasevalidates--vault-passphraseflag andGIT_CAS_PASSPHRASEenv var. - KDF algorithm validation —
vault initandvault rotatenow validate--algorithmagainst the supported set (pbkdf2,scrypt) before passing to the KDF. .casrcconfig validation —loadConfig()now validates all config values (types, ranges, enum membership) after JSON parsing.- Deprecated method names in docs — Updated
deleteAsset→inspectAssetandfindOrphanedChunks→collectReferencedChunksin README and GUIDE. - Missing error codes in SECURITY.md — Added
RESTORE_TOO_LARGEandENCRYPTION_BUFFER_EXCEEDEDsections.
CryptoPortBase.sha256()type —index.d.tsdeclaration corrected fromstring | Promise<string>toPromise<string>, matching the async implementation since v5.2.3.keyLengthpassthrough —KeyResolver.#resolveKeyFromPassphraseandderiveKekFromKdfnow forwardkdf.keyLengthtoderiveKey(), fixing a latent bug for vaults configured with non-default key lengths.- Deno test compatibility —
createCryptoAdapter.test.jsno longer crashes on Deno by guarding immutableglobalThis.Denorestoration with try/catch and skipping Node-only tests on non-Node runtimes. - README wording — "no public API changes" corrected to "no breaking API changes" in the v5.2.3 summary.
- Barrel re-export description — README and CHANGELOG now show the correct
export { default as X } from '...'syntax. - Vestigial
lastchat.txtremoved fromjsr.jsonexclude list.
keyResolveris now private —CasService.keyResolverchanged to#keyResolver, preventing external access to an internal implementation detail.VaultPassphraseRotator.js→rotateVaultPassphrase.js— renamed to follow camelCase convention for files that export a function (PascalCase is reserved for classes).resolveChunkervalidation —chunkSizenow validated as a finite positive number before constructingFixedChunker; invalid values fall through to CasService default.@fileoverviewJSDoc added toFileIOHelper.js,createCryptoAdapter.js, andresolveChunker.js.KeyResolverdesign note — class JSDoc now documents the directCryptoPort.deriveKey()call (bypassesCasService.deriveKey()).- Long function signature wrapped —
rotateVaultPassphrase()export signature broken across multiple lines. - Test hardening — salt assertion in
KeyResolver.resolveForStore,keyLengthround-trip test,resolveChunkeredge-case tests, guardedrmSyncteardown inFileIOHelper.test.js.
- Async
sha256()across all adapters —NodeCryptoAdapter.sha256()now returnsPromise<string>(was syncstring), matching Bun and Web adapters. Fixes Liskov Substitution violation; all callers alreadyawait.CryptoPortJSDoc andCasService.d.tsupdated toPromise<string>. - Extract
KeyResolver— ~170 lines of key resolution logic (wrapDek,unwrapDek,resolveForDecryption,resolveForStore,resolveRecipients,resolveKeyForRecipients, passphrase derivation, mutual-exclusion validation) extracted fromCasServiceintosrc/domain/services/KeyResolver.js. CasService delegates viathis.keyResolver. No public API changes. 24 new unit tests. - Move
createCryptoAdapter— runtime crypto detection moved fromindex.jstosrc/infrastructure/adapters/createCryptoAdapter.js; test helper now delegates instead of duplicating. - Factor out
resolveChunker— chunker factory resolution moved fromindex.jsprivate method tosrc/infrastructure/chunkers/resolveChunker.js. - Move file I/O helpers —
storeFile()andrestoreFile()moved fromindex.jstosrc/infrastructure/adapters/FileIOHelper.js; allnode:*imports removed from facade. - Factor out
rotateVaultPassphrase— passphrase rotation orchestration (~100 lines with retry/backoff) moved fromindex.jstosrc/domain/services/rotateVaultPassphrase.js;CasErrorandbuildKdfMetadataimports removed from facade. - Private
#configfield — facade constructor stores options in a single private#configfield instead of 10 publicthis.fooConfigproperties. - Barrel re-exports — 10 re-export-only modules (
NodeCryptoAdapter,Manifest,Chunk, ports, observers, chunkers) converted toexport { default as X } from '...'form, eliminating unnecessary local bindings. - Configurable retry —
rotateVaultPassphrase()now accepts optionalmaxRetries(default 3) andretryBaseMs(default 50) options for tuning optimistic-concurrency backoff. - Deterministic fuzz test — envelope fuzz round-trip test now uses a seeded xorshift32 PRNG instead of
Math.random(), making failures reproducible across runs. - DRY chunk verification — extracted
_readAndVerifyChunk()inCasService; both the buffered and streaming restore paths now delegate to the same single-chunk verification method. - DRY KDF metadata — extracted
buildKdfMetadata()helper (src/domain/helpers/buildKdfMetadata.js);VaultServiceandContentAddressableStoreboth call it instead of duplicating the KDF object construction.
tsconfig.checkjs.json— strictcheckJsconfiguration;tsc --noEmitpasses with zero errors.src/types/ambient.d.ts— ambient type declarations for@git-stunts/plumbingandbunmodules.@types/nodedev dependency for typecheck support.- JSDoc
@typedeftypes:EncryptionMeta,KdfParamSet,DeriveKeyParams(CryptoPort);VaultMetadata,VaultState,VaultEncryptionMeta(VaultService).
- Every exported and internal function, class method, and callback across all 32 source files now has complete JSDoc
@param/@returnsannotations. - CryptoPort return types widened to
string | Promise<string>(sha256),Buffer | Uint8Array(randomBytes), sync-or-async for encrypt/decrypt — accurately reflecting adapter implementations. - Port
@paramnames corrected to match underscore-prefixed abstract parameters (fixes TS8024). - Observer adapter methods (
SilentObserver,EventEmitterObserver,StatsCollector) fully typed. - CLI files (
bin/) comprehensively annotated with JSDoc types for all Commander callbacks and TUI render functions.
- CLI reference in
docs/API.mdforgit cas rotateandgit cas vault rotateflags.
- Rotation helpers in
CasServiceuse native#privatemethods, matching the facade's style. VAULT_CONFLICTandVAULT_METADATA_INVALIDerror code docs now listrotateVaultPassphrase().
rotateVaultPassphrasenow honourskdfOptions.algorithminstead of silently using the old algorithm.- Rotation integration test no longer flaps under CI load (reduced test-only KDF iterations).
- Key rotation without re-encrypting data —
CasService.rotateKey()re-wraps the DEK with a new KEK, leaving data blobs untouched. Enables key compromise response without re-storing assets. keyVersiontracking — manifest-level and per-recipientkeyVersioncounters track rotation history for audit compliance. Optional field, backward-compatible with existing manifests.git cas rotateCLI command — rotate a recipient's key via--slug(vault round-trip) or--oid(manifest-only). Supports--labelfor targeted single-recipient rotation.rotateVaultPassphrase()— rotate the vault-level encryption passphrase across all envelope-encrypted entries in a single atomic commit. Non-envelope entries are skipped with reporting.git cas vault rotateCLI command — rotate vault passphrase from the command line with--old-passphraseand--new-passphrase.ROTATION_NOT_SUPPORTEDerror code — thrown whenrotateKey()is called on a manifest without envelope encryption (legacy/direct-key).- 27 new unit tests covering key rotation, schema validation, and vault passphrase rotation.
- Envelope encryption (DEK/KEK) — multi-recipient model where a random DEK encrypts content and per-recipient KEKs wrap the DEK. Recipients can be added/removed without re-encrypting data.
RecipientSchema— Zod schema for validating recipient entries in manifests.recipientsfield onEncryptionSchema— optional array of{ label, wrappedDek, nonce, tag }entries.CasService.addRecipient()/removeRecipient()/listRecipients()— manage envelope recipients on existing manifests.--recipient <label:keyfile>CLI flag — repeatable flag ongit cas storefor envelope encryption.git cas recipient add/remove/listsubcommands — CLI management of envelope recipients.RecipientEntrytype re-exported fromindex.d.ts.- 48 new unit tests covering envelope store/restore, recipient management, edge cases, and fuzz round-trips.
_wrapDek/_unwrapDekmissingawait— these called asyncencryptBuffer()/decryptBuffer()withoutawait, silently producing garbage on Bun/Deno runtimes where crypto is async.--recipient+--vault-passphrasenot guarded — CLI now rejects combining--recipientwith--key-fileor--vault-passphrase.- Dead
_resolveEncryptionKeymethod removed — superseded by_resolveDecryptionKeybut left behind. - Redundant
RECIPIENT_NOT_FOUNDguards inremoveRecipientcollapsed into one. addRecipientduplicated unwrap loop replaced with_resolveKeyForRecipientsreuse.removeRecipientpost-filter guard — defense-in-depth check prevents zero recipients when duplicate labels exist in corrupted/crafted manifests.EncryptionSchemaempty recipients —recipientsarray now enforcesmin(1)to reject undecryptable envelope manifests.parseRecipientempty keyfile — CLI now rejects--recipient alice:(missing keyfile path) with a clear error.- CLI 30s hang in Docker —
process.exit()with I/O flushing preventssetTimeoutleak in containerized runtimes. - Deno Dockerfile — multi-stage Node 22 copy replaces
apt install nodejs, improving layer caching and image size. - Runtime-neutral Docker hint in integration tests;
afterAllguardsrmSyncagainst partialbeforeAllfailures.
CasServiceconstructor acceptschunkerport — a new optionalChunkingPortparameter controls chunking strategy. Existing code that does not passchunkeris unaffected (defaults toFixedChunker).- Major version bump — new hexagonal port (
ChunkingPort) and manifest schema extension warrant a semver-major release for downstream tooling awareness.
- Content-defined chunking (CDC) — Buzhash rolling-hash engine with configurable
minChunkSize(64 KiB),maxChunkSize(1 MiB), andtargetChunkSize(256 KiB). CDC limits the dedup blast radius to 1–2 chunks on incremental edits vs. total invalidation with fixed-size chunking. Benchmarked at 265 MB/s and 98.4% chunk reuse on small edits. ChunkingPort— new hexagonal port (src/ports/ChunkingPort.js) withasync *chunk(source),strategy, andparams. Abstracts chunking behind a pluggable interface.FixedChunker— adapter wrapping existing fixed-size buffer slicing behindChunkingPort.CdcChunker— adapter wrapping the buzhash CDC engine behindChunkingPort.chunkingmanifest field — optional{ strategy: 'fixed' | 'cdc', params: {...} }metadata in manifests. Fixed-strategy manifests omit the field for full backward compatibility.ChunkingSchema— Zod discriminated union (FixedChunkingSchema+CdcChunkingSchema) for manifest validation.INVALID_CHUNKING_STRATEGYerror code — thrown when an unrecognized chunking strategy is encountered in a manifest.- Facade
chunkingconfig —ContentAddressableStoreconstructor acceptschunking: { strategy, ... }declarative config or a rawchunkerport instance. - CDC benchmarks (
test/benchmark/chunking.bench.js) — throughput and dedup efficiency comparison. - 90 new unit tests (709 total).
CasService._chunkAndStore()refactored to delegate toChunkingPortinstead of inline buffer slicing.ChunkingPort,FixedChunker,CdcChunkerexported from the main entry point.
git cas verifycommand — verify stored asset integrity from the CLI (checks blob hashes; no key needed).--jsonglobal flag — structured JSON output for all commands (store,restore,verify,inspect,vault list/init/remove/info/history).runActionerror handler (bin/actions.js) — centralizedtry/catchwith CasError code display and actionable hints for 5 common errors.- Vault list
--filter <pattern>— glob-based slug filtering with TTY-aware table formatting. CryptoPortbase class — shared_validateKey(),_buildMeta(), andderiveKey()(template method pattern with_doDeriveKey()). Eliminates duplication across Node/Bun/Web adapters.- ADR-001 (
docs/ADR-001-vault-in-facade.md) — architectural decision record for vault service composition. - STATUS.md — project status dashboard with shipped versions, roadmap, dependency graph, and known concerns.
- COMPLETED_TASKS.md / GRAVEYARD.md — archived M1–M7 task cards and superseded tasks.
WebCryptoAdapter.finalize()guard — throwsSTREAM_NOT_CONSUMEDif called before encrypt stream is fully consumed.
verifycommand usesprocess.exitCode = 1instead ofprocess.exit(1)to allow stdout to drain on pipes.runActionusesprocess.exitCode = 1for consistent exit behavior across all commands.vault info --json --encryptionnow includes encryption metadata in JSON output.store --forcewithout--treenow throws immediately instead of silently ignoring the flag.inspect --jsonnow emits JSON even in TTY mode (previously fell through to rich view).vault history --jsonnow emits structured JSON array of{ commitOid, message }objects.NodeCryptoAdapter._validateKeyremoved — inherits base class which accepts bothBufferandUint8Array.CasService.encrypt()removed redundant_validateKeycall.matchGlobrejects patterns > 200 chars (ReDoS guard);?no longer matches/path separator.writeErrorguards against non-Error throws._doDeriveKeyinNodeCryptoAdapternow properlyawaits promisified calls.
CryptoPortis now the single source of truth for key validation, metadata building, and KDF parameter normalization. All three adapters override only_doDeriveKey().- ROADMAP.md pruned: completed M1–M7 task cards moved to COMPLETED_TASKS.md.
CasServiceno longer extendsEventEmitter— event subscriptions must use the newObservabilityPortadapters instead ofservice.on(). TheEventEmitterObserveradapter provides full backward compatibility for existing event-based code.observabilityis a required constructor port forCasService. The facade (ContentAddressableStore) defaults toSilentObserverwhen omitted.
- ObservabilityPort — new hexagonal port (
src/ports/ObservabilityPort.js) withmetric(channel, data),log(level, msg, meta?), andspan(name)methods. Decouples the domain layer from Node's event infrastructure. - SilentObserver — no-op adapter (default). Zero overhead when observability is not needed.
- EventEmitterObserver — bridges
metric()calls to EventEmitter events (chunk:stored,file:restored, etc.) for backward-compatible progress tracking. Exposes.on(),.removeListener(),.listenerCount(). - StatsCollector — accumulates metrics and exposes
summary()withchunksProcessed,bytesTotal,elapsed,throughput, anderrors. restoreStream()— new async generator onCasServiceand facade. ReturnsAsyncIterable<Buffer>for streaming restore with O(chunkSize) memory for unencrypted, uncompressed files. Encrypted/compressed files buffer internally but expose the same streaming API.restoreFile()now uses streaming I/O — writes viacreateWriteStream+pipelineinstead of buffering the entire file withwriteFileSync.- Parallel chunk I/O — new
concurrencyoption (default: 1). Store operations launch chunk writes through a counting semaphore. Streaming restore uses read-ahead for concurrent blob fetches.concurrency: 1produces identical sequential behavior. - Semaphore — internal counting semaphore (
src/domain/services/Semaphore.js) for concurrency control. - 43 new unit tests (567 total).
- CLI
storeandrestorecommands now create anEventEmitterObserverand pass it to the CAS instance, attaching progress tracking to the observer instead of the service. restore()reimplemented as a collector overrestoreStream()._chunkAndStore()refactored to use semaphore-gated parallel writes withPromise.all, sorting results by index after completion.- Progress tracking example (
examples/progress-tracking.js) updated to useEventEmitterObserverpattern.
- Interactive vault dashboard (
git cas vault dashboard) — TEA-based TUI with split-pane layout, manifest detail view, keyboard navigation (j/k/Enter//), and real-time filtering. - Manifest inspector (
git cas inspect <tree-oid>) — renders manifest details with chunk table, encryption info, and compression badges. - Progress bars for
storeandrestoreoperations — animated progress with throughput reporting, auto-disabled in non-TTY environments. - History timeline (
git cas vault history --pretty) — color-coded, paginated timeline view of vault commit history. - Encryption info card (
git cas vault info --encryption) — detailed KDF parameters and encryption configuration display. - Chunk heatmap — chunk-size distribution grid with colored legend, displayed in manifest detail views.
--quiet/-qflag to suppress all progress output.GIT_CAS_PASSPHRASEenvironment variable — alternative to--vault-passphraseflag for passphrase-based encryption.- New runtime dependencies:
@flyingrobots/bijou,@flyingrobots/bijou-node,@flyingrobots/bijou-tui.
- CLI
restorenow uses the canonicalreadManifestpath instead of duplicating manifest resolution logic. - Progress trackers wrapped in
try/finallyto prevent event listener leaks whenstoreFileorrestoreFilethrows. - Dashboard filter and error lines clamped to pane width to prevent wrapping artifacts in narrow terminals.
- Dashboard differentiates entry vs manifest load errors — a single manifest preload failure no longer sets global error state.
- Dashboard clamps cursor position after applying filter on entry load.
- Passphrase resolution uses nullish coalescing for correct falsy-value handling.
- Locale-agnostic number formatting in encryption card tests.
- Consolidated duplicated restore flag validation into
validateRestoreFlags(). - Eliminated
vi.mock('node:fs')pattern in progress tests for Bun Docker compatibility.
- Vault — GC-safe ref-based storage via
refs/cas/vault. A single Git ref pointing to a commit chain indexes all stored assets by slug.git gccan no longer silently discard stored data.initVault()— initialize the vault, optionally with passphrase-based encryption (vault-level KDF policy).addToVault()— add or update an entry by slug + tree OID, withforceflag for overwrites.listVault()— list all entries sorted by slug.removeFromVault()— remove an entry by slug.resolveVaultEntry()— resolve a slug to its tree OID.getVaultMetadata()— inspect vault metadata (encryption config, version).- Vault metadata (
.vault.json) supports versioning and optional encryption configuration. - CAS-safe writes with automatic retry (up to 3 attempts with exponential backoff) on concurrent update conflicts.
- Strict slug validation: rejects empty strings,
..traversal, control characters, oversized segments.
- New CLI subcommands:
vault init,vault list,vault info <slug>,vault remove <slug>,vault history. - CLI
store --treenow auto-vaults the entry (adds to vault after creating tree). - CLI
restorenow supports--slug(resolve via vault) and--oid(direct tree OID) flags. - CLI
--vault-passphraseflag for vault-level encryption onstore,restore, andvault init. - New error codes:
INVALID_SLUG,VAULT_ENTRY_NOT_FOUND,VAULT_ENTRY_EXISTS,VAULT_CONFLICT,VAULT_METADATA_INVALID,VAULT_ENCRYPTION_ALREADY_CONFIGURED. - TypeScript declarations for
VaultEntry,VaultMetadata,VaultState,VaultService,GitRefPorttypes. VaultService— first-class domain service with proper port/adapter separation (hexagonal architecture).GitRefPortandGitRefAdapter— new port/adapter for Git ref and commit operations.getVaultService()on facade exposes the underlyingVaultServicefor advanced usage.- Vault-specific integration tests (
test/integration/vault.test.js). - 46 vault unit tests + facade delegation smoke test.
#validateMetadatanow requireskdf.keyLengthin encryption metadata, preventing downstream KDF failures from manually edited.vault.jsonfiles.#casUpdateRefnow preserves the original error inVAULT_CONFLICTmeta for better diagnostics.- CLI
--vault-passphrasenow emits a stderr warning when the vault is not encrypted, instead of silently ignoring the passphrase. vault historycommand now usesVAULT_REFconstant instead of hardcoded string.- API docs: fixed invalid import path
@git-stunts/cas/vault→@git-stunts/cas. - API docs: fixed
_readVaultState()→readState()in error codes table. - API docs and GUIDE: added
textlanguage identifier to fenced code blocks (markdownlint MD040). - CLI version string updated from
2.0.0to3.0.0. - CLI
vault history --max-countnow validates input as a positive integer. - Stale JSDoc in
GitPersistenceAdaptercorrected (removed mention of retries). - CLI uses
program.parseAsync()instead ofprogram.parse()to prevent Bun from hanging on async action handlers.
- Vault promoted to domain layer — all vault logic extracted from facade (
index.js) intoVaultService(src/domain/services/VaultService.js) withGitRefPort/GitRefAdapterfor ref operations. Facade now delegates to VaultService. - CLI
restorecommand no longer takes a positional<tree-oid>argument. Use--oid <tree-oid>or--slug <slug>instead. - Purged completed milestones (M1–M7) and their task cards from ROADMAP.md, reducing it from 3,153 to 1,675 lines.
- Compression support (Task 7.1): Optional gzip compression pipeline via
compression: { algorithm: 'gzip' }option onstore(). Compression is applied before encryption when both are enabled. Manifests include a new optionalcompressionfield. Decompression onrestore()is automatic. - KDF support (Task 7.2): Passphrase-based encryption using PBKDF2 or scrypt via
deriveKey()method andpassphraseoption onstore()/restore(). KDF parameters are stored inmanifest.encryption.kdffor deterministic re-derivation. All three crypto adapters (Node, Bun, Web) implementderiveKey(). - Merkle tree manifests (Task 7.3): Large manifests (chunk count exceeding
merkleThreshold, default 1000) are automatically split into sub-manifests stored as separate blobs. Root manifest usesversion: 2withsubManifestsreferences.readManifest()transparently reconstitutes v2 manifests into flat chunk lists. Full backward compatibility with v1 manifests. - New schema fields:
version,compression,subManifestsonManifestSchema;kdfonEncryptionSchema. - New error code:
INVALID_OPTIONSfor mutually exclusive options or unsupported option values. - 62 new unit tests across three new test suites (compression, KDF, Merkle) plus expanded error tests.
- Updated API reference (
docs/API.md), guide (GUIDE.md), and README with v2.0.0 feature documentation.
- BREAKING: Manifest schema now includes
versionfield (defaults to 1). Existing v1 manifests are fully backward-compatible. CasServiceconstructor accepts newmerkleThresholdoption (must be a positive integer).ContentAddressableStoreconstructor now accepts and forwardsmerkleThresholdtoCasService.store()andstoreFile()acceptpassphrase,kdfOptions, andcompressionoptions.restore()acceptspassphraseoption.- Static imports for
createGzipandReadableinCasService(previously dynamic imports on every call).
- Sub-manifest blobs are now included as tree entries (
sub-manifest-N.json), preventing them from being garbage-collected bygit gc. storeFile()now forwardspassphrase,kdfOptions, andcompressionoptions tostore()(previously silently dropped).store()andrestore()reject when bothpassphraseandencryptionKeyare provided (INVALID_OPTIONS).store()rejects unsupported compression algorithms (INVALID_OPTIONS).restore()throws a descriptive error when passphrase is provided but manifest lacks KDF metadata.- Decompression errors are now wrapped as
CasErrorwith codeINTEGRITY_ERROR(previously raw zlib errors). NodeCryptoAdapter.deriveKey()usesBuffer.from(salt)for base64 encoding, preventing corrupt output when salt is aUint8Array.WebCryptoAdapter.deriveKey()now validates KDF algorithm and throws for unsupported values instead of silently falling through to scrypt.WebCryptoAdapterscrypt derivation now throws a descriptive error whennode:cryptois unavailable (e.g. in browsers).- Orphaned JSDoc blocks for
restore(),verifyIntegrity(), andstore()reattached to their correct methods. - Stale cross-reference in GUIDE.md ("Section 10" → "Section 13").
- API.md method signatures updated to include all v2 parameters.
- JSDoc comments on all exported TypeScript interfaces (
CryptoPort,CodecPort,GitPersistencePort,CasServiceOptions,EncryptionMeta,ManifestData,ContentAddressableStoreOptions) to reach 100% JSR symbol documentation coverage.
- npm publish workflow now uses OIDC trusted publishing (no stored token). Upgrades npm to >=11.5.1 at publish time since pnpm does not yet support OIDC natively.
- TypeScript declaration files (
.d.ts) for all three entrypoints and shared value objects, resolving JSR "slow types" scoring penalty. @ts-self-typesdirectives inindex.js,CasService.js, andManifestSchema.js.@fileoverviewmodule doc toCasService.js(required by JSR for module docs scoring).
- JSR package name corrected to
@git-stunts/git-cas. - JSR publication now excludes tests, docs, CI configs, and other non-distribution files via
jsr.jsonexclude list. index.d.tsadded topackage.jsonfiles array for npm distribution.
CasService.readManifest({ treeOid })— reads a Git tree, locates and decodes the manifest, returns a validatedManifestvalue object.CasService.deleteAsset({ treeOid })— returns logical deletion metadata ({ slug, chunksOrphaned }) without performing destructive Git operations.CasService.findOrphanedChunks({ treeOids })— aggregates referenced chunk blob OIDs across multiple assets, returning{ referenced: Set<string>, total: number }.- Facade pass-throughs for
readManifest,deleteAsset, andfindOrphanedChunksonContentAddressableStore. - New error codes:
MANIFEST_NOT_FOUND,GIT_ERROR. - 42 new unit tests across three new test suites.
CasServicenow extendsEventEmitterwith lifecycle events:chunk:stored,chunk:restored,file:stored,file:restored,integrity:pass,integrity:fail, anderror(guarded).- Comprehensive benchmark suite (
test/benchmark/cas.bench.js) covering store, restore, encrypt/decrypt, createTree, verifyIntegrity, and JsonCodec vs CborCodec at multiple data sizes. - 14 new unit tests for EventEmitter integration.
docs/API.md— full API reference for all public methods, events, value objects, ports, and error codes.docs/SECURITY.md— threat model, AES-256-GCM design, key handling, limitations.GUIDE.md— progressive-disclosure guide from zero knowledge to mastery.examples/directory with runnable scripts:store-and-restore.js,encrypted-workflow.js,progress-tracking.js.- ESLint config now ignores
examples/directory (runnable scripts useconsole.log).
- Native Bun support via
BunCryptoAdapter(usesBun.CryptoHasher). - Native Deno/Web standard support via
WebCryptoAdapter(usescrypto.subtle). - Automated, secure release workflow (
.github/workflows/release.yml) with:- NPM OIDC support including build provenance.
- JSR support via
jsr.jsonand automated publishing. - GitHub Releases with automated release notes.
- Idempotency & Version Checks to prevent failed partial releases.
- Dynamic runtime detection in
ContentAddressableStoreto pick the best adapter automatically. - Hardened
package.jsonwith repository metadata, engine constraints, and explicit file inclusion. - Local quality gates via
pre-pushgit hook andscripts/install-hooks.sh.
- Breaking Change:
CasServicecryptographic methods (sha256,encrypt,decrypt,verifyIntegrity) are now asynchronous to support Web Crypto and native optimizations. ContentAddressableStorefacade methods are now asynchronous to accommodate lazy service initialization and async crypto.- Project migrated from
npmtopnpmfor faster, more reliable dependency management. - CI workflow (
.github/workflows/ci.yml) now runs on all branches but prevents duplicate runs on PRs. Dockerfilenow usescorepackfor pnpm management.
- Fixed recursion bug in
BunCryptoAdapterwhererandomBytesshadowed the imported function. - Resolved lazy-initialization race condition in
ContentAddressableStorevia promise caching. - Fixed state leak in
WebCryptoAdapterstreaming encryption. - Consolidated double decrypt calls in integrity tests for better performance.
- Hardened adapter-level key validation with type checks.
CryptoPortinterface andNodeCryptoAdapter— extracted allnode:cryptousage from the domain layer.CasService.store()— acceptsAsyncIterable<Buffer>sources (renamed fromstoreFile).- Multi-stage Dockerfile (Node 22, Bun, Deno) with
docker-compose.ymlfor per-runtime testing. - BATS parallel test runner (
test/platform/runtimes.bats). - Devcontainer setup (
.devcontainer/) with all three runtimes + BATS. - Encryption key validation (
INVALID_KEY_TYPE,INVALID_KEY_LENGTHerror codes). - Encryption round-trip unit tests (110 tests including fuzz).
- Empty file (0-byte) edge case tests.
- Error-path unit tests for constructors and core failures.
- Deterministic test digest helper (
digestOf).
CasServicedomain layer has zeronode:*imports — all platform dependencies injected via ports.- Constructor requires
cryptoandcodecparams (no defaults); facade supplies them. - Facade
storeFile()now opens the file and delegates toCasService.store().
- None.
- None.
ContentAddressableStorefacade withcreateJsonandcreateCborfactory methods.CasServicecore withstoreFile,createTree,encrypt,decrypt, andverifyIntegrityoperations.- Hexagonal architecture via
GitPersistencePortinterface andGitPersistenceAdapterbacked by Git's object database. - Pluggable codec system with
JsonCodecandCborCodecimplementations. ManifestandChunkZod-validated, frozen value objects.CasErrorcustom error class for structured error handling.- Streaming AES-256-GCM encryption and decryption.
- Docker-based test runner for reproducible CI builds.
- None.
- None.
- None.