feat(ops): blobs/fetch — the verified Sub op and the fanout seam's consumer side (wave 7)

- the open triple (fetch_openables): the channels/blobs/sub spec with
  the channel_open marker on the alk/blobs ALPN (read action,
  closed-by-default), the awaited establisher whose reply fields carry
  the ack {source, len?, slice_digests?}, and the pump handler over
  the channel's stream (bounded read windows; the fanout feed
  forwarded chunk-for-chunk)
- verified streaming per ADR-006: store-first consult, a live
  broadcast taps (the tap-time-set discipline documented; the
  re-fetch is the partial-feed degrade), the ranged form carries
  read_range's slice-digest convention over exactly the served bytes
- the miss maps onto ADR-049's vocabulary (channel:open_failed,
  reason unknown_resource, the digest in the message; the declared
  BLOB_MISSING contract disclosed on the spec); unverifiable reads
  refuse with handler_error, never stream
- the producer-side design point decided and recorded against the A3
  text: fetches never produce read-side broadcasts (module doc,
  op doc, ops-surface.md)
- five harness tests through the channel halves: the verified whole
  round-trip + consumer hash-check, ranged + ack digests, the
  live-broadcast tap served without a commit, the late joiner served
  by the store path, the typed miss, the closed-by-default denial

Verification: cargo test (default) + --features ops + --all-features
green; clippy -D warnings on all three shapes; fmt --check clean;
cargo doc --no-deps clean on both shapes.
This commit is contained in:
glm-5.3-flash committed 2026-10-13 00:43:52 +00:00
1 parent 5144b6a733
commit d927da2ee5
4 files changed
+954 -24

No files matched your search

+28 -8
View File
@@ -1,7 +1,7 @@
---
status: draft
last_updated: 2026-10-03
notes: "ADR-012 §2 — put response {token, digest}; have token-renewal form; fetch verification wording; non-fleet token scope"
last_updated: 2026-10-13
notes: "ADR-012 §2 — put response {token, digest}; have token-renewal form; fetch verification wording; non-fleet token scope; fetch establishment ack + miss mapping"
---
# Ops surface (alkcall-backed network operations)
@@ -79,17 +79,37 @@ JSON control ops (`Visibility` per ADR-001 §Decision):
Binary channel ops (registered via the `channel_open` marker):
- **`blobs/fetch`** (`Sub` — the server→client streaming op shape)
- **`blobs/fetch`** (`Sub` — the server→client streaming op shape;
open-op id `channels/blobs/sub`, the `alk/blobs` data-plane ALPN)
— `{namespace, digest, ranges?}` in;
verified bytes out: the consumer hash-checks the received whole
against the carried digest (ADR-006's whole-blob verification —
there are no per-chunk or per-range checks derivable under the
canonical digest; ranged fetch responses carry the same ADR-006
slice-digest convention `read_range` returns). Broadcast fanout
above the store:
one reader, store arm + subscriber arms (POC #3 finding A3); late
joiners degrade to a normal post-commit `get` — identical bytes under
CAS. Slow-subscriber policy (drop-and-late-join) is ops-layer
slice-digest convention `read_range` returns). The establishment
phase (ADR-049) carries the ack in the open reply's fields:
`{source, len?, slice_digests?}` — `source` names the serving path
(`store`/`fanout`); the whole form acks `len` from the store path
(the hash-check's expected whole) and nothing from a fanout tap (a
live put's tail; the hash-check runs at stream end); the ranged
form acks one slice digest per range, in request order, over
exactly the served bytes (over-reads clamp; the digest covers the
clamp). The fanout consult (POC #3 finding A3's consumer side): a
live broadcast → a subscriber arm taps it; no broadcast, or one
that ended (a late joiner) → the normal post-commit `get` —
identical bytes under CAS; a mid-put tap has no prefix, so the
consumer's hash-check on a partial feed fails and the re-fetch is
the degrade. Fetches never produce read-side broadcasts (recorded
against the A3 text — the put handler's stream reader is the
canonical producer; concurrent fetchers each ride their own facade
read). The miss (absent digest, no broadcast) is the typed
missing-error schema: the declared `BLOB_MISSING` contract, answered
on the wire as the establishment refusal (`channel:open_failed`,
reason `unknown_resource`, ADR-049's "the requested resource does
not exist"), the digest in the refusal's message. The need
announcement is the fetch miss — the have/need diff is caller-side;
no separate need op exists (the wire carries only concrete fetch
requests). Slow-subscriber policy (drop-and-late-join) is ops-layer
policy, not a store concern.
- **`blobs/put`** (`Sink` — the client→server streaming op shape)
— `{namespace, digest?, len?}` offer +