feat(dao): proposal_vote.rs builder — Phase 3 unsigned tx
Mirrors proposal_create's shape: 3 inputs (stake script + proposal
script + funding wallet), 2 reference inputs (stake validator +
proposal validator), 2 outputs (mutated stake + mutated proposal +
maybe change), 2 plutus spends (PermitVote + Vote tag), no mints.
Pre-flight matches every Plutarch validator check from
Agora/Proposal/Scripts.hs PVote (~L484) + Stake/Redeemers.hs
ppermitVote (~L196):
- voter pkh is owner OR delegatee
- proposal status == VotingReady
- stake doesn't already have Voted lock for this proposal_id
- stake.staked_amount >= proposal.thresholds.vote (single-stake v1)
- result_tag is in proposal.votes keys (== effects keys)
- validity upper bound inside [starting_time + draft_time,
starting_time + draft_time + voting_time]
New stake datum prepends the Voted lock per paddNewLock = pcons.
New proposal datum increments votes[result_tag] by stake amount;
all other fields preserved bit-exact since validator does record `==`.
Voted.posix_time = caller-supplied validity_upper_ms — matches
PFullyBoundedTimeRange _ upperBound the validator extracts. Caller
(MCP tool) computes ms-from-slot via mainnet Shelley genesis.
9 unit tests covering happy path + every preflight reject + delegated
voter accepted.