Lot T4 : les tests d'avant suivent le seed de T4 sans rien perdre de ce qu'ils prouvent
Les tests qui lisaient F, N ou O rejouent leurs transitions sur le seed de M8 (tests/fixtures/seed-m8) : seed_quorum_texts, seed_rules, seed_proposals, api, quorum ; ils disent en plus que ces amendements sont dépassés par le retrait (superseded_by). Les comptes suivent le document de T4 : 27 articles, quatre Propositions seed (I1, E1, E12, E2), 33 modules à voter (27 articles, le titre, l'ordre de 5 sections). modules.rs : l'ordre sous vote se rejoue sur « conclusion » (K1, K2) ; « un ordre d'un seul article n'a rien à décider » sur une base née du seed de M8 (O1). seed_amend : tests unitaires de Version::SeedProposal et de la ligne seed_proposal (put_forward). order.rs ne lit que les colonnes d'avant 0012 : sections_now ne sert qu'à la naissance des modules, et article_amend n'en garde que les articles vivants. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -39,9 +39,17 @@
|
||||
//! engine on one grid (`grid`): W from 100, medians under 1 % and above 50 % of W, every quorum an
|
||||
//! author may set, the five levels — the closed list given to the reviewer is
|
||||
//! `docs/chantiers/acte-certification/t3c-liste-fermee.md`.
|
||||
//!
|
||||
//! Lot T4 (2026-11-03, Yvv: « ordonnancement, inertie, et formule de vote sont des infos de
|
||||
//! fonctionnement »): F1.1, F1.2 and N1.1 leave the document (ADR 0014); they are no texts to
|
||||
//! vote any more. Their last texts — the seed of M8 (de1b7eb), kept in
|
||||
//! `tests/fixtures/seed-m8/` — stay the reference of what the interface will say of the rules
|
||||
//! (lot U3), and every truth this file checks of them against the engine still holds there. The
|
||||
//! amendments that wrote them are replayed on that seed, as production played them.
|
||||
|
||||
use ld_engine::inertia::{InertiaLevel, Participation, Scale, gradient, quorum_bounds};
|
||||
use ld_engine::threshold::{FormulaParams, Tally, judged_voters, passes, required_ratio, wot_threshold, yes_needed};
|
||||
use ld_server::retire::RETIREMENT;
|
||||
use ld_server::seed_amend::{self, AMENDMENTS, Version};
|
||||
use ld_server::wot::PLAUSIBLE;
|
||||
use ld_server::{AppState, Config, open_db, seed};
|
||||
@@ -64,8 +72,15 @@ fn read(path: PathBuf) -> Value {
|
||||
serde_json::from_str(&std::fs::read_to_string(path).expect("read")).expect("json")
|
||||
}
|
||||
|
||||
fn today_seed() -> Value {
|
||||
read(Config::for_tests().seed_dir.join("acte-engagement-certification.json"))
|
||||
/// The seed production runs since M8 (de1b7eb): the last that holds F1.1, F1.2 and N1.1, and
|
||||
/// the reference of their texts since lot T4 took them out of the document.
|
||||
fn m8_seed() -> Value {
|
||||
read(fixtures().join("seed-m8/acte-engagement-certification.json"))
|
||||
}
|
||||
|
||||
/// A start with the seed of M8: lot T3 decides.
|
||||
fn m8_config(tag: &str) -> Config {
|
||||
config(seed_dir(&format!("{tag}-m8"), &m8_seed()))
|
||||
}
|
||||
|
||||
fn fixtures() -> PathBuf {
|
||||
@@ -89,7 +104,7 @@ fn m6b_texts() -> Value {
|
||||
|
||||
/// The seed production runs since M6b: today's, with the texts of lot T.
|
||||
fn m6b_seed() -> Value {
|
||||
with_texts(&today_seed(), &m6b_texts(), &CODES)
|
||||
with_texts(&m8_seed(), &m6b_texts(), &CODES)
|
||||
}
|
||||
|
||||
/// F1.1, F1.2 and N1.1 as lot T2 wrote them: production's since M6c (84f015a), unchanged by M7b.
|
||||
@@ -99,7 +114,7 @@ fn t2_texts() -> Value {
|
||||
|
||||
/// The seed production runs since M6c: today's, with the texts of lot T2.
|
||||
fn t2_seed() -> Value {
|
||||
with_texts(&today_seed(), &t2_texts(), &CODES)
|
||||
with_texts(&m8_seed(), &t2_texts(), &CODES)
|
||||
}
|
||||
|
||||
fn unit<'a>(seed: &'a Value, code: &str) -> &'a Value {
|
||||
@@ -481,7 +496,7 @@ fn formulas(code: &str, lang: &str) -> Vec<String> {
|
||||
/// article and language listed at once.
|
||||
#[test]
|
||||
fn every_number_of_the_quorum_texts_is_the_engine_s() {
|
||||
let seed = today_seed();
|
||||
let seed = m8_seed();
|
||||
let f = figures(&seed);
|
||||
let mut wrong = Vec::new();
|
||||
for code in CODES {
|
||||
@@ -821,7 +836,7 @@ fn t3_from_t2(code: &str, lang: &str, t2: &str) -> String {
|
||||
|
||||
#[test]
|
||||
fn the_texts_of_lot_t3_change_these_phrases_only() {
|
||||
let (t2, today) = (t2_seed(), today_seed());
|
||||
let (t2, today) = (t2_seed(), m8_seed());
|
||||
for code in CODES {
|
||||
for lang in LANGS {
|
||||
let (before, now) = (text(&t2, code, lang), text(&today, code, lang));
|
||||
@@ -1006,7 +1021,7 @@ fn share_at(w: u32, voters: f64, params: &FormulaParams) -> f64 {
|
||||
/// quorum of 2, two « pour » are asked, at every level, on every W.
|
||||
#[test]
|
||||
fn under_the_quorum_it_takes_as_many_for_as_if_the_quorum_had_voted() {
|
||||
let today = today_seed();
|
||||
let today = m8_seed();
|
||||
let said = [
|
||||
(
|
||||
"fr",
|
||||
@@ -1104,7 +1119,7 @@ fn under_the_quorum_it_takes_as_many_for_as_if_the_quorum_had_voted() {
|
||||
/// quorum 12), while the share asked falls from 73,5 % to 73,2 %.
|
||||
#[test]
|
||||
fn the_share_asked_falls_beyond_the_article_s_quorum_down_to_m() {
|
||||
let today = today_seed();
|
||||
let today = m8_seed();
|
||||
for (lang, phrase) in [
|
||||
(
|
||||
"fr",
|
||||
@@ -1175,7 +1190,7 @@ fn the_share_asked_falls_beyond_the_article_s_quorum_down_to_m() {
|
||||
/// asked of more voters than came; when everyone votes, M_k, at every quorum.
|
||||
#[test]
|
||||
fn the_share_of_a_level_is_asked_at_the_usual_turnout_beyond_the_article_s_quorum() {
|
||||
let today = today_seed();
|
||||
let today = m8_seed();
|
||||
for (lang, intro) in [
|
||||
(
|
||||
"fr",
|
||||
@@ -1247,7 +1262,7 @@ fn the_share_of_a_level_is_asked_at_the_usual_turnout_beyond_the_article_s_quoru
|
||||
/// share asked there is s beyond the article's quorum, and lower, judged as the quorum, under it.
|
||||
#[test]
|
||||
fn g_gives_the_share_of_the_level_at_the_usual_turnout_beyond_the_article_s_quorum() {
|
||||
let today = today_seed();
|
||||
let today = m8_seed();
|
||||
// The sentence of G closes N1.1, in each language; nothing of the sentence of T2 is left.
|
||||
for lang in LANGS {
|
||||
let (_, body) = text(&today, "N1.1", lang);
|
||||
@@ -1324,7 +1339,7 @@ fn g_gives_the_share_of_the_level_at_the_usual_turnout_beyond_the_article_s_quor
|
||||
/// reste entre » read as a fact, not as the rule that brings the median back.
|
||||
#[test]
|
||||
fn the_participation_habituelle_is_the_median_brought_back_within_its_bounds() {
|
||||
let today = today_seed();
|
||||
let today = m8_seed();
|
||||
// The texts write the defined term, never the paraphrase: in N1.1, the list, its five lines and
|
||||
// G (and, in French and Spanish, « comparé à la participation habituelle du groupe », already
|
||||
// there); in F1.2, « Par défaut ».
|
||||
@@ -1421,7 +1436,7 @@ fn the_participation_habituelle_is_the_median_brought_back_within_its_bounds() {
|
||||
/// paragraph says after it bring it back. The voted text is not changed for this.
|
||||
#[test]
|
||||
fn the_reference_turnout_is_a_parameter_the_bounds_bring_back() {
|
||||
let seed = today_seed();
|
||||
let seed = m8_seed();
|
||||
let f = figures(&seed);
|
||||
let vote_floor = f64::from(f.reference) / 2.0;
|
||||
for w in ELECTORATES.into_iter().chain([199, 200, 10_000, 10_001]) {
|
||||
@@ -1629,7 +1644,7 @@ fn close(a: f64, b: f64) -> bool {
|
||||
|
||||
#[test]
|
||||
fn the_formulas_of_the_texts_are_those_the_engine_computes() {
|
||||
let seed = today_seed();
|
||||
let seed = m8_seed();
|
||||
for lang in LANGS {
|
||||
let (_, f11) = text(&seed, "F1.1", lang);
|
||||
let (_, n11) = text(&seed, "N1.1", lang);
|
||||
@@ -1693,7 +1708,7 @@ fn the_formulas_of_the_texts_are_those_the_engine_computes() {
|
||||
|
||||
#[test]
|
||||
fn the_quorum_texts_follow_the_rules_of_form() {
|
||||
let seed = today_seed();
|
||||
let seed = m8_seed();
|
||||
let titles: HashMap<&str, Vec<&str>> = LANGS
|
||||
.iter()
|
||||
.map(|l| {
|
||||
@@ -1812,8 +1827,8 @@ fn the_amendments_of_lot_t2_went_from_the_texts_of_m6b_to_those_of_t2() {
|
||||
assert_eq!(seed_amend::fingerprint(title, body), a.to, "{code}: the text production holds since M6c");
|
||||
assert_ne!(a.from, a.to, "{code}");
|
||||
// It follows lot T's, which wrote the text it replaces; lot T3's replaces it in turn,
|
||||
// where it changes the text (F1.2, N1.1).
|
||||
let later = AMENDMENTS.iter().find(|l| l.follows == Some(a.id)).map(|l| l.id);
|
||||
// where it changes the text (F1.2, N1.1); F1.1 leaves the seed with lot T4 (ADR 0014).
|
||||
let later = AMENDMENTS.iter().find(|l| l.follows == Some(a.id)).map(|l| l.id).or(Some(RETIREMENT));
|
||||
assert_eq!(
|
||||
(a.follows, t.to, t.superseded_by, a.superseded_by),
|
||||
(Some(t.id), a.from, Some(a.id), later),
|
||||
@@ -1873,7 +1888,7 @@ async fn production_after_m6(tag: &str) -> AppState {
|
||||
let first = AppState::new(config(seed_dir(&format!("{tag}-born"), &born)), db.clone()).await.expect("state");
|
||||
seed::documents(&first).await.expect("born");
|
||||
start(&db, config(seed_dir(&format!("{tag}-m5"), &prod))).await;
|
||||
let m6 = with_texts(&today_seed(), &prod, &CODES);
|
||||
let m6 = with_texts(&m8_seed(), &prod, &CODES);
|
||||
start(&db, config(seed_dir(&format!("{tag}-m6"), &m6))).await
|
||||
}
|
||||
|
||||
@@ -2128,8 +2143,8 @@ async fn a_vote_a_ballot_or_a_member_s_translation_keeps_the_text_of_before() {
|
||||
#[tokio::test]
|
||||
async fn a_new_base_is_born_with_the_quorum_texts() {
|
||||
let db = open_db("sqlite::memory:").await.expect("db");
|
||||
let state = start(&db, Config::for_tests()).await;
|
||||
let today = today_seed();
|
||||
let state = start(&db, m8_config("new")).await;
|
||||
let today = m8_seed();
|
||||
for group in GROUPS {
|
||||
for code in CODES {
|
||||
let (_, title, body, revision) = proposal(&state, group, code).await;
|
||||
@@ -2430,7 +2445,7 @@ async fn a_base_born_with_the_texts_of_m5_gets_the_three_phrases() {
|
||||
let first = AppState::new(config(seed_dir("t2-born-m5", &prod)), db.clone()).await.expect("state");
|
||||
seed::documents(&first).await.expect("born");
|
||||
start(&db, config(seed_dir("t2-born-m5-m5", &prod))).await;
|
||||
start(&db, config(seed_dir("t2-born-m5-m6", &with_texts(&today_seed(), &prod, &CODES)))).await;
|
||||
start(&db, config(seed_dir("t2-born-m5-m6", &with_texts(&m8_seed(), &prod, &CODES)))).await;
|
||||
let base = start(&db, m6b_config("t2-born-m5")).await;
|
||||
for group in GROUPS {
|
||||
for code in CODES {
|
||||
@@ -2461,7 +2476,7 @@ const T3_CODES: [&str; 2] = ["F1.2", "N1.1"];
|
||||
|
||||
#[test]
|
||||
fn the_amendments_of_lot_t3_go_from_the_texts_of_t2_to_the_seed_of_today() {
|
||||
let (t2, today) = (t2_seed(), today_seed());
|
||||
let (t2, today) = (t2_seed(), m8_seed());
|
||||
for code in CODES {
|
||||
let t2a = amendment(code, "-phrases");
|
||||
let found = AMENDMENTS.iter().find(|a| a.unit == code && a.id.ends_with("-chiffres"));
|
||||
@@ -2470,7 +2485,7 @@ fn the_amendments_of_lot_t3_go_from_the_texts_of_t2_to_the_seed_of_today() {
|
||||
for lang in LANGS {
|
||||
assert_eq!(text(&t2, code, lang), text(&today, code, lang), "{code} {lang}");
|
||||
}
|
||||
assert_eq!(t2a.superseded_by, None, "{code}");
|
||||
assert_eq!(t2a.superseded_by, Some(RETIREMENT), "{code}: out of the seed since lot T4");
|
||||
continue;
|
||||
}
|
||||
let a = found.unwrap_or_else(|| panic!("{code}: the amendment of lot T3"));
|
||||
@@ -2481,10 +2496,11 @@ fn the_amendments_of_lot_t3_go_from_the_texts_of_t2_to_the_seed_of_today() {
|
||||
let (title, body) = text(&today, code, "fr");
|
||||
assert_eq!(seed_amend::fingerprint(title, body), a.to, "{code}: the text of the seed today");
|
||||
assert_ne!(a.from, a.to, "{code}");
|
||||
// It follows lot T2's, which wrote the text it replaces; nothing replaces it yet.
|
||||
// It follows lot T2's, which wrote the text it replaces; lot T4 takes the article out of
|
||||
// the seed (ADR 0014), and with it this amendment.
|
||||
assert_eq!(
|
||||
(a.follows, t2a.to, t2a.superseded_by, a.superseded_by),
|
||||
(Some(t2a.id), a.from, Some(a.id), None),
|
||||
(Some(t2a.id), a.from, Some(a.id), Some(RETIREMENT)),
|
||||
"{code}"
|
||||
);
|
||||
let t = amendment(code, "-quorum");
|
||||
@@ -2513,7 +2529,7 @@ fn history(revision: i64) -> Vec<&'static str> {
|
||||
#[tokio::test]
|
||||
async fn production_after_m6c_gets_the_texts_without_dated_figures_once() {
|
||||
let base = production_after_m6c("t3-apply").await;
|
||||
let (t2, today) = (t2_seed(), today_seed());
|
||||
let (t2, today) = (t2_seed(), m8_seed());
|
||||
// The base is as production after M6c: the texts of lot T2, F1.1 at revision 3, F1.2 and
|
||||
// N1.1 at revision 4.
|
||||
for group in GROUPS {
|
||||
@@ -2533,7 +2549,7 @@ async fn production_after_m6c_gets_the_texts_without_dated_figures_once() {
|
||||
let revisions = count(&base, "SELECT COUNT(*) FROM revisions").await;
|
||||
|
||||
// Started with today's seed.
|
||||
let state = start(&base.db, Config::for_tests()).await;
|
||||
let state = start(&base.db, m8_config("t3-apply")).await;
|
||||
let mut touched = Vec::new();
|
||||
for group in GROUPS {
|
||||
for code in T3_CODES {
|
||||
@@ -2597,7 +2613,7 @@ async fn production_after_m6c_gets_the_texts_without_dated_figures_once() {
|
||||
assert!(rows.iter().all(|(g, _)| g == "monnaie-libre"), "{rows:?}");
|
||||
assert_eq!(count(&state, "SELECT COUNT(*) FROM outbox").await, outbox + 2);
|
||||
// Decided once: a second start replays nothing.
|
||||
let again = start(&state.db, Config::for_tests()).await;
|
||||
let again = start(&state.db, m8_config("t3-apply-again")).await;
|
||||
assert_eq!(decisions(&again, "-chiffres").await, expected);
|
||||
assert_eq!(proposals(&again).await, proposals(&state).await);
|
||||
assert_eq!(translations(&again).await, translations(&state).await);
|
||||
@@ -2649,7 +2665,7 @@ async fn an_article_voted_on_keeps_the_text_of_t2_and_says_why() {
|
||||
luna.vote(&f12_essai, Some("against")).await;
|
||||
luna.vote(&f12_essai, None).await;
|
||||
|
||||
let state = start(&base.db, Config::for_tests()).await;
|
||||
let state = start(&base.db, m8_config("t3-refuse")).await;
|
||||
let refused =
|
||||
|g: &str, c: &str, why: &str| (g.to_owned(), c.to_owned(), "refused".to_owned(), Some(why.to_owned()));
|
||||
let applied = |g: &str, c: &str| (g.to_owned(), c.to_owned(), "applied".to_owned(), None);
|
||||
@@ -2662,7 +2678,7 @@ async fn an_article_voted_on_keeps_the_text_of_t2_and_says_why() {
|
||||
refused("monnaie-libre", "N1.1", "votes"),
|
||||
]
|
||||
);
|
||||
let (t2, today) = (t2_seed(), today_seed());
|
||||
let (t2, today) = (t2_seed(), m8_seed());
|
||||
for group in GROUPS {
|
||||
for code in T3_CODES {
|
||||
let kept = (group, code) != ("essai", "F1.2");
|
||||
@@ -2676,7 +2692,7 @@ async fn an_article_voted_on_keeps_the_text_of_t2_and_says_why() {
|
||||
assert!(queued(&state, "-chiffres").await.is_empty());
|
||||
// The refusals stand: decided once, never tried again, even once the vote is gone.
|
||||
sqlx::query("DELETE FROM votes WHERE proposal_id = ?").bind(&n11).execute(&state.db).await.expect("delete");
|
||||
let again = start(&state.db, Config::for_tests()).await;
|
||||
let again = start(&state.db, m8_config("t3-refuse-again")).await;
|
||||
assert_eq!(decisions(&again, "-chiffres").await, decisions(&state, "-chiffres").await);
|
||||
let (_, title, body, _) = proposal(&again, "monnaie-libre", "N1.1").await;
|
||||
assert_eq!((title.as_str(), body.as_str()), text(&t2, "N1.1", "fr"));
|
||||
@@ -2691,7 +2707,7 @@ async fn a_base_born_with_the_texts_of_m5_gets_the_texts_of_t3() {
|
||||
let first = AppState::new(config(seed_dir("t3-born-m5", &prod)), db.clone()).await.expect("state");
|
||||
seed::documents(&first).await.expect("born");
|
||||
start(&db, config(seed_dir("t3-born-m5-m5", &prod))).await;
|
||||
start(&db, config(seed_dir("t3-born-m5-m6", &with_texts(&today_seed(), &prod, &CODES)))).await;
|
||||
start(&db, config(seed_dir("t3-born-m5-m6", &with_texts(&m8_seed(), &prod, &CODES)))).await;
|
||||
start(&db, m6b_config("t3-born-m5")).await;
|
||||
let base = start(&db, t2_config("t3-born-m5")).await;
|
||||
for group in GROUPS {
|
||||
@@ -2700,8 +2716,8 @@ async fn a_base_born_with_the_texts_of_m5_gets_the_texts_of_t3() {
|
||||
assert_eq!(kinds(&base, &id).await, history(3), "{group} {code}: lots T and T2");
|
||||
}
|
||||
}
|
||||
let state = start(&db, Config::for_tests()).await;
|
||||
let today = today_seed();
|
||||
let state = start(&db, m8_config("t3-born-m5")).await;
|
||||
let today = m8_seed();
|
||||
for group in GROUPS {
|
||||
for code in T3_CODES {
|
||||
let (id, title, body, revision) = proposal(&state, group, code).await;
|
||||
@@ -2720,8 +2736,8 @@ async fn a_base_born_with_the_texts_of_m5_gets_the_texts_of_t3() {
|
||||
async fn a_base_born_with_the_texts_of_t2_gets_the_texts_of_t3() {
|
||||
let db = open_db("sqlite::memory:").await.expect("db");
|
||||
start(&db, t2_config("t3-born-t2")).await;
|
||||
let state = start(&db, Config::for_tests()).await;
|
||||
let today = today_seed();
|
||||
let state = start(&db, m8_config("t3-born-t2")).await;
|
||||
let today = m8_seed();
|
||||
for group in GROUPS {
|
||||
for code in CODES {
|
||||
let (id, title, body, revision) = proposal(&state, group, code).await;
|
||||
|
||||
Reference in New Issue
Block a user