diff --git a/CHANGELOG.md b/CHANGELOG.md index 4954bba..e8028ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,10 +28,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- **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](./ARCHITECTURE.md) now describes the shipped system instead of an older flat-manifest-only model, including Merkle manifests, the extracted `VaultService` and `KeyResolver`, current ports/adapters, and the real storage layout for trees and the vault. - **Architecture navigation clarified** — [ARCHITECTURE.md](./ARCHITECTURE.md) now distinguishes the public package boundary from internal domain helpers and links directly to [docs/THREAT_MODEL.md](./docs/THREAT_MODEL.md) as adjacent truth. - **Guide moved under docs and repaired** — the long-form guide now lives at [docs/GUIDE.md](./docs/GUIDE.md), links back to the canonical API/security docs, uses current `restore --oid` syntax, and no longer teaches stale EventEmitter-first or internal-import-heavy workflows for common use. +- **Markdown surface map added** — [docs/MARKDOWN_SURFACE.md](./docs/MARKDOWN_SURFACE.md) now records a per-file `KEEP` / `CUT` / `MERGE` / `MOVE` recommendation 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](./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](./README.md), [CONTRIBUTING.md](./CONTRIBUTING.md), [WORKFLOW.md](./WORKFLOW.md), [ARCHITECTURE.md](./ARCHITECTURE.md), [docs/API.md](./docs/API.md), and [docs/DOCS_CHECKLIST.md](./docs/DOCS_CHECKLIST.md) now link more directly to [SECURITY.md](./SECURITY.md) and [docs/THREAT_MODEL.md](./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@v6` and `actions/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 unprivileged `gitstunts` user. diff --git a/README.md b/README.md index d6611a7..eb99703 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +![git-cas](https://raw.githubusercontent.com/git-stunts/git-cas/2b0111bb5001123eb13b01e793bd63c338f86534/docs/git-cas.svg) + # @git-stunts/git-cas git-cas @@ -10,418 +12,257 @@ ### Git, freebased: pure CAS that’ll knock your SHAs off. LFS hates this repo. -Git isn’t source control. -Git is a content-addressed object database. -We use the object database. - -`git-cas` chunks files into Git blobs (dedupe for free), optionally encrypts them, and emits a manifest + a real Git tree so you can commit/tag/ref it like any other artifact. - -## What you get - -- **Dedupe for free** Git already hashes objects. We just lean into it. -- **Chunked storage** big files become stable, reusable blobs. Fixed-size or content-defined chunking (CDC). -- **Optional AES-256-GCM encryption** store secrets without leaking plaintext into the ODB. -- **Multi-recipient encryption** envelope model (DEK/KEK) — add/remove access without re-encrypting data. -- **Key rotation** rotate keys without re-encrypting data blobs. Respond to compromise in seconds. -- **Compression** gzip before encryption — smaller blobs, same round-trip. -- **Passphrase encryption** derive keys from passphrases via PBKDF2 or scrypt — no raw key management. -- **Merkle manifests** large files auto-split into sub-manifests for scalability. -- **Manifests** a tiny explicit index of chunks + metadata (JSON/CBOR). -- **Tree output** generates standard Git trees so assets snap into commits cleanly. -- **Full round-trip** store, tree, and restore — get your bytes back, verified. -- **Lifecycle management** `readManifest`, `inspectAsset`, `collectReferencedChunks` — inspect trees, plan deletions, audit storage. -- **Vault** GC-safe ref-based storage. One ref (`refs/cas/vault`) indexes all assets by slug. No more silent data loss from `git gc`. -- **Vault diagnostics** `git cas vault stats` summarizes size/dedupe/encryption coverage, and `git cas doctor` scans the vault for broken manifests before they surprise you. -- **Interactive dashboard** `git cas inspect` with chunk heatmap, animated progress bars, and rich manifest views. -- **Verify & JSON output** `git cas verify` checks integrity; `--json` on all current human-facing commands provides convenient structured output for CI/scripting, including `pnpm release:verify --json` for release automation. - -**Use it for:** binary assets, build artifacts, model weights, data packs, secret bundles, weird experiments, etc. - -git-cas demo - -## What's new in v5.3.2 - -**Patch release — runtime/test stabilization.** - -- **Explicit Vitest workspace projects** — unit, integration, and benchmark suites now run as named workspace projects, with the integration suite always pinned to `fileParallelism: false`. -- **Deterministic cross-runtime integration behavior** — Bun and Deno no longer depend on Vitest CLI argv shape to avoid subprocess `EPIPE` races. -- **CLI version sync** — `git-cas --version` now reads package metadata instead of a stale literal, so the binary reports the correct in-repo release line. - -See [CHANGELOG.md](./CHANGELOG.md) for the full list of changes. - -## What's new in v5.3.1 - -**Patch release — repeated chunk tree fix.** - -- **Unique chunk tree entries** — `createTree()` and `_createMerkleTree()` now emit one tree entry per unique chunk digest instead of repeating the same filename for repeated chunk occurrences. -- **Manifest remains authoritative** — chunk order and multiplicity still come entirely from the manifest; restore behavior is unchanged. -- **Clean `git fsck` on repetitive files** — repetitive content no longer yields duplicate tree filenames or `duplicateEntries` errors. -- **New regressions** — unit and integration coverage now exercises repeated-chunk files and validates real Git tree integrity with `git fsck --full`. - -See [CHANGELOG.md](./CHANGELOG.md) for the full list of changes. - -## What's new in v5.3.0 - -**M16 Capstone — audit remediation, CLI configuration, and security hardening.** - -- **`.casrc` config file** — JSON config at repo root sets defaults for all CLI flags (chunk size, strategy, concurrency, codec, compression, CDC params). -- **CLI store/restore flags** — all library-level options now accessible from the command line: `--gzip`, `--strategy`, `--chunk-size`, `--concurrency`, `--codec`, `--merkle-threshold`, and CDC-specific flags. -- **Passphrase-file support** — `--vault-passphrase-file ` on store, restore, and vault rotate (use `-` for stdin). -- **Memory restore guard** — `maxRestoreBufferSize` (default 512 MiB) prevents unbounded memory allocation on encrypted/compressed restore. -- **Web Crypto encryption buffer guard** — `maxEncryptionBufferSize` (default 512 MiB) for the one-shot AES-GCM API. -- **Orphaned blob tracking** — `STREAM_ERROR` now includes `meta.orphanedBlobs` for cleanup after partial store failures. -- **KDF brute-force awareness** — `decryption_failed` metric + CLI rate-limiting delay on `INTEGRITY_ERROR`. -- **Encryption counter** — vault metadata tracks `encryptionCount` with observability warning near GCM nonce bound. -- **Lifecycle method rename** — `inspectAsset()` / `collectReferencedChunks()` replace `deleteAsset()` / `findOrphanedChunks()` (old names preserved as deprecated aliases). -- **FixedChunker O(n²) fix** — pre-allocated buffer replaces `Buffer.concat()` loop. -- **Chunk size upper bound** — 100 MiB max enforced across all chunkers. -- **Constructor validation** — `chunkSize`, `maxRestoreBufferSize`, `maxEncryptionBufferSize` validated at construction time. -- **Cross-runtime portability** — `Error.captureStackTrace` guarded, crypto adapter contracts normalized. - -## What's new in v5.2.3 - -**Internal refactoring — no breaking API changes.** The facade, CasService, and crypto adapters were restructured for better separation of concerns: - -- **Consistent async `sha256()`** — `NodeCryptoAdapter.sha256()` now returns `Promise` like Bun and Web adapters, fixing a Liskov Substitution violation. -- **`KeyResolver` extracted** — ~170 lines of key resolution logic (DEK wrap/unwrap, passphrase derivation, envelope recipients) moved from CasService (1085 → 909 lines) into a dedicated `KeyResolver` service. -- **Facade decomposed** — `createCryptoAdapter`, `resolveChunker`, `FileIOHelper`, `rotateVaultPassphrase`, and `buildKdfMetadata` extracted from the monolithic `index.js` into focused modules. -- **Barrel re-exports** — 10 re-export-only modules converted to `export { default as X } from '...'` form. - -See [CHANGELOG.md](./CHANGELOG.md) for the full list of changes. - -## What's new in v5.2.1 +`git-cas` uses Git's object database as a storage layer for large, awkward, or +security-sensitive files. + +It stores content as chunk blobs, records how to rebuild that content in a +manifest, can emit a real Git tree for reachability, and can keep named assets +reachable through a GC-safe vault ref. + +This repo ships three surfaces over the same core: + +- a JavaScript library for Node-first applications +- a human CLI/TUI (`git-cas`, and `git cas` when installed as a Git subcommand) +- a machine-facing agent CLI for structured automation flows + +Primary runtime support is Node.js 22+. The project also maintains a Bun and +Deno test matrix. + +## What It Is Good At + +- storing binary assets, artifacts, bundles, and other files directly in Git +- chunk-level deduplication using fixed-size or content-defined chunking (CDC) +- optional gzip compression before storage +- optional AES-256-GCM encryption +- passphrase-derived keys via PBKDF2 or scrypt +- multi-recipient envelope encryption and recipient mutation +- key rotation without re-encrypting underlying data blobs +- manifest serialization in JSON or CBOR +- large-asset support through Merkle-style sub-manifests +- a GC-safe vault index under `refs/cas/vault` +- integrity verification, vault diagnostics, and an interactive inspector + +## What It Is Not + +`git-cas` is not: + +- a hosted blob service +- a secret-management platform +- an access-control system +- metadata-oblivious storage +- secure deletion + +Even when encryption is enabled, repository readers can still see metadata such +as slugs, filenames, chunk counts, object relationships, recipient labels, and +vault metadata. See +[SECURITY.md](https://github.com/git-stunts/git-cas/blob/main/SECURITY.md) +and +[docs/THREAT_MODEL.md](https://github.com/git-stunts/git-cas/blob/main/docs/THREAT_MODEL.md) +for the exact boundary. + +## Honest Operational Notes + +- Plaintext, uncompressed restore can stream chunk-by-chunk. +- Encrypted or compressed restore currently uses a buffered path guarded by + `maxRestoreBufferSize` (default `512 MiB`). +- Encryption removes most of the dedupe advantage of CDC because ciphertext is + pseudorandom. +- Git will happily retain a large number of blobs for you, but that does not + mean storage management disappears. You still need to think about repository + size, reachability, and maintenance. +- The manifest is the authoritative description of asset order and repeated + chunks. The emitted tree is a reachability artifact, not the reconstruction + source of truth. -Bug fix: `rotateVaultPassphrase` now honours `kdfOptions.algorithm` — previously the `--algorithm` flag was silently ignored, always reusing the old KDF algorithm. CLI flag tables in `docs/API.md` are now split per command with `--cwd` documented. - -See [CHANGELOG.md](./CHANGELOG.md) for the full list of changes. - -## What's new in v5.2.0 - -**Key rotation without re-encrypting data** — Rotate a recipient's key by re-wrapping the DEK. Data blobs are never touched. Respond to key compromise in seconds, not hours. - -```js -// Rotate a single recipient's key -const rotated = await cas.rotateKey({ - manifest, - oldKey: aliceOldKey, - newKey: aliceNewKey, - label: 'alice', -}); +## Install -// Rotate the vault passphrase (all entries, atomic commit) -const { commitOid, rotatedSlugs, skippedSlugs } = await cas.rotateVaultPassphrase({ - oldPassphrase: 'old-secret', - newPassphrase: 'new-secret', -}); -``` +For the library: ```bash -# Rotate a recipient key -git cas rotate --slug prod-secrets --old-key-file old.key --new-key-file new.key - -# Rotate vault passphrase -git cas vault rotate --old-passphrase old-secret --new-passphrase new-secret +npm install @git-stunts/git-cas @git-stunts/plumbing ``` -See [CHANGELOG.md](./CHANGELOG.md) for the full list of changes. - -## What's new in v5.1.0 - -**Multi-recipient envelope encryption** — Each file is encrypted with a random DEK; recipient KEKs wrap the DEK. Add or remove team members without re-encrypting data. - -```js -// API: store for multiple recipients -const manifest = await cas.storeFile({ - filePath: './secrets.tar.gz', - slug: 'prod-secrets', - recipients: [ - { label: 'alice', key: aliceKey }, - { label: 'bob', key: bobKey }, - ], -}); - -// Add a recipient later (no re-encryption) -const updated = await cas.addRecipient({ - manifest, - existingKey: aliceKey, - newRecipientKey: carolKey, - label: 'carol', -}); - -// List / remove recipients -const labels = await cas.listRecipients({ manifest }); -const trimmed = await cas.removeRecipient({ manifest, label: 'bob' }); -``` +For the CLI: ```bash -# CLI: store with multiple recipients -git cas store ./secrets.tar.gz --slug prod-secrets \ - --recipient alice:./keys/alice.key \ - --recipient bob:./keys/bob.key --tree - -# Manage recipients -git cas recipient list prod-secrets -git cas recipient add prod-secrets --label carol --key-file ./keys/carol.key --existing-key-file ./keys/alice.key -git cas recipient remove prod-secrets --label bob +npm install -g @git-stunts/git-cas ``` -See [CHANGELOG.md](./CHANGELOG.md) for the full list of changes. - -## What's new in v5.0.0 +## CLI Quick Start -**Content-defined chunking (CDC)** — Fixed-size chunking invalidates every chunk after an edit. CDC uses a buzhash rolling hash to find natural boundaries, limiting the blast radius to 1–2 chunks. Benchmarked at 98.4% chunk reuse on small edits vs 32% for fixed. +This is the shortest practical path from an empty repo to a stored and restored +asset. -```js -const cas = new ContentAddressableStore({ - plumbing, - chunking: { - strategy: 'cdc', - targetChunkSize: 262144, - minChunkSize: 65536, - maxChunkSize: 1048576, - }, -}); -``` - -**`ChunkingPort`** — new hexagonal port abstracts chunking strategy. `FixedChunker` and `CdcChunker` adapters ship out of the box. Bring your own chunker by extending `ChunkingPort`. - -See [CHANGELOG.md](./CHANGELOG.md) for the full list of changes. - -## What's new in v4.0.1 - -**`git cas verify`** — verify stored asset integrity from the CLI without restoring (`git cas verify --slug my-asset`). - -**`--json` everywhere** — all commands now support `--json` for structured output. Pipe `git cas vault list --json | jq` in CI. - -**Vault diagnostics** — `git cas vault stats` surfaces logical size, dedupe, chunking, and encryption coverage; `git cas doctor` scans the current vault and exits non-zero when it finds trouble. - -**CryptoPort base class** — shared key validation, metadata building, and KDF normalization. All three adapters (Node/Bun/Web) inherit from a single source of truth. - -**Centralized error handling** — `runAction` wrapper with CasError codes and actionable hints (e.g., "Provide --key-file or --vault-passphrase"). - -**Vault list filtering** — `git cas vault list --filter "photos/*"` with TTY-aware table formatting. - -See [CHANGELOG.md](./CHANGELOG.md) for the full list of changes. - -## What's new in v4.0.0 - -**ObservabilityPort** — `CasService` no longer extends `EventEmitter`. A new hexagonal `ObservabilityPort` decouples the domain from Node's event infrastructure. Three adapters ship out of the box: `SilentObserver` (no-op default), `EventEmitterObserver` (backward-compatible event bridge), and `StatsCollector` (metric accumulator). - -**Streaming restore** — `restoreStream()` returns an `AsyncIterable` with O(chunkSize) memory for unencrypted files. `restoreFile()` now writes via `createWriteStream` + `pipeline` instead of buffering. - -**Parallel chunk I/O** — new `concurrency` option gates store writes and restore reads through a counting semaphore. `concurrency: 4` can significantly speed up large-file operations. - -See [CHANGELOG.md](./CHANGELOG.md) for the full list of changes. - -## What's new in v3.1.0 +```bash +mkdir demo-cas +cd demo-cas +git init -**Interactive vault dashboard** — `git cas inspect --slug my-asset` renders a rich TUI with chunk heatmap, encryption card, and history timeline. Animated progress bars for long store/restore operations. +git-cas vault init -See [CHANGELOG.md](./CHANGELOG.md) for the full list of changes. +printf 'hello from git-cas\n' > hello.txt -## What's new in v3.0.0 +git-cas store hello.txt --slug demo/hello --tree +git-cas inspect --slug demo/hello +git-cas verify --slug demo/hello +git-cas restore --slug demo/hello --out hello.restored.txt +``` -**Vault** — GC-safe ref-based storage under `refs/cas/vault`. Assets are indexed by slug and survive `git gc`. Full CLI: `git cas vault init`, `list`, `info`, `remove`, `history`. Store with `--tree` to vault automatically. +If `git-cas` is installed on your `PATH`, Git can also invoke it as `git cas`. -See [CHANGELOG.md](./CHANGELOG.md) for the full list of changes. +Useful first commands: -## What's new in v2.0.0 +- `git-cas store --slug --tree` +- `git-cas restore --slug --out ` +- `git-cas inspect --slug ` +- `git-cas verify --slug ` +- `git-cas vault list` +- `git-cas vault stats` +- `git-cas doctor` -**Compression** — `compression: { algorithm: 'gzip' }` on `store()`. Compression runs before encryption. Decompression on `restore()` is automatic. +## Library Quick Start -**Passphrase-based encryption** — Pass `passphrase` instead of `encryptionKey`. Keys are derived via PBKDF2 (default) or scrypt. KDF parameters are stored in the manifest for deterministic re-derivation. Use `deriveKey()` directly for manual control. +```js +import GitPlumbing from '@git-stunts/plumbing'; +import ContentAddressableStore from '@git-stunts/git-cas'; -**Merkle tree manifests** — When chunk count exceeds `merkleThreshold` (default: 1000), manifests are automatically split into sub-manifests stored as separate blobs. `readManifest()` transparently reconstitutes them. Full backward compatibility with v1 manifests. +const plumbing = new GitPlumbing({ cwd: './demo-cas' }); +const cas = ContentAddressableStore.createJson({ plumbing }); -See [CHANGELOG.md](./CHANGELOG.md) for the full list of changes. +const manifest = await cas.storeFile({ + filePath: './hello.txt', + slug: 'demo/hello', +}); -## Install +const treeOid = await cas.createTree({ manifest }); +const reread = await cas.readManifest({ treeOid }); -```bash -npm install @git-stunts/git-cas -``` +await cas.restoreFile({ + manifest: reread, + outputPath: './hello.restored.txt', +}); -```bash -npx jsr add @git-stunts/git-cas +const ok = await cas.verifyIntegrity(reread); +console.log({ treeOid, ok }); ``` -## Usage (Node API) +Common library entry points: -```js -import GitPlumbing from '@git-stunts/plumbing'; -import ContentAddressableStore from '@git-stunts/cas'; +- `storeFile()` +- `createTree()` +- `readManifest()` +- `restoreFile()` +- `verifyIntegrity()` +- `inspectAsset()` +- `collectReferencedChunks()` +- `initVault()`, `addToVault()`, `listVault()`, `resolveVaultEntry()` +- `addRecipient()`, `removeRecipient()`, `listRecipients()`, `rotateKey()` +- `rotateVaultPassphrase()` -const git = new GitPlumbing({ cwd: './assets-repo' }); -const cas = new ContentAddressableStore({ plumbing: git }); +## Feature Overview -// Store a file -> returns a manifest (chunk list + metadata) -const manifest = await cas.storeFile({ - filePath: './image.png', - slug: 'my-image', - encryptionKey: myKeyBuffer, // optional (32 bytes) -}); - -// Turn the manifest into a Git tree OID -const treeOid = await cas.createTree({ manifest }); +### Chunking -// Restore later — get your bytes back, integrity-verified -await cas.restoreFile({ manifest, outputPath: './restored.png' }); +`git-cas` supports both fixed-size chunking and content-defined chunking. +Fixed-size chunking is simpler and predictable. CDC is more resilient to +insertions and shifting edits. See +[docs/BENCHMARKS.md](https://github.com/git-stunts/git-cas/blob/main/docs/BENCHMARKS.md) +for current published baselines. -// Read the manifest back from a tree OID -const m = await cas.readManifest({ treeOid }); +### Trees And Reachability -// Lifecycle: inspect deletion impact, collect referenced chunks -const { slug, chunksOrphaned } = await cas.inspectAsset({ treeOid }); -const { referenced, total } = await cas.collectReferencedChunks({ treeOids: [treeOid] }); +Stored chunks live as ordinary Git blobs. `createTree()` writes a manifest blob +plus the referenced chunk blobs into a Git tree so the asset becomes reachable +like any other Git object. -// v2.0.0: Compressed + passphrase-encrypted store -const manifest2 = await cas.storeFile({ - filePath: './image.png', - slug: 'my-image', - passphrase: 'my secret passphrase', - compression: { algorithm: 'gzip' }, -}); -``` +### Vault -## CLI (git plugin) +The vault is a commit-backed slug index rooted at `refs/cas/vault`. It exists +to keep named assets reachable across normal Git garbage collection and to make +slug-based workflows practical. -`git-cas` installs as a Git subcommand: +### Encryption -```bash -# Store a file — prints manifest JSON -git cas store ./image.png --slug my-image - -# Store and vault the tree OID (GC-safe) -git cas store ./image.png --slug my-image --tree - -# Restore from a vault slug -git cas restore --slug my-image --out ./restored.png - -# Restore from a direct tree OID -git cas restore --oid --out ./restored.png - -# Verify integrity without restoring -git cas verify --slug my-image - -# Inspect manifest (interactive dashboard) -git cas inspect --slug my-image - -# Vault management -git cas vault init -git cas vault list # TTY table -git cas vault list --json # structured JSON -git cas vault list --filter "photos/*" # glob filter -git cas vault stats # size / dedupe / coverage summary -git cas vault info my-image -git cas vault remove my-image -git cas vault history -git cas doctor # vault health scan -pnpm release:verify --json # machine-readable release report - -# Multi-recipient encryption -git cas store ./secret.bin --slug shared \ - --recipient alice:./keys/alice.key \ - --recipient bob:./keys/bob.key --tree -git cas recipient list shared -git cas recipient add shared --label carol --key-file ./keys/carol.key --existing-key-file ./keys/alice.key -git cas recipient remove shared --label bob - -# Key rotation (no re-encryption) -git cas rotate --slug shared --old-key-file old.key --new-key-file new.key -git cas rotate --slug shared --old-key-file old.key --new-key-file new.key --label alice - -# Vault passphrase rotation -git cas vault rotate --old-passphrase old-secret --new-passphrase new-secret - -# Encrypted vault round-trip (passphrase via env var or --vault-passphrase flag) -export GIT_CAS_PASSPHRASE="secret" -git cas vault init -git cas store ./secret.bin --slug vault-entry --tree -git cas restore --slug vault-entry --out ./decrypted.bin - -# Compression, chunking, codec, concurrency -git cas store ./data.bin --slug my-data --tree --gzip -git cas store ./data.bin --slug my-data --tree --strategy cdc -git cas store ./data.bin --slug my-data --tree --chunk-size 65536 --concurrency 4 -git cas store ./data.bin --slug my-data --tree --codec cbor - -# Restore with concurrency -git cas restore --slug my-data --out ./data.bin --concurrency 4 - -# JSON output on any command (for CI/scripting) -git cas store ./data.bin --slug my-data --tree --json -``` +The project supports: -### `.casrc` — Project Config File - -Place a `.casrc` JSON file at the repository root to set defaults for CLI flags. -CLI flags always take precedence over `.casrc` values. - -```json -{ - "chunkSize": 65536, - "strategy": "cdc", - "concurrency": 4, - "codec": "json", - "compression": "gzip", - "merkleThreshold": 500, - "maxRestoreBufferSize": 1073741824, - "cdc": { - "minChunkSize": 8192, - "targetChunkSize": 32768, - "maxChunkSize": 131072 - } -} -``` +- raw 32-byte encryption keys +- passphrase-derived keys +- recipient-based envelope encryption +- recipient mutation and key rotation +- vault passphrase rotation for envelope-encrypted vault entries -## Documentation +The cryptography is useful, but it is not invisible. Metadata remains visible. +Read +[SECURITY.md](https://github.com/git-stunts/git-cas/blob/main/SECURITY.md) +and +[docs/THREAT_MODEL.md](https://github.com/git-stunts/git-cas/blob/main/docs/THREAT_MODEL.md) +before treating this as a secrets solution. -- [Guide](./docs/GUIDE.md) — progressive walkthrough -- [API Reference](./docs/API.md) — full method documentation -- [Architecture](./ARCHITECTURE.md) — hexagonal design overview -- [Security](./SECURITY.md) — cryptographic design, limits, and operational guidance -- [Threat Model](./docs/THREAT_MODEL.md) — trust boundaries, exposed metadata, and explicit non-goals +### Observability -## When to use git-cas (and when not to) +The core domain is wired through an observability port rather than Node's event +system directly. The repo ships: -### "I just want screenshots in my README" +- `SilentObserver` +- `EventEmitterObserver` +- `StatsCollector` -Use an **orphan branch**. Seriously. It's 5 git commands, zero dependencies, and GitHub renders the images inline. Google "git orphan branch assets" — that's all you need. git-cas is overkill for public images and demo GIFs. +## Documentation Map -### "I need encrypted secrets / large binaries / deduplicated assets in a Git repo" +If you want depth instead of a front page: -That's git-cas. The orphan branch gives you none of: +- [docs/GUIDE.md](https://github.com/git-stunts/git-cas/blob/main/docs/GUIDE.md) + - long-form walkthrough +- [docs/API.md](https://github.com/git-stunts/git-cas/blob/main/docs/API.md) + - command and API reference +- [ARCHITECTURE.md](https://github.com/git-stunts/git-cas/blob/main/ARCHITECTURE.md) + - high-level system map +- [SECURITY.md](https://github.com/git-stunts/git-cas/blob/main/SECURITY.md) + - crypto and security-relevant implementation notes +- [docs/THREAT_MODEL.md](https://github.com/git-stunts/git-cas/blob/main/docs/THREAT_MODEL.md) + - attacker model, trust boundaries, exposed metadata, non-goals +- [docs/BENCHMARKS.md](https://github.com/git-stunts/git-cas/blob/main/docs/BENCHMARKS.md) + - published chunking baselines +- [examples/README.md](https://github.com/git-stunts/git-cas/blob/main/examples/README.md) + - runnable examples +- [CHANGELOG.md](./CHANGELOG.md) + - release history -| | Orphan branch | git-cas | -| --------------- | ----------------------------------- | ------------------------------------------------------------- | -| **Encryption** | None — plaintext forever in history | AES-256-GCM + passphrase KDF + multi-recipient + key rotation | -| **Large files** | Bloats `git clone` for everyone | Chunked, restored on demand | -| **Dedup** | None | Chunk-level content addressing | -| **Integrity** | Git SHA-1 | SHA-256 per chunk + GCM auth tag | -| **Lifecycle** | `git rm` (still in reflog) | Vault with audit trail + `git gc` reclaims | -| **Compression** | None | gzip before encryption | +## When To Use It -### "Why not Git LFS?" +Use `git-cas` when you want: -Because sometimes you want the Git object database to be the store — deterministic, content-addressed, locally replicable, commit-addressable — with no external server, no LFS endpoint, and no second system to manage. +- artifacts to stay inside Git instead of moving to a separate blob service +- explicit chunk-level storage and verification +- Git-native reachability via trees and refs +- encryption on top of Git's object database without inventing a second storage + system -If your team uses GitHub and needs file locking + web UI previews, use LFS. If you want encrypted, self-contained, server-free binary storage that travels with `git clone`, use git-cas. +Do not use `git-cas` when you actually need: ---- +- per-user authorization +- opaque metadata +- remote multi-tenant storage management +- secret recovery or escrow +- transparent large-file ergonomics with no Git tradeoffs -> _THIS HASH’LL KNOCK YOUR SHAs OFF! FIRST COMMIT’S FREE, MAN._ +## Examples -dhtux +Runnable examples live in +[examples/](https://github.com/git-stunts/git-cas/tree/main/examples): ---- +- [examples/store-and-restore.js](https://github.com/git-stunts/git-cas/blob/main/examples/store-and-restore.js) +- [examples/encrypted-workflow.js](https://github.com/git-stunts/git-cas/blob/main/examples/encrypted-workflow.js) +- [examples/progress-tracking.js](https://github.com/git-stunts/git-cas/blob/main/examples/progress-tracking.js) -## License +## Project Status -Apache-2.0 -Copyright © 2026 [James Ross](https://github.com/flyingrobots) +This is an active project with a real multi-runtime test matrix and an evolving +docs/planning surface. The public front door should be treated as: ---- +- README for orientation +- API/guide docs for detail +- changelog for release-by-release history -

-Built by FLYING ROBOTS -

+If you are evaluating the system seriously, read the security and threat-model +docs before designing around encrypted storage behavior. diff --git a/docs/BACKLOG/README.md b/docs/BACKLOG/README.md index a6af255..bfb5c03 100644 --- a/docs/BACKLOG/README.md +++ b/docs/BACKLOG/README.md @@ -32,8 +32,7 @@ Current backlog items: - [TR-005 — CasService Decomposition Plan](./TR-005-casservice-decomposition-plan.md) - [TR-008 — Empty-State Phrasing Consistency](./TR-008-empty-state-phrasing-consistency.md) - [TR-011 — Streaming Encrypted Restore](./TR-011-streaming-encrypted-restore.md) -- [TR-012 — Examples Surface Audit](./TR-012-examples-surface-audit.md) -- [TR-014 — Markdown Surface Rationalization](./TR-014-markdown-surface-rationalization.md) +- [TR-015 — Platform-Agnostic CLI Plan](./TR-015-platform-agnostic-cli-plan.md) Archived delivered backlog items: diff --git a/docs/BACKLOG/TR-015-platform-agnostic-cli-plan.md b/docs/BACKLOG/TR-015-platform-agnostic-cli-plan.md new file mode 100644 index 0000000..404b388 --- /dev/null +++ b/docs/BACKLOG/TR-015-platform-agnostic-cli-plan.md @@ -0,0 +1,61 @@ +# TR-015 — Platform-Agnostic CLI Plan + +## Legend + +- [TR — Truth](../legends/TR-truth.md) + +## Why This Exists + +`git-cas` already maintains a real Node, Bun, and Deno test matrix, but the +human CLI entrypoint is still a Node-specific launcher. + +[bin/git-cas.js](../../bin/git-cas.js) depends directly on: + +- the `#!/usr/bin/env node` launcher model +- `node:` built-ins for file, path, URL, and readline behavior +- direct `process` access for argv, env, stdin, stdout, and stderr +- Node-oriented file helpers and prompt flows under `bin/` and `src/` + +That means the repo is closer to "runtime-tested core with a Node CLI" than to +"portable command surface with multiple distribution options." + +## Target Outcome + +Produce a design-backed plan for making the CLI runtime-neutral at the command +core while being honest about distribution realities, including: + +- what must move out of the Node-specific launcher +- what runtime adapter boundary should exist for argv, stdio, prompts, file + access, and exit behavior +- whether file-backed store/restore helpers should stay Node-only or move + behind a portable interface +- what `@git-stunts/plumbing` assumptions still block true portability +- how per-platform packaged binaries should follow once the runtime boundary is + clean + +## Human Value + +Maintainers should be able to reason clearly about what "platform agnostic" +means here, what work is required to get there, and whether the repo should aim +for multi-runtime source portability, compiled binaries, or both. + +## Agent Value + +Agents should be able to propose bounded follow-on work around CLI portability +without hand-waving past the current Node-specific launcher, TTY helpers, and +Git runner assumptions. + +## Linked Invariants + +- [I-001 — Determinism, Trust, And Explicit Surfaces](../invariants/I-001-determinism-trust-and-explicit-surfaces.md) + +## Notes + +- distinguish runtime-agnostic command logic from platform-specific binary + packaging +- prefer a small runtime adapter boundary over scattering `globalThis.Bun` / + `globalThis.Deno` checks throughout command code +- treat Git runner behavior and subprocess semantics as first-class constraints, + not an afterthought +- do not promise a single universal binary; prefer a portable codebase with + explicit per-platform artifacts if packaging is pursued diff --git a/docs/MARKDOWN_SURFACE.md b/docs/MARKDOWN_SURFACE.md new file mode 100644 index 0000000..666065d --- /dev/null +++ b/docs/MARKDOWN_SURFACE.md @@ -0,0 +1,205 @@ +# Markdown Surface Map + +This document audits the tracked Markdown surface in `git-cas` and makes an +explicit recommendation for each file: + +- `KEEP` — the file belongs where it is +- `CUT` — the file should stop existing in its current tracked form +- `MERGE` — the file's value should move into one or more other docs +- `MOVE` — the file should live elsewhere in the repo + +More than one recommendation can apply at once. + +## Root Policy + +The repo root should contain only a small set of high-traffic, front-door, or +canonical project docs: + +- package/front-door docs +- contributor and workflow docs +- security and architecture docs +- release history + +Planning history, archive material, long-form tutorials, and tool-specific +instruction files should prefer `docs/`, `docs/archive/`, or local-only +surfaces. + +## Root Markdown + +- [README.md](../README.md): `KEEP` — package front door and adoption surface; + belongs at the repo root. +- [CHANGELOG.md](../CHANGELOG.md): `KEEP` — canonical release history; belongs + at the repo root. +- [CONTRIBUTING.md](../CONTRIBUTING.md): `KEEP` — contributor doctrine and + onboarding surface; belongs at the repo root. +- [SECURITY.md](../SECURITY.md): `KEEP` — canonical security guidance and + vulnerability-routing surface; belongs at the repo root. +- [WORKFLOW.md](../WORKFLOW.md): `KEEP` — planning and delivery model for fresh + work; belongs at the repo root. +- [ARCHITECTURE.md](../ARCHITECTURE.md): `KEEP` — canonical high-level + architecture map; still useful as a root-level reference. +- [ROADMAP.md](../ROADMAP.md): `MOVE`, `CUT` — useful as migration and sequence + context today, but too specialized and too drift-prone for permanent + root-level residency. +- [STATUS.md](../STATUS.md): `MERGE`, `CUT` — compact snapshot value is real, + but it largely overlaps with the README, roadmap, and changelog. +- [GRAVEYARD.md](../GRAVEYARD.md): `KEEP`, `MOVE` — still useful historical + context, but it belongs under `docs/archive/` instead of the repo root. +- [CLAUDE.md](../CLAUDE.md): `CUT`, `MOVE` — tool-specific instruction files + should not occupy tracked root doctrine alongside canonical project docs. + +## Canonical Docs Under `docs/` + +- [docs/API.md](./API.md): `KEEP` — canonical API reference; belongs under + `docs/`. +- [docs/THREAT_MODEL.md](./THREAT_MODEL.md): `KEEP` — canonical threat model; + belongs under `docs/`. +- [docs/BENCHMARKS.md](./BENCHMARKS.md): `KEEP` — benchmark guidance and + published baselines belong under `docs/`. +- [docs/RELEASE.md](./RELEASE.md): `KEEP` — release runbook belongs under + `docs/`. +- [docs/DOCS_CHECKLIST.md](./DOCS_CHECKLIST.md): `KEEP` — maintainer-facing docs + review checklist belongs under `docs/`. +- [docs/GUIDE.md](./GUIDE.md): `KEEP` — the long-form tutorial should exist, but + under `docs/`, not at the repo root. +- [docs/ADR-001-vault-in-facade.md](./ADR-001-vault-in-facade.md): `KEEP` — + accepted architecture decision record; current placement is fine until there + is a broader ADR collection. +- [docs/MARKDOWN_SURFACE.md](./MARKDOWN_SURFACE.md): `KEEP` — this audit belongs + under `docs/` as repo-maintainer guidance, not at the root. + +## Live Planning Surface + +- [docs/BACKLOG/README.md](./BACKLOG/README.md): `KEEP` — canonical live backlog + index. +- [docs/BACKLOG/TR-005-casservice-decomposition-plan.md](./BACKLOG/TR-005-casservice-decomposition-plan.md): + `KEEP` — active backlog work item. +- [docs/BACKLOG/TR-008-empty-state-phrasing-consistency.md](./BACKLOG/TR-008-empty-state-phrasing-consistency.md): + `KEEP` — active backlog work item. +- [docs/BACKLOG/TR-011-streaming-encrypted-restore.md](./BACKLOG/TR-011-streaming-encrypted-restore.md): + `KEEP` — active backlog work item. +- [docs/BACKLOG/TR-015-platform-agnostic-cli-plan.md](./BACKLOG/TR-015-platform-agnostic-cli-plan.md): + `KEEP` — active backlog work item. + +## Landed Design Surface + +- [docs/design/README.md](./design/README.md): `KEEP` — canonical landed design + index. +- [docs/design/0001-m18-relay-agent-cli.md](./design/0001-m18-relay-agent-cli.md): + `KEEP` — legacy-named landed cycle history; retain until touched. +- [docs/design/0002-m18-relay-write-flows.md](./design/0002-m18-relay-write-flows.md): + `KEEP` — legacy-named landed cycle history; retain until touched. +- [docs/design/0003-m18-relay-tree-creation.md](./design/0003-m18-relay-tree-creation.md): + `KEEP` — legacy-named landed cycle history; retain until touched. +- [docs/design/RL-001-agent-recipient-list.md](./design/RL-001-agent-recipient-list.md): + `KEEP` — landed cycle history. +- [docs/design/RL-002-agent-recipient-mutations.md](./design/RL-002-agent-recipient-mutations.md): + `KEEP` — landed cycle history. +- [docs/design/RL-003-agent-rotate.md](./design/RL-003-agent-rotate.md): + `KEEP` — landed cycle history. +- [docs/design/RL-004-agent-vault-rotate.md](./design/RL-004-agent-vault-rotate.md): + `KEEP` — landed cycle history. +- [docs/design/RL-005-agent-vault-lifecycle.md](./design/RL-005-agent-vault-lifecycle.md): + `KEEP` — landed cycle history. +- [docs/design/TR-001-architecture-reality-gap.md](./design/TR-001-architecture-reality-gap.md): + `KEEP` — landed cycle history. +- [docs/design/TR-002-threat-model.md](./design/TR-002-threat-model.md): + `KEEP` — landed cycle history. +- [docs/design/TR-003-benchmark-baselines.md](./design/TR-003-benchmark-baselines.md): + `KEEP` — landed cycle history. +- [docs/design/TR-004-design-doc-lifecycle.md](./design/TR-004-design-doc-lifecycle.md): + `KEEP` — landed cycle history. +- [docs/design/TR-006-docs-maintainer-checklist.md](./design/TR-006-docs-maintainer-checklist.md): + `KEEP` — landed cycle history. +- [docs/design/TR-007-security-doc-discoverability-audit.md](./design/TR-007-security-doc-discoverability-audit.md): + `KEEP` — landed cycle history. +- [docs/design/TR-009-pre-pr-doc-cross-link-audit.md](./design/TR-009-pre-pr-doc-cross-link-audit.md): + `KEEP` — landed cycle history. +- [docs/design/TR-010-planning-index-consistency-review.md](./design/TR-010-planning-index-consistency-review.md): + `KEEP` — landed cycle history. +- [docs/design/TR-012-examples-surface-audit.md](./design/TR-012-examples-surface-audit.md): + `KEEP` — landed cycle history. +- [docs/design/TR-013-guide-accuracy-audit.md](./design/TR-013-guide-accuracy-audit.md): + `KEEP` — landed cycle history. +- [docs/design/TR-014-markdown-surface-rationalization.md](./design/TR-014-markdown-surface-rationalization.md): + `KEEP` — landed cycle history. + +## Archive And Historical Intent + +- [docs/archive/README.md](./archive/README.md): `KEEP` — archive entrypoint. +- [docs/archive/design/README.md](./archive/design/README.md): `KEEP` — reserved + archive surface for retired design docs. +- [docs/archive/BACKLOG/README.md](./archive/BACKLOG/README.md): `KEEP` — + canonical archive index for retired backlog cards. +- [docs/archive/BACKLOG/RL-001-agent-recipient-list.md](./archive/BACKLOG/RL-001-agent-recipient-list.md): + `KEEP` — archived historical intent. +- [docs/archive/BACKLOG/RL-002-agent-recipient-mutations.md](./archive/BACKLOG/RL-002-agent-recipient-mutations.md): + `KEEP` — archived historical intent. +- [docs/archive/BACKLOG/RL-003-agent-rotate.md](./archive/BACKLOG/RL-003-agent-rotate.md): + `KEEP` — archived historical intent. +- [docs/archive/BACKLOG/RL-004-agent-vault-rotate.md](./archive/BACKLOG/RL-004-agent-vault-rotate.md): + `KEEP` — archived historical intent. +- [docs/archive/BACKLOG/RL-005-agent-vault-lifecycle.md](./archive/BACKLOG/RL-005-agent-vault-lifecycle.md): + `KEEP` — archived historical intent. +- [docs/archive/BACKLOG/TR-001-architecture-reality-gap.md](./archive/BACKLOG/TR-001-architecture-reality-gap.md): + `KEEP` — archived historical intent. +- [docs/archive/BACKLOG/TR-002-threat-model.md](./archive/BACKLOG/TR-002-threat-model.md): + `KEEP` — archived historical intent. +- [docs/archive/BACKLOG/TR-003-benchmark-baselines.md](./archive/BACKLOG/TR-003-benchmark-baselines.md): + `KEEP` — archived historical intent. +- [docs/archive/BACKLOG/TR-004-design-doc-lifecycle.md](./archive/BACKLOG/TR-004-design-doc-lifecycle.md): + `KEEP` — archived historical intent. +- [docs/archive/BACKLOG/TR-006-docs-maintainer-checklist.md](./archive/BACKLOG/TR-006-docs-maintainer-checklist.md): + `KEEP` — archived historical intent. +- [docs/archive/BACKLOG/TR-007-security-doc-discoverability-audit.md](./archive/BACKLOG/TR-007-security-doc-discoverability-audit.md): + `KEEP` — archived historical intent. +- [docs/archive/BACKLOG/TR-009-pre-pr-doc-cross-link-audit.md](./archive/BACKLOG/TR-009-pre-pr-doc-cross-link-audit.md): + `KEEP` — archived historical intent. +- [docs/archive/BACKLOG/TR-010-planning-index-consistency-review.md](./archive/BACKLOG/TR-010-planning-index-consistency-review.md): + `KEEP` — archived historical intent. +- [docs/archive/BACKLOG/TR-012-examples-surface-audit.md](./archive/BACKLOG/TR-012-examples-surface-audit.md): + `KEEP` — archived historical intent. +- [docs/archive/BACKLOG/TR-013-guide-accuracy-audit.md](./archive/BACKLOG/TR-013-guide-accuracy-audit.md): + `KEEP` — archived historical intent. +- [docs/archive/BACKLOG/TR-014-markdown-surface-rationalization.md](./archive/BACKLOG/TR-014-markdown-surface-rationalization.md): + `KEEP` — archived historical intent. + +## Invariants And Legends + +- [docs/invariants/README.md](./invariants/README.md): `KEEP` — invariants index. +- [docs/invariants/I-001-determinism-trust-and-explicit-surfaces.md](./invariants/I-001-determinism-trust-and-explicit-surfaces.md): + `KEEP` — active project invariant. +- [docs/legends/README.md](./legends/README.md): `KEEP` — legend index. +- [docs/legends/RL-relay.md](./legends/RL-relay.md): `KEEP` — active legend doc. +- [docs/legends/TR-truth.md](./legends/TR-truth.md): `KEEP` — active legend doc. + +## Examples And Test Doctrine + +- [examples/README.md](../examples/README.md): `KEEP` — examples index is useful, + and now reflects the maintained examples surface audit. +- [test/CONVENTIONS.md](../test/CONVENTIONS.md): `KEEP` — test doctrine belongs + near the test surface. +- [test/cycles/README.md](../test/cycles/README.md): `KEEP` — cycle-owned test + directory contract belongs near the test surface. + +## Local-Only And Non-Tracked Markdown + +These files are not part of the tracked Markdown audit and should remain out of +the canonical tracked doctrine surface unless the repo makes an explicit policy +change: + +- `AGENTS.md` +- `EDITORS-REPORT.md` +- `.claude/bad_code.md` +- `.claude/cool_ideas.md` + +## Immediate Follow-On Priorities + +If the repo wants to act on this audit, the highest-value next changes are: + +1. move [GRAVEYARD.md](../GRAVEYARD.md) under `docs/archive/` +2. decide whether [CLAUDE.md](../CLAUDE.md) should leave tracked root entirely +3. collapse [STATUS.md](../STATUS.md) into other canonical surfaces and remove + the duplicate snapshot doc +4. shrink and relocate [ROADMAP.md](../ROADMAP.md) once its remaining migration + value is absorbed elsewhere diff --git a/docs/archive/BACKLOG/README.md b/docs/archive/BACKLOG/README.md index e464700..4d6dc17 100644 --- a/docs/archive/BACKLOG/README.md +++ b/docs/archive/BACKLOG/README.md @@ -33,5 +33,9 @@ Landed archived backlog items: - landed as [TR-009 — Truth: Pre-PR Doc Cross-Link Audit](../../design/TR-009-pre-pr-doc-cross-link-audit.md) - [TR-010 — Planning Index Consistency Review](./TR-010-planning-index-consistency-review.md) - landed as [TR-010 — Truth: Planning Index Consistency Review](../../design/TR-010-planning-index-consistency-review.md) +- [TR-012 — Examples Surface Audit](./TR-012-examples-surface-audit.md) + - landed as [TR-012 — Truth: Examples Surface Audit](../../design/TR-012-examples-surface-audit.md) - [TR-013 — Guide Accuracy Audit](./TR-013-guide-accuracy-audit.md) - landed as [TR-013 — Truth: Guide Accuracy Audit](../../design/TR-013-guide-accuracy-audit.md) +- [TR-014 — Markdown Surface Rationalization](./TR-014-markdown-surface-rationalization.md) + - landed as [TR-014 — Truth: Markdown Surface Rationalization](../../design/TR-014-markdown-surface-rationalization.md) diff --git a/docs/BACKLOG/TR-012-examples-surface-audit.md b/docs/archive/BACKLOG/TR-012-examples-surface-audit.md similarity index 86% rename from docs/BACKLOG/TR-012-examples-surface-audit.md rename to docs/archive/BACKLOG/TR-012-examples-surface-audit.md index 7f0513c..b46a649 100644 --- a/docs/BACKLOG/TR-012-examples-surface-audit.md +++ b/docs/archive/BACKLOG/TR-012-examples-surface-audit.md @@ -2,7 +2,7 @@ ## Legend -- [TR — Truth](../legends/TR-truth.md) +- [TR — Truth](../../legends/TR-truth.md) ## Why This Exists @@ -35,7 +35,7 @@ which ones still belong in the repo and why. ## Linked Invariants -- [I-001 — Determinism, Trust, And Explicit Surfaces](../invariants/I-001-determinism-trust-and-explicit-surfaces.md) +- [I-001 — Determinism, Trust, And Explicit Surfaces](../../invariants/I-001-determinism-trust-and-explicit-surfaces.md) ## Notes diff --git a/docs/BACKLOG/TR-014-markdown-surface-rationalization.md b/docs/archive/BACKLOG/TR-014-markdown-surface-rationalization.md similarity index 88% rename from docs/BACKLOG/TR-014-markdown-surface-rationalization.md rename to docs/archive/BACKLOG/TR-014-markdown-surface-rationalization.md index 3f76a30..3b5bf7c 100644 --- a/docs/BACKLOG/TR-014-markdown-surface-rationalization.md +++ b/docs/archive/BACKLOG/TR-014-markdown-surface-rationalization.md @@ -2,7 +2,7 @@ ## Legend -- [TR — Truth](../legends/TR-truth.md) +- [TR — Truth](../../legends/TR-truth.md) ## Why This Exists @@ -40,7 +40,7 @@ equal apparent status. ## Linked Invariants -- [I-001 — Determinism, Trust, And Explicit Surfaces](../invariants/I-001-determinism-trust-and-explicit-surfaces.md) +- [I-001 — Determinism, Trust, And Explicit Surfaces](../../invariants/I-001-determinism-trust-and-explicit-surfaces.md) ## Notes diff --git a/docs/design/README.md b/docs/design/README.md index 23c771e..c6238c3 100644 --- a/docs/design/README.md +++ b/docs/design/README.md @@ -47,7 +47,9 @@ Landed cycle docs: - [TR-007 — Truth: Security Doc Discoverability Audit](./TR-007-security-doc-discoverability-audit.md) - [TR-009 — Truth: Pre-PR Doc Cross-Link Audit](./TR-009-pre-pr-doc-cross-link-audit.md) - [TR-010 — Truth: Planning Index Consistency Review](./TR-010-planning-index-consistency-review.md) +- [TR-012 — Truth: Examples Surface Audit](./TR-012-examples-surface-audit.md) - [TR-013 — Truth: Guide Accuracy Audit](./TR-013-guide-accuracy-audit.md) +- [TR-014 — Truth: Markdown Surface Rationalization](./TR-014-markdown-surface-rationalization.md) Archived or retired cycle docs: diff --git a/docs/design/TR-012-examples-surface-audit.md b/docs/design/TR-012-examples-surface-audit.md new file mode 100644 index 0000000..facbe94 --- /dev/null +++ b/docs/design/TR-012-examples-surface-audit.md @@ -0,0 +1,148 @@ +# TR-012 — Truth: Examples Surface Audit + +## Status + +Landed + +## Linked Legend + +- [TR — Truth](../legends/TR-truth.md) + +## Linked Invariants + +- [I-001 — Determinism, Trust, And Explicit Surfaces](../invariants/I-001-determinism-trust-and-explicit-surfaces.md) + +## Context + +The `examples/` directory is part of the repo's teaching surface, not just a +miscellaneous folder of throwaway scripts. + +That means it has the same truth burden as the rest of the docs: + +- examples should use current public APIs +- examples should not teach stale internal access patterns +- the example index should say which files are still maintained and why + +Before this cycle, the examples surface had drifted: + +- `examples/README.md` still taught manual manifest decoding via service + internals +- the README still described progress tracking as if `CasService` itself were + the event surface +- `store-and-restore.js` still demonstrated the older internal manifest-read + path instead of `readManifest()` + +## Human Users, Jobs, And Hills + +### Users + +- maintainers +- adopters reading examples before the API reference +- contributors checking whether examples still represent current usage + +### Jobs + +- trust that examples still teach maintained public APIs +- tell which examples remain worth keeping +- avoid copying internal or stale patterns into new code + +### Hill + +A maintainer or adopter can run the examples and learn current `git-cas` +workflows without inheriting stale internal access patterns. + +## Agent Users, Jobs, And Hills + +### Users + +- coding agents +- review agents +- documentation agents + +### Jobs + +- reason about which examples are maintained and what they teach +- repair or cite examples without treating stale patterns as canonical +- distinguish between public facade helpers and internal service plumbing + +### Hill + +An agent can use the examples surface as a maintained teaching aid instead of a +historical code dump. + +## Human Playback + +- Does `examples/README.md` now state which example files are kept and why? +- Do the maintained examples use current public APIs instead of internal + manifest-decoding patterns? +- Is the progress example described through `EventEmitterObserver` rather than + a stale EventEmitter claim about `CasService`? + +## Agent Playback + +- Can an agent tell which example files are worth keeping? +- Do the repaired examples favor public package entrypoints and public helpers? +- Does the examples surface now align with the guide and API docs more closely? + +## Explicit Non-Goals + +- no attempt to add a large new example suite +- no conversion of examples into tested fixtures or integration tests in this + cycle +- no removal of examples that are still accurate enough to teach current usage + +## Decisions + +### Keep The Current Example Set + +The current set is still small and useful: + +- `store-and-restore.js` +- `encrypted-workflow.js` +- `progress-tracking.js` + +No file currently needs to be cut or moved. + +### Refresh The Example Index + +`examples/README.md` should explicitly record the recommendation for each file +and should stop teaching stale internals in its reference snippets. + +### Prefer Public Helpers Over Internal Plumbing + +Where the public facade already provides a helper, the examples should prefer +that helper. `readManifest()` is the right example for tree-to-manifest reads. + +## Implementation Outline + +1. Audit `examples/README.md` and the three example programs. +2. Refresh the README with per-file recommendations and current API guidance. +3. Repair `store-and-restore.js` to use `readManifest()`. +4. Add this cycle doc, archive the consumed backlog card, update the Truth + indexes, and record the cycle in [CHANGELOG.md](../../CHANGELOG.md). + +## Tests To Write First + +No new automated tests. + +This cycle verifies the examples directly: + +- run the example scripts under Node +- validate formatting and whitespace on touched files +- run the repo lint and test gates after code changes + +## Risks And Unknowns + +- examples can still drift later if they are not treated as a maintained docs + surface +- the examples are Node-oriented and do not themselves prove Bun or Deno example + ergonomics +- future API changes may create pressure to split examples by audience or + runtime + +## Retrospective + +This was the right next cycle after rationalizing the Markdown surface. + +The examples did not need expansion. They needed a truth pass so the repo stops +teaching stale internal patterns through supposedly friendly entrypoint code. diff --git a/docs/design/TR-014-markdown-surface-rationalization.md b/docs/design/TR-014-markdown-surface-rationalization.md new file mode 100644 index 0000000..8d7b78d --- /dev/null +++ b/docs/design/TR-014-markdown-surface-rationalization.md @@ -0,0 +1,149 @@ +# TR-014 — Truth: Markdown Surface Rationalization + +## Status + +Landed + +## Linked Legend + +- [TR — Truth](../legends/TR-truth.md) + +## Linked Invariants + +- [I-001 — Determinism, Trust, And Explicit Surfaces](../invariants/I-001-determinism-trust-and-explicit-surfaces.md) + +## Context + +The repo had already fixed several individual documentation truth gaps: + +- stale architecture truth +- missing threat model +- benchmark publication +- design lifecycle clarity +- docs-review checklist and cross-link audits +- guide placement and guide accuracy + +What was still missing was one explicit map of the Markdown surface itself. + +Without that, each docs-heavy review had to rediscover the same questions: + +- which files actually belong at the repo root +- which ones are canonical versus migration surfaces +- which ones should be cut, merged, or moved instead of kept indefinitely + +## Human Users, Jobs, And Hills + +### Users + +- maintainers +- contributors opening docs-heavy pull requests +- reviewers trying to judge whether a file belongs where it is + +### Jobs + +- see the whole tracked Markdown surface at once +- distinguish canonical docs from migration and archive surfaces +- make future cut/move/merge decisions from one explicit map instead of memory + +### Hill + +A maintainer can look at one repo-native document and tell where each tracked +Markdown file belongs, whether it should stay, and which root-level artifacts +still need follow-on cleanup. + +## Agent Users, Jobs, And Hills + +### Users + +- coding agents +- review agents +- documentation agents + +### Jobs + +- reason about the Markdown surface without treating every file as equally + canonical +- plan docs changes from explicit keep/cut/merge/move recommendations +- stop rediscovering root-versus-docs placement policy on every branch + +### Hill + +An agent can use one tracked Markdown-surface map as the repo's current +placement and rationalization reference. + +## Human Playback + +- Is there now one explicit Markdown-surface map with a recommendation for each + tracked Markdown file? +- Does it say which artifacts belong at the root and which do not? +- Does it separate canonical, planning, archive, and local-only surfaces + clearly? + +## Agent Playback + +- Can an agent tell which Markdown files are canonical, archival, migration, or + tool-specific? +- Does the audit cover tracked files broadly enough to avoid branch-local + guesswork? +- Does it make future move/cut/merge work easier without performing all of it + prematurely? + +## Explicit Non-Goals + +- no attempt to execute every recommended move or cut in this cycle +- no full content-accuracy rewrite of every Markdown file in the repo +- no destruction of useful planning or archive history + +## Decisions + +### Publish One Explicit Markdown-Surface Map + +The repo should have one tracked Markdown-surface audit at +[docs/MARKDOWN_SURFACE.md](../MARKDOWN_SURFACE.md) instead of leaving these +placement decisions implicit. + +### Keep The Audit File-Specific + +This cycle should make a recommendation for each tracked Markdown file, not +just broad directory-level guidance. That keeps the result actionable for both +humans and agents. + +### Separate Placement Guidance From Execution + +The audit should identify candidates for `CUT`, `MERGE`, and `MOVE` without +trying to execute every recommendation at once. That keeps the cycle honest and +reviewable. + +## Implementation Outline + +1. Add [docs/MARKDOWN_SURFACE.md](../MARKDOWN_SURFACE.md) with per-file + recommendations across the tracked Markdown surface. +2. Add this cycle doc. +3. Archive the consumed backlog card, update the Truth indexes, and record the + audit in [CHANGELOG.md](../../CHANGELOG.md). + +## Tests To Write First + +No new executable tests. + +This is a documentation-truth cycle. Verification is: + +- cross-check against the tracked Markdown file inventory +- formatting validation for touched Markdown files +- planning-surface validation for backlog, archive, design, and legend updates + +## Risks And Unknowns + +- some recommendations may age as the repo evolves +- the audit surfaces content-accuracy follow-ons it does not itself fix +- tool-specific root files may require a separate policy decision before they + can be removed or relocated + +## Retrospective + +This cycle was necessary once the repo had enough documentation cleanup behind +it to stop making purely local decisions. + +The repo now has an explicit Markdown placement map. That does not finish the +cleanup, but it turns future cleanup into deliberate work instead of repeated +review improvisation. diff --git a/docs/git-cas-clay-loop.svg b/docs/git-cas-clay-loop.svg new file mode 100644 index 0000000..7bdebf7 --- /dev/null +++ b/docs/git-cas-clay-loop.svg @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/git-cas-ember-loop.svg b/docs/git-cas-ember-loop.svg new file mode 100644 index 0000000..2165dc7 --- /dev/null +++ b/docs/git-cas-ember-loop.svg @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/git-cas-honey-loop.svg b/docs/git-cas-honey-loop.svg new file mode 100644 index 0000000..d237d63 --- /dev/null +++ b/docs/git-cas-honey-loop.svg @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/git-cas-ice-loop.svg b/docs/git-cas-ice-loop.svg new file mode 100644 index 0000000..765125d --- /dev/null +++ b/docs/git-cas-ice-loop.svg @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/git-cas-ivory-loop.svg b/docs/git-cas-ivory-loop.svg new file mode 100644 index 0000000..e9f0a96 --- /dev/null +++ b/docs/git-cas-ivory-loop.svg @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/git-cas-moss-loop.svg b/docs/git-cas-moss-loop.svg new file mode 100644 index 0000000..c017240 --- /dev/null +++ b/docs/git-cas-moss-loop.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/git-cas-neon-loop.svg b/docs/git-cas-neon-loop.svg new file mode 100644 index 0000000..e53c505 --- /dev/null +++ b/docs/git-cas-neon-loop.svg @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/git-cas-plum-loop.svg b/docs/git-cas-plum-loop.svg new file mode 100644 index 0000000..c5597ac --- /dev/null +++ b/docs/git-cas-plum-loop.svg @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/git-cas-preview.html b/docs/git-cas-preview.html new file mode 100644 index 0000000..a580ea2 --- /dev/null +++ b/docs/git-cas-preview.html @@ -0,0 +1,284 @@ + + + + + + git-cas SVG Preview + + + +
+
+
+

Light Background

+

Each SVG stacked vertically on white.

+
+
+
+

git-cas.svg

+ git-cas default gradient logo on light background +
+
+

git-cas-neon-loop.svg

+ git-cas neon animated gradient logo on light background +
+
+

git-cas-moss-loop.svg

+ git-cas moss animated gradient logo on light background +
+
+

git-cas-ice-loop.svg

+ git-cas ice animated gradient logo on light background +
+
+

git-cas-plum-loop.svg

+ git-cas plum animated gradient logo on light background +
+
+

git-cas-ember-loop.svg

+ git-cas ember animated gradient logo on light background +
+
+

git-cas-clay-loop.svg

+ git-cas clay animated gradient logo on light background +
+
+

git-cas-honey-loop.svg

+ git-cas honey animated gradient logo on light background +
+
+

git-cas-slate-loop.svg

+ git-cas slate animated gradient logo on light background +
+
+

git-cas-ivory-loop.svg

+ git-cas ivory animated gradient logo on light background +
+
+
+ +
+
+

Dark Background

+

The same SVGs stacked vertically on black.

+
+
+
+

git-cas.svg

+ git-cas default gradient logo on dark background +
+
+

git-cas-neon-loop.svg

+ git-cas neon animated gradient logo on dark background +
+
+

git-cas-moss-loop.svg

+ git-cas moss animated gradient logo on dark background +
+
+

git-cas-ice-loop.svg

+ git-cas ice animated gradient logo on dark background +
+
+

git-cas-plum-loop.svg

+ git-cas plum animated gradient logo on dark background +
+
+

git-cas-ember-loop.svg

+ git-cas ember animated gradient logo on dark background +
+
+

git-cas-clay-loop.svg

+ git-cas clay animated gradient logo on dark background +
+
+

git-cas-honey-loop.svg

+ git-cas honey animated gradient logo on dark background +
+
+

git-cas-slate-loop.svg

+ git-cas slate animated gradient logo on dark background +
+
+

git-cas-ivory-loop.svg

+ git-cas ivory animated gradient logo on dark background +
+
+
+
+ + diff --git a/docs/git-cas-slate-loop.svg b/docs/git-cas-slate-loop.svg new file mode 100644 index 0000000..75f9550 --- /dev/null +++ b/docs/git-cas-slate-loop.svg @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/git-cas.svg b/docs/git-cas.svg new file mode 100644 index 0000000..7a1d781 --- /dev/null +++ b/docs/git-cas.svg @@ -0,0 +1 @@ + diff --git a/docs/legends/TR-truth.md b/docs/legends/TR-truth.md index 4b6688b..d687b9b 100644 --- a/docs/legends/TR-truth.md +++ b/docs/legends/TR-truth.md @@ -79,15 +79,16 @@ Current Truth design docs: - [TR-007 — Truth: Security Doc Discoverability Audit](../design/TR-007-security-doc-discoverability-audit.md) - [TR-009 — Truth: Pre-PR Doc Cross-Link Audit](../design/TR-009-pre-pr-doc-cross-link-audit.md) - [TR-010 — Truth: Planning Index Consistency Review](../design/TR-010-planning-index-consistency-review.md) +- [TR-012 — Truth: Examples Surface Audit](../design/TR-012-examples-surface-audit.md) - [TR-013 — Truth: Guide Accuracy Audit](../design/TR-013-guide-accuracy-audit.md) +- [TR-014 — Truth: Markdown Surface Rationalization](../design/TR-014-markdown-surface-rationalization.md) Current Truth backlog items: - [TR-005 — CasService Decomposition Plan](../BACKLOG/TR-005-casservice-decomposition-plan.md) - [TR-008 — Empty-State Phrasing Consistency](../BACKLOG/TR-008-empty-state-phrasing-consistency.md) - [TR-011 — Streaming Encrypted Restore](../BACKLOG/TR-011-streaming-encrypted-restore.md) -- [TR-012 — Examples Surface Audit](../BACKLOG/TR-012-examples-surface-audit.md) -- [TR-014 — Markdown Surface Rationalization](../BACKLOG/TR-014-markdown-surface-rationalization.md) +- [TR-015 — Platform-Agnostic CLI Plan](../BACKLOG/TR-015-platform-agnostic-cli-plan.md) Truth work under this legend is currently focused on: @@ -100,10 +101,16 @@ Truth work under this legend is currently focused on: - improving security doc discoverability from high-traffic repo surfaces - keeping the long-form guide accurate and positioned as a docs surface instead of a root-level front door +- keeping the examples surface aligned with current public APIs instead of + stale internal helper paths +- making the tracked Markdown surface explicit so root, docs, archive, and + local-only placement decisions stop living only in memory - running a lightweight pre-PR doc cross-link audit on doc-heavy branches - running planning-index consistency reviews and keeping empty-state language consistent over time - investigating lower-memory restore paths for encrypted and compressed assets +- defining a runtime-neutral CLI boundary before promising broader + cross-platform binary distribution ## Explicit Non-Goals diff --git a/examples/README.md b/examples/README.md index 015462f..7113149 100644 --- a/examples/README.md +++ b/examples/README.md @@ -2,6 +2,15 @@ This directory contains runnable examples demonstrating the core features of `@git-stunts/git-cas`. +Audit status: + +| File | Recommendation | Notes | +| -------------------------------- | -------------- | --------------------------------------------------------------------------------------------- | +| `examples/README.md` | Keep, refresh | Canonical index for the examples surface; keep it accurate and scoped to maintained examples. | +| `examples/store-and-restore.js` | Keep, refresh | Good first example; now uses the public `readManifest()` helper for tree-to-manifest reads. | +| `examples/encrypted-workflow.js` | Keep | Still a useful end-to-end encryption example with current public APIs. | +| `examples/progress-tracking.js` | Keep | Still the right observability example now that it teaches `EventEmitterObserver`. | + ## Prerequisites - Node.js 22 or later @@ -37,19 +46,21 @@ node progress-tracking.js **Demonstrates:** Basic CAS workflow with verification This example shows the complete lifecycle of storing and restoring a file: + 1. Creates a temporary Git bare repository 2. Stores a file in the content-addressable store 3. Creates a Git tree to persist the manifest -4. Reads the manifest back from the tree +4. Reads the manifest back from the tree with the public `readManifest()` helper 5. Restores the file to disk 6. Verifies the restored content matches the original 7. Runs integrity verification on the stored chunks **Key concepts:** + - `ContentAddressableStore.createJson()` factory - `storeFile()` to store files - `createTree()` to persist manifests in Git -- Reading manifests from Git trees +- `readManifest()` to reconstruct manifests from Git trees - `restoreFile()` to write files back to disk - `verifyIntegrity()` to check chunk digests @@ -58,6 +69,7 @@ This example shows the complete lifecycle of storing and restoring a file: **Demonstrates:** Encryption and decryption with AES-256-GCM This example shows how to work with encrypted content: + 1. Generates a secure 32-byte encryption key 2. Stores a file with encryption enabled 3. Restores the file using the correct key @@ -65,6 +77,7 @@ This example shows how to work with encrypted content: 5. Shows the encryption metadata stored in the manifest **Key concepts:** + - Generating encryption keys with `crypto.randomBytes(32)` - Storing encrypted files with `encryptionKey` parameter - Encryption metadata in manifests @@ -75,10 +88,12 @@ This example shows how to work with encrypted content: **Demonstrates:** Event-driven progress monitoring -This example shows how to track storage and restore operations using Node.js EventEmitter: -1. Accesses the CasService via `cas.getService()` -2. Attaches event listeners for various operations -3. Builds a progress logger that tracks: +This example shows how to track storage and restore operations using `EventEmitterObserver`: + +1. Creates an `EventEmitterObserver` +2. Passes it into `ContentAddressableStore` via the `observability` option +3. Attaches event listeners to the observer +4. Builds a progress logger that tracks: - Chunk storage progress - File storage completion - Chunk restoration progress @@ -86,7 +101,8 @@ This example shows how to track storage and restore operations using Node.js Eve - Integrity verification results **Key concepts:** -- Accessing the underlying CasService + +- `EventEmitterObserver` as the backward-compatible event bridge - Event types: `chunk:stored`, `file:stored`, `chunk:restored`, `file:restored`, `integrity:pass`, `integrity:fail`, `error` - Building real-time progress indicators - Calculating percentages based on chunk counts @@ -111,7 +127,7 @@ const manifest = await cas.storeFile({ filePath: '/path/to/file', slug: 'unique-identifier', filename: 'optional-name.txt', - encryptionKey: optionalKeyBuffer // 32-byte Buffer + encryptionKey: optionalKeyBuffer, // 32-byte Buffer }); // Create a Git tree @@ -125,13 +141,13 @@ const treeOid = await cas.createTree({ manifest }); await cas.restoreFile({ manifest, encryptionKey: optionalKeyBuffer, - outputPath: '/path/to/output' + outputPath: '/path/to/output', }); // Restore to memory (returns Buffer) const { buffer, bytesWritten } = await cas.restore({ manifest, - encryptionKey: optionalKeyBuffer + encryptionKey: optionalKeyBuffer, }); ``` @@ -145,19 +161,7 @@ const isValid = await cas.verifyIntegrity(manifest); ### Reading Manifests from Trees ```javascript -// Get the service -const service = await cas.getService(); - -// Read tree entries -const entries = await service.persistence.readTree(treeOid); - -// Find manifest entry -const manifestEntry = entries.find(e => e.name === 'manifest.json'); - -// Read and decode manifest blob -const manifestBlob = await service.persistence.readBlob(manifestEntry.oid); -const manifestData = service.codec.decode(manifestBlob); -const manifest = new Manifest(manifestData); +const manifest = await cas.readManifest({ treeOid }); ``` ## Encryption Keys @@ -179,20 +183,23 @@ const key = randomBytes(32); - All examples clean up temporary files and directories - The examples use temporary Git bare repositories to avoid polluting your working directory - Chunk size defaults to 256 KiB (262,144 bytes) -- File paths must be absolute paths, not relative paths -- The CAS service extends EventEmitter for progress tracking +- Relative file paths are fine; these examples happen to use temporary absolute paths +- Progress events are exposed through `EventEmitterObserver`, not by subscribing directly to `CasService` ## Troubleshooting **Error: "Encryption key must be 32 bytes"** + - Ensure your encryption key is exactly 32 bytes - Use `crypto.randomBytes(32)` or equivalent **Error: "INTEGRITY_ERROR"** + - Using wrong decryption key - Chunk corruption in Git object database - Run `verifyIntegrity()` to identify corrupted chunks **Error: "MISSING_KEY"** + - Attempting to restore encrypted content without providing the key - Check if `manifest.encryption.encrypted === true` diff --git a/examples/store-and-restore.js b/examples/store-and-restore.js index 4f0a111..8aab547 100755 --- a/examples/store-and-restore.js +++ b/examples/store-and-restore.js @@ -17,7 +17,7 @@ import { execSync } from 'node:child_process'; import path from 'node:path'; import os from 'node:os'; import GitPlumbing from '@git-stunts/plumbing'; -import ContentAddressableStore, { Manifest } from '@git-stunts/git-cas'; +import ContentAddressableStore from '@git-stunts/git-cas'; console.log('=== Store and Restore Example ===\n'); @@ -44,7 +44,7 @@ console.log('\n--- Step 1: Storing file ---'); const manifest = await cas.storeFile({ filePath: testFilePath, slug: 'example-file', - filename: 'sample.bin' + filename: 'sample.bin', }); console.log(`Stored successfully!`); @@ -61,24 +61,7 @@ console.log(`Git tree created: ${treeOid}`); // Step 3: Read the manifest back from the tree console.log('\n--- Step 3: Reading manifest from tree ---'); -const service = await cas.getService(); -const entries = await service.persistence.readTree(treeOid); - -console.log(`Tree contains ${entries.length} entries:`); -entries.forEach(entry => { - const label = entry.name.startsWith('manifest.') ? 'Manifest' : `Chunk ${entry.name.substring(0, 8)}...`; - console.log(` - ${label} (${entry.type}): ${entry.oid}`); -}); - -// Find and decode the manifest -const manifestEntry = entries.find(e => e.name === 'manifest.json'); -if (!manifestEntry) { - throw new Error('Manifest not found in tree'); -} - -const manifestBlob = await service.persistence.readBlob(manifestEntry.oid); -const manifestData = service.codec.decode(manifestBlob); -const restoredManifest = new Manifest(manifestData); +const restoredManifest = await cas.readManifest({ treeOid }); console.log('\nManifest successfully read from tree'); console.log(` Slug: ${restoredManifest.slug}`); @@ -89,7 +72,7 @@ console.log('\n--- Step 4: Restoring file to disk ---'); const outputPath = path.join(testDir, 'restored.bin'); const { bytesWritten } = await cas.restoreFile({ manifest: restoredManifest, - outputPath + outputPath, }); console.log(`File restored to: ${outputPath}`);