Lot T : F1.1, F1.2 et N1.1 disent le quorum par cran (seed, trois langues) ; un second amendement de seed gardé les remplace en production

- Le seed : « Formule du seuil WoT », « Paramètres par défaut », « Niveaux d'inertie »
  réécrits d'après l'ADR 0013 (sous le quorum, les votants qui manquent comptent comme
  « non » ; au-delà, la part du cran) et l'ADR 0011 (aucun article en vigueur au départ,
  les trois Propositions seed au cran 2, le titre au cran 4) ; le _source le dit.
- Les nombres : chacun, en fr, en et es, calculé par le moteur ou lu dans le seed
  (tests/seed_quorum_texts.rs) ; les trois formules évaluées telles qu'écrites contre
  threshold et inertia ; les règles de forme vérifiées.
- seed_amend : 2026-11-03-f1-1-quorum, -f1-2-quorum, -n1-1-quorum. Un amendement qui
  « follows » un autre admet la révision 2 « initial puis amendment » écrite par lui seul,
  rien d'autre ; ceux de M5 sur F1.2 et N1.1 sont « superseded_by » (plus rien décidé,
  sans erreur). La garde compte aussi les lignes de bulletin du tampon (« ballots »).
- Tests vus rouges : sept du lot T, deux unitaires de la garde ; les tests de M5 jouent
  sur le seed de M5 (la fixture de production).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Yvv
2026-11-03 07:16:57 +01:00
co-authored by Claude Opus 5.5
parent 39d5cd2633
commit 92ba485f71
6 changed files with 1269 additions and 30 deletions
+39 -11
View File
@@ -1,6 +1,10 @@
//! The four rule articles of the seed rewritten after the rules applied (Yvv,
//! 2026-11-02): F1.2, N1, N1.1 and O1 are open proposals at zero votes, not texts
//! in force; a guarded seed amendment replaces them where nobody touched them.
//!
//! Played with the seed M5 shipped (3f10a0e, the fixture of production): since lot T
//! (2026-11-03), today's seed gives F1.2 and N1.1 the texts of the quorum, and their
//! amendments of 02/11 are superseded (`tests/seed_quorum_texts.rs`).
use ld_server::seed_amend::{self, AMENDMENTS, Version};
use ld_server::{AppState, Config, open_db, seed};
@@ -21,6 +25,15 @@ fn today_seed() -> Value {
read(Config::for_tests().seed_dir.join("acte-engagement-certification.json"))
}
/// The seed M5 shipped: production's since 2026-11-02.
fn m5_dir() -> std::path::PathBuf {
std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/seed-production-2026-11-02")
}
fn m5_seed() -> Value {
read(m5_dir().join("acte-engagement-certification.json"))
}
fn version<'a>(seed: &'a Value, code: &str) -> &'a Value {
let u = seed["units"].as_array().expect("units").iter().find(|u| u["code"] == code).expect("unit");
&u["versions"][0]
@@ -30,12 +43,12 @@ fn fr(v: &Value) -> (&str, &str) {
(v["title"]["fr"].as_str().expect("title"), v["body"]["fr"].as_str().expect("body"))
}
/// Today's seed with the four rule articles of before.
/// The seed of M5 with the four rule articles of before.
fn seed_dir_before(tag: &str) -> std::path::PathBuf {
let dir = std::env::temp_dir().join(format!("ld-seed-rules-{tag}-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).expect("dir");
let mut seed = today_seed();
let mut seed = m5_seed();
let old = before();
for u in seed["units"].as_array_mut().expect("units") {
let code = u["code"].as_str().expect("code").to_owned();
@@ -59,9 +72,11 @@ async fn base_before(tag: &str) -> AppState {
state
}
/// The same base, started again with today's seed and its amendments.
/// The same base, started again with the seed of M5 and its amendments.
async fn restart(before: &AppState) -> AppState {
let state = AppState::new(Config::for_tests(), before.db.clone()).await.expect("state");
let mut cfg = Config::for_tests();
cfg.seed_dir = m5_dir();
let state = AppState::new(cfg, before.db.clone()).await.expect("state");
seed::ensure(&state).await.expect("seed");
state
}
@@ -92,19 +107,29 @@ async fn decisions(state: &AppState) -> Vec<(String, String, String, Option<Stri
}
#[test]
fn the_shipped_amendments_go_from_the_texts_of_before_to_the_seed_of_today() {
let (old, today) = (before(), today_seed());
fn the_shipped_amendments_go_from_the_texts_of_before_to_the_seed_of_m5() {
let (old, m5, today) = (before(), m5_seed(), today_seed());
for code in CODES {
let a = AMENDMENTS.iter().find(|a| a.unit == code).unwrap_or_else(|| panic!("no amendment for {code}"));
assert_eq!(a.doc, "acte-engagement-certification");
assert_eq!(a.version, Version::Open("1.0.0"), "{code}: the open proposal of the seed");
let (t, b) = fr(&old["units"][code]);
assert_eq!(seed_amend::fingerprint(t, b), a.from, "{code}: the text production holds");
let v = version(&today, code);
let v = version(&m5, code);
assert_eq!((v["status"].as_str(), v["label"].as_str()), (Some("open"), Some("1.0.0")), "{code}");
let (t, b) = fr(v);
assert_eq!(seed_amend::fingerprint(t, b), a.to, "{code}: the text of the seed today");
assert_eq!(seed_amend::fingerprint(t, b), a.to, "{code}: the text of the seed of M5");
assert_ne!(a.from, a.to, "{code}");
// Today's seed gives it, or another text whose amendment follows this one.
let (t, b) = fr(version(&today, code));
let now = seed_amend::fingerprint(t, b);
match a.superseded_by {
None => assert_eq!(now, a.to, "{code}: still the text of today"),
Some(later) => {
let l = AMENDMENTS.iter().find(|l| l.id == later).expect("the later amendment");
assert_eq!((l.unit, l.follows, l.from, l.to), (code, Some(a.id), a.to, now.as_str()), "{code}");
}
}
}
}
@@ -123,7 +148,7 @@ async fn untouched_rule_articles_are_replaced_once_and_a_vote_keeps_its_text() {
.await
.expect("vote");
let state = restart(&base).await;
let (old, today) = (before(), today_seed());
let (old, today) = (before(), m5_seed());
for group in ["essai", "monnaie-libre"] {
for code in CODES {
let (id, title, body, revision) = proposal(&state, group, code).await;
@@ -202,6 +227,9 @@ async fn a_new_base_is_born_with_the_rules_of_today() {
assert_eq!(((title.as_str(), body.as_str()), revision), (fr(version(&today, code)), 1), "{group} {code}");
}
}
assert!(decisions(&state).await.iter().all(|(_, _, o, _)| o == "present"));
assert_eq!(decisions(&state).await.len(), 8);
// N1 and O1: present. F1.2 and N1.1: their amendments, superseded, decide nothing.
let got = decisions(&state).await;
assert!(got.iter().all(|(_, _, o, _)| o == "present"), "{got:?}");
let units: Vec<&str> = got.iter().map(|(_, u, _, _)| u.as_str()).collect();
assert_eq!(units, ["N1", "O1", "N1", "O1"]);
}