Skip to content

feat(antd, antd-go): ADR-0006 manifest endpoints: build, decode and link - #346

Merged
Nic-dorman merged 6 commits into
mainfrom
nic/v2-1570-antd-manifest-endpoints
Oct 16, 2026
Merged

Nic-dorman merged 6 commits into
mainfrom
nic/v2-1570-antd-manifest-endpoints

Conversation

@Nic-dorman

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

Copy link
Copy Markdown
Member

This is stacked on the pointer API PR, #326, which is stacked on the ant-core 0.13.0 repin (#325). It needs nothing beyond ant-core 0.13.0. The SHA-256 work, which waits on ant-client#229, is a separate PR stacked on this one: #347. Linear: V2-1570.

Daemon callers (Indelible, the SDKs) can now build and read ADR-0006 .ant manifests and ant:// links without re-implementing the format. antd keeps no state. Encoding, validation, content addresses, the link form and the portable path rules all come from ant-core's manifest module.

REST

  • POST /v1/manifests/build
    • Input: entries of data_map (hex, embedded) or address (public), plus an optional name and torrent info hashes. size is recorded as given, as ant-core's builder does.
    • Embedding: with embed_roots (default true), a shrunk DataMap is resolved, and its root is embedded when it is at most 64 KiB, as embeddable_data_map decides.
    • Output: the .ant bytes, the link when it fits in 1,500 bytes, and each entry's effective name and content address.
    • Errors: every invalid entry is named in the 400 (entries[i]: ...).
  • POST /v1/manifests/decode
    • Input: .ant bytes or any link (manifest link, file link or bare address).
    • Output: embedded entries return their DataMap in the hex form the download routes accept.
    • resolve: fetches DataMaps and wrapper records for exact sizes. One entry's failure goes in that entry's error; it doesn't fail the call.

gRPC

  • Service: ManifestService (manifests.proto), with Build and Decode as unary calls.
  • Request size: a manifest is one document of at most 64 MiB, produced and parsed whole. Requests are therefore accepted up to the REST body limit (100 MiB) rather than split into a stream.

antd-go

  • Methods: BuildManifest, DecodeManifest and DecodeManifestLink, on both clients. The gRPC client raises its per-call receive limit.
  • Stubs: regenerated with protoc 34.1.
  • Docs: OpenAPI and the llms docs are updated.

Of the SDKs, only antd-go has client methods. It's the client Indelible, the first consumer, uses. The other SDKs don't have manifests.proto stubs yet.

Stacking

This is based on #326, not #325, because the two features add entries at the same spot in 8 files:

  • antd/build.rs
  • antd/src/grpc/mod.rs
  • antd/src/rest/mod.rs
  • antd/src/types.rs
  • antd-go/grpc_client.go
  • antd-go/scripts/generate-protos.sh
  • llms.txt
  • llms-full.txt

Merge commit 6c907ed resolves them once by keeping both sides, pointers first. The pointer fan-out PRs (#327–#343) never see the overlap.

Merge order: #325 → #326 → this PR → #347.

Verified

From the original commit:

  • antd: 126 unit tests (13 new, run against an in-memory network), fmt, and clippy with -D warnings.
  • antd-go: tests, go vet, and golangci-lint v1.64.8.
  • Live on mainnet with ant 0.3.11-rc.1 (the final 0.3.11 only re-versions it):
    • A manifest the CLI built from two public videos decodes.
    • Rebuilding it from the decoded root DataMaps gives the CLI's exact bytes. So does rebuilding from the link's shrunk DataMaps, with wrapper records fetched over the network.
    • The CLI shows and downloads both entries from manifests antd built (54,903,201 and 11,572,127 bytes).
    • resolve reports exact sizes and a per-entry not-found error.
    • REST and gRPC (through antd-go) produce identical bytes.

On this branch after the merge, with the same commands CI runs:

  • antd: fmt, clippy with -D warnings, cargo doc, and cargo test --locked. All 147 tests pass, pointer and manifest tests together.
  • antd-go: go vet, go test, and golangci-lint v1.64.8.

CI note. Until #326 picks up the two CI fixes already on #325 (the rustls 0.23.46 bump from main, and the notices fix c226153), this PR inherits #326's two red checks: Security audit and Third-party notices. Neither is caused by this change.

🤖 Generated with Claude Code

Nic-dorman and others added 2 commits October 15, 2026 11:34
…570)

