Files
alkblobs/docs/architecture/overview.md
T
glm-5.3-flash 6f6ab19b8e docs(architecture): fix YAML-invalid frontmatter; changelog text moves to notes
- last_updated annotations containing ': ' ('family composition: ...',
  'feature graph re-pinned: ...', 'REQ-5 added: ...') broke Gitea's
  frontmatter parser in docs/architecture/README.md,
  backends-and-dispatch.md, and requirements.md
- last_updated is now a bare date; the annotation lives in a quoted
  notes field across all annotated architecture docs
- convention documented in .opencode/agents/architect.md: bare
  last_updated date, quoted notes, quotes required for ': ' scalars

Verification: all docs/**/*.md frontmatter parsed with yaml.safe_load
(16/16 OK); cargo test, clippy --all-targets -D warnings, fmt --check pass
2026-10-11 17:20:50 +00:00

191 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
status: draft
last_updated: 2026-10-11
notes: "ADR-013 — family composition & reactive boundary; OQ-11 resolved, feature graph re-pinned to engine features"
---
# Overview
## What this crate is
`alkblobs` is content-addressed blob storage in the alk* family: a
put/get/verify store keyed under one canonical content hash (the
git-family derivation; legacy SHA-1-tolerant), 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).
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) 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-off, 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)