Repository navigation
feat(antd-zig): pointer API (REST) - #337
Conversation
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>
dirvine
left a comment
There was a problem hiding this comment.
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.
Part of the pointer API fan-out. Stacked on #326; review that first.
Client gains the daemon's pointer surface:
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:
update, and finality open at counter 1
through the recipient
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