Daemon callers (Indelible, the SDKs) can now build and read `.ant`
manifests and `ant://` links without re-implementing the format. antd
keeps no state: encoding, validation, content addresses, link form and
the portable path rules all come from ant-core's manifest module
(ant-core 0.13.0, first shipped on this branch's repin).

REST:
- POST /v1/manifests/build: entries of `data_map` (hex, embedded) or
  `address` (public), optional name and torrent info hashes. `size` is
  recorded as given, as ant-core's builder does. With `embed_roots`
  (default true) a shrunk DataMap is resolved and its root embedded when
  it is at most 64 KiB, as `embeddable_data_map` decides. Returns the
  `.ant` bytes, the link when it fits 1,500 bytes, and each entry's
  effective name and content address. Every invalid entry is named in
  the 400 (`entries[i]: ...`).
- POST /v1/manifests/decode: `.ant` bytes or any link (manifest link,
  file link, bare address). Embedded entries return their DataMap in the
  hex form the download routes accept. `resolve` fetches DataMaps and
  wrapper records for exact sizes; one entry's failure is reported in
  its `error`, not as a failed call.

gRPC: ManifestService (manifests.proto), Build and Decode, unary. A
manifest is one document of at most 64 MiB, produced and parsed whole,
so requests are accepted up to the REST body limit (100 MiB) instead of
being framed into a stream.

antd-go: BuildManifest, DecodeManifest, DecodeManifestLink on both
clients (gRPC raises its per-call receive limit), stubs regenerated with
protoc 34.1. OpenAPI and llms docs updated.

Verified:
- antd: 126 unit tests (13 new, against an in-memory network), fmt,
  clippy -D warnings.
- antd-go: tests, go vet, golangci-lint v1.64.8.
- Live on mainnet with `ant` 0.3.11-rc.1:
  - a manifest the CLI built from two public videos decodes; rebuilding
    it from the decoded root DataMaps gives the CLI's exact bytes;
  - rebuilding from the link's shrunk DataMaps (wrapper records fetched
    over the network) also gives the CLI's exact bytes;
  - the CLI shows and downloads both entries from antd-built manifests
    (54,903,201 and 11,572,127 bytes);
  - resolve reports exact sizes and a per-entry not-found error;
  - REST and gRPC (via antd-go) produce identical bytes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The manifest endpoints now stack on the pointer API instead of beside
it, so the two features' overlap is resolved once, here, and the
pointer fan-out PRs never see it.

Every conflict was both features adding an entry at the same spot, so
both are kept, pointers first:
- antd/build.rs: both protos are compiled.
- antd/src/grpc/mod.rs: both services are imported and registered.
- antd/src/rest/mod.rs: both route modules.
- antd/src/types.rs: both type sections.
- antd-go/grpc_client.go: both service clients.
- antd-go/scripts/generate-protos.sh: PROTOS lists both.
- llms.txt, llms-full.txt: both REST tables and both gRPC sections.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Nic-dorman and others added 2 commits October 15, 2026 15:18
The manifest operations' OpenAPI descriptions said "added in antd
0.16.0". The release number isn't set yet, so say "the antd release
after 0.15.0", the wording the pointer and SDK docs settled on. The
same guess in llms-full.txt is reworded by the docs PR stacked on this
one, so it's left alone here to keep that PR's diff clean.

Co-Authored-By: Claude Opus 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>
Nic-dorman added a commit that referenced this pull request Oct 15, 2026
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>
Nic-dorman added a commit that referenced this pull request Oct 15, 2026
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>
Nic-dorman added a commit that referenced this pull request Oct 15, 2026
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. No conflicts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Nic-dorman added a commit that referenced this pull request Oct 16, 2026
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 added a commit that referenced this pull request Oct 16, 2026
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 added a commit that referenced this pull request Oct 16, 2026
Brings up #325's final ant-cli-v0.3.11 repin via #326 and #346. antd/Cargo.toml and antd/Cargo.lock conflicted on the ant-core and ant-protocol pins; both keep this branch's side, the DEV PIN on WithAutonomi/ant-client#229 with its ant-protocol [patch], because the SHA-256 change needs #229 and ships with it. No other conflicts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@dirvine

dirvine commented Oct 16, 2026 •

Copy link
Copy Markdown
Member

Hermes review handoff — ant-sdk #346

Review record: WithAutonomi/ant-sdk#346@4cf292a4761fe07e0db82ba270c177382a4ef7b5

Linked Linear ticket: V2-1570. The PR head was read again before posting and still matches the reviewed revision. Required whole-PR panel coverage remains incomplete; this post is not approval.

