Store layout (key–value mapping)

A store is any key–value namespace with string keys and byte-string values, supporting at least get and set (an absent key reads as absent, and a null/absent distinction at the driver level MUST both mean absent). Optional capabilities (has, key enumeration by prefix, delete) enable existence checks and garbage collection but are not required for conformance.

Three key spaces share the namespace:

  key                                              value
  ───────────────────────────────────────────────  ─────────────────────────
  meta:format             (store metadata, §10.3)  canonical CBOR map

  blocks:ciqk2p…q4        (base32 of the multihash) raw block bytes
  blocks:ciqfx3…zb                                 canonical CBOR node bytes

  refs:heads:main         (hierarchy spelled ':')  binary CIDv1 (§4)
  refs:heads:release                               binary CIDv1
  refs:backup-2026-01-01                           <empty> = tombstone

#10.1 Blocks

blocks:<base32(multihash)> → the block bytes. The key is the unpadded lowercase base32 (§3.2) of the ref's binary multihash — hash-function code, digest length, digest (§4) — and of nothing else: the codec is excluded, because block identity is the multihash alone (§4). Under SHA2-256 every block key therefore starts blocks:ciq (the base32 of the 0x12 0x20 multihash prefix). This is the same keying decision mainstream IPLD block stores converged on (storage keyed by the raw multihash, codec-agnostic): a unfs store and an IPLD blockstore agree on what a block is, and blocks referenced under both codecs are stored once.

The block namespace is append-only in normal operation; only garbage collection (§15.2) ever deletes.

#10.2 Named refs

refs:<name> → the ref's binary CIDv1 (§4), bare (no multibase prefix). Readers MUST validate the value under the full rules of §4 — CIDv1, codec raw or dag-cbor, the store's multihash — before use; a value that fails (including a well-formed CID with an unsupported codec) is an undecodable ref value and, during garbage collection, aborts the sweep fail-closed (§15.2).

Name rules. A ref name MUST be 1–512 UTF-8 bytes and MUST NOT contain NUL, /, \, or ?, and MUST NOT have a leading, trailing, or repeated :. Names MAY be hierarchical, spelled with : (e.g. heads:main).

Rationale (binding): the accepted names are exactly the fixed points of key normalization — common storage layers map / and \ to :, collapse : runs, and strip leading/trailing :. Allowing a non-fixed-point name would let two distinct names alias onto one underlying key — the second binding silently destroys the first, and any liveness analysis rooted at names then treats the loser's entire tree as garbage. : itself is safe because it is the target of that mapping, never a source. Block keys need no such rule: lowercase base32 contains no separator characters.

Tombstones. Deleting a name on a store without a delete capability is performed by writing an empty value. Readers MUST treat an empty value as "name absent", on every store — including stores that do have delete: a tombstone already written is never reinterpreted as data. A tombstone is not a reservation, though: a later set of a non-empty ref value rebinds the name normally, exactly as if the key had never existed.

#10.3 Store metadata

meta:format → a canonical-CBOR map (§5) that makes the store self-describing:

  • v — integer, MUST be 1 for this version (§15.1).
  • hash — text, a hash function identifier (§10.4): the multicodec registry name of a multihash function. MUST name the store's hash function (§3.4), and MUST agree with the multihash code carried by every ref in the store (§4).
  • params — map, OPTIONAL: the writer parameters in effect (§14), with integer fields inlineThreshold, pageThreshold, maxChunkRefs, chunkMin, chunkAvg, chunkMax and a text field chunker, each OPTIONAL; an absent field means "the default". The §14 constraints MUST hold over the effective values (each absent field taking its default); a set that violates them is an error, exactly as an unknown field is. The chunkMin/chunkAvg/chunkMax triple is the bounds of the fastcdc chunker (§9.4).

Writers MUST write meta:format when initializing a store, before the first block or ref. Readers MUST consult it before interpreting any block or ref: an absent key, an unknown map field, an unrecognized v, or an unsupported hash is an error. (An unrecognized v here is the future-version signal, distinct from corruption — see §15.1.)

The metadata value is not content-addressed and is never hashed; rewriting it is an administrative act. Changing hash on a non-empty store is never valid (§3.4: one store = one hash).

Reserved keys. Every key outside the three key spaces above — including all of meta: beyond meta:format — is reserved for future versions of this specification. Writers MUST NOT create reserved keys; readers MUST ignore them.

#10.4 Hash function identifiers

A hash function identifier is the multicodec registry name of a multihash function (§2.5) — the registry, not this specification, mints identifiers. The store metadata records the name; every ref carries the corresponding code (§4); the two MUST agree.

identifierregistry codehash functionoutput length
"sha2-256"0x12SHA2-25632

sha2-256 is the default and the only function this version requires implementations to support. A deployment MAY use any other registered multihash function whose output length satisfies §3.4 ([20, 64] bytes; never the identity function, code 0x00), naming it by its registry name here and by its registry code in refs; two deployments interoperate at the store level only when they support the same function. An unregistered function has no name or code to carry and cannot be used.

unfs  A filesystem you can put in any key–value store.