Lot S, phase 2 : le quorum dans le serveur (ADR 0013), vu rouge puis vert

- migration 0011 (additive ; 0010 reste au lot V) : documents.quorums (par cran,
  NULL = vote_floor à chaque cran, le C d'avant), units.quorum (NULL = celui du
  cran), journaux quorum_changes et quorum_amendments ;
- quorum effectif = units.quorum ?? quorums[cran] ; les propositions d'inertie et
  les modules en héritent ; p0 = 2 × vote_floor / W, jamais le quorum d'un article ;
- route PUT /units/{id}/quorum { "quorum": n | null } : trustin_access::sets_quorum
  (l'auteur de la première version, un administrateur pour le seed), refusée dès
  qu'un vote existe (quorum::voted_units : vote du serveur, bulletin de la chaîne
  en attente ou confirmé, adoption) ; bornes 2..W, 1..W dans le groupe d'essai ;
- amendement gardé 2026-11-03-quorums-par-cran : écrit les quorums du seed là où
  il n'y en a pas, une fois par document, consigné, file de publication là où il
  publie ;
- JSON : evaluation.quorum / yesNeeded / noNeeded ; l'unité : quorum,
  quorumDefault, canSetQuorum ; doc.quorums ; levels[].quorum ; document.json
  publié : quorums ; adoptions.params : quorum (plus de constant) ;
- seed : quorums 12 · 12 · 50 · 50 · 50 (Monnaie libre), 1 partout (essai).
- tests : tests/quorum.rs (5) et les règles pures ; les scénarios du groupe
  d'essai qui demandaient deux voix posent le quorum 2 de l'article, comme son
  auteur le ferait ; les chiffres de Monnaie libre suivent (9 au cran 2, 39 au
  cran 3, 43 au cran 4).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Yvv
2026-11-03 05:00:41 +01:00
co-authored by Claude Opus 5.5
parent 37147d1f59
commit 01b7841472
20 changed files with 1205 additions and 95 deletions
+28
View File
@@ -237,6 +237,14 @@ impl Scale {
}
}
/// The quorums an author may set (ADR 0013): from 2 — one voice never passes a text
/// alone — to W; from 1 in the test group, where one voice passes to try things out. A
/// tiny electorate never brings the lowest under 2.
pub fn quorum_bounds(electorate: u32, test_group: bool) -> (u32, u32) {
let lowest = if test_group { 1 } else { 2 };
(lowest, electorate.max(lowest))
}
#[cfg(test)]
mod tests {
use super::*;
@@ -393,6 +401,26 @@ mod tests {
}
}
#[test]
fn an_author_sets_a_quorum_from_two_to_w_from_one_in_the_test_group() {
assert_eq!(quorum_bounds(5745, false), (2, 5745));
assert_eq!(quorum_bounds(2, true), (1, 2));
assert_eq!(quorum_bounds(1, true), (1, 1));
assert_eq!(quorum_bounds(1, false), (2, 2), "never under two, even on a tiny electorate");
// At the lowest bound outside the test group, one voice never passes, whatever the
// level, the electorate and the usual participation.
for w in [1, 2, 3, 10, 120, 5745, 7224] {
let (lowest, _) = quorum_bounds(w, false);
for p in [Participation::LOWEST, 0.1, Participation::HIGHEST] {
let scale = Scale { electorate: w, participation: p, quorums: [lowest; 5] };
for k in InertiaLevel::ALL {
let lone = scale.evaluate(Tally { for_votes: 1, against: 0 }, k, lowest).expect("valid");
assert!(!lone.reached, "W {w}, p̃ {p}, k {}", k.get());
}
}
}
}
#[test]
fn under_the_quorum_one_vote_never_passes_in_monnaie_libre() {
for voters in [1, 72, 120, 360, 3612] {