Skip to content

feat(sdk): multiple choice decision proposals in rs-sdk, wasm-sdk and js-evo-sdk - #5903

Merged
QuantumExplorer merged 3 commits into
v5.1-devfrom
claude/multiple-choice-proposal-sdk
Nov 13, 2026
Merged

QuantumExplorer merged 3 commits into
v5.1-devfrom
claude/multiple-choice-proposal-sdk

Conversation

@QuantumExplorer

@QuantumExplorer QuantumExplorer commented Nov 12, 2026 •

Copy link
Copy Markdown
Member

Basic explanation

What this does: A multiple choice proposal is a question put to the masternodes whose answers ("choices") anyone, or only its author, can enter; the masternodes vote and the choice with the most votes is kept. The chain already supports this (protocol version 15), but an app had to assemble every piece by hand from generic document and vote calls. This adds ready-made helpers to the Rust SDK and the JavaScript SDKs: build a proposal, edit it, enter a choice, list proposals, read the vote, and cast a masternode's vote.

Value: Yes/no proposals already had these helpers; multiple choice proposals had none. The detail apps were most likely to get wrong is now handled in one place: a vote for a choice must carry the choice's document id, not the id of the identity that entered it (the chain refuses the latter). Apps can also show the cost before signing: 0.2 Dash for a proposal, 0.1 Dash for a choice.

Risks: Low. No consensus code changes: this is client code, plus constants and two small pure functions in rs-dpp that nothing on the block execution path calls. One existing SDK function (get_contested_dpns_vote_state) now delegates to a shared paging function; its existing tests pass unchanged. One behaviour of it changes: for a contest that has ended it used to return every contender whatever limit was passed, and it now returns at most limit. It also stops reading at a finished contest's record, where it used to ask for one more, empty, page. Nothing here was exercised against a live network, because no protocol version 15 network exists yet, and the JavaScript specs were not run locally.

Issue being fixed or feature implemented

Part of #5679 (decision proposals). The multiple choice kind (#5848, #5895, #5897) shipped with no client helpers: docs/protocol/decision-proposals.md said "They have no helpers of their own yet."

What was done?

Before and after

Before, voting for a choice from JavaScript:

const votePoll = new VotePoll({
  contractId: '3iNyoNwjCTkb7XS3RHyWUtLx7pgC6AFAEQNNDHzxqngF',
  documentTypeName: 'choice',
  indexName: 'byProposal',
  indexValues: [proposalIdBytes],
});
await sdk.voting.masternodeVote({
  masternodeProTxHash, votePoll,
  voteChoice: ResourceVoteChoice.TowardsIdentity(choiceDocumentId), // an identity id is refused
  votingKey, signer,
});

After:

await sdk.decisionProposals.multipleChoice.vote({
  masternodeProTxHash, proposalId,
  voteChoice: { choiceId }, // or 'abstain', or 'noneOfTheChoices'
  votingKey, signer,
});

