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:
@@ -14,10 +14,14 @@ struct Client {
|
||||
|
||||
impl Client {
|
||||
async fn new() -> Self {
|
||||
Self::with_state().await.0
|
||||
}
|
||||
|
||||
async fn with_state() -> (Self, AppState) {
|
||||
let db = open_db("sqlite::memory:").await.expect("db");
|
||||
let state = AppState::new(Config::for_tests(), db).await.expect("state");
|
||||
seed::ensure(&state).await.expect("seed");
|
||||
Self { router: app(state), cookie: None }
|
||||
(Self { router: app(state.clone()), cookie: None }, state)
|
||||
}
|
||||
|
||||
fn other(&self) -> Self {
|
||||
@@ -91,6 +95,21 @@ fn version(doc: &Value, code: &str, which: &str) -> String {
|
||||
v["id"].as_str().expect("id").to_owned()
|
||||
}
|
||||
|
||||
/// The quorum of an article of the test group set to 2, as its author — an administrator,
|
||||
/// for a text of the seed — sets it before the first vote (ADR 0013): where the quorum of
|
||||
/// every level is 1 and one voice passes, one voice of two then no longer does.
|
||||
async fn two_voices(state: &AppState, code: &str) {
|
||||
let n = sqlx::query(
|
||||
"UPDATE units SET quorum = 2 WHERE code = ? AND doc_id IN (SELECT id FROM documents WHERE group_slug = 'essai')",
|
||||
)
|
||||
.bind(code)
|
||||
.execute(&state.db)
|
||||
.await
|
||||
.expect("quorum")
|
||||
.rows_affected();
|
||||
assert_eq!(n, 1, "{code}");
|
||||
}
|
||||
|
||||
async fn decisions(c: &mut Client, group: &str) -> Value {
|
||||
c.get(&format!("/api/groups/{group}/decisions")).await
|
||||
}
|
||||
@@ -139,8 +158,11 @@ async fn a_group_one_does_not_open_shows_nothing_of_what_is_coming() {
|
||||
async fn decisions_one_line_per_doc_with_the_modules_left_to_vote() {
|
||||
// In the test group, which votes on the server (Monnaie libre votes on the chain:
|
||||
// tests/ballots.rs counts « sans ton vote » there).
|
||||
let mut c = Client::new().await;
|
||||
let (mut c, state) = Client::with_state().await;
|
||||
c.login("Chiara", false).await;
|
||||
// Someone else, who will not vote: W(essai) = 2.
|
||||
let mut yvv = c.other();
|
||||
yvv.login("Yvv", false).await;
|
||||
let d = decisions(&mut c, "essai").await;
|
||||
assert_eq!(d["canVote"], true);
|
||||
let docs = d["docs"].as_array().expect("docs");
|
||||
@@ -154,14 +176,15 @@ async fn decisions_one_line_per_doc_with_the_modules_left_to_vote() {
|
||||
assert_eq!(docs[0]["withoutMyVote"], 41);
|
||||
|
||||
let doc = the_doc(&mut c, "essai").await;
|
||||
// Votes that adopt nothing: the quorum 2 (ADR 0013), one voice of two.
|
||||
two_voices(&state, "E3").await;
|
||||
two_voices(&state, "I1").await;
|
||||
c.vote(&version(&doc, "E3", "0"), "for").await;
|
||||
c.vote(&version(&doc, "E3", "1"), "against").await;
|
||||
c.vote(&version(&doc, "I1", "0"), "for").await;
|
||||
let d = decisions(&mut c, "essai").await;
|
||||
assert_eq!((d["docs"][0]["toVote"].as_u64(), d["docs"][0]["withoutMyVote"].as_u64()), (Some(41), Some(39)));
|
||||
// Someone else has not voted yet.
|
||||
let mut yvv = c.other();
|
||||
yvv.login("Yvv", false).await;
|
||||
assert_eq!(decisions(&mut yvv, "essai").await["docs"][0]["withoutMyVote"], 41);
|
||||
}
|
||||
|
||||
@@ -171,11 +194,11 @@ async fn an_adopted_module_leaves_the_count_and_a_vote_to_reconfirm_does_not_cou
|
||||
yvv.login("Yvv", false).await;
|
||||
let mut chiara = yvv.other();
|
||||
chiara.login("Chiara", false).await;
|
||||
// Dev identities are members of every group: W(essai) = 2, C = 1; two « for » adopt.
|
||||
// Dev identities are members of every group: W(essai) = 2, the quorum 1 at every level
|
||||
// (ADR 0013): one « for » adopts.
|
||||
let doc = the_doc(&mut yvv, "essai").await;
|
||||
let e3 = version(&doc, "E3", "0");
|
||||
yvv.vote(&e3, "for").await;
|
||||
let v = chiara.vote(&e3, "for").await;
|
||||
let v = yvv.vote(&e3, "for").await;
|
||||
assert_eq!(v["entered"][0]["code"], "E3");
|
||||
let d = decisions(&mut yvv, "essai").await;
|
||||
assert_eq!((d["docs"][0]["toVote"].as_u64(), d["docs"][0]["withoutMyVote"].as_u64()), (Some(40), Some(40)));
|
||||
|
||||
Reference in New Issue
Block a user