Skip to content

feat(antd-rust): manifest API (REST + gRPC) - #359

Merged
Nic-dorman merged 5 commits into
mainfrom
nic/v2-1580-manifests-rust
Oct 19, 2026
Merged

Nic-dorman merged 5 commits into
mainfrom
nic/v2-1580-manifests-rust

Conversation

@Nic-dorman

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

Copy link
Copy Markdown
Member

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.

Both clients gain the daemon's ADR-0006 manifest surface, in a new
manifests module:

  • build_manifest (+ _with_options): .ant bytes, the link when it fits
    1,500 bytes, and each entry's effective name and content address
  • decode_manifest(bytes, resolve) and decode_manifest_link(link,
    resolve), for manifest links, file links and bare addresses

ManifestEntryInput sets exactly one of data_map (embedded) or address
(public), with ManifestEntryInput::embedded / ::public as constructors
and an optional path and size. Both or neither is BadRequest naming
entries[i] before anything is sent; an empty string counts as unset, as
on the wire. BuildManifestOptions carries name, torrent_info_hashes and
keep_shrunk_data_maps, so its Default embeds roots; embed_roots is sent
explicitly either way. Nothing is uploaded or paid for.

The public API takes and returns raw .ant bytes. REST carries them as
base64 in JSON (manifest in the build response and the decode request)
and the client converts at the boundary; gRPC carries bytes. Sizes are
Option, as file sizes are u64 elsewhere in the crate: a field REST
omits, or a proto3 optional without presence, reads as None, never 0,
and a recorded 0 stays Some(0). Fields the wire leaves empty (link,
data_map, error, name, torrent hashes, path) read as None.

The gRPC client gains a ManifestService stub. tonic sets message limits
on the generated client, not per call, so in connect_with_max_message_size
the stub decodes up to the larger of the connect ceiling and 256 MiB
(antd-go's per-call figure: a manifest can be 64 MiB and a decode
carries its DataMaps in hex) and encodes up to 100 MiB, the daemon's
request limit. Other services keep the uniform ceiling.

No new AntdError variants. A REST 400 is BadRequest with the daemon's
message. Over gRPC the manifest methods map INVALID_ARGUMENT to
BadRequest too, so the two transports agree; every other status maps as
before. One entry's resolve failure is its error field, not a failed
call. The change is additive: new methods, types and a private field.

src/generated/antd.v1.rs is regenerated with manifests.proto added to
the drift test's list (protoc 25.3, tonic-build 0.12.3, prost-build
0.13.5). The existing code regenerated byte-identically; the diff is
524 added lines.

Tests: 15 unit tests over a mock REST daemon and a mock ManifestService:
the base64 round trip, unset sizes as None (and 0 as Some(0)), the
both-or-neither check on both transports, the embed_roots default and
keep_shrunk, 400 and INVALID_ARGUMENT as BadRequest with the message,
a 40 MiB build response through a default client and through one
connected with a 1 MiB ceiling, a 40 MiB decode request going out, and
one over 100 MiB stopped in the client. tests/live_manifests.rs is
gated on ANTD_TEST_URL and ANTD_TEST_MANIFEST_ADDRESSES, with the gRPC
half on ANTD_TEST_GRPC, and makes no paid calls. It passed against antd
on a 10-node devnet:

  • a build of two public addresses and a DataMap, with paths: kinds,
    effective names, content addresses, no recorded size, a link
  • decodes of the bytes and the link with resolve: exact sizes equal to
    the fixtures, the DataMap entry embedded with its DataMap
  • a bare address as a file link, and the fixture link
  • an invalid path (../x) as BadRequest naming entries[1]
  • the same over gRPC, with bytes and decodes equal to REST's

examples/10-manifests.rs uploads two small public files, builds a
manifest named "example" of notes/one.txt and notes/two.txt, and decodes
its link with resolve. It ran once against the same daemon: a 184-byte
manifest, a link, exact sizes of 26 and 43 bytes. cargo fmt --check,
cargo clippy --all-targets --all-features -D warnings, cargo doc and
cargo test --locked are clean.

