Skip to content

feat(antd, antd-go): carry the object size on gRPC OUT_OF_RANGE - #316

Merged
Nic-dorman merged 1 commit into
mainfrom
nic/v2-1502-grpc-range-size
Oct 12, 2026
Merged

Nic-dorman merged 1 commit into
mainfrom
nic/v2-1502-grpc-range-size

Conversation

@Nic-dorman

@Nic-dorman Nic-dorman commented Oct 12, 2026 •

Copy link
Copy Markdown
Member

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.

  • REST answers 416 with Content-Range: bytes */size. antd-go puts it in RangeNotSatisfiableError.Size.
  • gRPC returned OUT_OF_RANGE with the size only in the message text, so antd-go set Size: -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 to main once that branch is deleted.

What changes

  • Daemon (antd/src/error.rs): AntdError::RangeNotSatisfiable → tonic::Status::out_of_range now carries x-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.
  • antd-go (grpc_client.go): reads that trailer on both ways a ranged stream can fail before its first frame: an error from Header(), or a trailers-only response whose status comes from Recv(). A daemon without this change sends no trailer, so Size stays -1.

The data.proto comment 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 time data.proto changes.

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:

  • antd: 111 pass. range_not_satisfiable_is_grpc_out_of_range now asserts the metadata.
  • antd-go: all pass. The mock daemon sends the trailer, and an old-daemon key doesn't. TestGrpcDataStreamRangeOutOfRange checks Size 6 (private) and 5 (public), and -1 for the old daemon.
  • fmt, clippy -D warnings and go vet are 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.

🤖 Generated with Claude Code

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 dirvine left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

  1. Malformed-trailer edge test (seat 4) — a unit test where the trailer is present but not parseable (unsatisfiedSize → −1) would pin the fallback path in rangeStreamError directly; the "old-daemon" test only covers the absent-trailer case. antd-go/range_test.go:193-208.
  2. Defensive guard before parseContentRange (seat 6) — if a future grpc-go ever surfaced trailers-only metadata via Header(), the bytes */size value 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.
  3. Implicit pointer aliasing (seat 6) — rangeStreamError mutates rerr.Size on the pointer returned by errorFromGrpc; correct today (fresh allocation per call), a one-line comment would aid future readers. antd-go/grpc_client.go:449-464.
  4. expect → debug_assert (seat 6, judgement call) — the metadata-value .expect() is provably safe for u64 rendering; not worth changing.

Claim checks (highlights)

  • Both pre-first-frame failure paths read the trailer — verified. openGrpcRange wraps the Header() error path and the trailers-only Recv() path with rangeStreamError; Trailer() is available once the RPC has terminated with a status. antd-go/grpc_client.go:423-464.
  • Old daemon → Size: -1 — verified. errorFromGrpc seeds Size: -1; the trailer only overrides when present; the old-daemon mock 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 416 Content-Range construction in antd/src/error.rs:204-209; the success path already uses x-content-range in 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 .proto in it.
  • Test/CI/devnet claims — CI on head 13c2795 is 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 Size 4,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.

@Nic-dorman
Nic-dorman changed the base branch from nic/v2-1425-antd-byte-range-download to main October 12, 2026 12:23
@Nic-dorman
Nic-dorman merged commit 1b07482 into main Oct 12, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants