Skip to content

feat(antd-zig): pointer API (REST) - #337

Merged
Nic-dorman merged 1 commit into
mainfrom
nic/v2-1568-pointers-zig
Oct 19, 2026
Merged

Nic-dorman merged 1 commit into
mainfrom
nic/v2-1568-pointers-zig

Conversation

@Nic-dorman

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

Copy link
Copy Markdown
Member

Part of the pointer API fan-out. Stacked on #326; review that first.

Client gains the daemon's pointer surface:

  • pointerKeys, pointerCreate, pointerUpdate, pointerTransfer
  • pointerGet, pointerResolve, pointerFinality, pointerController
  • pointerPrepare (+ pointerPrepareWithOptions for include_signed_quotes)
    and pointerFinalize, for an external signer

The models follow antd-rust. PointerOp is a tagged union of create,
update and transfer. PointerFinalityStatus is a tagged union with an
unknown arm that keeps a status a newer daemon may add. Every result
type has a deinit that frees its nested strings, states and quotes.
Unset values (transferred_to, the finality counter, state and majority)
are null whether the daemon sent null or an empty string. Addresses come
back as bare hex, so PointerKeys.contains and pointerAddressEql ignore a
0x prefix and hex case.

Counters are u64. The daemon sends them as decimal strings, because a
final state's 2^64-1 is past what JSON numbers keep exactly in several
languages. std.fmt.parseInt alone would take a sign and underscores, so
a counter must be ASCII digits within u64; anything else fails the call
with error.JsonError rather than reading as 0.

Errors map by the body's code. POINTER_FINAL, POINTER_FORKED and
CONFLICT are 409s and no longer read as AlreadyExists.
CLOSE_GROUP_SHORTFALL is a 503 that sets ErrorInfo.retryable only for
the JSON true a pointer finalize sends when it kept the paid state.
These are four new AntdError members (PointerFinal, PointerForked,
Conflict, CloseGroupShortfall), so a switch without an else prong needs
new prongs; the README's upgrade note says so. Any call can now return
CloseGroupShortfall where a daemon with the pointer API used to send a
500 (error.Internal). The SDK never retries a pointer write; the doc
comments and README say that each attempt pays.

Tests: src/pointer_tests.zig runs the Client against a mock daemon on a
loopback socket for every method. It checks the request paths and bodies,
the error mapping by code, counters up to u64 max, malformed counters,
and that a failed paid write is sent once. It runs under
std.testing.allocator, so the error paths are checked for leaks too.
src/live_pointers_test.zig is a round trip gated on ANTD_TEST_URL and
ANTD_TEST_POINTER_OWNERS. It passed against a 10-node devnet:

  • create, AlreadyExists, get, resolve with the bytes fetched back,
    update, and finality open at counter 1
  • a transfer with finality final, then get, controller and resolve
    through the recipient
  • POINTER_FINAL on update and transfer
  • a prepare with signed quotes, then CONFLICT on update and prepare
    while it is pending, and a finalize without payment refused as
    BadRequest

The new examples/09-pointers.zig (ANTD_POINTER_OWNER, optional
ANTD_BASE_URL) created, updated, resolved and read the finality of a
pointer on the same devnet, and a second run took its AlreadyExists path.

Version bumps happen at release time; nothing here bumps a package version.

🤖 Generated with Claude Code

Client gains the daemon's pointer surface:
- pointerKeys, pointerCreate, pointerUpdate, pointerTransfer
- pointerGet, pointerResolve, pointerFinality, pointerController
- pointerPrepare (+ pointerPrepareWithOptions for include_signed_quotes)
  and pointerFinalize, for an external signer

The models follow antd-rust. PointerOp is a tagged union of create,
update and transfer. PointerFinalityStatus is a tagged union with an
unknown arm that keeps a status a newer daemon may add. Every result
type has a deinit that frees its nested strings, states and quotes.
Unset values (transferred_to, the finality counter, state and majority)
are null whether the daemon sent null or an empty string. Addresses come
back as bare hex, so PointerKeys.contains and pointerAddressEql ignore a
0x prefix and hex case.

Counters are u64. The daemon sends them as decimal strings, because a
final state's 2^64-1 is past what JSON numbers keep exactly in several
languages. std.fmt.parseInt alone would take a sign and underscores, so
a counter must be ASCII digits within u64; anything else fails the call
with error.JsonError rather than reading as 0.

Errors map by the body's code. POINTER_FINAL, POINTER_FORKED and
CONFLICT are 409s and no longer read as AlreadyExists.
CLOSE_GROUP_SHORTFALL is a 503 that sets ErrorInfo.retryable only for
the JSON true a pointer finalize sends when it kept the paid state.
These are four new AntdError members (PointerFinal, PointerForked,
Conflict, CloseGroupShortfall), so a switch without an else prong needs
new prongs; the README's upgrade note says so. Any call can now return
CloseGroupShortfall where a daemon with the pointer API used to send a
500 (error.Internal). The SDK never retries a pointer write; the doc
comments and README say that each attempt pays.

Tests: src/pointer_tests.zig runs the Client against a mock daemon on a
loopback socket for every method. It checks the request paths and bodies,
the error mapping by code, counters up to u64 max, malformed counters,
and that a failed paid write is sent once. It runs under
std.testing.allocator, so the error paths are checked for leaks too.
src/live_pointers_test.zig is a round trip gated on ANTD_TEST_URL and
ANTD_TEST_POINTER_OWNERS. It passed against a 10-node devnet:
- create, AlreadyExists, get, resolve with the bytes fetched back,
  update, and finality open at counter 1
- a transfer with finality final, then get, controller and resolve
  through the recipient
- POINTER_FINAL on update and transfer
- a prepare with signed quotes, then CONFLICT on update and prepare
  while it is pending, and a finalize without payment refused as
  BadRequest

The new examples/09-pointers.zig (ANTD_POINTER_OWNER, optional
ANTD_BASE_URL) created, updated, resolved and read the finality of a
pointer on the same devnet, and a second run took its AlreadyExists path.

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

Formal review: APPROVE

Repository: WithAutonomi/ant-sdk, PR #337.
Reviewed head: 3a6ce1fdb6dab2253876172a93fa3168b65352dc. Original merge base: 8e67c611c8f54d032b036bab55bf1b7a2a00b64c.

No material blocker remains in the Zig pointer SDK. Complete-source inspection and offline compilation refute the missing-helper/compile claim. A real input-robustness gap in read-path construction is recorded below as non-blocking. The partial panel returned 15/18 substantive reviews: 9 APPROVE, 5 CHANGES_REQUIRED, 1 UNCERTAIN.

Source and test coverage

  • Complete Zig pointer methods, models, JSON builders/parsers, errors, tests and example against daemon REST/domain types and the Rust family reference.
  • The verification worker executed Zig 0.14.1 library build-obj, test-root compilation with --test-no-exec for aarch64-linux-musl, and example 09 build-obj: all exited 0. These are compilation results only; no tests were run. Parent checked the exact frozen head and relevant source.

Findings and adjudication

  • Non-blocking robustness gap: pointerRead interpolates an unescaped/unvalidated address into the GET path (src/antd.zig:709-712). Malformed arguments can select another pointer read route instead of the requested resource. Add address validation or path-segment escaping, with tests for reserved words and /?#. The offline route-construction demo is not a live HTTP/security-boundary test; no paid-write path is implicated.
  • dupeU64 is present at json_helpers.zig:273. Pointer request builders and parsers are also present in that file; their omission from one evidence fragment was not omission from the checkout. The library, test root and example compile results refute the asserted unconditional compile failure.
  • kind_tag is u8 in the daemon domain model itself (antd/src/types.rs:706,720,729). Proto uint32 is a wider wire container. Zig's out-of-range fallback is an unknown-kind sentinel, not the claimed hard parse failure; a daemon tag above u8 is not part of the current contract.
  • Unlike C# #328/PHP #334, Zig exposes include_signed_quotes and signed_quotes in the pointer prepare API.

Panel dissent and its disposition

  • The missing-helper and kind-width blockers are refuted by complete-source inspection, compile-only execution and the daemon's own u8 model. The unescaped-address report is confirmed, but its demonstrated impact concerns malformed read arguments; it remains an explicit follow-up, not a demonstrated payment or access-control failure. Original dissent is retained below.

Review team: actual participation

15/18 scope/role/model seats returned substantive, receipt-validated reviews; 3/3 distinct named models. Partial panel; provisional coverage limits apply to this formal verdict. A receipt is evidence of participation, not proof that its findings are correct. Counts below are per evidence scope, not additional independent models.

  • source-1: glm: correctness=UNCERTAIN, adversarial=APPROVE; qwen: correctness=APPROVE, adversarial=CHANGES_REQUIRED; ds4: correctness=CHANGES_REQUIRED, adversarial=CHANGES_REQUIRED.
  • source-2: glm: correctness=APPROVE, adversarial=APPROVE; qwen: correctness=APPROVE, adversarial=CHANGES_REQUIRED; ds4: correctness=unavailable (Incomplete termination; unavailable seat), adversarial=unavailable (Incomplete termination; unavailable seat).
  • source-3: glm: correctness=APPROVE, adversarial=APPROVE; qwen: correctness=APPROVE, adversarial=APPROVE; ds4: correctness=unavailable (Incomplete termination; unavailable seat), adversarial=CHANGES_REQUIRED.
  • Models: GLM 5.3 / OpenRouter z-ai/glm-5.3; local Qwen 3.8 / qwen38-27b; local DS4 Flash / deepseek-v4-flash. Missing seats received the configured same-model retry; no replacement model is counted as an independent vote.

Publication gates and limits

  • Live head rechecked against the reviewed head. Live base cea236950084f8738106cc37247c148f0b411d18: binary diff matches the reviewed diff, merge base is unchanged, no intersecting base-drift paths, and trial merge exited 0.
  • GitHub check snapshot: no checks reported; this is not a passing CI result.
  • Partial panel; all three named models contributed, but three scope/role/model seats remained unavailable after retry. No full-panel consensus is claimed.
  • No GitHub checks are reported for this head. No Zig tests were executed. Native zig build could not link against this macOS 26 SDK with Zig 0.14.1; cross-target compile-only checks do not prove native runtime behaviour.
  • No paid or live-network tests. No merge, deployment, source change, or Linear state change. This verdict applies only to the exact head above; it is not a fleet-wide or cross-SDK integration sign-off.

@Nic-dorman
Nic-dorman merged commit af74f0f into main Oct 19, 2026
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