Overlap with this SDK's pointer PR #341. A trial merge conflicts only in: antd-rust/Cargo.toml, antd-rust/README.md. 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

Nic-dorman and others added 4 commits October 15, 2026 14:25
Both clients gain the daemon's ADR-0006 manifest surface, in a new
manifests module:
- build_manifest (+ _with_options): `.ant` bytes, the link when it fits
  1,500 bytes, and each entry's effective name and content address
- decode_manifest(bytes, resolve) and decode_manifest_link(link,
  resolve), for manifest links, file links and bare addresses

ManifestEntryInput sets exactly one of data_map (embedded) or address
(public), with ManifestEntryInput::embedded / ::public as constructors
and an optional path and size. Both or neither is BadRequest naming
entries[i] before anything is sent; an empty string counts as unset, as
on the wire. BuildManifestOptions carries name, torrent_info_hashes and
keep_shrunk_data_maps, so its Default embeds roots; embed_roots is sent
explicitly either way. Nothing is uploaded or paid for.

The public API takes and returns raw `.ant` bytes. REST carries them as
base64 in JSON (manifest in the build response and the decode request)
and the client converts at the boundary; gRPC carries bytes. Sizes are
Option<u64>, as file sizes are u64 elsewhere in the crate: a field REST
omits, or a proto3 optional without presence, reads as None, never 0,
and a recorded 0 stays Some(0). Fields the wire leaves empty (link,
data_map, error, name, torrent hashes, path) read as None.

The gRPC client gains a ManifestService stub. tonic sets message limits
on the generated client, not per call, so in connect_with_max_message_size
the stub decodes up to the larger of the connect ceiling and 256 MiB
(antd-go's per-call figure: a manifest can be 64 MiB and a decode
carries its DataMaps in hex) and encodes up to 100 MiB, the daemon's
request limit. Other services keep the uniform ceiling.

No new AntdError variants. A REST 400 is BadRequest with the daemon's
message. Over gRPC the manifest methods map INVALID_ARGUMENT to
BadRequest too, so the two transports agree; every other status maps as
before. One entry's resolve failure is its error field, not a failed
call. The change is additive: new methods, types and a private field.

src/generated/antd.v1.rs is regenerated with manifests.proto added to
the drift test's list (protoc 25.3, tonic-build 0.12.3, prost-build
0.13.5). The existing code regenerated byte-identically; the diff is
524 added lines.

Tests: 15 unit tests over a mock REST daemon and a mock ManifestService:
the base64 round trip, unset sizes as None (and 0 as Some(0)), the
both-or-neither check on both transports, the embed_roots default and
keep_shrunk, 400 and INVALID_ARGUMENT as BadRequest with the message,
a 40 MiB build response through a default client and through one
connected with a 1 MiB ceiling, a 40 MiB decode request going out, and
one over 100 MiB stopped in the client. tests/live_manifests.rs is
gated on ANTD_TEST_URL and ANTD_TEST_MANIFEST_ADDRESSES, with the gRPC
half on ANTD_TEST_GRPC, and makes no paid calls. It passed against antd
on a 10-node devnet:
- a build of two public addresses and a DataMap, with paths: kinds,
  effective names, content addresses, no recorded size, a link
- decodes of the bytes and the link with resolve: exact sizes equal to
  the fixtures, the DataMap entry embedded with its DataMap
- a bare address as a file link, and the fixture link
- an invalid path (`../x`) as BadRequest naming entries[1]
- the same over gRPC, with bytes and decodes equal to REST's

examples/10-manifests.rs uploads two small public files, builds a
manifest named "example" of notes/one.txt and notes/two.txt, and decodes
its link with resolve. It ran once against the same daemon: a 184-byte
manifest, a link, exact sizes of 26 and 43 bytes. cargo fmt --check,
cargo clippy --all-targets --all-features -D warnings, cargo doc and
cargo test --locked are clean.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ient-side manifest refusals match the daemon

gRPC INVALID_ARGUMENT now maps to AntdError::BadRequest(message) in
From<tonic::Status>, so every GrpcClient call agrees with a REST 400, as
antd-go does. The manifest-only mapping is removed. The agreement rule
is kept: a status carrying x-antd-error-code is typed only when the tag
fits its code, so an INVALID_ARGUMENT with a pointer tag is a plain
BadRequest, while a tagged FAILED_PRECONDITION still maps to
PointerFinal. This changes existing calls, which returned
AntdError::Grpc; the README's upgrade notes say so.