WithAutonomi/ant-sdk #346 — bounded review

Recommendation: NO NEW MATERIAL BLOCKER CONFIRMED; approval gate remains incomplete. This is an advisory review comment, not a formal GitHub APPROVE/REQUEST_CHANGES review or permission to merge.

  • PR: feat(antd, antd-go): ADR-0006 manifest endpoints: build, decode and link #346
  • Reviewed head: 4cf292a4761fe07e0db82ba270c177382a4ef7b5.
  • Reviewed diff base (merge base): d66e50df085c26afb0134b6c857afeaef62303ef.
  • GitHub target branch revision captured initially: d66e50df085c26afb0134b6c857afeaef62303ef.
  • Local checkout is clean and matches the reviewed head. The live head was read again before report generation and still matches.
  • Changed-file inventory: 19 files. Full changed-production patch coverage manifests are included with the evidence. Documentation, tests, generated stubs and locks are classified separately; a production-scope panel is not a review of all SDK builds.

Findings

The daemon delegates manifest encoding/validation to its pinned ant-core, preserves root/shrunk-map policy, canonicalizes names, bounds resolver concurrency at eight, and carries the same build/decode defaults across REST and gRPC. Local manifest tests pass. No new blocker was verified in the #346 delta. The branch still inherits #326's paid-finalize defect; fix and retest the stack in order.

Non-blocking: wide integer precision in the Go REST client

The new manifest methods reuse the existing untyped JSON helpers. A parent-executed httptest response with size and exact_size equal to 9007199254740993 returns 9007199254740992 over REST. The gRPC probe returns the original integer exactly. A base-control probe on #326's existing DataCost API shows the same numeric helper already loses this integer. This is newly exposed on manifest sizes, but inherited from the SDK's float64 JSON pattern, not a new numeric implementation in #346.

This occurs beyond 2^53 bytes; no realistic blocking workload was demonstrated. Do not promote it to a release blocker solely because the synthetic boundary is executable. A typed JSON decoder or json.Decoder.UseNumber plus exact ParseUint would remove it. Do not change the server wire type without a compatibility decision. Artifacts: 346/precision-probe/head-parent.log, base-parent.log, and both probe sources.

Panel dissent resolved against full source

Qwen requested changes because manifest REST handlers allegedly had no body bound. This claim is false: the routes at antd/src/rest/mod.rs:150–151 inherit .layer(DefaultBodyLimit::max(MAX_BODY_SIZE)) at line 157; MAX_BODY_SIZE is 100 MiB at line 17. A route-specific override is not required for the global bound to apply. The original scoped patch did not show all unchanged enclosing context. GLM also flagged that context as an uncertainty, not proof of no limit.

The server's 100 MiB gRPC setting is a receive cap, not a 100 MiB send cap. The Go 256 MiB receive allowance does not itself conflict with that setting. The pinned codec's send default and the manifest's own 64 MiB bound are separate controls. No unbounded-body DoS finding is accepted from the panel output.

