- 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.
11 KiB
id, name, status, depends_on, scope, risk, impact, level, tags
| id | name | status | depends_on | scope | risk | impact | level | tags | |||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| ops-fetch | blobs/fetch — the verified Sub op and the fanout seam's consumer side | completed |
|
moderate | medium | component | implementation |
|
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 thechannel_openmarker (alkcall ADR-047) — the binary data plane rides achannel_open-allocated channel; the establishment phase (alkcall ADR-049) carries the fetch's ack shape in theEstablishmentreply 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) orStore::read_range(the ranged form). Ranged fetch (ranges?): the response carries the same ADR-006 slice-digest conventionread_rangereturns — 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_openmarker + 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 opsgreen; clippy/fmt green on both shapes;cargo doc --no-depsclean
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
Implementation agent, 2026-10-13. The op landed as fetch_openables(ctx) -> (OperationSpec, OpenEstablisher, OpenHandler) in src/ops.rs —
the Sub open triple the embedder assembles through
ChannelCore::register_openable_with_establisher (spec
channels/blobs/sub, OperationType::Sub, channel_open marker on
the alk/blobs ALPN BLOBS_CHANNEL_ALPN; the standard shape, so the
ALPN derives from the name and no explicit channel_open_alpn rides).
Exports: fetch_openables, BLOBS_CHANNEL_ALPN, FETCH_OP_ID.
Design points decided and recorded (op doc + the module doc's fetch section + ops-surface.md's fetch bullet):
- The ack rides ADR-049 amendment 3's establishment reply fields:
the open reply merges
{channel_id, source, len?, slice_digests?}.sourcenames the serving path (store/fanout— the test criterion "asserted by which path served" is wire-visible, and a consumer knows whether the stream is a live put's tail); the whole form acks{source: "store", len}or{source: "fanout"}(no length on a tapped broadcast — the hash-check runs at stream end); the ranged form acks{source: "store", slice_digests[]}— read_range's convention verbatim, one per range in request order, over exactly the served bytes (over-reads clamp; the digest covers the clamp). - The establisher's consult order: store
getfirst (a commit that raced the broadcast's end is the same bytes under CAS; the Blob opens into the plan at establishment, closing the establish→pump race window), then the fanout subscribe (a live broadcast taps), then the miss. The ranged form servesread_rangeon the committed entry only — a not-yet-committed digest's ranged fetch is the miss (ranges are the committed entry's shapes; no tap-window feed serves them). The ranged form's reads happen at establishment because the slice digests must ride the ack, which lands before the pump runs. - The miss maps onto ADR-049's established vocabulary — the only
op-plane failure surface an open op has: the establishment phase
refuses with reason
unknown_resource("the requested resource does not exist"), the digest in the refusal's message; the declaredBLOB_MISSINGErrorDefinition (same code + digest-details schema the JSON ops declare) is the op's disclosed missing-error contract.channel:open_failed+details.reasonis what a consumer branches on; a phantom channel with an in-band error frame (the ADR-049 antipattern) is never reachable. The same closure covers the refusal-to-serve-unverifiable shape: a store-core error mid-establishment (including a verification-failed read) refuses with reasonhandler_errorrather than streaming unverified bytes. - The producer-side design point (decided, against the A3 text): fetches do NOT produce read-side broadcasts — the put handler's stream reader is the canonical producer, concurrent fetchers of one stored digest each ride their own facade read. Recorded in the module doc's fanout section (a named design point paragraph), the op doc, and ops-surface.md's fetch bullet. Rationale: A3's fanout is the put stream's seam (its late-joiner note degrades to get, never get-then-rebroadcast); a read-side broadcast adds a second producer class with no lifecycle owner (its end would be an arbitrary policy, its arms hold memory where the large tier exists to avoid materializing), for a concurrency case bounded by the put window the put-side broadcast already covers; under CAS every per-fetcher read returns identical bytes.
- Slow-subscriber policy stays producer-owned (per-broadcast, set
by the put at
begin— the foundation's registry semantics); a fetch tap rides the broadcast's policy. The tap-time-set discipline (a mid-put tap has no prefix) is documented on the op + registry: the consumer's hash-check on a partial feed fails and the re-fetch is the degrade, identical bytes post-commit. - A mid-stream note for the whole form: between a store-hit
establishment and the pump, the entry cannot vanish (the Blob read
is already open; committed entries are immutable), so the ack's
lenis trustworthy; a mid-stream store/transport failure ends the pump (teardown closes the stream), the consumer's hash-check fails, and the re-fetch is the degrade.
Verification: cargo test (default) green, cargo test --features ops
green (the five harness tests: verified whole round-trip + consumer
hash-check; ranged + ack slice digests vs read_range; live-broadcast
tap served without a commit; late joiner served by the store path; the
typed miss; the closed-by-default read denial), --all-features green,
clippy -D warnings on all three shapes, cargo fmt --check green,
cargo doc --no-deps clean on both shapes (the private
FETCH_WINDOW doc link de-linked, the PGLO-1 class). The pure
--no-default-features --features ops build fails on main already
(engine-typed match arms upstream of this task, unreachable without an
engine feature) — pre-existing, not this task's regression.
Summary
blobs/fetch landed complete: the verified Sub op as the
fetch_openables open triple (channels/blobs/sub + the
channel_open marker on alk/blobs; read-gated, closed-by-default),
the establishment ack in ADR-049's reply fields
({source, len?, slice_digests?}), whole-blob consumer-side
verification per ADR-006, the ranged form carrying read_range's
slice-digest convention, the fanout seam's consumer side (live tap /
late-joiner store degrade, the path named in the ack), the miss as the
typed missing-error refusal (BLOB_MISSING disclosed; the wire shape is
ADR-049's unknown_resource), and the producer-side design point
decided and recorded against the A3 text (fetches never produce
read-side broadcasts). All acceptance criteria verified; harness tests
green; all gates green on both feature shapes.