Client-side manifest refusals now read as the daemon's 400. The
both-or-neither entry check collects every bad entry and reports them
as "{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 and
then "; and {k} more". An empty decode input (bytes or link) says "needs
a manifest (base64 .ant bytes) or a link". All are BadRequest, with no
SDK prefix.

Tests: the unit tests assert the exact messages, including two bad
entries, 25 bad entries and the empty inputs, and the gRPC mapping and
tag rule. The live test sends the same bad build past the client
straight to the daemon and checks its text, minus REST's "Bad request: "
prefix, equals the client's, over REST and gRPC. fmt, clippy -D
warnings, doc and 170 unit tests pass.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Brings up the CI fixes already on the repin branch (#325): rustls 0.23.46 from main (#344, RUSTSEC-2026-0346..0349) and the notices generator reading .licenses/ (c226153, for granit-parser). No conflicts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings up #325's move from the rc.1 pin to the released ant-core 0.13.0 (ant-cli-v0.3.11) and ant-protocol 5.0.0, via #326. No conflicts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Nic-dorman
Nic-dorman marked this pull request as ready for review October 16, 2026 09:08
@Nic-dorman
Nic-dorman changed the base branch from nic/v2-1570-antd-manifest-endpoints to main October 16, 2026 14:34

@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.

Formal review verdict: APPROVE — antd-rust manifest client (build/decode/link, manifest_tests, README, example wiring) at head b08747d783486e9b2cf01cbba68de5a9e74f695f.

What was reviewed: every changed file at the merge base — antd-rust/src/manifest.rs, src/manifest_tests.rs, src/lib.rs, README and example wiring; full PR diff read.

Findings — no material blockers.

  • embed_roots is sent explicitly on both transports, both ways: true by default (tests assert the default is present) and false with keep_shrunk_data_maps; the daemon never depends on its own default.
  • Presence semantics kept: size and exact size are options, unset is absent, 0 survives as 0; base64 is handled REST-side; gRPC uses the proto bytes and oneof.
  • Low, non-blocking: the 256 MiB receive allowance is scoped to the manifest stubs only; other stubs keep their defaults.

Verification: cargo test --locked exit 0 (170 tests) and cargo fmt --check exit 0 at the reviewed head (offline; no daemon, no live network). GitHub CI at the reviewed commit: Check (antd), Check (antd-rust), Check (antd-js), Third-party notices, Security audit — all SUCCESS.

Panel coverage: 4/6 reviewed — partial panel; missing: DS4 correctness and DS4 adversarial (timeout 240s after one narrowed retry each). Substantive reviews: GLM 5.3 (correctness, adversarial) and Qwen 3.8 (correctness, adversarial).

Dissent adjudication: Qwen's adversarial seat returned CHANGES_REQUIRED, but its own evidence states "No defect found in this specific logic", and each of its three listed risks is described by that seat itself as a design choice rather than a defect (naming of keep_shrunk_data_maps, hex validation delegated to the daemon, the 256 MiB ceiling). GLM's independent adversarial pass reviewed the same logic and found it correct against the tests. Adjudicated non-material; receipts retained on disk.

Base note: main has moved since review (live base ea389c79c2ac40601c574e93031a66a9791db1d2); the merge base is unchanged (4cf292a4761fe07e0db82ba270c177382a4ef7b5). Re-check: reviewed head still the PR head, full diff matches the reviewed diff, trial merge with current main clean, no file changed on main intersects this PR (359/base-revalidation.json).

Authority: formal verdict requested by Nic. Nothing merged; no Linear ticket state changed.

main now has the pointer fan-out. Conflicts resolved exactly as in the
integration preview #367 (bc339ab): antd-rust/Cargo.toml antd-rust/README.md
The result matches #367 for antd-rust byte for byte.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Nic-dorman
Nic-dorman merged commit 905736e into main Oct 19, 2026
5 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