Links:

  • Router-wide limit:
    .route("/v1/manifests/build", post(manifests::manifest_build))
    .route("/v1/manifests/decode", post(manifests::manifest_decode))
    // Wallet
    .route("/v1/wallet/address", get(wallet::wallet_address))
    .route("/v1/wallet/balance", get(wallet::wallet_balance))
    .route("/v1/wallet/approve", post(wallet::wallet_approve))
    // Layers (innermost first)
    .layer(DefaultBodyLimit::max(MAX_BODY_SIZE))
    .
  • Limit constant:
    /// 100 MB body limit for all requests.
    const MAX_BODY_SIZE: usize = 100 * 1024 * 1024;
    .
  • Go manifest API:
    package antd
    import (
    "context"
    "errors"
    "fmt"
    "net/http"
    "google.golang.org/grpc"
    pb "github.com/WithAutonomi/ant-sdk/antd-go/proto/antd/v1"
    )
    // Manifests (ADR-0006): `.ant` files and `ant://` links that describe a set
    // of files, each with an optional path and size and either an embedded
    // DataMap or a public DataMap address. The daemon builds and decodes them
    // through ant-core, so callers never re-implement the format. Nothing is
    // uploaded or paid for.
    // ManifestEntryInput is one file for BuildManifest. Set exactly one of
    // DataMap or Address.
    type ManifestEntryInput struct {
    // Path is the relative path the entry extracts under, subject to the
    // portable path rules. Empty: it extracts under its content address.
    Path string
    // Size is the plaintext size to record, as given (the creator's claim).
    // Nil: none is recorded; readers take the exact size from an embedded
    // root DataMap.
    Size *uint64
    // DataMap is a hex DataMap, as the upload methods return it. The entry
    // embeds it: the manifest is the key to the file.
    DataMap string
    // Address is a public file's DataMap address (hex, 32 bytes).
    Address string
    }
    // BuildManifestOptions are the optional parts of a BuildManifest call.
    type BuildManifestOptions struct {
    // Name is a suggested root directory name (one portable path component).
    Name string
    // TorrentInfoHashes are BitTorrent info hashes of the same files: hex,
    // 40 characters for v1 and/or 64 for v2.
    TorrentInfoHashes []string
    // KeepShrunkDataMaps embeds shrunk DataMaps exactly as given. By default
    // the daemon resolves each one's root (fetching its wrapper records) and
    // embeds the root when it is at most 64 KiB, so recipients skip that
    // fetch (ADR-0006's policy).
    KeepShrunkDataMaps bool
    }
    // ManifestEntryInfo is what a manifest says about one entry.
    type ManifestEntryInfo struct {
    // EffectiveName is Path, or the content address in hex when there is none.
    EffectiveName string
    Path string
    // Size is the size recorded in the manifest: the creator's claim.
    Size *uint64
    // Kind is "embedded" or "public".
    Kind string
    // ContentAddress (hex) is the file's public DataMap address, whether or
    // not it was ever made public; the same for private and public uploads
    // of one file.
    ContentAddress string
    // DataMap is the embedded DataMap in the hex form the download methods
    // accept. Decode only; empty for public entries.
    DataMap string
    // ExactSize is the exact plaintext size: from an embedded root DataMap,
    // or after a resolving decode.
    ExactSize *uint64
    // Error says why a resolving decode failed for this entry.
    Error string
    }
    // BuiltManifest is the result of BuildManifest.
    type BuiltManifest struct {
    // Manifest is the `.ant` bytes.
    Manifest []byte
    // Link is the `ant://manifest/` link, set only when it fits the
    // recommended 1,500 bytes.
    Link string
    // Entries are in encoded order (sorted by effective name).
    Entries []ManifestEntryInfo
    }
    // DecodedManifest is the result of DecodeManifest and DecodeManifestLink.
    type DecodedManifest struct {
    // Source is "manifest", or "file_link" for a file link or bare address
    // (reported as one public entry with no path).
    Source string
    Name string
    TorrentInfoHashV1 string
    TorrentInfoHashV2 string
    Entries []ManifestEntryInfo
    }
    // maxManifestGrpcMessage is the receive ceiling for manifest calls: a
    // manifest can be 64 MiB, and a decode response carries its DataMaps in hex.
    const maxManifestGrpcMessage = 256 * 1024 * 1024
    func checkManifestEntries(entries []ManifestEntryInput) error {
    for i, e := range entries {
    if (e.DataMap == "") == (e.Address == "") {
    return fmt.Errorf("antd: manifest entry %d: set exactly one of DataMap or Address", i)
    }
    }
    return nil
    }
    // --- REST ---
    // BuildManifest builds a `.ant` manifest from DataMaps and public addresses
    // already in hand, via POST /v1/manifests/build. opts may be nil.
    //
    // Requires antd >= 0.16.0.
    func (c *Client) BuildManifest(ctx context.Context, entries []ManifestEntryInput, opts *BuildManifestOptions) (*BuiltManifest, error) {
    if err := checkManifestEntries(entries); err != nil {
    return nil, err
    }
    if opts == nil {
    opts = &BuildManifestOptions{}
    }
    wire := make([]map[string]any, 0, len(entries))
    for _, e := range entries {
    m := map[string]any{}
    if e.Path != "" {
    m["path"] = e.Path
    }
    if e.Size != nil {
    m["size"] = *e.Size
    }
    if e.DataMap != "" {
    m["data_map"] = e.DataMap
    } else {
    m["address"] = e.Address
    }
    wire = append(wire, m)
    }
    body := map[string]any{
    "entries": wire,
    "embed_roots": !opts.KeepShrunkDataMaps,
    }
    if opts.Name != "" {
    body["name"] = opts.Name
    }
    if len(opts.TorrentInfoHashes) > 0 {
    body["torrent_info_hashes"] = opts.TorrentInfoHashes
    }
    j, _, err := c.doJSON(ctx, http.MethodPost, "/v1/manifests/build", body)
    if err != nil {
    return nil, err
    }
    raw, err := b64Decode(str(j, "manifest"))
    if err != nil {
    return nil, fmt.Errorf("antd: BuildManifest: manifest is not valid base64: %w", err)
    }
    return &BuiltManifest{
    Manifest: raw,
    Link: str(j, "link"),
    Entries: parseManifestEntries(arrAt(j, "entries")),
    }, nil
    }
    // DecodeManifest decodes `.ant` bytes via POST /v1/manifests/decode. With
    // resolve, the daemon fetches each entry's DataMap (and wrapper records) to
    // report ExactSize and confirm the DataMap is on the network; content chunks
    // are not fetched, and one entry's failure is reported in its Error.
    //
    // Requires antd >= 0.16.0.
    func (c *Client) DecodeManifest(ctx context.Context, manifest []byte, resolve bool) (*DecodedManifest, error) {
    if len(manifest) == 0 {
    return nil, errors.New("antd: DecodeManifest: manifest is empty")
    }
    return c.decodeManifest(ctx, map[string]any{"manifest": b64Encode(manifest), "resolve": resolve})
    }
    // DecodeManifestLink decodes an `ant://` manifest link, file link or bare
    // 64-hex address via POST /v1/manifests/decode. See DecodeManifest for
    // resolve.
    //
    // Requires antd >= 0.16.0.
    func (c *Client) DecodeManifestLink(ctx context.Context, link string, resolve bool) (*DecodedManifest, error) {
    if link == "" {
    return nil, errors.New("antd: DecodeManifestLink: link is empty")
    }
    return c.decodeManifest(ctx, map[string]any{"link": link, "resolve": resolve})
    }
    func (c *Client) decodeManifest(ctx context.Context, body map[string]any) (*DecodedManifest, error) {
    j, _, err := c.doJSON(ctx, http.MethodPost, "/v1/manifests/decode", body)
    if err != nil {
    return nil, err
    }
    return &DecodedManifest{
    Source: str(j, "source"),
    Name: str(j, "name"),
    TorrentInfoHashV1: str(j, "torrent_info_hash_v1"),
    TorrentInfoHashV2: str(j, "torrent_info_hash_v2"),
    Entries: parseManifestEntries(arrAt(j, "entries")),
    }, nil
    }
    func optUint64(m map[string]any, key string) *uint64 {
    if _, ok := m[key].(float64); !ok {
    return nil
    }
    v := unum64(m, key)
    return &v
    }
    func parseManifestEntries(raw []any) []ManifestEntryInfo {
    out := make([]ManifestEntryInfo, 0, len(raw))
    for _, e := range raw {
    m, ok := e.(map[string]any)
    if !ok {
    continue
    }
    out = append(out, ManifestEntryInfo{
    EffectiveName: str(m, "effective_name"),
    Path: str(m, "path"),
    Size: optUint64(m, "size"),
    Kind: str(m, "kind"),
    ContentAddress: str(m, "content_address"),
    DataMap: str(m, "data_map"),
    ExactSize: optUint64(m, "exact_size"),
    Error: str(m, "error"),
    })
    }
    return out
    }
    // --- gRPC ---
    // BuildManifest is the gRPC twin of Client.BuildManifest. opts may be nil.
    //
    // Requires antd >= 0.16.0.
    func (c *GrpcClient) BuildManifest(ctx context.Context, entries []ManifestEntryInput, opts *BuildManifestOptions) (*BuiltManifest, error) {
    if err := checkManifestEntries(entries); err != nil {
    return nil, err
    }
    if opts == nil {
    opts = &BuildManifestOptions{}
    }
    embedRoots := !opts.KeepShrunkDataMaps
    req := &pb.BuildManifestRequest{
    TorrentInfoHashes: opts.TorrentInfoHashes,
    EmbedRoots: &embedRoots,
    }
    if opts.Name != "" {
    req.Name = &opts.Name
    }
    for _, e := range entries {
    in := &pb.ManifestEntryInput{Size: e.Size}
    if e.Path != "" {
    path := e.Path
    in.Path = &path
    }
    if e.DataMap != "" {
    in.Source = &pb.ManifestEntryInput_DataMap{DataMap: e.DataMap}
    } else {
    in.Source = &pb.ManifestEntryInput_Address{Address: e.Address}
    }
    req.Entries = append(req.Entries, in)
    }
    ctx, cancel := c.ctx(ctx)
    defer cancel()
    resp, err := c.manifest.Build(ctx, req, grpc.MaxCallRecvMsgSize(maxManifestGrpcMessage))
    if err != nil {
    return nil, errorFromGrpc(err)
    }
    return &BuiltManifest{
    Manifest: resp.GetManifest(),
    Link: resp.GetLink(),
    Entries: fromPbManifestEntries(resp.GetEntries()),
    }, nil
    }
    // DecodeManifest is the gRPC twin of Client.DecodeManifest.
    //
    // Requires antd >= 0.16.0.
    func (c *GrpcClient) DecodeManifest(ctx context.Context, manifest []byte, resolve bool) (*DecodedManifest, error) {
    if len(manifest) == 0 {
    return nil, errors.New("antd: DecodeManifest: manifest is empty")
    }
    return c.decodeManifest(ctx, &pb.DecodeManifestRequest{
    Input: &pb.DecodeManifestRequest_Manifest{Manifest: manifest},
    Resolve: resolve,
    })
    }
    // DecodeManifestLink is the gRPC twin of Client.DecodeManifestLink.
    //
    // Requires antd >= 0.16.0.
    func (c *GrpcClient) DecodeManifestLink(ctx context.Context, link string, resolve bool) (*DecodedManifest, error) {
    if link == "" {
    return nil, errors.New("antd: DecodeManifestLink: link is empty")
    }
    return c.decodeManifest(ctx, &pb.DecodeManifestRequest{
    Input: &pb.DecodeManifestRequest_Link{Link: link},
    Resolve: resolve,
    })
    }
    func (c *GrpcClient) decodeManifest(ctx context.Context, req *pb.DecodeManifestRequest) (*DecodedManifest, error) {
    ctx, cancel := c.ctx(ctx)
    defer cancel()
    resp, err := c.manifest.Decode(ctx, req, grpc.MaxCallRecvMsgSize(maxManifestGrpcMessage))
    if err != nil {
    return nil, errorFromGrpc(err)
    }
    return &DecodedManifest{
    Source: resp.GetSource(),
    Name: resp.GetName(),
    TorrentInfoHashV1: resp.GetTorrentInfoHashV1(),
    TorrentInfoHashV2: resp.GetTorrentInfoHashV2(),
    Entries: fromPbManifestEntries(resp.GetEntries()),
    }, nil
    }
    .

