# ADR-001: Substrate posture and ops-surface placement ## Status Accepted ## Context Phase 0 settled that a network ops surface for the blob store exists and rides alkcall (OQ-BL-01). Two residuals were left: 1. **Placement** — feature-gated `ops` module in this crate vs a sibling crate. Phase 0's lean for the module was gated on "deciding input: what alkgit's replicator wires first" — a deferral pattern a review of the convergence has rejected as Schrödinger's code: alkgit is paused *because* this crate is being built; "what the paused consumer does first" never becomes a deciding input. The deferral would never collapse; the decision must stand on evidence in hand. 2. **Deviation framing** — the user-level concern that a crate whose store layer carries no alkcall dependency *deviates from the family pattern* (alktty, alktunnels, alksocks all depend on `alkcall = 0.8.x`). If this is a deviation, its rationale must be explicit rather than assumed; if it is not, the conformance claim must also be explicit, or every future session re-litigates it. Also loaded here: the user-level concern that the family pattern must be stated so the two *coming network consumers* (alkgit, alkfs) — which will certainly use alkcall — see this crate fitting under it, not beside it. ## What is actually decided ### 1. The family pattern is "nothing invents wire/ops/ACL", not "everyone depends on alkcall" The family pattern (conventions 4–6) is: wire framing, op dispatch, and authorization are invented at most once in the family — by alkcall — and consumed everywhere else. Whether a crate *depends* on alkcall is then a function of whether it faces the network, not of family membership: - alkcall itself: zero sibling deps (it *is* the substrate; the pattern's home). - alktty / alktunnels / alksocks: depend on alkcall because they define network-facing protocol layers. - alkblobs' **store layer**: does not face the network. It is a library seam — the same posture as alkcall's own `Connection` (handed in, not dialed) and alktty's `TtyBackend` (backend-injected). A store cannot carry alkcall without violating convention 4 (substrate-agnostic by construction): the store must not know whether bytes arrive over a network. - alkblobs' **ops surface**: faces the network; rides alkcall like every other network-facing family member. The store layer's alkcall-freeness is therefore **conformance, not deviation**: it is the pattern applied to a layer that has no network side. The deviation framing dissolves once layers are named — a crate may contain both a substrate-free core and a substrate-backed shell, as this one does. What would be an actual deviation — an ops surface that invents its own framing/ACL to avoid the alkcall dependency — is rejected. ### 2. Why alkcall for the ops surface (kept from Phase 0, now recorded) Both op shapes the blob family needs exist there with validation: JSON ops with schema validation + typed error schemas (alkcall ADR-016), and binary streaming via `channel_open` (alkcall ADR-047) in both directions (Sub ADR-021 = verified fetch; Pub/Sink ADR-046 = put). ACL maps onto `AccessControl` (alkcall ADR-011) + the ADR-017 privilege model; an in-band scheme would be a second authorization story (convention 6). Not adopting iroh-blobs' wire surface ("tickets", postcard, provider protocol) remains deliberate (convention 7) — the choice is not "iroh's protocol vs nothing", it is "the family substrate vs inventing one". ### 3. Placement: feature-gated ops module, decided (no deferral) The ops surface ships as an `ops` module gated behind feature `ops` (default-off; the alkcall dep is feature-gated so the base crate stays lean — convention 8). Decided on evidence in hand: - The op family is **store-shaped** (hashes in, streams out), not policy-shaped; a sibling crate buys nothing until a second store-adjacent op family appears. - Convention 8 is established family precedent (alktunnels/alksocks feature-gate their alkcall-facing layers the same way). - Module→sibling promotion, if ever needed, is mechanically additive — the types move, the consumers re-point; no stored bytes change. The cost of being wrong is bounded and small. Promotion criteria (documented, not a hedge): if a second store-adjacent op family appears outside this crate's scope (e.g., an alkfs sync op family that wants blob ops but not this crate's registration), the ops module may be promoted to a sibling crate. That would be a new ADR — the decision recorded here is where it ships *now*. ### 4. The store layer never grows wire concerns — even in ops code The ops module is a consumer of the store facade, not a side door: all ops go through put/get/stat/read_range/pin (ADR-005, ADR-006). There is no ops-internal path touching backends directly. This is what makes the claim "if the ops surface were re-homed, the store loses nothing" true rather than aspirational. ## Consequences **Positive** - Family conformance is explicit and traceable; no future session re-litigates "why doesn't the store use alkcall". - The base crate builds lean and substrate-free; the ops story exists without forcing an alkcall dependency onto non-network consumers. - alkgit/alkfs integrate the store core and the ops shell à la carte, with the authorization story unified in alkcall. **Negative** - The feature-gate is a configuration surface to preserve in CI (default + all-features builds; see AGENTS conventions). - Two-layer naming ("alkblobs core" vs "alkblobs ops") must be kept tight in docs so consumers don't mistake the shell for the core. **Neutral** - alkgit/alkfs will still carry *their own* direct alkcall deps for their protocol layers; nothing about ADR-001 constrains them. ## References - `docs/research/phase-0.md` OQ-BL-01 (substrate settlement, promoted into this ADR) - `docs/sdd_process.md` (deferral policy — Schrödinger's-code rule) - [overview.md](../overview.md); [ops-surface.md](../ops-surface.md) - alkcall ADRs 011/012/016/017/021/046/047/050 - ADR-003 (backends stay substrate-free too); ADR-005 (facade-only ops access); ADR-006 (verified fetch)