alkblobs 0.2.0

Content-addressed blob storage: multi-hash put/get/verify store with pluggable backends, transport- and protocol-agnostic
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
//! The ops surface's state substrate + the JSON control plane's
//! spec/handler pairs (ADR-001 §3 placement; the wave-7 foundation and
//! the ops-json-ops handler family): the feature-gated module's context
//! type, the pin-token layer, the namespace→liveness-source binding, the
//! crate-internal store-core renewal access, and the fetch fanout
//! registry — everything the op handlers ride — plus the JSON control
//! plane's exported pairs, `blobs/fetch` (the binary data plane's `Sub`
//! op), and `blobs/put` (the `Sink` op — the verified offer/stream/commit
//! path behind the module's `blobs/put` section).
//!
//! **The ops layer is a store-facade consumer** (ADR-001 §4): its
//! store access is the public facade plus exactly one crate-internal
//! seam — the renewal access (`Store::ops_pin_renew`/
//! `Store::ops_pin_check`, ADR-010 §4), which the *store core* owns
//! and this module consumes as an in-crate peer. There is no backend
//! side-door, no engine type on this module's surface, no public-API
//! widening for the seam.
//!
//! **The module's state sits in [`OpsContext`]** — the type the
//! handler pairs (later tasks) capture. It binds the store
//! (`Arc<Store>`), the module config ([`OpsConfig`]), the token
//! registry (the token↔(key, owner) mapping, process lifetime), the
//! per-namespace converter bindings, and the fanout registry. The
//! registries are also constructible standalone ([`FanoutRegistry`];
//! the token registry under its context) — the fanout registry's
//! mechanics unit-test standalone here.
//!
//! ## The pin-token layer (ADR-010 §4; ADR-012 §2)
//!
//! The token *is* the pin — there is no second pin state. The durable
//! state is the pin row (fleet) or the in-process guard (non-fleet);
//! the token is the ops layer's opaque handle over it, and the
//! token↔(key, owner) mapping is ops-layer in-memory state with
//! **process lifetime**: it outlives the minting handler (a renewal
//! must be answerable long after the `blobs/put` response completed)
//! and never outlives the process. The mapping's retention
//! discipline: an entry retires at **conversion-confirmed** (the
//! [`OpsContext::confirm`] verb's success removes it) or at **process
//! end** — the mapping carries no expiry machinery of its own. The
//! pin row's expiry is the fleet liveness signal, and nothing expires
//! non-fleet: the mapping's absence of TTL is deliberate on both
//! sides.
//!
//! **The admission posture (ADR-019, resolving OQ-16 — review-002
//! C-2): the registry admits up to its cap and refuses mints beyond
//! it; the non-fleet custody loop's attack shape is on record.**
//! The concrete shape (POC-L, deliberately unpatched in the consumer
//! POC and on record since): a hostile client streaming *conforming*
//! content and abandoning the confirm step each iteration — each
//! minted token a held pin and a registry entry, unbounded registry
//! growth plus never-expiring pin custody on a non-fleet store (the
//! content-mismatch variant rejects the put with no mint). The
//! decided landing: the registry measures live entries against
//! [`OpsConfig::token_registry_max_entries`] (default
//! [`TokenRegistry::DEFAULT_CAP`]; `usize::MAX` uncapped) and refuses
//! — never evicts — past it ([`crate::Error::RegistryFull`], a mint
//! refusal that drops a custodial guard and takes no custody), with
//! the `blobs/put` handler answering the already-committed put
//! honestly via the declared [`MINT_REFUSED`] wire error (the
//! committed digest in the details; the CAS-idempotent re-put is the
//! recovery). The registry's cap bounds its own resources — registry
//! memory and never-expiring custody; it does not police the loop's
//! *reach*: an authenticated writer can still fill the pool with
//! committed bytes between sweeps (the ACL write grant's story plus
//! the GC cycle). The mint policy at the deployer's door is the
//! embedder's responsibility; **for a door that cannot prove confirm
//! — the untrusted multi-tenant upload shape, the LFS pattern — the
//! fleet form is the safe posture** (the pin row's TTL bounds every
//! held pin with or without a confirm). The fleet TTL posture made
//! available non-fleet was rejected (ADR-019: the TTL is the fleet
//! core's row machinery, not a knob, and an expiry would break the
//! confirm verb's contract); re-opening it is a new ADR.
//!
//! **Named design point — the token's minting form (decided): a
//! CSPRNG id.** [`PinToken`] is 32 bytes of OS-entropy output
//! (`getrandom`, the `ops`-gated dependency) — unguessable by
//! construction, opaque by construction (no key, owner, or namespace
//! bytes ride the token). Rejected forms: a counter (sequential ids
//! are guessable — the token is a capability, and a guessable
//! capability is a broken one: renewal and confirmation are
//! token-authenticated acts) and a derivable form (anything derived
//! from key/owner/namespace is derivable by anyone who knows a put's
//! identity — equally broken, and no longer opaque). Wire form: hex
//! (the `{token, key}` response shape carries it as the `token`
//! field's string; lowercase canonical decode case-insensitive).
//!
//! **Named design point — the validity domain: the minting node.**
//! The registry is per-process state; there is no cross-node form.
//! Renewal probes must reach the node that minted the token; a fleet
//! peer's registry does not know it and answers
//! [`RenewalOutcome::UnknownToken`] — `present: false`, not an error
//! (ADR-012 §2's refusal discipline). A putter whose minting node
//! died is in ADR-005's in-flight-put loss case; the backstop is the
//! re-put (delete-then-recover, ADR-005), and routing renewals to the
//! serving node is a documented embedder duty, never new machinery.
//!
//! ## The guard-custody split (ADR-010 §4, the token layer's core
//! branch)
//!
//! The two custody forms are total — a token's entry is exactly one
//! of them, and [`OpsContext::mint_fleet`] /
//! [`OpsContext::mint_custodial`] enforce the pairing against the
//! bound store's fleet declaration:
//!
//! - **Fleet**: the mint records only the mapping (the serving
//!   instance's id as the `owner` half — the mint validates the
//!   passed owner against the store's, refusing a mint over a
//!   non-fleet store or with a foreign owner). The put handler's
//!   `Pin` guard **drops at response completion** — the handler's
//!   act, after the token is minted — because a guard held for the
//!   token's whole life would make the TTL machinery dead code on the
//!   ops path: the pin row's expiry, not the guard's presence, is the
//!   fleet liveness signal (ADR-010 §4), which is exactly why the
//!   renewal form exists.
//! - **Non-fleet** (any engine — sqlite, single-instance postgres,
//!   pg-lo, `local`, `mem`; ADR-012 §2's generalization): the
//!   registry **holds the guard** — the token is the in-process
//!   guard's handle, nothing expires — and minting is refused on a
//!   declared fleet store for the mirror reason. The guard's fate is
//!   the custody lifecycle: conversion at the embedder's confirmed
//!   registration, else process-lifetime hold.
//!
//! **Embedder-facing warning (review-001 S-5; the ADR-019 posture): a
//! custodial pin held forever is unreapable, and the registry caps
//! how many such holds exist.** The non-fleet registry holds the put's
//! guard until conversion-confirmed or process end — no expiry
//! machinery exists to release it. An embedder that mints the
//! custodial form and never calls [`OpsContext::confirm`] holds that
//! guard for the process's whole life: GC can never reap the pinned
//! entry, and the held `Pin` keeps the row live across every
//! arbitration. The confirm verb is the hand-over contract's closing
//! act — an embedder that intends a pin to outlive the process must
//! convert it (the converter registration is the embedder's own
//! liveness story); no code path confirms on the embedder's behalf.
//! The admission cap (ADR-019, above) bounds what such an embedder's
//! loop can hold — mints past the cap are refused, not silently held
//! — but every hold taken before the cap behaves exactly as warning
//! states, so the duty stands.
//!
//! ## The namespace→liveness-source binding
//!
//! Every ops payload carries an explicit `namespace` — the store core
//! never sees it (backends are namespace-blind); it selects the
//! liveness source and the ACL resource. The binding this module
//! ships is the embedder's converter seam: **per-namespace
//! registration of the converter the embedder plugs its registry
//! into** — [`PinConverter::convert`] is the registration act (the
//! embedder registers the key into its own registry), and the
//! conversion itself rides the crate-internal ordered-step primitive
//! (`RegistryCore::convert` — the pin's release and the converter's
//! registration are one ordered step under the store's arbitration
//! lock; a concurrent arbitration never observes
//! neither-pin-nor-registered). The converter runs **under the
//! arbitration lock** in the guarded form: it must be a quick,
//! non-blocking, sync act — the same duty a [`crate::facade::LivenessSource`]'s
//! delete-time probe carries.
//!
//! **Named design point — the conversion trigger (decided): an
//! embedder-called confirm verb.** [`OpsContext::confirm(namespace,
//! token)`] is the trigger; a probe-triggered conversion is rejected:
//! the have probe is a read-gated existence op whose caller is the
//! remote putter — a protection-lifecycle mutation keyed off it would
//! give a read op write semantics and put the registration act under
//! the putter's arrival, while the hand-over contract's shape is
//! explicit that the *embedder* observes the registration landed
//! (`blobs/have` returns present after registration) and then acts.
//! The duty text is the embedder's; the mechanism (this verb) is this
//! module's.
//!
//! ## The crate-internal store-core renewal access (ADR-010 §4)
//!
//! The renewal probe's composition (decided): **verify-the-mapping-
//! then-renew** — (a) the token lookup (the validity-domain
//! enforcement: an unknown token answers `UnknownToken`, present:
//! false, no renewal), (b) the namespace parity check (ADR-017 §3:
//! the mint records the namespace the put arrived under, and the
//! presented namespace must match it — confirm's refusal discipline
//! is the parity's shape; a mismatch answers `UnknownToken`,
//! mapping intact), (c) the digest check (one token binds one pin
//! row; a mismatch answers `DigestMismatch`), (d) the store core's
//! `Store::ops_pin_check` classifies the row (the wave-6 review's
//! W6-3 named consumer) — a row not live answers `Expired` with no
//! renewal write, (e) the store core's `Store::ops_pin_renew` —
//! its bool is the final answer: never resurrects an expired or
//! absent row, so a check/renew race lands on its `false` (`Expired`)
//! the same way. Renew-answers-the-verification (a bare
//! `pin_renew(key, owner)` answering the probe outright) was
//! rejected: the verify steps are where the validity domain, the
//! namespace parity, and the one-token-one-row binds are enforced,
//! and the W6-3 named consumer
//! is exactly the verify step's classifier. Under non-fleet the probe
//! answers **from custody**: a held guard is present, nothing expires
//! (ADR-012 §2), and no engine call runs.
//!
//! What never happens: a *consumer* reaching the seam, or a public
//! API widening for this. The have handler (the renewal form below)
//! rides [`OpsContext::renew`] and sees none of the engine.
//!
//! ## The fetch fanout registry (ops-surface.md; POC #3 finding A3)
//!
//! Per-digest broadcast state above the store: one reader (the
//! producer's own consumption — the put handler's stream is read
//! exactly once), the **store arm** (the producer's own persistence)
//! and **subscriber arms** (the [`FanoutRegistry::subscribe`] taps).
//! Channels are **bounded** ([`OpsConfig`]'s `fanout_channel_bound`,
//! the POC's 8): the backpressure discipline is that a slow
//! subscriber stalls the broadcast — correct for a put (the put must
//! land); subscriber count is bounded
//! (`fanout_max_subscribers`) and slow-subscriber handling is policy:
//! **`Stall`** holds the broadcast on a full channel (the put
//! producer's form), **`Drop`** is the drop-and-late-join policy — a
//! full arm is dropped, its consumer's channel closes, and the
//! consumer degrades to the late-joiner path. The **late-joiner
//! signal** is [`FanoutRegistry::subscribe`] answering `None` (no
//! live broadcast, broadcast ended, at the subscriber bound) or a
//! receiver's channel end: the degrade is a normal post-commit
//! verified `get` — identical bytes under CAS, either way.
//!
//! The producer wiring is the put handler's (the `blobs/put` section
//! below), the consumer wiring the fetch handler's (the `blobs/fetch`
//! section); the registry's mechanics are unit-tested standalone. `begin`
//! returning `None` means a broadcast for the digest is already live —
//! the producer continues store-arm-only (CAS guarantees the incumbent's
//! bytes are the digest's bytes), and a publisher's drop (finished
//! or abandoned) removes the entry.
//!
//! **Accepted-uncovered availability residuals (review-001 S-7)** —
//! the fanout's two griefing residuals are real, bounded, and
//! recorded as decisions, not oversights:
//!
//! 1. **Broadcast squatting under the unverified offered claim** —
//!    the put's broadcast begins pre-read (the producer-side shape,
//!    the `blobs/put` section), keyed under the *offered* key while
//!    the derivation under the key's own algorithm is still unverified
//!    against the stream, so a hostile putter can squat a key's
//!    broadcast
//!    and feed garbage to the concurrent fetchers' taps. **Integrity
//!    is intact** — the consumer-side ADR-006 hash-check is the
//!    barrier, garbage never verifies, and a failed check degrades to
//!    the verified re-fetch; the residual is the wasted re-fetches
//!    (an availability cost, accepted).
//! 2. **The `Stall` policy's read-peg** — a read-granted fetcher can
//!    tap a live put and simply stop reading: the bounded arm channel
//!    backpressures the broadcast, and with it the producer's
//!    streaming; no deadline exists anywhere in the put path. The peg
//!    is real while it lasts and bounded: alkcall's channel lifecycle
//!    (a disconnect/teardown closes the tap and the broadcast's send
//!    unblocks — the dead arm is removed), the put's own completion
//!    (the broadcast's end), and the subscriber bound capping who can
//!    tap at all.
//!
//! Neither note names machinery to build — that this is the record,
//! not a todo, is the point: a verified key for the broadcast and a
//! put-path deadline would both be hardening if a threat model ever
//! asks. The bounds named are the accepted coverage.
//!
//! **Named design point — the fetch is the fanout's consumer side, not
//! a producer (decided, against POC #3 finding A3's text).** The A3
//! seam is the *put stream's* broadcast: one reader (the put handler's
//! stream), the store arm (persistence) and the subscriber arms (the
//! fetches tapping it), with late joiners degrading to the post-commit
//! verified `get` — A3 never makes the get path a rebroadcast. `blobs/
//! fetch` keeps that exactly: a fetch taps a live broadcast or serves
//! from the store, and it **does not begin a read-side broadcast of its
//! own** — concurrent fetchers of one stored digest each ride their own
//! facade read, not one shared store read fanned out. The rejected
//! shape adds a second producer class whose lifecycle has no owner (a
//! read-side broadcast's end would be an arbitrary policy bolted onto
//! the store serve, and its arms hold memory exactly 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 one of the per-fetcher reads returns identical bytes. The put
//! handler's stream reader remains the canonical producer
//! (serve-while-persisting), and it decides the slow-subscriber policy
//! for the broadcast it begins.
//!
//! ## The renewal-cadence boundary (ADR-013 §1)
//!
//! This module ships the renewal *mechanism* (the have form's
//! handler, the JSON control plane's renewal arm, composing
//! [`OpsContext::renew`]). The TTL/3
//! *cadence* — the putter's renewal worker — is **embedder
//! composition above the facade** (scheduler/queues; ADR-008's
//! "embedder owns cadence"), never in-crate machinery: no renewal
//! worker, scheduler, or timer exists here, and none may.
//!
//! ## Dependency posture (convention 8; ADR-013 §4)
//!
//! `serde`/`serde_json` (the wire payloads' handling, the handlers'
//! concern) and `getrandom` (the CSPRNG) are `ops`-gated optional
//! dependencies: the default build's dependency tree is unchanged,
//! and `ops` pulls alkcall plus these, default-off. The base crate
//! stays lean.
//!
//! ## The JSON control plane (ops-surface.md §Op family; alkcall
//! ADR-016's spec/schema/handler shape):
//! [`stat_pair`]/[`have_pair`]/[`delete_pair`] — the three JSON ops
//! exported as spec/handler pairs the embedder assembles (the
//! `OperationRegistryBuilder` registration; the module's test tree
//! realizes the assembly pattern). The binary data plane
//! ([`fetch_openables`], [`put_openables`]) exports the same shape for
//! the channel ops (spec + establisher + handler triples).
//!
//! **The machinery ridden (never re-invented)**: JSON Schema validation
//! at the registry (`INVALID_INPUT` on violation, before the handler),
//! the typed error schemas (`ErrorDefinition`s), the ACL at op entry —
//! alkcall's `AccessControl`.
//!
//! **The model, named (ADR-017)**: the pool is a CAS global pool; a
//! namespace grant is capability-to-ask, not data partitioning. A
//! deployment whose threat model requires data isolation between
//! namespaces runs separate store instances (separate pools) — the
//! topology fact, already ruled in ADR-003, named on this surface too.
//! Read that before the per-namespace naming below could re-imply
//! partitioning: the `namespace` selects the ACL resource (and the
//! liveness source) only — content is addressed by digest, and digest
//! knowledge plus *any* namespace's read capability reaches the
//! content.
//!
//! The **namespace-as-resource mapping**:
//! `resource_type: "blob-namespace"` with the `resource_id_path`
//! selecting the payload's `namespace` field; stat/have carry the
//! **read** action; **unlisted namespaces deny by default** (OQ-09's
//! closed-by-default — no ACL entry ⇒ no read, no write; a grant per
//! namespace is the deferred cost, deliberately paid over accidental
//! exposure). This module never re-implements an authorization check
//! (convention 6).
//!
//! **The digest wire form is tag-prefixed key hex (ADR-020 §Decision
//! 2)** — the physical key encoding verbatim: `[registry byte] +
//! digest bytes`, hex-encoded — 42 chars for the 21-byte keys
//! (`0x02`/`0x03`), 66 for the 33-byte keys (`0x01`/`0x04`) — parsed
//! by the key layer's ingress (`Key::from_bytes` over the decoded
//! bytes, `wire.rs`'s `digest_ingress`): the same
//! rejects-never-guesses decoder the persistence boundary already
//! trusts — the tag byte validated, the digest length checked per
//! tag, never inferred. **Bare digest hex dies**: no implicit
//! default-family reading survives. Malformed input is rejected at
//! the schema (the protocol's `INVALID_INPUT`) and at the handlers'
//! ingress ([`INVALID_DIGEST`], declared) — rejects, never guesses.
//! The wire's addressable classes and the pool's are the same set
//! (coherence's invariant, review-003's correction), and the wire
//! couples only to bytes already frozen — ADR-002's one-way door is
//! the wire form. Gate and ingress agree on every input (the S-5
//! discipline: lowercase-only `[0-9a-f]` at both; nothing the gate
//! passes decodes differently below it, nothing the ingress accepts
//! the gate rejects).
//!
//! **The handlers are store-facade consumers** (ADR-001 §4): `stat`
//! rides `Store::stat` (kv first, then the large tier — the fall-through
//! general rule, ADR-012 §6.1), the non-token `have` rides `Store::has`
//! (ADR-008's has discipline — a pure existence probe), `delete` rides
//! `Store::delete` (the arbitration-then-delete path), and the renewal
//! form rides exactly the crate-internal seam this module already owns
//! ([`OpsContext::renew`]) — no backend side-door, no engine types.
//! `delete` is **`Visibility::Internal`** — never callable from the
//! wire (an external discovery answers the protocol's `NOT_FOUND`), not
//! namespace-gated (the `namespace` field exists for audit/triage
//! scoping), gated by the internal authority context the embedder
//! declares at registration (alkcall ADR-017: internal calls switch
//! authority context, never skip ACL).
//!
//! ## `blobs/fetch` (the binary data plane's consumer side: the
//! verified `Sub` op, ops-surface.md §Op family; alkcall ADR-047/049)
//!
//! The server→client streaming op: `{namespace, digest, ranges?}` in,
//! verified bytes out over a `channel_open`-allocated channel. The op
//! rides alkcall's machinery — `channels/blobs/sub` (the standard
//! `channels/<alpn>/sub` shape, `OperationType::Sub`), the
//! `channel_open` marker on the `alk/blobs` data-plane ALPN
//! ([`BLOBS_CHANNEL_ALPN`]; the same ALPN `blobs/put`'s `Sink` op
//! will share), the **read** action on the namespace resource
//! (closed-by-default, exactly like stat/have), and the awaited
//! **establishment phase** ([`fetch_openables`]' establisher) whose
//! success reply fields carry the wire ack.
//!
//! **The establishment ack** (alkcall ADR-049 amendment 3's reply
//! fields): merged into the open reply beside the wrapper-owned
//! `channel_id` — `{source, len?, slice_digests?}`. `source` names the
//! serving path (`"store"` / `"fanout"` — which path served, the
//! consumer's provenance signal). The **whole form** stores ack
//! `{source: "store", len}` (the entry's length: the hash-check's
//! expected whole) and a tapped broadcast acks `{source: "fanout"}`
//! (no length: the bytes are streaming from a live put; the
//! hash-check runs at stream end). The **ranged form** acks
//! `{source: "store", slice_digests[]}` — the ADR-006 slice-digest
//! convention, SHA-256 over exactly the returned bytes, one per
//! range in request order, carried out-of-band; the establishment
//! phase reads per range only to compute those digests (bytes
//! transient — the pump streams per range via a fresh `read_range`
//! pass, the double-read tradeoff accepted on the plan doc: memory-
//! bounded beats read-count on a network-facing path, and digests at
//! stream-end would change the ack contract); over-reads clamp at
//! the entry's end and the digest covers exactly the clamped bytes.
//! The ranged form is **bounded** — the schema's `maxItems` (the
//! `OpsConfig` `fetch_max_ranges` knob, an over-count open the
//! registry's `INVALID_INPUT`) plus a total-bytes bound summed over
//! the requested lens at the establisher's ingress (the
//! `fetch_max_total_bytes` knob; the schema cannot sum — an
//! over-bytes request is the establishment `HandlerError` refusal,
//! stable domain text).
//!
//! **The serving paths** (the fanout seam's consumer side, in consult
//! order): (1) the whole form consults the store first — a present
//! entry streams through the facade's verified `get` (a store hit
//! precedes a fanout tap: a commit that raced the broadcast's end is
//! the same bytes under CAS); (2) absent, a **live broadcast** for
//! the digest exists → a **subscriber arm taps it**
//! ([`FanoutRegistry::subscribe`]) and the pump forwards the arm's
//! feed — a tap has no prefix (the tap-time set per the A3 shape), so
//! a mid-put tap serves a suffix; the consumer's hash-check fails on
//! a partial feed and the re-fetch is the degrade — post-commit, the
//! same request is the store path, identical bytes; (3) absent and no
//! broadcast (or at the subscriber bound) → **the miss**. The ranged
//! form serves the committed entry only ([`crate::Store::read_range`]); a
//! not-yet-committed digest's ranged fetch is the miss (ranges have
//! no relationship to a broadcast's tap-window feed), never a fanout
//! tap. The tap path's griefing residuals (broadcast squatting under
//! the unverified offered key; the `Stall` read-peg) are the
//! accepted-uncovered record on the fanout registry section above.
//!
//! **The miss** (the need announcement; ops-surface.md's have/need
//! note — the diff is caller-side, no need op exists): a fetch of an
//! absent digest answers as the establishment refusal — alkcall
//! ADR-049's typed vocabulary, reason `unknown_resource` ("the
//! requested resource does not exist"), the digest carried in the
//! refusal's message; the declared [`BLOB_MISSING`] error definition
//! is the op's disclosed missing-error contract (the same code +
//! digest-details schema the JSON ops declare), and the wire shape a
//! consumer branches on is `channel:open_failed` +
//! `details.reason = "unknown_resource"`. Never a phantom channel
//! with an in-band error frame — that is exactly the ADR-049
//! antipattern.
//!
//! **Verified streaming is consumer-side** (ADR-006 decision 4): the
//! server streams verified-store bytes (the facade's `get` re-derives
//! canonically on the kv tier) and performs no second verification of
//! its own stream; the *consumer* hash-checks the received whole
//! against the digest it carried. Whole-blob verification only — no
//! per-chunk checks are derivable under the canonical digest; the
//! ranged form's integrity is the slice-digest ack. Handlers touch
//! only the store facade — no backend side-door (ADR-001 §4).
//!
//! ## blobs/put — the verified Sink op (ops-surface.md §Op family; alkcall ADR-046/047 + ADR-014)
//!
//! The client→server streaming op: the `{namespace, key, len}` offer
//! — both claim fields mandatory (ADR-020 §Decision 3: the wire never
//! discovers addresses) — rides the open invocation, the byte stream
//! rides the adopted channel; the response is `{token, key}`
//! (ADR-020 §Decision 4, the echoed key — both fields always present)
//! — or the refusal families
//! below, among them the admission cap's [`MINT_REFUSED`], whose
//! answer is an error over a **committed** put (the one response still;
//! the putter branches on its code). The op rides alkcall's
//! amendment-4 machinery: `channels/blobs/pub` over
//! [`BLOBS_CHANNEL_ALPN`] (the `channel_open` marker, `Pub`-typed),
//! registered through `ChannelCore::register_pub_openable` (the
//! responder-of-record adopt path — the wrapper awaits the handler and
//! merges the ack reply fields + the handler's result into the op's
//! single response), established by the awaited establishment phase
//! whose reply fields carry the offer ack. The **write** action on the
//! namespace resource (closed-by-default, exactly like read). The
//! putter-side contract is ADR-014 §2's settled shape: open
//! (allocating the channel locally), **stream without waiting for any
//! ack** (a sink op responds once, at stream end — a pre-stream wait
//! deadlocks), await the single response, branch on its result.
//!
//! **Verification before commit** (ADR-006): the handler rides the
//! facade's one derivation-explicit put seam (`put_as`) under the
//! offered key's algorithm; a stream that fails the key's own
//! derivation — wrong length, corrupt bytes — rejects with zero
//! residue on both tiers (the facade's stage hygiene carries the large
//! tier; the buffered kv arm is the same discard). The **offered key**
//! is a claim, the derivation under its own algorithm the authority:
//! the put's tee-reader opens at stream start under the offered key's
//! algorithm (the mandatory `len` feeds the preamble-carrying members'
//! derivations; the identity family hashes the preimage verbatim — the
//! check timing is a question the `Algorithm` answers, not the offer),
//! runs its derivation pass over the one stream read, and cross-checks
//! the claim **before the facade's EOF** — the one claim-check moment
//! for every family (ADR-020 §Decision 4) — rejecting the mismatch
//! with the facade's put killed pre-commit (zero residue). No deferred
//! post-check arm exists, and no unknown-length wire put exists (an
//! unmeasured remote stream buffers at its own layer before offering —
//! ADR-020 §Decision 3's recorded consequence).
//!
//! **The pin hand-over** (ops-surface.md §Pin hand-over; ADR-010 §4):
//! the put lands pinned (pin-before-publish), the handler owns the
//! returned `Pin` guard, and the custody split is the foundation's
//! design realized: **fleet** — the token mints over the committed
//! row ([`OpsContext::mint_fleet`], owner = the serving instance's id)
//! and the guard **drops before the handler returns the response** (the
//! row's expiry, not the guard's presence, is the protection — the TTL
//! machinery stays live on the ops path); **non-fleet** — the token
//! registry **takes custody** ([`OpsContext::mint_custodial`]): the
//! token is the in-process guard's handle and nothing expires. The
//! dedup re-put rides the facade's CAS unchanged (fleet: the joint
//! tx's upsert-renewal of `(key, owner)`; non-fleet: a fresh custody
//! re-mint) and answers `{token, key}` both times.
//!
//! **The admission refusal at the hand-over (ADR-019)**: the put
//! handler mints only after the facade's commit, so a mint refused at
//! the registry's cap ([`crate::Error::RegistryFull`]) answers an
//! already-verified, already-committed stream — the mint-contract
//! decision the cap had to make. The answer is the declared
//! [`MINT_REFUSED`] error: the stable text names what happened
//! (committed and verified; no token issued; no custody taken —
//! non-fleet the entry stands as unreferenced content a sweep may
//! reclaim, fleet the pin row stands and expires by its TTL) and
//! carries the committed key hex in its details (the CAS re-put
//! anchor). Operational mint failures (entropy, engine) keep the
//! `internal` mapping; only the cap refusal is the typed contract
//! answer.
//!
//! **The remote-putter role requirement (ADR-017 §4, made explicit)**:
//! **a principal performing remote puts into a fleet must hold
//! read+write on its namespace.** The pin hand-over contract *already*
//! requires a remote putter to probe `blobs/have` to confirm its
//! registration landed before relying on its pin (ops-surface.md step
//! 2); the role has been implicitly read+write since it shipped, and
//! the pairing is now stated rather than discovered by pin-expiry
//! loops. Renewal (the have-token form) rides read the same way, as
//! does the confirmation probe. The pairing is a role-level duty, not
//! a grant implication: the ACL matrix's `write ⊄ read` capability
//! algebra is untouched, no new op and no per-payload action mapping
//! exists, and no authorization is re-implemented inside this module
//! (ADR-001's alkcall-ridden ACL). The documented consequence: an
//! embedder granting put-without-read knowingly configures a role
//! that cannot complete the fleet pin duties.
//!
//! **The fanout seam's producer side** (POC #3 finding A3): the put's
//! stream reader is the canonical fanout producer — **one read** of the
//! incoming wire stream (never polled twice), each read chunk handed on
//! once to the **store arm** (the facade put's pipe feed) and to the
//! **subscriber arms** (a live broadcast begun under the offered key,
//! serving concurrent fetchers while the put lands). The broadcast
//! starts pre-read (no subscriber ever taps a put that already committed:
//! the tap is the establishment-time consult, store-first) and
//! `SlowSubscriberPolicy::Stall` is the put's form — a slow subscriber
//! stalls the broadcast, and the put must land; backpressure is the
//! bounded arm channel plus the store arm's bounded pipe (the facade put
//! consumes at its own pace; the putter's stream is backpressured by
//! both). A refused begin (a broadcast already live for the key) runs
//! store-arm-only — the incumbent's bytes are the key's bytes under CAS.
//!
//! ## The directory layout (the wave-8 split; review-001 D-1)
//!
//! The old single `src/ops.rs` is a directory module of pure moves:
//!
//! - `tokens.rs` — the pin-token layer plus the
//!   [`OpsContext`]/[`OpsConfig`] spine. Named design point (recorded,
//!   `ops` task Notes): the context rides the token layer — its
//!   custody verbs (the mint pairing, the renewal composition, the
//!   confirm conversion) reach the registry's private ticket state, so
//!   the alternative in the decomposition proposal ("or it lives with
//!   the token layer") is the one taken.
//! - `fetch.rs` — the fetch fanout (the registry, the
//!   publisher, the subscriber arms) and the `blobs/fetch` op with its
//!   pumps.
//! - `wire.rs` — the declared domain-error codes, the
//!   data-plane constants, the wire-error synthesis (`put_refused_error`,
//!   `store_internal_error`, et al.) and the
//!   shared wire-schema helpers.
//! - `specs.rs` — the three JSON control-plane pairs
//!   (`blobs/stat`/`blobs/have`/`blobs/delete`).
//! - `put.rs` — the `blobs/put` op: the offer/stream/commit
//!   path, the tee pipe, the hand-over.
//! - `harness.rs` (test) — the in-process alkcall harness machinery;
//!   `tests.rs` (test) — the shared test support and the e2e tree; the
//!   smaller unit tests ride their subject modules.
//!
//! Named design point (recorded, the `ops` task Notes): **no
//! `acl.rs`** — the namespace→resource mapping and the action tables
//! exist in the code only as the specs' `AccessControl` values and the
//! `resource_id_path` selections inside the spec constructors (the
//! closed-by-default enforcement is alkcall's, at op entry); a
//! separate module would have been an empty shell.

mod fetch;
#[cfg(test)]
mod harness;
mod put;
mod specs;
#[cfg(test)]
mod tests;
mod tokens;
mod wire;

pub use fetch::{
    fetch_openables, FanoutChunk, FanoutPublisher, FanoutReceiver, FanoutRegistry,
    SlowSubscriberPolicy,
};

/// Fuzz/test-only surface: the ranged-fetch bounds ingress and its
/// data carriers, exposed for the ops-ingress fuzz target's drive —
/// not contract surface, not consumer API, no stability guarantee.
#[doc(hidden)]
pub use fetch::{fetch_ranges_ingress, FetchRange, RangedBounds};
pub use put::put_openables;
pub use specs::{delete_pair, have_pair, stat_pair};
pub use tokens::{OpsConfig, OpsContext, PinConverter, PinToken, RenewalOutcome, TokenRegistry};
pub use wire::{
    BLOBS_CHANNEL_ALPN, BLOB_MISSING, DIGEST_MISMATCH, FETCH_OP_ID, GC_REFUSED, INVALID_DIGEST,
    INVALID_RENEWAL_FORM, MINT_REFUSED, PUT_OP_ID, PUT_REFUSED, VERIFICATION_REFUSED,
};