Execution and CI evidence

  • Parent-run cargo test --locked, daemon: test result: ok. 147 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.35s
  • Parent-run cargo test --locked, Rust SDK (each result is a separate test binary/doc-test group):
    • test result: ok. 154 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.22s
    • test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
    • test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.15s
    • test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s
    • test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.06s
  • Local Go tests and go vet passed. Go race tests were not run for this head; do not transfer feat(antd, antd-go, antd-rust): pointer API with ownership transfer #326 race coverage to it.
  • Formatting: changed hand-written Rust sources passed edition-correct checks.
  • The first daemon build failed because a shared target contained stale generated protobuf outputs from another worktree. After cargo clean -p antd, the saved fresh-proto daemon run exited 0. The failed first run is retained, not reported as a passing command. Per-worktree targets should be used for future stack validation.
  • Exact-head GitHub checks at final read: {'SUCCESS': 15, 'SKIPPED': 2}. This is observed CI, distinct from local execution.
  • No paid upload, live-network pointer operation, full daemon payment E2E, browser/mobile SDK build, or complete lint/security reproduction ran locally. Unit tests and loopback probes do not establish these gates.

Actual original panel participation

The design was two independent roles (correctness and adversarial) for each of GLM 5.3/OpenRouter, local Qwen 3.8 27B, and local DS4 Flash. Production evidence was split into complete named scopes, so a role/model required multiple receipts to cover a whole PR. A receipt is not an extra independent model.

  • GLM 5.3 / OpenRouter: 1/4 scoped role receipts returned complete structured opinions; verdicts {'APPROVE': 1}; missing {'Incomplete termination; unavailable seat': 3}.
  • local Qwen 3.8 27B: 4/4 scoped role receipts returned complete structured opinions; verdicts {'APPROVE': 2, 'CHANGES_REQUIRED': 2}; missing {}.
  • local DS4 Flash: 0/4 scoped role receipts returned complete structured opinions; verdicts {}; missing {'TimeoutError': 4}.

