alkblobs 0.2.2

Content-addressed blob storage: multi-hash put/get/verify store with pluggable backends, transport- and protocol-agnostic
Documentation
---
status: stable
last_updated: "2026-10-16 (flipped stable at the wave-12 pre-release review gate - the plan's release-readiness wave; eleven waves of implementation verify against it; aligned 2026-10-18 to the registry posture — ADR-018's family + ADR-020's no-default invariant)"
notes: "ADR-013 — family composition & reactive boundary; OQ-11 resolved, feature graph re-pinned to engine features. ADR-018/020 — the hash registry, never a default derivation"
---

# Overview

## What this crate is

`alkblobs` is content-addressed blob storage in the alk* family: a
put/get/verify store keyed under a registry of git-family content
derivations — canonical git-blob-sha-256, tolerated legacy SHA-1, and
the git-object derivation family (ADR-002/018) — with pluggable tier
engines (small blobs in a kv store per ADR-007/011, over-threshold
blobs on the `large` tier per ADR-008/009 — the tier formerly named
`fs`), split from any wire/ops-protocol layer. Network ops ride the
alkcall substrate behind a feature-gated `ops` module (ADR-001).
Every entry is keyed under a named derivation — the caller states the
algorithm at every address-forming boundary; the crate never picks a
derivation for its caller (ADR-020's invariant), and there is no
default put.

Deployment-relevant consumer facts — what each use case forces on this
crate — are pinned in [requirements.md](requirements.md) (REQ-1..4,
with the **node/pool/fleet/engine** vocabulary defined there). This
tree references REQs by ID; per-session re-derivation from vision prose
is exactly what that document exists to prevent.

## Why it exists — the extracted shared core

Two downstream projects were converging on the same storage stack, so it
is being built once, here:

- **alkgit** was paused mid-Phase-1 (its architecture is reviewed,
  `docs/architecture/` in the alkgit repo) to stop duplicated
  storage-layer work. Its object backend needs git's own oid derivation,
  a better small-object backend than its proposed default file layout,
  and a git-lfs-shaped large-blob story. Under the canonical-hash
  resolution (ADR-002, extended by the derivation family ADR-018) git
  objects are first-class pool entries with
  zero indirection.
- **alkfs** (Phase 0 pending) needs the appfile/workspace shape: small
  content in a fast kv store, large content on the filesystem, path→hash
  mapping above. The path-tree/manifest layer is alkgit's and alkfs's,
  not this crate's (ADR-004).

Both demanding consumers (p2p git replicators, agent workspaces) reduce
to the same shape: **a pooled CAS per node**, where repos and workspaces
are *sets of hash references*, not walled-off object stores — cross-repo
dedup by construction (ADR-005). That pooling property is why the
storage layer was pulled out of both consumers rather than built twice
or three times.

## Layer map

```
┌────────────────────────────────────────────────────────────┐
│ consumers (above this crate)                               │
│   alkgit object storage · alkfs path trees/workspaces      │
│   refs, manifests, path→hash maps, git semantics           │
└────────────┬───────────────────────────────────────────────┘
             ▼
┌────────────────────────────────────────────────────────────┐
│ ops surface   (feature "ops"; ADR-001)                     │
│   have/need + verified fetch + put; JSON control ops,      │
│   binary data channels; ACL via alkcall AccessControl      │
└────────────┬───────────────────────────────────────────────┘
             ▼
┌────────────────────────────────────────────────────────────┐
│ store core    (always on)                                  │
│   hashing & keys (ADR-002) · put/get/stat/range/pin seams  │
│   mark-and-sweep GC + liveness registration (ADR-005)      │
│   size-threshold dispatch (ADR-003); GC state owned        │
│   here; fleet state hosts on the fleet's kv engine         │
│   via the seam (ADR-010/012)                               │
└───────┬───────────────────┬───────────────────────────────┘
        ▼                   ▼
┌──────────────────────┐  ┌─────────────────────────────────┐
│ kv tier (ADR-003/    │  │ large tier (ADR-003/008/011;    │
│ 007/011)             │  │  formerly "fs")                 │
│   sqlite ("sqlite";  │  │   local (ungated; ADR-013 §4)   │
│    default engine)   │  │   pg-lo ("pg-lo"; ADR-009)      │
│   postgres ("postgres")  │                              │
│   mem ("mem"; tests +     │                             │
│    contract reference)    │                             │
└──────────────────────┘  └─────────────────────────────────┘
        ▲                   ▲
        └── one Backend trait (ADR-003/010: read cursor +
            staged put I/O seams); engine = per-node
            constructor choice (ADR-011's table)

   transport: none, anywhere. alkgit/alkfs or the embedder dials;
   an alkcall Connection is handed in at the ops boundary.
```

## Dependency posture

| Layer | Dependencies | Notes |
|---|---|---|
| base crate (store core + tier dispatch + GC) | `tokio`, `thiserror`, `parking_lot` | no alkcall, no reactive substrate, no serialization frameworks |
| engine features (ADR-010 §1, extended to every engine by ADR-013 §4) | each engine pulls only its own driver cluster | `sqlite` (default-on, `rusqlite`), `postgres` (default-off, `tokio-postgres`+`deadpool-postgres`), `mem` (default-on — ADR-013 §4's 2026-10-15 amendment, nothing), `pg-lo` (default-off, the shared pg cluster); `local` is ungated (dependency-free). Every engine-feature combination is a coherent node shape (e.g. `large`=pg-lo + kv=sqlite, ADR-010 §1); the feature-gated engines' conformance/sweep-safety gates run under `--all-features` (ADR-007/009) |
| `ops` feature | adds `alkcall` (feature-gated, default-off) | JSON op specs + binary channels + `AccessControl`; ADR-001 |
| consumer crates | depend on alkblobs (and on alkcall directly when they speak ops) | alkgit, alkfs — which also deploy the sibling reactive substrate beside it (below) |

**The sibling reactive substrate (ADR-013 §1/§3):** **alkstore**
(v0.1.0 — notify/locks/queues/streams/outbox/scheduler behind its own
engine crates) ships beside this crate, not under it. The core keeps
no alkstore dependency and no reactive machinery: cadence and
invalidation wiring are deployment-layer composition above the facade
(ADR-005's "embedder owns cadence" stands, with the sibling the
named substrate; sweep outcomes are report data — ADR-012 §1 — whose
pinned consumer is that wiring). Cross-store atomicity is unachievable
by construction; the composition properties are idempotent re-puts,
windowed deletes, and reports-as-data (ADR-013 §3, the citable
downstream contract).

Tiers are **constructor composition**, not features (ADR-013 §4);
engine features as in the table above. The default build is
batteries-included (sqlite default-on — opt-out rather than opt-in,
the alkgit feature-model pattern), and a pg-pure build (sqlite off;
kv=postgres, `large`=pg-lo if wanted) is a supported configuration
this crate does not default to — REQ-2's consolidation posture
(ADR-009), now carried by the feature graph instead of parked on it.
Wasm: the tier
engines are not a wasm story; a wasm client is a consumer of the `ops`
surface (which rides alkcall, itself wasm-clean), never an in-process
embedder (ADR-001 §Consequences).

## Non-goals (each enforced by an ADR)

- No wire framing, op dispatch, ACL, or transport inside the store layer
  (ADR-001).
- **No adopted iroh-blobs wire surface — no tickets, no postcard, no
  provider protocol (AGENTS convention 7; ADR-001 §Context).**
- No namespaces-as-physical-storage; the pool is flat, namespaces are
  consumer-level reference sets (ADR-005).
- No manifest/path-tree/vfs tables; consumers never implement a
  Backend to model paths (ADR-004).
- No chunk-tree/CDC storage encoding; whole-file CAS is the store's only
  granularity (ADR-006).
- No reactive coordination machinery in-core — no notify, no scheduler,
  no queues, no invalidation hooks: reactivity is the sibling
  substrate's (alkstore), composed at the deployment layer above the
  facade (ADR-013 §1).
- No per-repo or per-namespace walled stores — one pooled CAS
  (ADR-005).

## Design Decisions

| ADR | Decision | Summary |
|---|---|---|
| [001](decisions/001-substrate-posture-and-ops-placement.md) | Substrate posture & ops placement | store stays substrate-free; ops ride alkcall feature-gated |
| [002](decisions/002-canonical-hash-and-key-encoding.md) | Canonical hash + key encoding | git-blob-sha-256 canonical; algorithm in-key; one-way door |
| [003](decisions/003-backend-contract-and-dispatch.md) | Backend contract & dispatch | two backends, size-threshold routing, no migration |
| [004](decisions/004-two-backends-no-third.md) | Two backends, no third | manifest layers are consumer-side |
| [005](decisions/005-pooled-cas-and-gc-mechanism.md) | Pooled CAS & GC | flat pool, registered liveness, delete windows |
| [006](decisions/006-verification-posture-and-transfer-encoding.md) | Verification posture | whole-blob verification; chunk trees excluded by scoping |
| [007](decisions/007-two-kv-engines-sqlite-and-postgres.md) | Two kv engines | sqlite default + postgres behind one trait; one relational engine + large tier per node |
| [008](decisions/008-trait-size-probe-fleet-gc-and-large-engines.md) | `size` probe + fleet GC + large-tier engines | trait length probe; fleet pins/sweeps engine-backed; the large tier (now `large`) engine-selectable |
| [009](decisions/009-pg-lo-large-tier-engine.md) | pg-lo large engine | postgres Large Objects as the large tier's second engine, admitted on POC #7; a fleet node can run postgres-only |
| [010](decisions/010-backend-io-seams-and-fleet-state-home.md) | I/O seams + GC-state home | read cursor / staged put; store-core-owned GC state via the engine-state seam; kv fleet-validity rule; pin-token renewal |
| [012](decisions/012-pre-decomposition-consistency-rulings.md) | Sweep-abort shape + composition rulings | sweeps never error (`GcAborted` retired, `GcRefuse` for direct-delete); token wire shape; fleet mode = constructor declaration; GC state hosts on the kv engine; sqlite seam reduction |
| [011](decisions/011-vocabulary-tiers-engines-and-fleet-state.md) | Vocabulary + rename | tier/engine/instance/node/fleet pinned; mem joins the kv tier; large tier renamed `large`; constructor modes |
| [013](decisions/013-family-composition-reactive-boundary-and-feature-graph.md) | Family composition & reactive boundary | no sibling dependency in-core; sweep lease semantics (provider: raw advisory clause); co-tenancy + no-cross-store-atomicity; OQ-11 resolved (engine features pull own drivers) |

## Open Questions

Tracked in [open-questions.md](open-questions.md). Ones affecting the
crate level:

- **OQ-07**: how alkgit's object-storage seam consumes alkblobs
  (externally-owned: alkgit's architecture process)
- **OQ-08**: alkfs requirement intake (externally-owned: alkfs Phase 0)
- **OQ-09**: default namespace visibility in network ops —
  resolved closed-by-default (deny-unlisted; reopen only on a named
  open-read requirement)
- **OQ-10**: second kv engine — resolved by ADR-007 (sqlite + postgres
  shipped behind one trait; per-node constructor choice)
- **OQ-11**: pg-only kv node feature graph — resolved by ADR-013 §4
  (engine features pull their own driver clusters; `sqlite` default-on;
  pg-pure builds supported, non-default)
- **POC #7** (`docs/research/poc-pglo-findings.md`): pg Large Objects
  as the large tier's `pg-lo` engine — **passed** 2026-10-03; admitted as
  a shipped engine by ADR-009 (REQ-2's consolidation option)

## References

- `docs/research/phase-0.md` — the converged Phase 0 record; inputs to
  every ADR above
- `docs/research/poc-trait-dispatch-findings.md`,
  `docs/research/poc-largeblob-findings.md`,
  `docs/research/iroh-blobs-eval.md` — the evidence trail
- `docs/sdd_process.md` — the process governing this tree
- `/workspace/@alkdev/alkgit/docs/architecture/backend.md` — the
  reviewed consumer-side seam that OQ-07 maps onto alkblobs
- AGENTS.md — family conventions (conventions 4-8 gate the decisions)