--- id: ops-fetch name: blobs/fetch — the verified Sub op and the fanout seam's consumer side status: pending depends_on: [ops-foundation, ops-call-harness] scope: moderate risk: medium impact: component level: implementation tags: [wave-7, ops, fetch, fanout, verification] --- ## Description `blobs/fetch` (ops-surface.md §Op family) — the server→client streaming op: `{namespace, digest, ranges?}` in; **verified bytes out** over the binary data channel. - **The op shape**: `OperationType::Sub` (alkcall ADR-021's streaming handler kind) with the **`channel_open` marker** (alkcall ADR-047) — the binary data plane rides a `channel_open`-allocated channel; the establishment phase (alkcall ADR-049) carries the fetch's ack shape in the `Establishment` reply fields as the wire requires. The **read** action (ACL exactly like stat/have — closed-by-default). - **Verified streaming** (ADR-006; ops-surface.md): the *consumer* hash-checks the received whole against the carried digest — whole-blob verification; there are no per-chunk or per-range checks derivable under the canonical digest. The handler streams from the facade only: `Store::get` (the whole) or `Store::read_range` (the ranged form). **Ranged fetch** (`ranges?`): the response carries the same ADR-006 slice-digest convention `read_range` returns — SHA-256 over exactly the returned bytes, out-of-band. - **The fanout seam's consumer side** (ops-surface.md; POC #3 finding A3): the fetch handler consults the foundation's fanout registry — a live broadcast for the digest → a **subscriber arm** taps it; no live broadcast, or one that ended (a **late joiner**) → the normal post-commit verified `get` — identical bytes under CAS. The slow-subscriber policy (drop-and-late-join) is ops-layer policy, not a store concern. Named design point: **the producer side's shape** — the put handler's stream reader as the canonical producer (serve-while-persisting, A3's shape), and whether fetches also produce read-side broadcasts (concurrent fetchers of one stored digest sharing one store read) — decide and record against the A3 text. - **The miss shape**: a fetch of an absent digest is the typed missing-error schema (the need announcement *is* the fetch miss — the have/need diff is caller-side; no separate need op exists). - **Tests through the harness**: the whole-blob verified round-trip (byte-exact; the client's hash-check passes); the ranged form (bytes + slice digest matching `read_range`'s convention); the fanout consumer side (a live broadcast tapped — the subscriber receives while the producer runs; a late joiner degrades to the get path — asserted by which path served); the miss shape; the ACL denial (no read grant on the namespace → denied). ## Acceptance Criteria - [ ] fetch exported as a Sub spec/handler pair with the `channel_open` marker + ALPN; the establishment reply fields carry the specced ack shape - [ ] Whole-blob verification: the received whole hash-checks against the carried digest (the consumer-side discipline documented on the op; the server streams verified-store bytes) - [ ] Ranged fetch carries the ADR-006 slice-digest convention (out-of-band, over exactly the returned bytes) - [ ] The fanout consumer side: a live broadcast → subscriber arm; a late joiner → the verified get path; the producer-side design point decided and recorded - [ ] The miss shape is the typed missing error; the have/need diff stays caller-side (no need op invented) - [ ] ACL: the read action; closed-by-default verified by test - [ ] Handlers touch only the store facade (no backend side-door) - [ ] Harness tests green; `cargo test` (default) green; `cargo test --features ops` green; clippy/fmt green on both shapes; `cargo doc --no-deps` clean ## References - docs/architecture/ops-surface.md (§Op family — the fetch bullet + the fanout text; the need-half note) - docs/architecture/decisions/006-verification-posture-and-transfer-encoding.md (whole-blob verification; the slice-digest convention) - docs/architecture/decisions/008-trait-size-probe-fleet-gc-and-large-engines.md §3 (the re-routing topology this op serves — large-blob access through a storage node) - docs/research/poc-largeblob-findings.md finding A3 (the fanout seam: one reader, store arm + subscriber arms; late joiners degrade to get-after-commit) - alkcall (upstream checkout): ADR-021 (streaming handler for subscriptions), ADR-046/047/049 (the sink kind, the channel_open marker, the establishment phase), `src/channels/client.rs` - tasks/ops-foundation.md (the fanout registry's mechanics), tasks/ops-call-harness.md (the harness) ## Notes > To be filled by implementation agent ## Summary > To be filled on completion