UNCERTAIN is unresolved, not approval. Missing, truncated and timed-out seats do not agree. These counts include complete UNCERTAIN opinions and therefore do not mean complete substantive sign-off. No whole-PR six-seat consensus exists. Recommendations without a verified defect remain provisional.

Scope-by-scope receipts

  • manifest-core / correctness: glm: Incomplete termination; unavailable seat; qwen: APPROVE; ds4: TimeoutError
  • manifest-core / adversarial: glm: Incomplete termination; unavailable seat; qwen: APPROVE; ds4: TimeoutError
  • manifest-transports-go / correctness: glm: Incomplete termination; unavailable seat; qwen: CHANGES_REQUIRED; ds4: TimeoutError
  • manifest-transports-go / adversarial: glm: APPROVE; qwen: CHANGES_REQUIRED; ds4: TimeoutError
Changed-file scope and classification manifest
{
  "pr": 346,
  "head": "4cf292a4761fe07e0db82ba270c177382a4ef7b5",
  "base": "d66e50df085c26afb0134b6c857afeaef62303ef",
  "changed_file_count": 19,
  "files": [
    {
      "path": "antd-go/grpc_client.go",
      "classification": "production",
      "scope": "manifest-transports-go"
    },
    {
      "path": "antd-go/grpc_client_test.go",
      "classification": "tests",
      "scope": null
    },
    {
      "path": "antd-go/manifest.go",
      "classification": "production",
      "scope": "manifest-transports-go"
    },
    {
      "path": "antd-go/manifest_test.go",
      "classification": "tests",
      "scope": null
    },
    {
      "path": "antd-go/proto/antd/v1/manifests.pb.go",
      "classification": "generated",
      "scope": null
    },
    {
      "path": "antd-go/proto/antd/v1/manifests_grpc.pb.go",
      "classification": "generated",
      "scope": null
    },
    {
      "path": "antd-go/scripts/generate-protos.sh",
      "classification": "production",
      "scope": "manifest-transports-go"
    },
    {
      "path": "antd/build.rs",
      "classification": "production",
      "scope": "manifest-transports-go"
    },
    {
      "path": "antd/openapi.yaml",
      "classification": "docs",
      "scope": null
    },
    {
      "path": "antd/proto/antd/v1/manifests.proto",
      "classification": "production",
      "scope": "manifest-transports-go"
    },
    {
      "path": "antd/src/grpc/mod.rs",
      "classification": "production",
      "scope": "manifest-transports-go"
    },
    {
      "path": "antd/src/grpc/service.rs",
      "classification": "production",
      "scope": "manifest-transports-go"
    },
    {
      "path": "antd/src/main.rs",
      "classification": "production",
      "scope": "manifest-transports-go"
    },
    {
      "path": "antd/src/manifests.rs",
      "classification": "production",
      "scope": "manifest-core"
    },
    {
      "path": "antd/src/rest/manifests.rs",
      "classification": "production",
      "scope": "manifest-transports-go"
    },
    {
      "path": "antd/src/rest/mod.rs",
      "classification": "production",
      "scope": "manifest-transports-go"
    },
    {
      "path": "antd/src/types.rs",
      "classification": "production",
      "scope": "manifest-transports-go"
    },
    {
      "path": "llms-full.txt",
      "classification": "docs",
      "scope": null
    },
    {
      "path": "llms.txt",
      "classification": "docs",
      "scope": null
    }
  ],
  "scopes": [
    {
      "pr": 346,
      "scope": "manifest-core",
      "head": "4cf292a4761fe07e0db82ba270c177382a4ef7b5",
      "base": "d66e50df085c26afb0134b6c857afeaef62303ef",
      "evidence": "/Users/studio1/.hermes/cache/scratch/ant-sdk-review-evidence/346/panel-evidence/manifest-core.txt",
      "bytes": 40099,
      "files": [
        "antd/src/manifests.rs"
      ]
    },
    {
      "pr": 346,
      "scope": "manifest-transports-go",
      "head": "4cf292a4761fe07e0db82ba270c177382a4ef7b5",
      "base": "d66e50df085c26afb0134b6c857afeaef62303ef",
      "evidence": "/Users/studio1/.hermes/cache/scratch/ant-sdk-review-evidence/346/panel-evidence/manifest-transports-go.txt",
      "bytes": 34833,
      "files": [
        "antd-go/grpc_client.go",
        "antd-go/manifest.go",
        "antd-go/scripts/generate-protos.sh",
        "antd/build.rs",
        "antd/proto/antd/v1/manifests.proto",
        "antd/src/grpc/mod.rs",
        "antd/src/grpc/service.rs",
        "antd/src/main.rs",
        "antd/src/rest/manifests.rs",
        "antd/src/rest/mod.rs",
        "antd/src/types.rs"
      ]
    }
  ]
}

