forked from yvv/decision
v3 : le vote par la chaîne Ğ1, côté moteur et serveur
Le bulletin scellé (ld1: + blake2b-256 du bulletin canonique, sel de 128 bits), l'ordre de la chaîne (bloc, puis rang de l'événement : le dernier fait foi), les adresses SS58 comparées par clé (ld-engine, pur). trustin-access : Monnaie libre vote par la chaîne, les groupes gérés sur le serveur ; le droit de vote se lit à la confirmation. Serveur : bulletins en attente, confirmation sur l'indexeur (virement, auteur, identité, membre), identité idty:<index> créée au besoin, tâche de fond toutes les 10 s, expiration à 24 h, vote groupé en une écriture dans les groupes gérés. Migration additive 0006. Sans LD_VOTE_ACCOUNT, le vote par la chaîne est éteint. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -14,7 +14,9 @@
|
||||
//! - everything is open to a member except access management, which belongs
|
||||
//! to trustinWallet's administrators;
|
||||
//! - where the electorate is the web of trust (Monnaie libre), only its members
|
||||
//! vote (2026-10-21, independent review: one person, one vote).
|
||||
//! vote (2026-10-21, independent review: one person, one vote) — through the
|
||||
//! Ğ1 chain, one transaction per vote, for the identity that signs it, member
|
||||
//! when the vote is confirmed (2026-10-30, ADR 0008).
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -101,16 +103,46 @@ pub fn opens(identity: &Identity, group: &Group, granted: bool) -> bool {
|
||||
is_member(identity, group, granted)
|
||||
}
|
||||
|
||||
/// Can this identity vote in the group? Where the electorate is the Ğ1 web of
|
||||
/// trust (policy `G1Members`), only its members vote: the threshold is computed
|
||||
/// on the size of the web, and an invitation identity next to a Ğ1 identity
|
||||
/// would give one person two votes. Development identities stand in for members
|
||||
/// (local only: the dev login is never enabled in production).
|
||||
/// How a group votes.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum VoteWay {
|
||||
/// On the server: one vote per identity of the group (managed groups).
|
||||
Server,
|
||||
/// On the Ğ1 chain: one transaction per vote, signed by the person's wallet
|
||||
/// (ADR 0008). The vote counts for the Ğ1 identity of the signer.
|
||||
Chain,
|
||||
}
|
||||
|
||||
/// Where the electorate is the Ğ1 web of trust (policy `G1Members`), votes go
|
||||
/// through the chain (Yvv, 2026-10-30: « une transaction par vote »): one
|
||||
/// person, one voice, proven by the identity that signs — not by the way one
|
||||
/// signed in here. Elsewhere, on the server.
|
||||
pub fn vote_way(group: &Group) -> VoteWay {
|
||||
match group.policy {
|
||||
GroupPolicy::G1Members => VoteWay::Chain,
|
||||
GroupPolicy::Managed => VoteWay::Server,
|
||||
}
|
||||
}
|
||||
|
||||
/// Can this identity vote on the server in the group? In a managed group, every
|
||||
/// member. Where the vote goes through the chain, nobody: an invitation identity
|
||||
/// next to a Ğ1 identity would give one person two voices.
|
||||
pub fn votes_in(identity: &Identity, group: &Group, granted: bool) -> bool {
|
||||
opens(identity, group, granted)
|
||||
&& (group.policy != GroupPolicy::G1Members
|
||||
|| identity.kind == IdentityKind::Dev
|
||||
|| (identity.kind == IdentityKind::G1 && identity.g1_member))
|
||||
opens(identity, group, granted) && vote_way(group) == VoteWay::Server
|
||||
}
|
||||
|
||||
/// Can this identity ask for a ballot to sign with a wallet? Whoever opens a
|
||||
/// group that votes on the chain: who votes is decided by the chain itself, at
|
||||
/// confirmation (`chain_vote_counts`).
|
||||
pub fn asks_ballot(identity: &Identity, group: &Group, granted: bool) -> bool {
|
||||
opens(identity, group, granted) && vote_way(group) == VoteWay::Chain
|
||||
}
|
||||
|
||||
/// Does a vote signed on the chain count? Only for an identity that is a member
|
||||
/// of the web of trust when the vote is confirmed (the electorate W is the web).
|
||||
pub fn chain_vote_counts(member: bool) -> bool {
|
||||
member
|
||||
}
|
||||
|
||||
/// Access management stays with trustinWallet's administrators.
|
||||
@@ -191,15 +223,32 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn only_wot_members_vote_where_the_electorate_is_the_web_of_trust() {
|
||||
fn where_the_electorate_is_the_web_of_trust_the_vote_goes_through_the_chain() {
|
||||
let ml = group("monnaie-libre");
|
||||
assert!(votes_in(&who(IdentityKind::G1, true, false), &ml, false));
|
||||
assert_eq!(vote_way(&ml), VoteWay::Chain);
|
||||
assert_eq!(vote_way(&group("essai")), VoteWay::Server);
|
||||
// Nobody votes on the server there, whatever the way one signed in.
|
||||
for kind in [IdentityKind::G1, IdentityKind::Invite, IdentityKind::Dev] {
|
||||
assert!(!votes_in(&who(kind, true, false), &ml, true), "{kind:?}");
|
||||
}
|
||||
// Whoever opens the group may ask for a ballot to sign with a wallet...
|
||||
let invited = who(IdentityKind::Invite, false, true);
|
||||
assert!(opens(&invited, &ml, true), "an invitation may open Monnaie libre to read and propose");
|
||||
assert!(!votes_in(&invited, &ml, true), "but its votes would not be web-of-trust votes");
|
||||
assert!(!votes_in(&who(IdentityKind::G1, false, false), &ml, true), "a former member reads, does not vote");
|
||||
assert!(votes_in(&invited, &group("essai"), true), "managed groups: every member votes");
|
||||
assert!(!votes_in(&invited, &group("essai"), false));
|
||||
assert!(asks_ballot(&invited, &ml, true));
|
||||
assert!(asks_ballot(&who(IdentityKind::G1, true, false), &ml, false));
|
||||
assert!(!asks_ballot(&invited, &ml, false), "not without opening the group");
|
||||
// ...and the vote counts only for a member of the web of trust, read on the chain.
|
||||
assert!(chain_vote_counts(true));
|
||||
assert!(!chain_vote_counts(false), "a former member, or an account without identity, does not vote");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn in_a_managed_group_every_member_votes_on_the_server() {
|
||||
let essai = group("essai");
|
||||
let invited = who(IdentityKind::Invite, false, false);
|
||||
assert!(votes_in(&invited, &essai, true), "managed groups: every member votes");
|
||||
assert!(!votes_in(&invited, &essai, false));
|
||||
assert!(!asks_ballot(&invited, &essai, true), "no chain there: the test group stays easy to try");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user