Files
alkblobs/tasks/ops-put.md
T
glm-5.3-flash 0b66bc55c8 review(ops): the wave-7 ops-vs-alkcall conformance gate — every gate check green, three inline findings fixed
The gate read the landed ops implementation against the pinned text
line by line (ops-surface.md; ADR-001/006/008/010 §4/012 §2/013 §1/§4;
OQ-09; POC #3 A3; ADR-014; alkcall 0.9 ADR-016/017/021/046/047/049)
and found it conformant on every check: the machinery ridden end to
end (the JSON plane's OperationSpec + JSON Schema + typed error
schemas; the channel_open marker, establishment, and Sub/Pub kinds
incl. amendment-4's marked-pub surface; AccessControl at op entry),
the ACL mapping closed-by-default verified by test, delete
internal-authority-only, the pin-token contract exact against
ADR-010 §4/ADR-012 §2 (the token is the pin; the renewal form's three
answers; the validity domain; the custody split; no in-crate renewal
worker), the no-side-door enumeration re-verified against the code,
and the fanout seam against the A3 text with the producer-side design
point recorded. The operator's task-hash log matched git log.

Findings fixed inline:

- W7-1: the fleet TTL row's no-leaked-renewal assertion raced an
  in-flight renewal write at the guard's drop (pre-existing wave-6
  harness race — reproduces with ops compiled out). Freezing is now
  asserted across two settled reads beyond the expiry horizon, with
  the never-moves-backwards guard; leak detection unchanged.
- W7-2: stale "wave 7" forward references rewritten to landed
  readings (ops.rs's fanout wiring note, store.rs's two re-routing
  notes, pg_lo.rs's orphan-recovery note, gc.rs's convert/pin_count
  clause — now reading the confirm verb as the ops build's consumer).
- W7-3: task-text hygiene — ops-fetch/ops-e2e-flows acceptance boxes
  checked (work verified and Summarized), ops-put's leftover
  placeholder lines removed, the audit's fleet/gc access correctly
  labeled the facade's crate-internal pin/fleet access.

No wave-8 amendments; the fuzzing target inventory proceeds per plan.

Verification: cargo test 73/19/3 (default); --features ops 112/19/3;
--all-features 271/125/4 live against the harness server;
--no-default-features --features postgres and postgres,pg-lo green
(pure builds, no ops); clippy --all-targets -D warnings green on
default/ops/all-features/pg-pure; cargo fmt --check green;
cargo doc --no-deps zero warnings (default and ops; all-features at
the recorded pre-existing 35-warning private-link baseline).

Wave 8 (fuzzing) decomposition is unblocked.
2026-10-14 07:21:07 +00:00

15 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 completed
ops-foundation
ops-call-harness
moderate high component implementation
wave-7
ops
put
tokens
hand-over
verification

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 the channel_open marker (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 (len present) 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's put(None, …) form, the mid-stream stage flush and the restart-over-staged-bytes derivation pass riding unchanged). When the offer carries digest?, 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 Pin guard); 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): digest is the stored entry's canonical digest hex (the unknown-length putter cannot know it beforehand and needs it for every later op); token is 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_open marker; 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; digest is 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 ops green; clippy/fmt green on both shapes; cargo doc --no-deps clean

(The known-length mismatch arm implements the pre-commit check exactly; the unknown-length arm's cross-check lands at the put's completion by mechanical necessity — see Notes.)

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

Pre-implementation ruling (ADR-014, 2026-10-13) — the alkcall amendment-4 gate

Gate status: satisfied (2026-10-14). alkcall 0.9.0 landed the amendment-4 surface (S-01..S-04, the stub sweep): open_pub_channel (putter-side stream-then-open — allocate the channel id locally, get the streaming halves immediately, PubOpenReply::resolve() returning the single merged response) and register_pub_openable (responder-adopts, wrapper is the responder of record, loud publish-chunk rejection, relay lifting). The alkcall dependency floor is bumped to 0.9 (verified green: cargo test default/ops/ all-features, clippy, fmt). This task proceeds as designed — the upstream capability exists, no design adjustment is needed. The ruling below is retained as the gate's record; the two clarifications stand unchanged and govern the implementation.

The gate's deliverables (all now present in alkcall 0.9.0): Pub-typed marked opens (adoption of the initiator-allocated channel), the wrapper awaiting the handler's JoinHandle and merging ack reply fields + the handler's result into the single response, loud rejection of call.published frames on marked ops, and the relay's PubTypedOpen lifting.

Two clarifications from the settled design (acceptance criteria unchanged — both are already true of them as written):

  1. "The establishment reply fields carry the offer ack" remains true — but the reply is the op's single, post-handler response: ack fields + {token, digest} merge into one envelope emitted after the put handler completes (a sink op gets exactly one call.responded, ADR-046 §5). The ack is informative-in-result; it is NOT a gating ack, and the putter side must stream without waiting for it (a wait deadlocks — the response cannot be produced before the stream completes). Mirror-image note: fetch acks at establishment before any byte; put acks in the result (ADR-014 §2).
  2. Establishment rejections (offer validation, the kv-only threshold failure — ADR-012 §6.2's put-time error) reach the putter only in the response, after some or all bytes were streamed; the torn-down channel discards them and the CAS re-put is the backstop. Accepted cost of the correct primitive, not a defect to engineer around.

Implementation notes (2026-10-14)

  • The tee reader. The put handler's flow is one read of the wire stream in bounded windows (PUT_WIRE_CHUNK, 16 KiB); each read's chunk is handed on exactly once per arm — the derivation hash pass, the fanout broadcast's subscriber taps, and the store arm's pipe feed (the facade put consumes a bounded Arc<Vec<u8>> channel, PUT_PIPE_BOUND 64, backpressuring the tee and with it the putter's stream). A kill flag (PutPipeWriter::kill) is the pre-commit abort: it arms the facade-side reader's next read into an error ahead of the sender's drop (every failure path — offered-digest mismatch, torn wire, facade-ahead rejection — goes through it), so the facade put always ends on its stream-error path (no commit, zero residue) and a blocked read is always woken. The tee checks the facade task's is_finished before each next wire read — the deadlock guard for a facade that rejected ahead of the stream.
  • The offered-digest cross-check's two arms (mechanically pinned). Known-length: the tee runs its own incremental derivation (preamble known up front) and the mismatch rejects before the facade's EOF — the pre-commit rejection is exact, and it is also what protects the dedup re-put's renewal (a corrupt re-put never reaches the facade's commit). Unknown-length: the preamble is only derivable at stream end, and the derivation pass itself is the facade's put(None) (ADR-003's restart-over-staged-bytes rule — the store owns buffering+staging; the ops layer must not re-implement it, and would-be buffering violates the bounded-put discipline), so the claim compares against the facade's derived key at the put's completion — the response still rejects (DIGEST_MISMATCH, no token, the guard drops, the entry stands as valid CAS content its own derivation justifies and GC reaps). Recorded here as the honest reading of the AC's "rejects before commit": exact on the encouraged known-length form; on the unknown-length form, post-derivation by mechanical necessity (the store's own single-derivation-pass posture).
  • The offer ack. The establisher validates the claim's post-schema form and contributes one reply field — accepted: true — which the wrapper merges beside {token, digest} in the single response (ADR-014 §2's merge order), exactly the informative-in-result shape; no gating ack exists.
  • The declared error codes (spec error_schemas, message-details schema for each): DIGEST_MISMATCH (details also name the derived hex), VERIFICATION_REFUSED (the facade's Error::Verification — zero residue), and PUT_REFUSED (the kv-only put-time over-threshold rejection of ADR-012 §6.2 mapped from Error::Io, plus other put-time store failures — nothing committed).
  • Verification (all green): cargo test (default, 73); cargo test --features ops (108); cargo test --all-features (263 lib + 125 conformance) with the fleet e2e test executed live against the harness server (ALKBLOBS_PG_HOST=127.0.0.1, the pglo-poc container); clippy -D warnings (all-targets, all-features); cargo fmt --check; cargo doc --no-deps. The postgres_fleet_sweep_tests::sql_arbitration_refuses... test flaked once under full-suite parallel load against the shared harness server (passes in isolation on a stashed clean main too — pre-existing shared-server scheduling, unrelated to the put).

Summary

blobs/put (wave 7) is landed — the verified Sink op (channels/blobs/pub over the alk/blobs ALPN, Pub-typed, the channel_open marker), riding alkcall 0.9's amendment-4 machinery exactly as the pre-implementation ruling expected (open_pub_channel putter-side stream-then-open; register_pub_openable responder-of- record adopt path; the wrapper awaits the handler and merges the ack reply fields + the handler's result into the single response).

What landed in src/ops.rs:

  • The op (put_openables — the spec/establisher/PubOpenHandler triple, exported beside fetch_openables): the offer's post-schema ingress, the accepted: true offer ack in the establishment reply fields (merged into the response beside {token, digest} — ADR-014 §2's one-response synthesis), the write action on the namespace resource (closed-by-default).
  • The tee handler — one read of the wire stream in bounded windows, handed once each to the store arm (the facade put's bounded pipe, an AsyncRead bridge with a kill-flag failure injection), the derivation hash pass (the offered-digest claim's cross-check on the known-length form: pre-facade-EOF, pre-commit), and the fanout broadcast's subscriber arms (serve-while-persisting; the Stall policy is the put's form; no-digest offers run store-arm-only).
  • Verification before commit (ADR-006) rides the facade; every failure path kills the put pre-commit through the injected stream error — zero residue on both tiers. VERIFICATION_REFUSED, PUT_REFUSED (ADR-012 §6.2's kv-only put-time rejection included), and DIGEST_MISMATCH are the declared codes; the unknown-length claim arm compares at completion (mechanical necessity — the derivation pass is the facade's own), still rejecting the put and minting nothing.
  • The pin hand-over (ADR-010 §4): fleet mints OpsContext::mint_fleet over the committed row and drops the guard at the response (the row's expiry is the protection); non-fleet mint_custodial hands the guard to the token registry. The dedup re-put renews (fleet: the joint tx's pin-row upsert — verified in the fleet e2e on the live harness server; non-fleet: the custody re-mint — each round's token renewed by the wire's renewal form).

New tests (harness, wave-7 assembly): known/unknown round-trips, the verification-failure zero-residue shape (dual-tier sqlite+local, the staging dir counted), the offered-digest mismatch on both offer forms, the custody conversion, the dedup re-put, the ACL denial, the fanout tap of a live put stream (a contiguous suffix feed, the put still resolving), the kv-only threshold on both forms, and the fleet e2e (pg-gated; ran green against the live harness server). Status: completed.