Linear review comment: https://linear.app/autonominetwork/issue/V2-1570/antd-manifest-endpoints-adr-0006-build-decode-and-link-over-rest-grpc#comment-b2032716

…endpoints (#346)

Brings up 3a2a29a: paid pointer writes now run to completion in their own task. No conflicts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Nic-dorman added a commit that referenced this pull request Oct 16, 2026
@Nic-dorman
Nic-dorman changed the base branch from nic/v2-1567-antd-pointer-api to main October 16, 2026 12:46

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

APPROVE — current head after #326 merged

Reviewed head: 532ae15cc2d9595e97d9162b9cfe741958b54653.
Reviewed merge base: 3a2a29aebb8b4f966444c170b360a61f2cdfe31d; target main captured at cea236950084f8738106cc37247c148f0b411d18.

No material blocker remains from this review. The pointer cancellation correction from merged #326 is present. All 19 manifest-change file blobs are identical to the previously inspected feature revision; the inherited change and exact-head checks were reviewed separately. This replaces the earlier provisional/comment-only handoff with a formal verdict, not permission to merge.

Verified checks

  • Exact-head cargo test --locked for antd: 148 passed, 0 failed.
  • Changed handwritten Rust sources: rustfmt --check --edition 2021 exited 0.
  • Exact-head antd-go: go test -json ./..., go vet ./..., and go test -race ./... each exited 0.
  • GitHub CI at publication: 15 successful, 2 skipped, none failed or pending. Skipped CI jobs are not passing checks; the Go race check was run locally.

Review coverage and dissent

6/12 scoped role opinions completed: 5 APPROVE and 1 UNCERTAIN, from 2/3 named models. Partial panel; no full-team consensus. There are two production scopes, each with correctness/adversarial roles and three requested models; these are not twelve independent models.

  • Manifest core: Qwen completed both roles (correctness APPROVE, adversarial UNCERTAIN). GLM and DS4 did not complete either role after one same-model retry each.
  • Transports/Go/OpenAPI: GLM and Qwen completed both roles, all APPROVE. DS4 did not complete either role after one same-model retry each.
  • Missing seats ended with incomplete termination or bounded wall/request timeout. They are not votes.

Qwen's core UNCERTAIN remains recorded, not relabelled APPROVE. Its concerns were missing caller/dependency context and then-pending tests, not a demonstrated failing path. Parent inspection confirms REST validates entries before build, gRPC does the same, and parse_entries enforces the entry cap. The pinned ant-core 0.13.0 codec rejects bytes above 64 MiB before decoding, and the daemon preserves the inspected core error categories. Current-head tests now pass. The compiler run proves the exercised toolchain, not an untested oldest-supported Rust version. No material defect was confirmed from those uncertainties.

Non-blocking limits and follow-ups

  • The earlier unbounded-REST-body finding is refuted: these routes inherit the router-wide 100 MiB body limit. gRPC's 100 MiB receive cap and the Go client's 256 MiB receive allowance are different controls. Finite caps do not establish peak-memory behaviour under concurrent load.
  • Go REST's inherited float64 JSON decoding can lose uint64 size precision above 2^53; gRPC is exact. Keep the typed/UseNumber-decoding follow-up. No realistic blocking workload was shown.
  • Whole-manifest operations and network reads for root embedding/resolve remain potentially expensive. No paid/live-network E2E, production finalize fault-injection, load test, or full SDK/platform matrix was run by this review. Author-reported live checks are not counted as my execution.
  • Generated output was checked separately through prior source/descriptor inspection, exact blob equality and current drift CI; it was not all sent to every production reviewer. Standalone tests/docs were inspected separately.

No merge, release, #347 approval, or Linear state change is authorised or performed by this verdict.

@Nic-dorman
Nic-dorman merged commit ea389c7 into main Oct 16, 2026
17 checks passed
Nic-dorman added a commit that referenced this pull request Oct 19, 2026
main now has the manifest endpoint docs (#346) and the 16 pointer SDK
PRs. The one conflict is llms-full.txt's Go method list, where #339 adds
the pointer methods and #346 added the manifest methods at the same
spot. Kept both, pointers first, as in the integration preview (#367).
The merge's diff against main equals #339's own diff (19 files,
+1214/-11), so nothing else changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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