rs-dpp (dpp::decision_proposal::multiple_choice, new)

  • Names: CHOICE_DOCUMENT_TYPE_NAME, CHOICE_CONTEST_INDEX_NAME, proposal_properties, choice_properties, is_choice. A test pins each to the contract crate's constant.
  • multiple_choice_proposal_vote_poll(proposal_id): the contest a proposal's choices run in, from the proposal's id alone. A test pins it to the poll a choice create derives from its proposalId, in each form the id can be written.
  • MultipleChoiceVoteChoice { Choice(id), Abstain, NoneOfTheChoices }, its conversion to ResourceVoteChoice (a choice's document id travels as TowardsIdentity, none of the choices is Lock), and multiple_choice_proposal_vote(proposal_id, choice) -> Vote.

rs-sdk (dash_sdk::platform::decision_proposals::multiple_choice, new)

  • Build: Sdk::build_multiple_choice_proposal, build_multiple_choice_proposal_edit, Sdk::build_choice (and the *_document forms that take the contract and entropy).
  • Cost: Sdk::multiple_choice_proposal_processing_fee, Sdk::choice_fund_to_join(proposal_id).
  • Read: Sdk::fetch_multiple_choice_proposal, ..._by_owner, ..._by_created_at, MultipleChoiceProposal::from_document, Choice::from_document.
  • Vote: Sdk::fetch_multiple_choice_proposal_vote (every choice with its tally while the vote runs; the outcome with every choice's final tally once it ended), Sdk::fetch_kept_choice, Sdk::fetch_masternode_multiple_choice_vote.
  • Shared pieces pulled out instead of copied: Sdk::fetch_contested_resource_vote_state(vote_poll, limit) (the paging loop get_contested_dpns_vote_state had, which now calls it) and Sdk::contest_fund_to_join_vote_poll(vote_poll) (the counting half of prefunded_voting_balance_to_join).

wasm-sdk and js-evo-sdk

  • wasm-sdk: buildMultipleChoiceProposal, buildMultipleChoiceProposalEdit, buildMultipleChoiceProposalChoice, getMultipleChoiceProposalProcessingFee, getMultipleChoiceProposalChoiceFund, getMultipleChoiceProposal, getMultipleChoiceProposalsByOwner, getMultipleChoiceProposalsByCreatedAt, getMultipleChoiceProposalVotePoll, getMultipleChoiceProposalVote, getMultipleChoiceProposalKeptChoice, getMasternodeMultipleChoiceProposalVote, multipleChoiceProposalVote. They read options and call rs-sdk.
  • js-evo-sdk: sdk.decisionProposals.multipleChoice (build, buildEdit, buildChoice, processingFee, choiceFund, get, byOwner, byCreatedAt, votePoll, voteState, keptChoice, masternodeVote, vote).

What the helpers do not check

The builders write what they are given. Which descriptions, lengths and choices Platform takes is the contract's to say and is checked on chain, so a proposal with neither a description document nor a url is built and then refused by Platform (it pays its gas). The three properties that name a description document travel together as one DescriptionDocument, so a partial reference cannot be built. The only refusals made client-side are the ones the yes/no helpers already make: an edit of a document that is not a multiple choice proposal, and an edit that changes nothing (Platform would accept it and charge for it).

Docs

docs/protocol/decision-proposals.md ("From the SDKs") and the js-evo-sdk README.

In-place changes to shipped generations

None. No versioned function is touched. get_contested_dpns_vote_state and prefunded_voting_balance_to_join in rs-sdk (client code, not consensus) now call the shared functions. The only behaviour change is the limit on a finished contest described under Risks.

How Has This Been Tested?

Run locally:

  • cargo test -p dpp --features all-system_contracts,fixtures-and-mocks,validation --lib -- decision_proposal: 14 passed (4 new: the names against the contract crate, the poll against the one a choice create derives, the vote choice mapping, is_choice).
  • cargo test -p dash-sdk --features mocks,decision-proposals-contract --lib -- decision_proposals contest_fund contest_vote_state contested: 39 passed (10 new). The new tests build a proposal and a choice and validate both against the contract's schema, check that a choice joins the contest its proposal's id names and states that contest's fund, cover the edit (title, url, description document set and removed, no-op and wrong-type refusals), read a running and a finished vote, check every listing is served through the index the schema declares, and read the choice fund and the vote through a mock SDK. The existing DPNS vote state paging tests pass through the shared function, and a new one serves a finished contest of three contenders to pages of 1 and 2, and reads a finished record of 150 contenders in one page.
  • RUST_MIN_STACK=16777216 cargo test -p drive-abci --lib -- multiple_choice_proposal: 14 passed. Three of them now also assert what fetch_kept_choice relies on: a document query of a proposal's choices finds none while the vote runs, none after a lock, and only the kept choice after an award.
  • CARGO_TARGET_WASM32_UNKNOWN_UNKNOWN_RUNNER=wasm-bindgen-test-runner cargo test -p wasm-sdk --target wasm32-unknown-unknown --lib -- multiple_choice: 5 passed. They cover the JavaScript shape of the vote state (no choice, running, kept a choice, kept none) and the vote choice round trip.
  • cargo check -p wasm-sdk --target wasm32-unknown-unknown, and cargo clippy on dpp, dash-sdk (all targets) and wasm-sdk (wasm target): clean. cargo fmt --all.

Not run:

  • The two new JavaScript specs (packages/js-evo-sdk/tests/unit/facades/multiple-choice-proposals.spec.ts, packages/wasm-sdk/tests/unit/multiple-choice-proposals.spec.ts) and ESLint: this checkout has no built wasm bundle or node_modules. CI runs them.
  • Anything against a network: no protocol version 15 network exists.
  • Sdk::fetch_multiple_choice_proposal_vote, fetch_kept_choice and fetch_masternode_multiple_choice_vote end to end: the mock SDK holds no system contract, so their parts are tested separately (the query, and the reading of its result).

Breaking Changes

None.

Checklist:

  • I have performed a self-review of my own code
  • My Rust, FFI, WASM, Swift or Kotlin changes follow the coding conventions
  • I have commented my code, particularly in hard-to-understand areas
  • I have added or updated relevant unit/integration/functional/e2e tests
  • I have added "!" to the title and described breaking changes in the corresponding section if my code contains any
  • I have made corresponding changes to the documentation if needed
  • If I added or changed GroveDB structure, I described it in the area's structure.rs, regenerated grovedb-structure.json, and checked the structure viewer link posted on this pull request
  • If I added or changed a system contract's document schemas, I checked the contract viewer link posted on this pull request

For repository code-owners and collaborators only

  • I have assigned this pull request to a milestone

🤖 Generated with Claude Code

PR Hygiene · 966c1a5

  • Bots — thepastaclaw ✓
  • Self-review — post /self-reviewed
  • Build failed
  • Approvals
    • files with no dedicated owner — you own it
    • js-wasm-sdk (packages/js-evo-sdk/README.md, packages/js-evo-sdk/src/decision-proposals/facade.ts, packages/js-evo-sdk/src/decision-proposals/multiple-choice-facade.ts and 6 more) — shumkov
    • dpp — you own it
    • rs-drive-abci — you own it
    • rust-sdk (packages/rs-sdk/src/platform/decision_proposals.rs, packages/rs-sdk/src/platform/decision_proposals/multiple_choice.rs, packages/rs-sdk/src/platform/documents/contest_fund.rs and 3 more) — lklimek or shumkov

When every merge requirement is met, the PR Hygiene check passes. Reviewer limits do not block merging; other required GitHub checks and protections still apply.

… js-evo-sdk

Multiple choice proposals (PV15) had no client helpers: an app assembled the
proposal, its choices and the vote from generic document and contested vote
calls, and had to know that a vote for a choice carries the choice's document
id where a contest among identities carries an identity id.

rs-dpp names the contest a proposal's choices run in from the proposal's id
alone and maps a multiple choice answer (a choice, abstain, none of the
choices) to the resource vote the contest takes. rs-sdk builds a proposal, its
edit and a choice, gives the proposal's processing fee and the fund a choice
pays, lists proposals, and reads the vote, the kept choice and a masternode's
answer. wasm-sdk and js-evo-sdk (sdk.decisionProposals.multipleChoice) bind
them.

The contested vote state paging loop of get_contested_dpns_vote_state and the
contender count of prefunded_voting_balance_to_join are now shared functions
taking the poll; both keep their behaviour.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 29 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

Run configuration

Commits

Reviewing files that changed from the base of the PR and between c5bac66 and 966c1a5.

Files selected for processing: 19

  • docs/protocol/decision-proposals.md
  • packages/js-evo-sdk/README.md
  • packages/js-evo-sdk/src/decision-proposals/facade.ts
  • packages/js-evo-sdk/src/decision-proposals/multiple-choice-facade.ts
  • packages/js-evo-sdk/src/sdk.ts
  • packages/js-evo-sdk/tests/unit/facades/multiple-choice-proposals.spec.ts
  • packages/rs-dpp/src/decision_proposal/mod.rs
  • packages/rs-dpp/src/decision_proposal/multiple_choice.rs
  • packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/masternode_vote/multiple_choice_proposal_tests.rs
  • packages/rs-sdk/src/platform/decision_proposals.rs
  • packages/rs-sdk/src/platform/decision_proposals/multiple_choice.rs
  • packages/rs-sdk/src/platform/documents/contest_fund.rs
  • packages/rs-sdk/src/platform/documents/contest_vote_state.rs
  • packages/rs-sdk/src/platform/documents/mod.rs
  • packages/rs-sdk/src/platform/dpns_usernames/contested_queries.rs
  • packages/wasm-sdk/src/decision_proposals.rs
  • packages/wasm-sdk/src/decision_proposals/multiple_choice.rs
  • packages/wasm-sdk/src/queries/voting/yes_no.rs
  • packages/wasm-sdk/tests/unit/multiple-choice-proposals.spec.ts
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@github-actions github-actions Bot added this to the v5.1.0 milestone Nov 12, 2026
@github-actions

github-actions Bot commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor

Waiting for bot review — thepastaclaw not yet. Wait for the missing reviews, or a writer can post /skip-bots to proceed without them; blocking findings still need addressing.
Full checklist in the description.

@github-actions github-actions Bot added the waiting-bots Waiting for the review bots to report on this head label Nov 12, 2026
@thepastaclaw

thepastaclaw commented Nov 12, 2026 •

Copy link
Copy Markdown
Collaborator

✅ Final review complete — no blockers (commit 966c1a5) · triage: normal

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Final validation — Phase 1 + Phase 2

Static verification at b8cdb7f found no blocking issues and three non-blocking suggestions concerning finalized-result limits, unnecessary cloning, and WASM result coverage. The preliminary kept-choice concern is incorrect: active choices live in contest storage, and only the awarded choice enters ordinary document storage. Current Rust, wasm32, JavaScript build/lint, Evo SDK, and platform test-suite checks pass; Dashmate Local network E2E fails while synchronizing Core blocks to height 1392, with no demonstrated connection to these client changes.

🟡 3 suggestion(s)

Approved: nothing here blocks the merge. The non-blocking findings are optional: fix them forward, in this PR or a follow-up, without waiting for another review round.

Review provenance

Source: reviewer 1: muse-spark-1.3-contributor (agent: phase1-reviewer, role: general); reviewer 2: muse-spark-1.3-contributor (agent: phase1-reviewer, role: rust-quality); reviewer 3: gpt-6.1-sol (agent: phase2-reviewer, role: general); reviewer 4: gpt-6.1-sol (agent: phase2-reviewer, role: architecture-layering); reviewer 5: gpt-6.1-sol (agent: phase2-reviewer, role: ffi-engineer); reviewer 6: gpt-6.1-sol (agent: phase2-reviewer, role: rust-quality); reviewer 7: gpt-6.1-sol (agent: phase2-reviewer, role: general); reviewer 8: gpt-6.1-sol (agent: phase2-reviewer, role: architecture-layering); reviewer 9: gpt-6.1-sol (agent: phase2-reviewer, role: ffi-engineer); reviewer 10: gpt-6.1-sol (agent: phase2-reviewer, role: rust-quality); final verifier: gpt-6.1-sol (agent: sol-verifier, role: final-verifier)

  • Triage: normal by gpt-6.1-sol (effort low) — This is a large cross-cutting SDK, WASM, and DPP helper implementation involving proposal, voting, paging, and fee-related logic, but it does not directly change consensus, cryptography, signatures, storage, or other critical surfaces.
  • Phase 1 reviewers: muse-spark-1.3-contributor — general (completed, effort xhigh); agent phase1-reviewer, muse-spark-1.3-contributor — rust-quality (completed, effort xhigh); agent phase1-reviewer
  • Phase 1 model: muse-spark-1.3-contributor — not quota-gated; passed over gemini-3.8-flash-high (antigravity quota lookup failed), glm-5.3-flash (not used above high effort; tier asks max)
  • Second Phase-2 pass: on this first full review every Phase-2 reviewer also ran a second, independent time, and the verifier weighed both sets
  • Fresh verifier: gpt-6.1-sol — final-verifier; agent sol-verifier
  • Phase 2 reviewers: gpt-6.1-sol — general (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — architecture-layering (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — ffi-engineer (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — rust-quality (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — general (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — architecture-layering (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — ffi-engineer (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — rust-quality (completed, effort high); agent phase2-reviewer
🤖 Prompt for all review comments with AI agents
These findings are from an automated code review (PastaClaw). How to work through them:
- Verify each finding against the code; fix only what is real, as narrowly as the finding.
- If a finding is wrong, decline it on its thread in one or two sentences; the bot answers once and stops.
- To defer one, link a tracking issue on its thread (or reply asking the bot to file one).
- Suggestions don't block the merge, but most are worth fixing in this PR; judge each on its merits.
- Keep the PR's scope; don't grow it to satisfy hardening suggestions.
- Carried items were raised in an earlier round: read the thread first, and don't re-fix what you already fixed, declined or deferred there. Disputed and deferred items are already handled and are not listed.

In `packages/rs-sdk/src/platform/documents/contest_vote_state.rs`:
- [SUGGESTION] packages/rs-sdk/src/platform/documents/contest_vote_state.rs:60-63: Enforce the contender limit for finalized contests
  The helper documents `limit` as the maximum number of contenders returned, but it accepts the entire decoded page without enforcing that bound. For a finalized contest, `verify_vote_poll_vote_state_proof_v0` reconstructs every final contender tally from the single stored-info item, irrespective of the requested page limit, and `Contenders::FromProof` preserves that complete map. A finalized contest with three choices therefore returns all three for `Some(1)`; the next iteration only notices that the limit has already been exceeded. This behavior is carried into the new public helper by the moved DPNS implementation. Bound the accumulated contender map while retaining the winner and aggregate tallies, and add a regression test with a finalized response larger than the requested limit.

In `packages/rs-sdk/src/platform/decision_proposals/multiple_choice.rs`:
- [SUGGESTION] packages/rs-sdk/src/platform/decision_proposals/multiple_choice.rs:272-273: Move each owned contender document instead of cloning it
  The preceding `into_iter()` transfers ownership of each decoded contender into this closure, but `contender.document().clone()` deep-copies its document immediately before the original is dropped. That repeats property and string allocations for every running choice on each vote-state read. Use the existing `take_document()` accessor to move the document into `Choice::from_document`. The same conversion also clones the document-type schema through `to_owned_document_type()`; iterating the serialized contenders with `try_to_contender(document_type, platform_version)` would additionally avoid that schema copy.

In `packages/wasm-sdk/src/decision_proposals/multiple_choice.rs`:
- [SUGGESTION] packages/wasm-sdk/src/decision_proposals/multiple_choice.rs:272-282: Exercise the vote-state JavaScript result shape offline
  The added tests do not exercise `vote_to_js`: native tests stop at `MultipleChoiceProposalVote::from_contenders`, the WASM JavaScript tests cover edits and option parsing, and facade tests stub `getMultipleChoiceProposalVote`. Consequently, the new public result shape—base58 map keys, wrapped choice documents, optional tallies, and awarded-versus-locked finalization results—has no regression coverage across the JavaScript boundary. Add offline WASM fixtures for empty, running, awarded, and locked votes and assert the converted result fields; this does not require a protocol-15 network.

@github-actions

Copy link
Copy Markdown
Contributor

Bots are done — your move: post /self-reviewed.
Full checklist in the description.

@github-actions github-actions Bot added waiting-self-review Waiting for the author to post /self-reviewed and removed waiting-bots Waiting for the review bots to report on this head labels Nov 13, 2026
A contest that ended answers a vote state page with every contender of its
record, whatever limit the page asked for, so
fetch_contested_resource_vote_state (and get_contested_dpns_vote_state, which
calls it) returned more contenders than `limit`. The contenders are now cut to
the limit, the first in id order kept; the winner and the abstain and lock
tallies are returned whole.

The multiple choice vote reader moves each decoded choice document instead of
cloning it, and no longer copies the document type. New wasm binding tests
cover the JavaScript shape of the vote state (no choice, running, kept a
choice, kept none) and the vote choice round trip.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Nov 13, 2026 •

Copy link
Copy Markdown
Contributor

Waiting for bot review — thepastaclaw not yet. Wait for the missing reviews, or a writer can post /skip-bots to proceed without them; blocking findings still need addressing.
Full checklist in the description.

@github-actions github-actions Bot added waiting-bots Waiting for the review bots to report on this head and removed waiting-self-review Waiting for the author to post /self-reviewed labels Nov 13, 2026

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review — Final validation — Phase 1 + Phase 2

Two blocking correctness defects remain in the new SDK read helpers. Finalized contests can cause the shared pagination loop to repeat indefinitely, and fetching a kept choice does not consult the finalized vote winner, so it can return an active choice or fail when multiple choices exist. CI reports no completed code-check failures, although the Rust and platform test jobs are still in progress.

🔴 2 blocking

Review provenance

Source: reviewer 1: muse-spark-1.3-contributor (agent: phase1-reviewer, role: general); reviewer 2: muse-spark-1.3-contributor (agent: phase1-reviewer, role: rust-quality); reviewer 3: gpt-6.1-sol (agent: phase2-reviewer, role: general); reviewer 4: gpt-6.1-sol (agent: phase2-reviewer, role: architecture-layering); reviewer 5: gpt-6.1-sol (agent: phase2-reviewer, role: ffi-engineer); reviewer 6: gpt-6.1-sol (agent: phase2-reviewer, role: rust-quality); final verifier: gpt-6.1-sol (agent: sol-verifier, role: final-verifier)

  • Triage: normal by gpt-6.1-sol (effort low) — This is a large cross-language SDK and WASM feature spanning proposal construction, document queries, voting, fee calculations, and shared paging behavior, but it does not modify consensus, funds movement, cryptography, key handling, network deserialization, or storage migrations.
  • Phase 1 reviewers: muse-spark-1.3-contributor — general (completed, effort xhigh); agent phase1-reviewer, muse-spark-1.3-contributor — rust-quality (completed, effort xhigh); agent phase1-reviewer
  • Phase 1 model: muse-spark-1.3-contributor — not quota-gated; passed over gemini-3.8-flash-high (antigravity quota lookup failed), glm-5.3-flash (not used above high effort; tier asks max)
  • Fresh verifier: gpt-6.1-sol — final-verifier; agent sol-verifier
  • Phase 2 reviewers: gpt-6.1-sol — general (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — architecture-layering (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — ffi-engineer (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — rust-quality (completed, effort high); agent phase2-reviewer
🤖 Prompt for all review comments with AI agents
These findings are from an automated code review (PastaClaw). How to work through them:
- Verify each finding against the code; fix only what is real, as narrowly as the finding.
- If a finding is wrong, decline it on its thread in one or two sentences; the bot answers once and stops.
- To defer one, link a tracking issue on its thread (or reply asking the bot to file one).
- Suggestions don't block the merge, but most are worth fixing in this PR; judge each on its merits.
- Keep the PR's scope; don't grow it to satisfy hardening suggestions.
- Carried items were raised in an earlier round: read the thread first, and don't re-fix what you already fixed, declined or deferred there. Disputed and deferred items are already handled and are not listed.

In `packages/rs-sdk/src/platform/documents/contest_vote_state.rs`:
- [BLOCKING] packages/rs-sdk/src/platform/documents/contest_vote_state.rs:67-72: Stop pagination when a finalized contest returns its full record
  The loop decides whether to issue another page solely from the returned page length. For a finalized contest, the proof verifier reconstructs the complete contender map even when the query includes a limit and start position, and `page.winner.is_some()` identifies that finalized response. With `limit = None`, `fetch_multiple_choice_proposal_vote` therefore receives the full record, sets `start_at` to the same last contender, receives the same record again, and loops indefinitely for contests with at least `DEFAULT_QUERY_LIMIT` contenders. Stop pagination as soon as the page contains finalized winner information; this also avoids the unnecessary extra proved query for a finalized contest.

In `packages/rs-sdk/src/platform/decision_proposals/multiple_choice.rs`:
- [BLOCKING] packages/rs-sdk/src/platform/decision_proposals/multiple_choice.rs:614-622: Use the finalized vote winner to fetch the kept choice
  `fetch_kept_choice` queries the `byProposal` contested index directly and never reads the contest's winner. While voting is still active, that index contains the active choices: with one choice this returns an active choice even though the method documents `None` until finalization, while multiple choices make a single-document fetch non-unique. Read the bounded contest state first, return `None` unless the winner is `WonByIdentity(choice_id)`, and then fetch the choice by that exact document id. Return `None` for a running vote, a locked vote, and a proposal with no choices.

@github-actions

Copy link
Copy Markdown
Contributor

Your move: thepastaclaw requested changes on this head; dismiss the review or push a fix; thepastaclaw left review threads unresolved; resolve them.
Full checklist in the description.

@github-actions github-actions Bot added waiting-self-review Waiting for the author to post /self-reviewed and removed waiting-bots Waiting for the review bots to report on this head labels Nov 13, 2026
A contest that ended answers its first vote state page with its whole record.
The read loop took a full page as a sign of more and asked for the next one,
which a finished contest answers empty: one proved query for nothing. The loop
now stops at a page that names a winner.

fetch_kept_choice stays one document query through the byProposal index, and
its comment says why that is enough: the choices a vote holds are in the vote's
own storage, so the index holds a choice only once a vote has kept it. The
consensus tests now assert it: a document query of a proposal's choices finds
none while the vote runs, none after a lock, and only the kept choice after an
award.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Nov 13, 2026 •

Copy link
Copy Markdown
Contributor

Waiting for bot review — thepastaclaw not yet. Wait for the missing reviews, or a writer can post /skip-bots to proceed without them; blocking findings still need addressing.
Full checklist in the description.

@github-actions github-actions Bot added waiting-bots Waiting for the review bots to report on this head and removed waiting-self-review Waiting for the author to post /self-reviewed labels Nov 13, 2026

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review — Final validation — Phase 1 + Phase 2

The current head resolves all five prior findings: finalized contest reads now stop after the complete record and enforce limits, vote-state conversion moves decoded data without cloning, and the WASM result shape has offline coverage. One remaining interop mismatch is present in the new multiple-choice listing APIs: the Rust helper emits null for missing documents while both WASM and facade declarations advertise undefined; Rust CI passed, while JavaScript and WASM checks were still queued in the supplied CI snapshot.

🔴 0 blocking | 🟡 0 suggestion(s) | 💬 0 nitpick(s)

Approved: nothing here blocks the merge. The non-blocking findings are optional: fix them forward, in this PR or a follow-up, without waiting for another review round.

Follow-up candidates (optional) (1)

New non-blocking observations on code unchanged since the last full review. No inline threads are opened for them and later reviews do not repeat them: fix forward or track separately if they are worth it.

🟡 Suggestion: Declare null values in multiple-choice proposal listing maps
packages/wasm-sdk/src/decision_proposals/multiple_choice.rs:482-485

The new getMultipleChoiceProposalsByOwner and getMultipleChoiceProposalsByCreatedAt bindings declare Map<string, Document | undefined>, but documents_map converts a missing Rust document to JsValue::NULL. The JavaScript facade also repeats the Document | undefined return type at multiple-choice-facade.ts:69-80, so consumers can receive null while TypeScript says only undefined is possible. Change both WASM return annotations and both facade return types to Document | null, or change documents_map to emit undefined consistently.

source: gpt-6.1-sol (phase2-reviewer: ffi-engineer)

Review provenance

Source: reviewer 1: muse-spark-1.3-contributor (agent: phase1-reviewer, role: general); reviewer 2: muse-spark-1.3-contributor (agent: phase1-reviewer, role: rust-quality); reviewer 3: gpt-6.1-sol (agent: phase2-reviewer, role: general); reviewer 4: gpt-6.1-sol (agent: phase2-reviewer, role: architecture-layering); reviewer 5: gpt-6.1-sol (agent: phase2-reviewer, role: ffi-engineer); reviewer 6: gpt-6.1-sol (agent: phase2-reviewer, role: rust-quality); reviewer 7: gpt-6.1-sol (agent: phase2-reviewer, role: general); reviewer 8: gpt-6.1-sol (agent: phase2-reviewer, role: architecture-layering); reviewer 9: gpt-6.1-sol (agent: phase2-reviewer, role: ffi-engineer); reviewer 10: gpt-6.1-sol (agent: phase2-reviewer, role: rust-quality); final verifier: gpt-6.1-sol (agent: sol-verifier, role: final-verifier)

  • Triage: normal by gpt-6.1-sol (effort low) — This is a large, cross-cutting client-library change spanning Rust, WASM, and JavaScript with voting and paging behavior changes, but it does not itself modify consensus, cryptography, signatures, or other critical surfaces.
  • Phase 1 reviewers: muse-spark-1.3-contributor — general (completed, effort xhigh); agent phase1-reviewer, muse-spark-1.3-contributor — rust-quality (completed, effort xhigh); agent phase1-reviewer
  • Phase 1 model: muse-spark-1.3-contributor — not quota-gated; passed over gemini-3.8-flash-high (antigravity quota lookup failed), glm-5.3-flash (not used above high effort; tier asks max)
  • Fresh final gate: an independent Phase-2 review ran after iterative findings were reconciled
  • Code unchanged since the last final review (f0621c6c): 1 new suggestion(s) listed as follow-up candidates, not posted inline
  • Fresh verifier: gpt-6.1-sol — final-verifier; agent sol-verifier
  • Phase 2 reviewers: gpt-6.1-sol — general (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — architecture-layering (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — ffi-engineer (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — rust-quality (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — general (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — architecture-layering (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — ffi-engineer (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — rust-quality (completed, effort high); agent phase2-reviewer
🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify the current code and confirm that no unresolved issues remain.

No unresolved findings remain from the prior review on this head.

@github-actions

Copy link
Copy Markdown
Contributor

Bots are done — your move: post /self-reviewed.
Full checklist in the description.

@github-actions github-actions Bot added waiting-self-review Waiting for the author to post /self-reviewed and removed waiting-bots Waiting for the review bots to report on this head labels Nov 13, 2026
@QuantumExplorer
QuantumExplorer merged commit 2c0f93c into v5.1-dev Nov 13, 2026
64 of 66 checks passed
@QuantumExplorer
QuantumExplorer deleted the claude/multiple-choice-proposal-sdk branch November 13, 2026 14:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

waiting-self-review Waiting for the author to post /self-reviewed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants