6.2 KiB
6.2 KiB
id, name, status, depends_on, scope, risk, impact, level, tags
| id | name | status | depends_on | scope | risk | impact | level | tags | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| ops-put | blobs/put — the verified Sink op, the pin hand-over, and the {token, digest} response | pending |
|
moderate | high | component | implementation |
|
Description
blobs/put (ops-surface.md §Op family) — the client→server streaming
op: {namespace, digest?, len?} offer + the byte stream in; the
server verifies against the canonical derivation before commit;
the response is {token, digest} (ADR-012 §2).
- The op shape: the Sink handler kind (alkcall ADR-046's
Pub/Sink — the client→server streaming shape) with thechannel_openmarker (alkcall ADR-047); the establishment phase carries the offer ack. The write action (ACL; closed-by-default). - Verification before commit (ADR-006; ops-surface.md): the
server hash-checks the received stream against the canonical
derivation (
H("blob <len>\0" + content)) before commit. A known-length offer (lenpresent) is the encouraged one-pass path; an unknown-length offer rides the store's pre-threshold buffering path (ADR-003's option (d) semantics — the facade'sput(None, …)form, the mid-stream stage flush and the restart-over-staged-bytes derivation pass riding unchanged). When the offer carriesdigest?, the derived key is checked against it — a mismatch rejects before commit (the offered digest is a claim; the derivation is the authority). - The pin hand-over (ops-surface.md §Pin hand-over; ADR-010 §4):
the put lands pinned (pin-before-publish — the facade's put
returns the
Pinguard); the handler owns the guard; the custody split is the foundation's design, realized here:- fleet: the guard drops at response completion — the pin row's expiry is the only protection from that moment (the TTL machinery stays live on the ops path); the token mints over the committed row (the token↔(key, owner) mapping, owner = the serving instance's id);
- non-fleet: the token registry takes custody of the guard
— the token is the in-process guard's handle; nothing expires;
the guard's fate is the custody lifecycle (conversion on the
embedder's confirmation, or process-lifetime hold).
The response returns
{token, digest}— both fields always present (ADR-012 §2):digestis the stored entry's canonical digest hex (the unknown-length putter cannot know it beforehand and needs it for every later op);tokenis the minted opaque handle. Known-length putters receive the same shape.
- 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 stream (never polled twice), the store arm persists, subscriber arms serve concurrent fetchers of that digest while the put lands (serve-while-persisting); the wiring into the foundation's fanout registry. Backpressure: bounded subscriber channels — a slow subscriber stalls the broadcast, which is correct for a put (the put must land); the drop-and-late-join escape is the fetch side's policy.
- The dedup re-put: CAS dedup means no new entry — the re-put
renews the pin row (fleet: the joint tx's upsert-renewal of
(key, owner)) or re-mints the custody guard (non-fleet); the re-put backstop's mechanics ride the facade's CAS unchanged. - Tests through the harness: the known-length round-trip (the response's digest matches the git derivation — the byte-exact standard); the unknown-length round-trip (the buffering path through the wire); the verification-failure shape (a byte-corrupted stream → no commit, the typed error, zero residue on both tiers); the offer-digest mismatch rejection; the hand-over custody split (fleet: the guard dropped at response — observable via the row's expiry path; non-fleet: the registry holds the guard — observable via have-present); the minted token renews via have's renewal form (the cross-task contract); the dedup re-put; the ACL denial (no write grant → denied).
Acceptance Criteria
- put exported as a Sink spec/handler pair with the
channel_openmarker; the establishment reply fields carry the offer ack - Server-side verification before commit; an offer-digest mismatch rejects before commit; a verification failure leaves zero residue on both tiers
- The response is
{token, digest}, both fields always present;digestis the canonical hex (the unknown-length putter's later-op handle) - The hand-over custody split implemented per ADR-010 §4 (fleet: the guard drops at response completion — the row is the signal; non-fleet: the registry holds the guard — nothing expires)
- The fanout producer side wired: one read of the incoming stream, the store arm + subscriber arms (serve-while- persisting)
- The dedup re-put renews (row upsert fleet-form / custody re-mint non-fleet)
- 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 put bullet; §Pin hand-over for remote puts — the cross-wire contract)
- docs/architecture/decisions/012-pre-decomposition-consistency-rulings.md §2 (the {token, digest} response; both fields always present)
- docs/architecture/decisions/010-backend-io-seams-and-fleet-state-home.md §4 (the guard drops at response time — the TTL-machinery-alive rationale; the non-fleet custody)
- docs/architecture/decisions/006-verification-posture-and-transfer-encoding.md (verification before commit)
- docs/architecture/decisions/003-backend-contract-and-dispatch.md (the staged-put contract; the unknown-length path the wire rides)
- docs/research/poc-largeblob-findings.md finding A3 (the fanout producer shape)
- tasks/ops-foundation.md (the token layer, the custody design, the fanout registry), tasks/ops-call-harness.md (the harness), tasks/ops-json-ops.md (the renewal form the minted token rides)
Notes
To be filled by implementation agent
Summary
To be filled on completion