Repository navigation
feat(antd-ruby): manifest API (REST + gRPC) - #351
Conversation
Antd::Client and Antd::GrpcClient can now build and read ADR-0006 `.ant` manifests and `ant://` links through the daemon: - build_manifest(entries, name:, torrent_info_hashes:, embed_roots: true) - decode_manifest(bytes, resolve: false) - decode_manifest_link(link, resolve: false) The models are keyword structs: ManifestEntryInput (a Hash with the same keys works too), ManifestEntryInfo, BuiltManifest and DecodedManifest, plus ManifestEntryKind and ManifestSource string constants. The code lives in two new files, lib/antd/manifests.rb (models and REST) and lib/antd/grpc_manifests.rb, which reopen the client classes and are required from lib/antd.rb, so client.rb, grpc_client.rb and models.rb are untouched. Nothing is uploaded or paid for, and the README says so. The API always takes and returns raw `.ant` bytes (a binary String). REST carries them as base64 in `manifest`; a build response that is not valid base64 raises InternalError. gRPC sends them as-is. Sizes are Integers. Anything unset reads as nil on both transports: REST's omitted fields, and over gRPC the proto3 optionals (checked with has_size?, has_exact_size?, has_path?, has_name?) and empty strings. A missing size or exact_size is nil, never 0, and a recorded 0 stays 0. The link, data_map, error and torrent info hashes are nil when absent, as the pointer models' unset fields are. embed_roots defaults to true and is always sent, false matching antd-go's KeepShrunkDataMaps. Before anything is sent, an entry with both or neither of data_map and address (nil and "" count as unset), a size that is not a non-negative Integer, or an empty manifest or link raises ArgumentError, as antd-go's checkManifestEntries does. An entry the daemon rejects is its 400 (gRPC INVALID_ARGUMENT) as the existing BadRequestError, keeping the daemon's message naming entries[i]. One entry's resolve failure is in its error, not a failed call. There are no new error types. grpc-ruby has no per-call message limit, so ManifestService gets a stub on a channel of its own, made on first use, with grpc.max_receive_message_length and grpc.max_send_message_length at 256 MiB (MANIFEST_GRPC_MAX_MESSAGE, antd-go's ceiling). The other services keep gRPC's defaults. Tests: 22 unit tests (12 REST over webmock, 10 gRPC against a fake ManifestService stub answering with real proto messages) cover the base64 round trip, unset sizes as nil, the both-or-neither check, the embed_roots default and presence, and the 400 / INVALID_ARGUMENT mapping. Two of them check the gRPC limit: the channel args the manifest stub is built with, and an in-process server sending a 5 MiB manifest that a default stub refuses (RESOURCE_EXHAUSTED) and the client receives and decodes. The full suite passes (151 runs, the two live tests skipped). test/test_live_manifests.rb is gated on ANTD_TEST_URL and ANTD_TEST_MANIFEST_ADDRESSES (ANTD_TEST_GRPC adds the gRPC run). Against a 10-node devnet it passed over REST and gRPC (118 assertions): a build from two public addresses and a private DataMap, with paths; decoding the bytes and the link with resolve, exact sizes matching the fixtures and the DataMap entry embedded; a bare address as a file link; the fixture manifest link; and a bad path as BadRequestError. It pays nothing. examples/09_manifests.rb (ANTD_BASE_URL) stored two pieces of public data, built a 184-byte manifest with its link, and decoded the link with resolve (26 and 43 bytes) against the same daemon. Stubs regenerated with grpc-tools 1.84.0, the version the script pins; the existing stubs regenerate byte-identically. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
build_manifest, decode_manifest and decode_manifest_link refused bad input with ArgumentError, so a caller could tell a client-side refusal from the daemon's 400. They now raise BadRequestError (status 400), with the daemon's wording, on both Antd::Client and Antd::GrpcClient. - build_manifest collects every bad entry (both data_map and address, or neither; nil and "" count as unset) and reports them as the daemon's entry_errors_message does: "N invalid entries: entries[i]: give data_map or address, not both; entries[j]: needs a data_map or an address", at most 20 shown, then "; and K more". The text is bare, with no SDK prefix. - An empty manifest or link raises "needs a manifest (base64 .ant bytes) or a link". - The SDK's other input checks stay (a size that is not a non-negative Integer, a Hash with an unknown key, an entry that is neither a Hash nor a ManifestEntryInput), now as BadRequestError named "entries[i]: ..." and aggregated the same way; entries that is not an Array raises "entries must be an Array". - Response parsing failures (invalid base64 from the daemon) stay InternalError. Tests assert the type, status 400 and exact message, including a two-bad- entry case and the 20-entry cap, over REST and gRPC. The live test, which passed over both transports (136 assertions), sends the same bad request past the check over REST and compares the daemon's message with the client's once the daemon's "Bad request: " prefix is set aside. The full suite passes with -w (152 runs, the two live tests skipped). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
dirvine
left a comment
There was a problem hiding this comment.
APPROVE: #351 — Ruby REST and gRPC
Reviewed head: 9461db70c83df9bd5ad6a776b950e7d1590613c1. Original base: 6c907eda10948c37cd92dd921d9dd5f68bd3e817.
No material blocker remains after the source-backed adjudication below.
Actual panel coverage: 3/6 scoped opinions — partial panel
Independent first passes used GLM 5.3/OpenRouter, local Qwen 3.8 and local DS4 Flash, two roles per evidence scope. A narrowed retry replaces a missing opinion; it is not an extra vote. This is not full-team consensus.
- source-1 / correctness: GLM 5.3 APPROVE; Qwen 3.8 APPROVE; DS4 Flash unavailable after same-model retry (Incomplete termination; unavailable seat)
- source-1 / adversarial: GLM 5.3 unavailable after same-model retry (Incomplete termination; unavailable seat); Qwen 3.8 CHANGES_REQUIRED; DS4 Flash unavailable after same-model retry (Incomplete termination; unavailable seat)
Evidence and dissent adjudication
Qwen adversarial requested changes on the assumption that manifest sizes are JSON strings. The actual Serde DTO and axum handler emit numeric u64 sizes, not proto-JSON strings. The parent compiled that DTO and verified omission/zero/positive/u64-max numbers; the Ruby parser probe preserves them. Ruby Integers do not have the proposed fixed Integer::MAX ceiling. Shared Antd.manifest_entry_inputs runs before the protobuf mapper. Thus the stated defect is not on the supported request path. GLM mentioned possible TLS downgrade; grpc_client.rb:42-46 already offers an insecure target-only channel API, so the new stub does not bypass an existing credentials option. These factual resolutions are parent adjudication, not changed model votes.
Source authority: actual daemon DTO and REST serializer. These are Serde REST fields; a proto uint64 declaration alone does not establish proto-JSON serialization on this endpoint.
Verified checks and limits
Ruby 4.0.7: a direct probe of the actual Client#manifest_size passed nil, zero, 123 and u64 max. Full Ruby REST/gRPC suites remain UNRUN: earlier attempts stopped on missing webmock/minitest and minitest/mock dependencies. No paid/live daemon test.
Head CI: {"SUCCESS": 7}. The live head was rechecked unchanged. The base moved to ea389c79c2ac40601c574e93031a66a9791db1d2: the reviewed diff is byte-identical, no moved-base path overlaps the PR, merge base is unchanged, and git merge-tree succeeds. This proves a clean trial merge, not integrated runtime behaviour.
Scope
Panel production/example fragments: antd-ruby/examples/09_manifests.rb; antd-ruby/lib/antd.rb; antd-ruby/lib/antd/grpc_manifests.rb; antd-ruby/lib/antd/manifests.rb; antd-ruby/scripts/generate-protos.sh.
Not sent as full diff fragments to the panel: antd-ruby/README.md [docs]; antd-ruby/lib/antd/v1/manifests_pb.rb [generated]; antd-ruby/lib/antd/v1/manifests_services_pb.rb [generated]; antd-ruby/test/test_grpc_manifests.rb [tests]; antd-ruby/test/test_live_manifests.rb [tests]; antd-ruby/test/test_manifests.rb [tests]. Separate source-worker review covers the full recorded file inventory; that worker is not a named-model vote. Test execution above is separate from panel inspection. Checked wire base64/raw-byte boundaries, unset versus zero, default root embedding, input validation, endpoint paths, exports and transport limits where applicable. No live payment, deployment, merge or Linear state change.
Part of the manifest API fan-out. Stacked on #346 (the manifest endpoints in antd and antd-go), which sits on the pointer API #326; review those first.
Antd::Client and Antd::GrpcClient can now build and read ADR-0006
.antmanifests and
ant://links through the daemon:The models are keyword structs: ManifestEntryInput (a Hash with the same
keys works too), ManifestEntryInfo, BuiltManifest and DecodedManifest, plus
ManifestEntryKind and ManifestSource string constants. The code lives in
two new files, lib/antd/manifests.rb (models and REST) and
lib/antd/grpc_manifests.rb, which reopen the client classes and are
required from lib/antd.rb, so client.rb, grpc_client.rb and models.rb are
untouched. Nothing is uploaded or paid for, and the README says so.
The API always takes and returns raw
.antbytes (a binary String). RESTcarries them as base64 in
manifest; a build response that is not validbase64 raises InternalError. gRPC sends them as-is.
Sizes are Integers. Anything unset reads as nil on both transports: REST's
omitted fields, and over gRPC the proto3 optionals (checked with has_size?,
has_exact_size?, has_path?, has_name?) and empty strings. A missing size
or exact_size is nil, never 0, and a recorded 0 stays 0. The link,
data_map, error and torrent info hashes are nil when absent, as the
pointer models' unset fields are. embed_roots defaults to true and is
always sent, false matching antd-go's KeepShrunkDataMaps.
Before anything is sent, an entry with both or neither of data_map and
address (nil and "" count as unset), a size that is not a non-negative
Integer, or an empty manifest or link raises ArgumentError, as antd-go's
checkManifestEntries does. An entry the daemon rejects is its 400
(gRPC INVALID_ARGUMENT) as the existing BadRequestError, keeping the
daemon's message naming entries[i]. One entry's resolve failure is in its
error, not a failed call. There are no new error types.
grpc-ruby has no per-call message limit, so ManifestService gets a stub on
a channel of its own, made on first use, with grpc.max_receive_message_length
and grpc.max_send_message_length at 256 MiB (MANIFEST_GRPC_MAX_MESSAGE,
antd-go's ceiling). The other services keep gRPC's defaults.
Tests: 22 unit tests (12 REST over webmock, 10 gRPC against a fake
ManifestService stub answering with real proto messages) cover the base64
round trip, unset sizes as nil, the both-or-neither check, the embed_roots
default and presence, and the 400 / INVALID_ARGUMENT mapping. Two of them
check the gRPC limit: the channel args the manifest stub is built with,
and an in-process server sending a 5 MiB manifest that a default stub
refuses (RESOURCE_EXHAUSTED) and the client receives and decodes. The full
suite passes (151 runs, the two live tests skipped).
test/test_live_manifests.rb is gated on ANTD_TEST_URL and
ANTD_TEST_MANIFEST_ADDRESSES (ANTD_TEST_GRPC adds the gRPC run). Against a
10-node devnet it passed over REST and gRPC (118 assertions): a build from
two public addresses and a private DataMap, with paths; decoding the bytes
and the link with resolve, exact sizes matching the fixtures and the
DataMap entry embedded; a bare address as a file link; the fixture
manifest link; and a bad path as BadRequestError. It pays nothing.
examples/09_manifests.rb (ANTD_BASE_URL) stored two pieces of public data,
built a 184-byte manifest with its link, and decoded the link with resolve
(26 and 43 bytes) against the same daemon.
Stubs regenerated with grpc-tools 1.84.0, the version the script pins;
the existing stubs regenerate byte-identically.
Overlap with this SDK's pointer PR #336. A trial merge conflicts only in: antd-ruby/README.md, antd-ruby/scripts/generate-protos.sh. Resolve by keeping both sides when the two land.
Version bumps happen at release time; nothing here bumps a package version.
🤖 Generated with Claude Code