docs(architecture): clarify Backend trait scope and kv/sqlite framing

The backends spec read as if the Backend trait were a general storage
abstraction and 'two backends, exactly' banned other storage in a
deployment — so a downstream wanting sqlite manifests appeared forced
into implementing a third backend, the exact weld ADR-004 forbids.

- backends-and-dispatch.md: state plainly that the trait is a CAS-tier
  contract (immutable digest-keyed entries, GC-shaped list/delete) and
  never a host for manifest/index storage; consumer storage is
  beside-the-pool. 'Two, exactly' counts trait impls shipped, not
  storage per deployment. kv: tier name vs pinned sqlite engine made
  explicit.
- ADR-004: add the explicit corollary under the consumer-seams point.
This commit is contained in:
glm-5.3-flash committed 2026-10-02 08:13:29 +00:00
1 parent b18521a0e6
commit e403b22e26
2 files changed
+49 -5

No files matched your search

+38 -5
View File
@@ -1,6 +1,6 @@
---
status: draft
last_updated: 2026-10-01
last_updated: 2026-10-02
---
# Backends and dispatch
@@ -13,6 +13,25 @@ dispatch that routes between them. The trait is the crate's second
stable seam (with the key encoding, ADR-002): backends must survive
digest-layer evolution, so the boundary stays dumb.
## What the Backend trait is (and is not)
The trait is a **CAS-tier contract**: it accepts, serves, and enumerates
immutable content-addressed entries (`hash → bytes`), and its `list()`/
`delete()` shapes exist to feed GC (ADR-005). That is its whole job, and
the contract assumes it: complete-list-equals-sweep-safety only makes
sense for a store where every entry is a standalone immutable blob.
It follows that the trait is *not* a general-purpose storage abstraction,
and a downstream must never implement it to host data that is not
content-addressed blobs — a manifest table, a queryable index, or any
mutable/structured state. Such storage is **consumer-owned, beside the
pool** (a downstream's own sqlite/whatever, ADR-004): it needs no
digest-byte addressing, no `list()`-completeness, and no sweep
participation, so the CAS-tier contract fits it worse than it fits any
purpose-built option. The wrong turn to rule out is "my data isn't
blobs, therefore I implement a Backend for it"; nothing a consumer needs
is ever reachable through that door.
## The Backend trait contract (ADR-003)
- **Opaque byte keys, opaque byte values.** Backends never learn what a
@@ -42,12 +61,26 @@ digest-layer evolution, so the boundary stays dumb.
## Shipped backends
Two, exactly — this is the complete set the problem requires; the
"third backend" fear is a category error fixed in ADR-004.
Two, exactly — this counts *trait implementations this crate ships*, the
CAS tiers the dispatch routes between; it is the complete set the
problem requires, and the "third backend" fear is a category error fixed
in ADR-004. It says nothing about other storage existing in a deployment:
a downstream runs whatever else it needs on its own media, beside the
pool, above the store's seams (see "What the Backend trait is").
### kv backend (feature `kv`, default-on; sqlite)
### kv backend (feature `kv`, default-on; shipped engine: sqlite)
Small blobs. Evidence (POC #3 finding A4, first-party measured):
Small blobs — **`kv` is the tier name; sqlite is the pinned shipped
engine** (ADR-003's consequences carry that trade: sqlite's on-disk
format compat is upstream's guarantee). The shipped trait impl writes
plain content-addressed rows into it, nothing else. "Pinned engine, not
a kv abstraction" does not open a substitution seam inside this tier:
a downstream wanting a different engine there would be new ADRs (a new
impl, its own sweep-safety proof); and a downstream's *other* storage —
schemas, manifests, whatever it likes including some other sqlite file —
never comes near this tier at all, per "What the Backend trait is".
Evidence (POC #3 finding A4, first-party measured):
sqlite is ~9-10× faster than fs at 1-16 KiB (the git small-blob regime
— most git objects, workspace files, manifests), with the crossover at
~128-256 KiB where fs stops paying the B-tree row rewrite and wins.
@@ -57,6 +57,17 @@ boundary.)
large-file ranges;
- pinned batch puts — manifest-atomic multi-file writes are
protected in flight (ADR-005).
Explicit corollary (added after a downstream misread): "consumers
build it on their own storage" means a consumer's manifest storage —
sqlite or whatever else — lives *beside the pool*, never behind the
`Backend` trait. The trait is a CAS-tier contract (immutable
digest-keyed entries, complete list, GC-participating delete); it
cannot express manifest storage, so a consumer implementing a
Backend to host a manifest was never a live option this boundary
forecloses — the boundary forecloses the crate *absorbing* such
layers, and the "no third backend" count is trait impls this crate
ships, not storage that may exist in a deployment.
4. **Namespacing at the store level is logical only** (ADR-005):
reference tables above, flat bytes below. If a consumer wants
physical isolation per tenant/repo, that is two pool deployments (a