packages/cas/src/cas.nu — a content-addressed store over BLAKE3.
The keystone primitive of a verifiable build/distribution chain: bytes go in, their BLAKE3-256 hex comes back, and from then on the hash IS the name. Storing the same bytes twice is free (dedup by existence), and every read re-hashes what it found and refuses to return bytes that don't match their name — a store you can rsync, cache, or fetch from an untrusted mirror without losing integrity.
Layout (git/OCI-shaped, two-level fan-out): <root>/objects/<hex[0:2]>/<hex[2:64]> immutable objects <root>/tmp/ staging for atomic writes
Writes stage into tmp/ and fs_rename into place — readers never see a torn object, and concurrent writers of the same content converge on the same file. Objects are plain files: no index, no database, no lock; the filesystem is the whole data structure.
( cas_open /path/to/store ) → !Cas String ( cas_put c bytes ) → !String String BLAKE3 hex ( cas_put_file c path ) → !String String ( cas_get c hex ) → !( Vec u ) String (verified) ( cas_has c hex ) → b ( cas_object_path c hex ) → String ( cas_hash_hex bytes ) → String the naming function ( cas_free c )
: Cas: Cas {
String root
}
@ cas_free Cas c → v@ cas_hash_hex ( Vec u ) data → StringThe naming function: BLAKE3-256, lowercase hex.
@ cas_open s root → !Cas String@ cas_object_path Cas c s hex → String<root>/objects/<hex[0:2]>/<hex[2:]>
@ cas_has Cas c s hex → b@ cas_put Cas c ( Vec u ) data → !String StringStore bytes; returns their hex name. Existing object = dedup hit (the name already proves the content — cas_get verifies on the way out). New object: stage under tmp/ with a unique name, then one atomic rename into place.
@ cas_put_file Cas c s path → !String String@ cas_get Cas c s hex → !( Vec u ) StringFetch by name — and PROVE the name. The stored bytes are re-hashed and compared to the requested hex; a corrupted or tampered object is an error, never silently returned. This is the verifiability loop: any store you can point cas_get at (local disk, a synced mirror, a cache you don't trust) yields either the exact original bytes or a failure.
cas — a content-addressed store over BLAKE3, from the command line.
cas put file.bin store bytes → prints the hash cas hash file.bin name without storing cas get <hash> [-o out.bin] fetch + VERIFY (stdout by default) cas snapshot ./dir whole tree → one manifest hash cas ls <manifest-hash> list a snapshot cas checkout <manifest-hash> ./dest materialise + verify cas verify <hash> deep-prove a blob or a whole snapshot
The store is plain files under --store DIR (default $CAS_STORE, else ~/.cas): rsync it, mirror it, cache it — get/checkout/verify re-hash everything they touch, so a lying store is an error, never silent corruption.
@ main → ipackages/cas/src/manifest.nu — the minimal manifest: a named set of files as (hash, size, path) triples, serialised CANONICALLY so that the same tree always produces the same bytes — and therefore the same BLAKE3 name when the manifest is stored in the CAS. That makes a manifest hash a Merkle root: one 64-hex string names an entire tree, dedups against every other tree sharing files, and verifies all the way down.
Wire format (text, one object kind, self-identifying): cas-manifest v1\n <64-hex> <size> <path>\n entries sorted bytewise by path
Paths are relative, '/'-separated, no leading './'. The format is the canonical encoding — decode(encode(m)) round-trips and encode(decode(b)) == b for any valid manifest, so hashes are stable across implementations.
( manifest_new ) → CasManifest ( manifest_add m hex size path ) → v ( manifest_encode m ) → ( Vec u ) canonical bytes ( manifest_decode bytes ) → !CasManifest String ( manifest_len m ) / ( manifest_hash m i ) / ( manifest_size m i ) / ( manifest_path m i ) → accessors (borrowed views) ( manifest_free m )
Store-level operations built on it (import src/cas.nu first): ( cas_snapshot c dir ) → !String String manifest hex ( cas_checkout c manifest_hex dest ) → !i String files written ( cas_verify c hex ) → !i String objects proven
: CasManifest: CasManifest {
( Vec String ) hashes
( Vec i ) sizes
( Vec String ) paths
}
@ manifest_new → CasManifest@ manifest_free CasManifest m → v@ manifest_len CasManifest m → i@ manifest_hash CasManifest m i k → StringAccessors return OWNED copies (vec_get on Vec String hands back an aliasing copy the compiler tracks — copying keeps call sites simple).
@ manifest_size CasManifest m i k → i@ manifest_path CasManifest m i k → String@ manifest_add CasManifest m s hex i size s path → v@ manifest_encode CasManifest m → ( Vec u )Canonical bytes: header + entries sorted bytewise by path. Sorting happens on an index permutation so the manifest itself is untouched.
@ string_cmp_bytes String a String b → iBytewise string compare for the canonical sort (string_cmp in the stdlib is locale-free already, but be explicit and self-contained).
@ manifest_is ( Vec u ) data → bTrue iff the bytes carry the manifest header (cheap kind probe).
@ manifest_decode ( Vec u ) data → !CasManifest String@ cas_snapshot Cas c s dir → !String StringRecursively walk dir, cas_put every regular file, then cas_put the canonical manifest of the tree. Returns the manifest's hex — one name for the whole snapshot.
@ cas_checkout Cas c s manifest_hex s dest → !i StringMaterialise a snapshot into dest. Every object read is verified by cas_get; a checkout therefore cannot produce bytes that differ from what was snapshotted. Returns the number of files written.
@ cas_verify Cas c s hex → !i StringDeep-verify a name: a blob proves itself (cas_get re-hashes); a manifest additionally proves every entry it references. Returns the total number of objects proven (manifest itself included).