Files
alkblobs/docs/architecture/decisions/004-two-backends-no-third.md
T
glm-5.3-flash 4b5009d85a docs(architecture): Phase 1 spec tree — 7 specs, ADRs 001-006, OQ tracker
- overview/hashing/store-api/backends/gc/ops specs + README index,
  all draft status with YAML frontmatter and cross-referenced ADR/OQ
- ADR-001: substrate posture (store layer alkcall-free = family
  conformance, not deviation) + ops placement decided (feature-gated
  module, no consumer-waited deferral)
- ADR-002: canonical git-blob-sha-256 + key encoding — the declared
  wire-format ADR (one-way door)
- ADR-003: backend contract, sqlite/fs/mem tiers, pure-function
  dispatch, no migration, unknown-length buffering semantics pinned
- ADR-004: exactly two production backends; manifest/path-tree layers
  are consumer-side (the corrected two-vs-three-backends framing)
- ADR-005: pooled CAS, three liveness sources, delete windows with
  pin-before-publish + delete-time arbitration (race closed), no
  ambient scheduling
- ADR-006: whole-blob verification; chunk-tree encodings excluded by
  scoping, not deferred on a paused consumer
- open-questions.md: OQ-BL-01..06 resolved with ADR cross-refs; OQ-07
  /08 externally-owned, OQ-09 deferred(scope) with SDD tracker task
- AGENTS.md: status updated to Phase 1, gix-odb path corrected

Verification: cargo test / clippy -D warnings / fmt --check clean
2026-10-01 14:45:32 +00:00

4.6 KiB
Raw Blame History

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.)

  1. 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.
  2. 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).
  3. 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).
  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 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. alkfs itself), 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