sim-rs: con-rs emits CIP-0164 per-vote messages, not bundles
The sim's `VoteBundle` aggregation is a pre-CIP-0164 simplification —
in the real protocol every PV / NPV vote is one BLS signature on the
wire, diffused independently, aggregated only at the certifier. The
con-rs adapter now mirrors that shape:
model.rs:
Vote, VoteId<Node>, VoteKind (no `weight` field — weight is
re-derived at verification time from the persistent-committee
registry / NPV-VRF check, matching CIP wire encoding)
linear_wire.rs:
Message::{AnnounceVote, RequestVote, Vote(Arc<Vote>)}
CpuTask::{VoteGenerated, VoteValidated}
Vote class costs the matching `persistent_vote_bytes` or
`non_persistent_vote_bytes` from VotingConfig.
con_rs.rs:
`votes: BTreeMap<VoteId, VoteState>` (was `vote_bundles`)
`emit_vote` produces ONE Vote per (voter, EB) honouring Part A's
PV-xor-NPV partition. Receivers call
`Elections::weight_for(voter_id, tag, sig)` to re-derive weight —
same code path net-rs uses.
events.rs / sim-cli:
Event::{VoteGenerated, VoteSent, VoteReceived} alongside the
existing bundle variants. `weight` is carried on
`VoteGenerated` for telemetry aggregation (NOT on the wire).
sim-cli stats aggregator handles the new events.
`linear_leios.rs` is untouched in behaviour — bundle Message /
CpuTask variants stay live for it; the per-vote variants are
`unreachable!` from its dispatch. Conversely the bundle variants
are unreachable in con-rs. Strict adapter ownership keeps the
union-typed `Message` enum honest.
Empirical at NA,0.350 / wfa-ls / 750n / -s 200:
1085 PV-only signatures + 57 NPV-only signatures = 1142 votes
0 dual emissions (Part A partition holding)
0 bundles emitted by con-rs (`"There were 0 bundle(s) of votes"`)
weighted vote total 2825 matches PV-multi-seat + NPV-unit-seat
aggregation.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>