Dissolve OQ-10's circular "standing offer" framing and record the engine decision the POC evidence already supported: - ADR-007 (new): kv tier ships sqlite (default) + postgres (feature `postgres`, default-off), constructor-selected per node; records the deployment economics (classical kv+fs+relational downstream stack collapses to one relational engine + fs), the pg impl contract (POC #5 B5 posture deltas), the CI sweep-safety gate under --all-features, redb ruled out (POC #6), and posture defaults as deferred cost in the ADR-006 pattern - ADR-003: amended status (engine pin widened by ADR-007); kv entry notes the widened engine set - ADR-004: reconciliation note — the "two backends" count is tiers, not engines - backends-and-dispatch.md: tier≠engine distinction, pg engine section, co-tenancy note (same-file consumer writes share sqlite's single-writer ceiling), substitution-door precedent - open-questions.md: OQ-10 resolved-by-ADR-007, with the why-this-is-not-Schrödinger's-code record (a deployment running alkblobs cannot pre-exist the pg engine — OQ-09 precedent); parked index narrowed to OQ-07/OQ-08 - overview.md: layer map + dependency posture reflect the `postgres` feature; README.md: ADR-007 in tables + corollary applications - phase-0.md / pg+redb findings: promotion + supersession notes Trigger framing survives only as deployment guidance (which engine a node chooses — topology, not throughput), not as a gate on the crate's own work. Verified: cargo test, clippy --all-targets -D warnings, fmt --check. Docs-only change (Phase 1 architecture); no implementation yet.
5.7 KiB
ADR-004: Exactly two backends; manifest layers stay above the crate
Status
Accepted
Context
An early Phase 0 framing — "using two backends is ugly" — shaped several rounds before being edited away, but the correction it was reaching for arrived only in the Phase 1 review, and needs recording so no future round reabsorbs the concern incorrectly:
- There are always two physical backends in this problem: storage for small content (fast, kv/sqlite) and the filesystem for large content. This is not ugly, it is the measured shape of the problem (POC #3 A4: crossover ~128-256 KiB; SQLite ~9-10× faster below it).
- The "two backends is ugly" sentiment was actually about a third layer in the old alknet-filesystem research — the virtual filesystem / appfile stack: path trees, branches, tombstones, filename↔hash maps living alongside the blob store (in that research, literally a co-equal SQLite store beside iroh-blobs).
If left implicit, this misframing has two failure modes: (a) this crate absorbs a path-tree/manifest layer ("three backends"), growing tables, schemas, and branching semantics that belong to consumers; (b) this crate shapes itself such that a future vfs builder must hack around it. alkfs is a pending consumer precisely of that manifest layer — this ADR is the boundary it will build against.
Decision
This crate ships exactly two production backends and nothing more;
every manifest/namespace/path-tree layer is a consumer, by construction
and by contract. (A non-production ephemeral mem tier exists for
tests — ADR-003; it is not a storage story and does not extend this
boundary.)
Scope note (post-ADR-007 reconciliation): the "two" here counts tiers, not engines. The kv tier is one backend; ADR-007 widened the engines behind it to a set (sqlite default + postgres, feature-gated) so a node's kv rides its relational engine of choice. The count that matters to this ADR — trait impls this crate ships as CAS tiers — remains two (kv + fs); a second kv engine does not make a third backend, and nothing in the engine set touches the manifest-layer boundary below.
-
Two production backends is the complete set. kv/sqlite (small)
- fs (large) cover the scale economics with measured evidence (ADR-003); no other physical tier is needed by any current or planned consumer's blob needs.
-
The "third backend" is not this crate's layer. The vfs/appfile shape — path→hash mapping, branches/tombstones/deltas per workspace, the alknet-filesystem lineage — is storage above the pool: reference sets over hashes (ADR-005's namespace machinery), not a Backend implementation. Consumers build it on their own storage (alkfs's path tree, alkgit's refs).
-
The store provides the seams the manifest layer needs, and nothing that forces it to hack:
- hash-addressed put/get with a verified, git-interop-compatible addressing scheme (ADR-002) — manifests reference entries by the same canonical addresses git uses;
- liveness registration so manifest-holds maps to GC roots without the store learning manifest formats (ADR-005);
stat/read_range— enough for serving path-resolved content and 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
Backendtrait. 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. -
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 topology choice), never a namespace flag inside one.
Consequences
Positive
- The crate's scope stays testable and small: two backends, one pool, one hash domain. No schema/versioning surface for manifest formats — consumers iterate on theirs freely.
- alkgit and alkfs build their (very different) mapping layers on one shared pool without either bending the store; their manifests interop at the address level by construction.
- The alknet-filesystem lineage is honored as shape guidance for consumers (branches, write sessions, chain walks), with zero of its storage-layer mechanics (SQLite path tables, honker wiring, CRDT sync) leaking in here.
Negative
- Consumers each re-build the small amount of manifest plumbing they need (a reference table and a root registration). This is deliberate: the manifest needs of alkgit (git refs) and alkfs (path trees) are different enough that a shared one would fit neither.
Neutral
- If a future consumer did want a first-party manifest layer, it
would be a sibling crate over this one (e.g.
alkfsitself), not an extension of this crate.
References
docs/research/phase-0.md(settled approach: pooled CAS; OQ-BL-05)/workspace/@alkdev/alknet/docs/research/alknet-filesystem/poc-summary.md— the historical lineage this ADR explicitly bounds (not a design input)- ADR-003 (the two backends), ADR-005 (namespaces as reference sets)
- overview.md (consumer map: alkgit, alkfs); backends-and-dispatch.md