forked from yvv/decision
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:
@@ -353,9 +353,13 @@ async fn the_production_base_turns_its_untouched_texts_into_seed_proposals_once(
|
||||
kept.push(v);
|
||||
}
|
||||
}
|
||||
// The articles of the quorum (F1.1, F1.2, N1.1) are amended at the same start, by the
|
||||
// seed amendments of lot T (`tests/seed_quorum_texts.rs`): left out here.
|
||||
let translations = |state: AppState| async move {
|
||||
sqlx::query_as::<_, (String, String, String, String, String)>(
|
||||
"SELECT proposal_id, lang, title, body, source_hash FROM translations ORDER BY proposal_id, lang",
|
||||
"SELECT t.proposal_id, t.lang, t.title, t.body, t.source_hash FROM translations t
|
||||
JOIN proposals p ON p.id = t.proposal_id JOIN units u ON u.id = p.unit_id
|
||||
WHERE u.code NOT IN ('F1.1', 'F1.2', 'N1.1') ORDER BY t.proposal_id, t.lang",
|
||||
)
|
||||
.fetch_all(&state.db)
|
||||
.await
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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"]);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user