Repository navigation
feat(antd-kotlin): manifest API (REST + gRPC) - #358
Conversation
Both clients, and IAntdClient, can now build and read ADR-0006 `.ant` manifests and `ant://` links through the daemon: - buildManifest(entries, options), from ManifestEntryInput values that each set exactly one of a hex `dataMap` (embedded) or `address` (public), with an optional path and size. BuildManifestOptions carries the name, the torrent info hashes and embedRoots. - decodeManifest(bytes, resolve) and decodeManifestLink(link, resolve), for `.ant` bytes and for a manifest link, file link or bare address. They return BuiltManifest (the bytes, the link, or null when it does not fit 1,500 bytes, and the entries) and DecodedManifest (source, name, both torrent hashes, the entries), whose ManifestEntryInfo mirrors the daemon's. Nothing is uploaded or paid for. The types and the wire mapping live in a new Manifests.kt, so each client gains only three thin overrides, placed between the file and wallet methods, away from where the pointer branch adds its own. The public API takes and returns raw bytes on both transports. REST carries them as base64 in the JSON `manifest` field and the client converts; a response whose manifest is not base64 is an InternalException. gRPC carries proto `bytes`. manifests.proto is copied byte-for-byte into lib/src/main/proto, so the kotlin-proto-copy drift check passes. Stubs are generated at build time as before (protoc 4.27.2, protoc-gen-grpc-java 1.65.1, protoc-gen-grpc-kotlin 1.4.1), nothing generated is committed, and the other proto copies are unchanged. Sizes are ULong?, as the SDK's other sizes are ULong. Unset is null, never 0: REST omits the field, and gRPC's proto3 `optional` fields are read by presence (hasSize, hasExactSize, hasPath, hasName), not by their zero value. Sizes go out as exact u64 JSON numbers and as all 64 bits of the proto's uint64. The proto's plain strings that are empty when unset (link, data_map, error, the torrent hashes) read as null, as REST's omitted fields do. embedRoots defaults to true and is always sent, so the wire never relies on the daemon's own default. The manifest stub raises grpc-java's message limits to 256 MiB both ways (withMaxInboundMessageSize and withMaxOutboundMessageSize, antd-go's value), because a manifest can be 64 MiB and the default receive limit is 4 MiB. Every call on that stub carries them; the other services keep the defaults. An entry with both or neither source, or empty decode input, is an IllegalArgumentException before anything is sent, as antd-go's checkManifestEntries is a plain error. A daemon rejection keeps the daemon's message, which names `entries[i]`, and is a BadRequestException on both transports (400, INVALID_ARGUMENT). One entry's resolve failure is in its `error`. There are no new exceptions. Not breaking: the three methods have default bodies on IAntdClient that throw UnsupportedOperationException naming the method, as the pointer methods do, so a caller's own implementation keeps compiling. Tests: 24 unit tests over a mock REST daemon and an in-process gRPC server. They cover the base64 round trip, unset sizes reading as null, sizes up to u64::MAX, the both-or-neither check, the embed_roots default, presence on every optional field, and the 400 and INVALID_ARGUMENT mapping. One gRPC test reads the manifest stub's call options. Another sends and receives a 5 MiB manifest over a loopback HTTP/2 connection, where a stub with the defaults fails with RESOURCE_EXHAUSTED. A compat test implements IAntdClient without the manifest methods. LiveManifestsTest is gated on ANTD_TEST_URL and ANTD_TEST_MANIFEST_ADDRESSES and makes no paid calls. It passed against a 10-node devnet, with ANTD_TEST_GRPC set: - a build from two public addresses and a private DataMap, with the kinds, names, content addresses and link checked - decodes of the bytes and the link, with and without resolve: exact sizes match the fixtures, and the DataMap entry is embedded with its DataMap - a decode of the fixture link - a BadRequestException naming entries[0] for the path `../x` - the same over gRPC, which builds the same bytes as REST ./gradlew clean build on dev2: 128 tests, 127 passed and the live test skipped without its env, no compiler warnings. Example 09 (Example09Manifests.kt) uploads two public notes, builds a manifest of them as notes/one.txt and notes/two.txt named "example", prints the link and each entry's content address, and decodes the link with resolve, checking the exact sizes. It reads ANTD_BASE_URL and is left out of "all", since it needs the antd release after 0.15.0. It ran clean against the same devnet: a 184-byte manifest whose link decoded with exact sizes 39 and 48. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The both-or-neither source check and the empty decode input threw IllegalArgumentException with an SDK-flavoured message. They now throw BadRequestException with status 400, the error a daemon 400 maps to, so a caller cannot tell a client-side refusal from the daemon's. buildManifest collects every bad entry and reports them together in the daemon's wording from parse_entries and entry_errors_message: "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". An empty string counts as unset. An empty manifest or link is "needs a manifest (base64 .ant bytes) or a link". The text is bare, as gRPC sends it; the REST error field adds a "Bad request: " prefix that the client does not synthesize. Response parsing failures (bad base64) stay InternalException. Tests now assert type, status and exact message on both transports, including two bad entries and the 20-entry cap. The live test sends the same bad request to the daemon and checks its 400 body contains the client's message. ./gradlew clean build on dev2: 129 tests, 128 passed and the live test skipped without its env, no compiler warnings. LiveManifestsTest passed against the devnet, REST and gRPC. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
PROVISIONAL: #358 — Kotlin manifest APIRepository: WithAutonomi/ant-sdk. Reviewed head: No material code defect found in the source pass or the completed Qwen reviews. No formal GitHub verdict: the panel is below the two-model quorum. This is a review-coverage limit, not a reason to request code changes. A human maintainer can submit the formal review. Actual review-team participation: 2/6 scoped seats; 1/3 named modelsTwo roles were requested from GLM 5.3/OpenRouter (
Source findings
Tests and evidence limits
|
dirvine
left a comment
There was a problem hiding this comment.
Formal verdict: APPROVE — partial panel
Repository: WithAutonomi/ant-sdk
Head: 602ce320756575c9590302e033eec8336c1d517d
Current target base verified at publication: ea389c79c2ac40601c574e93031a66a9791db1d2
Coverage: 4/6 role seats reviewed; 2/3 named models. GLM 5.3 (z-ai/glm-5.3, OpenRouter) and local Qwen 3.8 (qwen38-27b) each completed correctness and adversarial reviews of the same source-1 evidence hash 756390036517dc7e63ff89c6b07e1a582c73b04ceafb748f25f189fbaf77127a. Qwen's completed exact-head receipts were preserved; GLM's previously missing seats were rerun. Missing: local DS4 Flash (deepseek-v4-flash) correctness and adversarial; earlier attempts timed out or ended without a complete verdict. A fresh bounded DS4 recovery is queued separately and is NOT counted here. This is a quorum partial verdict, not full-team consensus.
Source and findings
Reviewed Kotlin REST/gRPC manifest mapping, input checks, transport interface compatibility and the example. No material defect remains in the supplied production/schema scope. The daemon uses serde Option with no string serializer for size/exact_size; REST returns the struct directly as Json, so the JSON-number assumption is supported.
Non-blocking: empty-entry handling is delegated to the daemon; the client surfaces its error. Test files exist but were excluded from the production bundle; a reviewer’s statement that no tests appeared in the bundle is not evidence that the PR has no tests.
Checks and limits
Local Kotlin tests were NOT run: this host has no JDK. The live GitHub checks, including the proto drift check, must be green at publication. No live-daemon or paid example was run. These are coverage limits, not passing runtime tests.
The head is unchanged from the frozen evidence. Saved base-drift revalidation found the reviewed diff unchanged, no overlapping base edits, and a clean trial merge; the publisher re-checks the live head, base and CI immediately before this review.
No unresolved material dissent among the completed reviews. This verdict does not authorise merge, release, deployment, or payment. No merge performed.
main now has the pointer fan-out. Conflicts resolved exactly as in the integration preview #367 (bc339ab): antd-kotlin/examples/src/main/kotlin/com/autonomi/examples/Main.kt Also carries the #367 fix-up for this SDK: antd-kotlin/README.md The result matches #367 for antd-kotlin byte for byte. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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, and IAntdClient, can now build and read ADR-0006
.antmanifests and
ant://links through the daemon:each set exactly one of a hex
dataMap(embedded) oraddress(public), with an optional path and size. BuildManifestOptions carries
the name, the torrent info hashes and embedRoots.
for
.antbytes and for a manifest link, file link or bare address.They return BuiltManifest (the bytes, the link, or null when it does not
fit 1,500 bytes, and the entries) and DecodedManifest (source, name,
both torrent hashes, the entries), whose ManifestEntryInfo mirrors the
daemon's. Nothing is uploaded or paid for. The types and the wire
mapping live in a new Manifests.kt, so each client gains only three thin
overrides, placed between the file and wallet methods, away from where
the pointer branch adds its own.
The public API takes and returns raw bytes on both transports. REST
carries them as base64 in the JSON
manifestfield and the clientconverts; a response whose manifest is not base64 is an
InternalException. gRPC carries proto
bytes. manifests.proto is copiedbyte-for-byte into lib/src/main/proto, so the kotlin-proto-copy drift
check passes. Stubs are generated at build time as before (protoc
4.27.2, protoc-gen-grpc-java 1.65.1, protoc-gen-grpc-kotlin 1.4.1),
nothing generated is committed, and the other proto copies are
unchanged.
Sizes are ULong?, as the SDK's other sizes are ULong. Unset is null,
never 0: REST omits the field, and gRPC's proto3
optionalfields areread by presence (hasSize, hasExactSize, hasPath, hasName), not by
their zero value. Sizes go out as exact u64 JSON numbers and as all 64
bits of the proto's uint64. The proto's plain strings that are empty
when unset (link, data_map, error, the torrent hashes) read as null, as
REST's omitted fields do. embedRoots defaults to true and is always
sent, so the wire never relies on the daemon's own default.
The manifest stub raises grpc-java's message limits to 256 MiB both
ways (withMaxInboundMessageSize and withMaxOutboundMessageSize, antd-go's
value), because a manifest can be 64 MiB and the default receive limit
is 4 MiB. Every call on that stub carries them; the other services keep
the defaults.
An entry with both or neither source, or empty decode input, is an
IllegalArgumentException before anything is sent, as antd-go's
checkManifestEntries is a plain error. A daemon rejection keeps the
daemon's message, which names
entries[i], and is a BadRequestExceptionon both transports (400, INVALID_ARGUMENT). One entry's resolve failure
is in its
error. There are no new exceptions.Not breaking: the three methods have default bodies on IAntdClient that
throw UnsupportedOperationException naming the method, as the pointer
methods do, so a caller's own implementation keeps compiling.
Tests: 24 unit tests over a mock REST daemon and an in-process gRPC
server. They cover the base64 round trip, unset sizes reading as null,
sizes up to u64::MAX, the both-or-neither check, the embed_roots
default, presence on every optional field, and the 400 and
INVALID_ARGUMENT mapping. One gRPC test reads the manifest stub's call
options. Another sends and receives a 5 MiB manifest over a loopback
HTTP/2 connection, where a stub with the defaults fails with
RESOURCE_EXHAUSTED. A compat test implements IAntdClient without the
manifest methods.
LiveManifestsTest is gated on ANTD_TEST_URL and
ANTD_TEST_MANIFEST_ADDRESSES and makes no paid calls. It passed against
a 10-node devnet, with ANTD_TEST_GRPC set:
kinds, names, content addresses and link checked
sizes match the fixtures, and the DataMap entry is embedded with its
DataMap
../x./gradlew clean build on dev2: 128 tests, 127 passed and the live test
skipped without its env, no compiler warnings.
Example 09 (Example09Manifests.kt) uploads two public notes, builds a
manifest of them as notes/one.txt and notes/two.txt named "example",
prints the link and each entry's content address, and decodes the link
with resolve, checking the exact sizes. It reads ANTD_BASE_URL and is
left out of "all", since it needs the antd release after 0.15.0. It ran
clean against the same devnet: a 184-byte manifest whose link decoded
with exact sizes 39 and 48.
Overlap with this SDK's pointer PR #340. A trial merge conflicts only in: antd-kotlin/examples/src/main/kotlin/com/autonomi/examples/Main.kt. 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