Repository navigation
feat(antd, antd-go): carry the object size on gRPC OUT_OF_RANGE - #316
Conversation
A ranged read that starts at or past the end of the object tells REST callers the object's size, in the 416's `Content-Range: bytes */size`, but gRPC callers only got it inside the status message text. antd-go's gRPC client therefore set RangeNotSatisfiableError.Size to -1. The daemon now attaches the same value to the OUT_OF_RANGE status as `x-content-range: bytes */size` metadata. tonic sends it with the trailers-only response, alongside the existing message. antd-go reads that trailer on both ways a ranged stream can fail before its first frame, so Size is the object size on either transport. A daemon that predates this sends no trailer and Size stays -1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dirvine
left a comment
There was a problem hiding this comment.
Six-seat independent review panel — PR #316 (gRPC OUT_OF_RANGE carries object size)
Reviewed head: 13c2795731d5a831b506663b2f62491e5029513f (branch → base nic/v2-1425-antd-byte-range-download, stacked on #313). 4 files, +62/−11. CI on this head: all green (Check antd, antd-rust, antd-js, Lint + Test antd-go, security audit).
Summary
Daemon-side, AntdError::RangeNotSatisfiable { size } now maps to tonic::Status::out_of_range with x-content-range: bytes */<size> metadata — the exact value REST's 416 already puts in Content-Range. antd-go reads that trailer in a new rangeStreamError helper on both pre-first-frame failure paths (Header() error, and trailers-only status surfaced by Recv()); a daemon without the trailer leaves Size: -1 as before. Message text unchanged; no .proto touched (comment-only regen deferred, documented in the PR body). All six seats found no blocking defects.
Per-seat verdicts
| Seat | Tool | Verdict | Key findings |
|---|---|---|---|
| 1 | gpt-5.1-codex (via OpenRouter) | APPROVE | Mapping correct; .expect() safe (u64 renders as ASCII digits); test pins the metadata |
| 2 | gpt-5.1-codex (via OpenRouter) | APPROVE | Both failure paths wrapped; errorFromGrpc returns a fresh *RangeNotSatisfiableError per call, so mutation is safe; no findings |
| 3 | gpt-5.1-codex (via OpenRouter) | APPROVE | Key/value valid as gRPC metadata (lowercase key, ASCII, short value); no initial-metadata/trailer collision (different phases of the same RPC) |
| 4 | gpt-5.1-codex (via OpenRouter) | APPROVE | Test coverage solid (Size 6 private, 5 public, −1 old-daemon); suggests a malformed-trailer edge test |
| 5 | gpt-5.1-codex (via OpenRouter) | APPROVE | Additive compatibility verified; REST/gRPC parity holds; proto-comment omission reasonable |
| 6 | GLM-5.2 (OpenRouter, independent model seat) | APPROVE | Implicit pointer-aliasing comment suggestion; defensive strings.HasPrefix guard before parseContentRange (future-proofing, unreachable today); debug_assert! instead of expect (optional) |
Dissent: none. All six seats approve.
Blocking issues
None.
Non-blocking findings (deduplicated)
- Malformed-trailer edge test (seat 4) — a unit test where the trailer is present but not parseable (
unsatisfiedSize→ −1) would pin the fallback path inrangeStreamErrordirectly; the "old-daemon" test only covers the absent-trailer case.antd-go/range_test.go:193-208. - Defensive guard before
parseContentRange(seat 6) — if a future grpc-go ever surfaced trailers-only metadata viaHeader(), thebytes */sizevalue would feed the success-path parser and yield a generic malformed error instead of*RangeNotSatisfiableError. Unreachable with current grpc-go; optional.antd-go/grpc_client.go:434. - Implicit pointer aliasing (seat 6) —
rangeStreamErrormutatesrerr.Sizeon the pointer returned byerrorFromGrpc; correct today (fresh allocation per call), a one-line comment would aid future readers.antd-go/grpc_client.go:449-464. expect→debug_assert(seat 6, judgement call) — the metadata-value.expect()is provably safe foru64rendering; not worth changing.
Claim checks (highlights)
- Both pre-first-frame failure paths read the trailer — verified.
openGrpcRangewraps theHeader()error path and the trailers-onlyRecv()path withrangeStreamError;Trailer()is available once the RPC has terminated with a status.antd-go/grpc_client.go:423-464. - Old daemon →
Size: -1— verified.errorFromGrpcseedsSize: -1; the trailer only overrides when present; theold-daemonmock test asserts −1.antd-go/grpc_client.go:173,antd-go/range_test.go:197-208. - Same key/value as REST — verified.
bytes */{size}matches the REST 416Content-Rangeconstruction inantd/src/error.rs:204-209; the success path already usesx-content-rangein initial metadata, so the key is consistent across the RPC lifecycle. - Message text unchanged — verified.
e.to_string()still feeds the status message. - No proto change — verified. The diff touches exactly 4 files; no
.protoin it. - Test/CI/devnet claims — CI on head
13c2795is green (checked live); the 25-node devnet run is the PR author's report and was not re-executed by the panel.
Limitations
- Seats ran static inspection (no local builds/tests); execution evidence is the CI run on this exact head.
- The devnet evidence (4.5 GiB object, gRPC
Size4,831,850,553 matching REST) is the PR body's report, not independently reproduced.
Verdict
APPROVE — no blocking issues; four non-blocking follow-ups, none worth blocking the merge.
Summary
Follow-up to #313 (review note 2): a gRPC range request that starts at or past the end of the object now tells the caller how big the object is, as REST already does.
Content-Range: bytes */size. antd-go puts it inRangeNotSatisfiableError.Size.OUT_OF_RANGEwith the size only in the message text, so antd-go setSize: -1.Stacked on #313. The base is
nic/v2-1425-antd-byte-range-download, so the diff is this change only. Merge after #313. If #313 is merged with a merge commit, GitHub retargets this PR tomainonce that branch is deleted.What changes
antd/src/error.rs):AntdError::RangeNotSatisfiable→tonic::Status::out_of_rangenow carriesx-content-range: bytes */<size>metadata. That's the same value as REST's header and the same key the success path already uses. tonic sends it in the trailers-only response; the message text is unchanged.grpc_client.go): reads that trailer on both ways a ranged stream can fail before its first frame: an error fromHeader(), or a trailers-only response whose status comes fromRecv(). A daemon without this change sends no trailer, soSizestays-1.The
data.protocomment on the range fields isn't updated here. Changing it would mean regenerating the Go/Swift/Rust stubs and the Kotlin copy for a comment. The key is documented on the antd-go error type and at the daemon mapping; it can go into the proto next timedata.protochanges.Compatibility
Additive. Old clients ignore the extra metadata. New antd-go against an old daemon gets
Size: -1, exactly as before.Test evidence
Unit tests:
range_not_satisfiable_is_grpc_out_of_rangenow asserts the metadata.old-daemonkey doesn't.TestGrpcDataStreamRangeOutOfRangechecks Size 6 (private) and 5 (public), and -1 for the old daemon.fmt,clippy -D warningsandgo vetare clean.Devnet: On a 25-node devnet with a 4.5 GiB object (4,831,850,553 bytes), using a daemon built from this branch, the #313 range check passes (ALL PASS). This time it also requires the object size on gRPC.
OUT_OF_RANGE, and antd-go'sRangeNotSatisfiableError.Sizeis 4,831,850,553, matching REST's 416.RangeNotSatisfiable { size: Some(4831850553) }.🤖 Generated with Claude Code