Fusion de chantier/acte-certification (lots H et S) dans le lot V : le tampon et ses états, avec le quorum et les Propositions seed

- conflits résolus des deux côtés : kept_params garde le quorum (S) et la part simulée, la version déplacée, le cran d'avant (V) ; proposals.rs garde seedMinimal / seedProposal (S) et tallyPending / tallySimulated / myVoteState (V) ;
- simulation::reexamine juge sur le quorum gardé par l'adoption (params.quorum, à défaut celui de l'article) ;
- l'acte répété n'examine plus une adoption qui tient (params.reexamined) ;
- tests V repris sans texte en vigueur au départ (I1, E1 : la Proposition seed) et sur un quorum 2 (small_web) ; le test overview d'une personne invitée dans Monnaie libre suit la simulation (elle vote, chaîne fermée).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Yvv
2026-11-03 05:32:51 +01:00
co-authored by Claude Opus 5.5
66 changed files with 5134 additions and 701 deletions
+36 -1
View File
@@ -27,6 +27,9 @@
//! - while the chain is closed, the vote is simulated: whoever may ask for a
//! ballot votes on the server, marked as simulated — `casts_simulated`
//! (2026-11-02, lot V, ADR 0012).
//! - the quorum of an article is set by the author of its first version — for a
//! text of the seed, by an administrator — and only before the first vote —
//! `sets_quorum` (2026-11-03, ADR 0013).
use serde::{Deserialize, Serialize};
@@ -197,6 +200,24 @@ pub fn reads_message(identity: &Identity, recipient: &str) -> bool {
identity.id == recipient
}
/// Who sets the quorum of an article (Yvv, 2026-11-02: « L'auteur, avant le 1er vote »,
/// ADR 0013): the author of its first version; for an article without author (a text of
/// the seed), an administrator of trustinWallet — never one for an author. Once a vote
/// exists on the article (`voted`, the server's `quorum::a_vote_exists`), nobody: the
/// votes cast were judged on it.
pub fn sets_quorum(identity: &Identity, first_author: Option<&str>, voted: bool) -> bool {
if voted {
return false;
}
match first_author {
Some(author) => identity.id == author,
None => identity.admin,
}
}
/// The test group: hidden, the quorum 1 at every level — the one place one voice passes.
pub const TEST_GROUP: &str = "essai";
/// The groups a fresh deployment starts with. Ordered as displayed.
pub fn default_groups() -> Vec<Group> {
let g = |slug: &str, name: &str, parent: Option<&str>, policy, hidden| Group {
@@ -217,7 +238,7 @@ pub fn default_groups() -> Vec<Group> {
GroupPolicy::Managed,
false,
),
g("essai", "Groupe d'essai", None, GroupPolicy::Managed, true),
g(TEST_GROUP, "Groupe d'essai", None, GroupPolicy::Managed, true),
]
}
@@ -335,6 +356,20 @@ mod tests {
assert!(!lifts_objection(&other, "luna") && !reiterates_objection(&other, "luna"));
}
#[test]
fn the_author_sets_the_quorum_before_the_first_vote_an_administrator_for_the_seed() {
let chiara = Identity { id: "chiara".into(), ..who(IdentityKind::Invite, false, false) };
let admin = Identity { id: "yvv".into(), ..who(IdentityKind::G1, true, true) };
let other = Identity { id: "luna".into(), ..who(IdentityKind::G1, true, false) };
assert!(sets_quorum(&chiara, Some("chiara"), false));
assert!(!sets_quorum(&chiara, Some("chiara"), true), "after the first vote, nobody");
assert!(!sets_quorum(&other, Some("chiara"), false));
assert!(!sets_quorum(&admin, Some("chiara"), false), "an administrator never sets it for an author");
assert!(sets_quorum(&admin, None, false), "a text of the seed: an administrator");
assert!(!sets_quorum(&admin, None, true));
assert!(!sets_quorum(&other, None, false));
}
#[test]
fn a_message_is_read_by_its_recipient_only() {
let admin = Identity { id: "yvv".into(), ..who(IdentityKind::G1, true, true) };