Bitspark constellation
accepted source ↗

ADR 0022 — adstrate storage is space-partitioned: the space owns the bytes, not just the binding

  • Status: accepted
  • Date: 2026-06-14
  • Refines: 0021 (access is space-governed — this deepens "spaced binding, placeless payload" into "spaced binding, spaced storage")
  • Builds on: 0017 (the adstrate — interface in facts, payload off-record), 0016 (the CAS port)
  • Relates: 0013 (federation — the property this completes), 0019 (per-space accounting is a sibling of per-space visibility)

The space is the storage key, not a tag on a global pool. 0021 made access space-governed but kept the payload "placeless" — a spaced binding pointing into a single global byte store. That leaves the bytes unattributable (a global pool cannot say whose bytes these are) and makes the per-space-copy behaviour an emergent distribution artifact rather than the model. Key the byte store on (space, digest): a blob is stored within the space it is written to and owned by that space. The same bytes in two spaces are two owned objects; dedup is within a space, never across. The space's read/write ACL is the content's ACL, and every stored object belongs to exactly one space — so storage is attributable (quota, billing, lifecycle per space). The bare digest is still a space-independent identity; (space, digest) is now the storage, access, and accounting key.

Context

0021 fixed the one asymmetry in the substrate: a protected payload reached around the space model by bare hash. It made access keyed on (space, identity) and routed every read through the binding's projection. But it deliberately kept the payload itself "placeless… and should be: that is what gives dedup, integrity, and location-independence" (0021 §Decision.1) — the binding was spaced, the bytes were a global pool.

Two things were left unresolved by that split, and both point the same way:

  • Storage is unattributable. A global byte pool cannot answer "whose bytes are these, and who should be charged for them?" The binding says a space references a digest, but many spaces can reference the same pooled bytes, so there is no owner to bill, quota, or hold responsible for a blob's lifecycle. For a substrate that means to bill space owners for what they store, the accounting unit (the space) and the storage unit (the global pool) did not line up.
  • The per-space copy was an afterthought, not the model. 0021 itself noted, as a distribution consequence, that "global dedup relaxes to locality… each node hosts its own copy." That is the right behaviour — but framing it as an edge case left "one node, one global dedup pool" as the assumed default, and distribution as the exception that perturbs it. The default had the asymmetry 0021 set out to remove, just moved down a layer: the bindings were partitioned by space, the bytes were not.

The fix is the same move 0021 made for access, applied to storage: stop treating the space as metadata on a global pool, and make it the key.

Decision

1. The byte store is keyed on (space, digest). A blob is stored within the space it is written to. Storage is space-partitioned: every stored object is owned by exactly one space. (0021 already keyed access on (space, identity); this keys storage the same way, so the two no longer disagree about what a space is.)

2. Dedup is within a space, not across it. The same bytes written into two spaces are two owned objects. 0021's "global dedup relaxes to locality" is no longer a distribution-only relaxation — it is the model everywhere, including on a single node. Cross-space sharing of identical bytes is traded, deliberately, for the ability to attribute every object to one owner. Truly-global content is still expressed the 0021 way — bound (and stored) high, at root or a shared ancestor, so it projects widely — not by a flat shared pool.

3. The space's read/write ACL is the content's ACL. Reaching a blob needs read on the space; writing one needs write. There is no content-specific permission predicate: space read ⇒ CAS read for that space, space write ⇒ CAS write. The read path is 0021's gate unchanged (one authorized projection read, proof to root, uniform denial). The write path is its mirror — "may this caller assert content into this space" — and is the one piece not yet expressible client- side (see Consequences); it does not change the decision that the space governs the write.

4. Attribution is the payoff (space, digest) unlocks. Because every object belongs to exactly one space, storage is accountable per space: usage roll-up, quota, billing, and lifecycle are all keyed on the owning space. This is the concrete reason the space must be in the key and not a side fact — a global pool with reference-bindings can never recover it.

5. This realizes 0021's distribution rather than merely enabling it. 0021 argued the binding is delegable/federable because it is a fact, and the bytes are replicable because they are identity-addressed. With storage space-partitioned, a space now carries both the authority over its content and the ownership of its bytes — so a space (with its content) can be delegated, federated, hosted, or billed as a unit. The federation property 0013 protects is completed, not just kept open: no byte sits outside the space model.

6. This is the general adstrate rule. As with 0021, the same (space, identity) storage and accounting applies to tabula (state) and cursus (flow), not just corpus. corpus is the worked first instance.

Consequences

  • corpus's store is reworked to (space, digest). store.Put/Get/Has take a space; the on-disk layout nests the space path; store.Usage(space) is the per-space accounting primitive; malformed/traversal-unsafe space segments are rejected. The read gate is 0021's, now serving the bytes from the space's partition. (corpus PR store: key blobs by (space, hash).)
  • The caller-scoped write gate is open work. A symmetric "may this caller assert content into this space" check has no client-side helper in stele today (the read side has read.MayReadWithGrants; the write side only self-proves via AuthorizeAssert). Until a MayWriteWithGrants lands, corpus asserts the binding with its own space grant, so corpus's authorization bounds which spaces it writes — attribution holds, but the writer is corpus, not the end caller. Recorded as the next refinement.
  • GC becomes naturally per-space. 0021 deferred reclamation because a global "is this digest referenced anywhere?" needs the whole fact set. With storage owned per space, the natural shape is per-space: a space reclaims its own objects against its own bindings — a local, distribution-safe decision. Still deferred, but no longer requiring the global view.
  • "Placeless payload" (0021 §Decision.1) is refined, not reversed. Off-record ≠ placeless. The bytes keep their space-independent identity (the digest re-checks anywhere, untrusted replication still holds — integrity and location-independence are properties of the identity, unchanged). What gains a space is the storage location and the accounting. 0021's bare-hash identity references and (space, digest) access references are unchanged.
  • No repo or topology change. This refines the adstrate definition; it mints nothing and moves no layer. corpus/tabula/cursus remain draft adstrate members.

The Bitspark constellation — how the systems are built and relate.

GitHub