forked from yvv/decision
Lot U3c : les tests d'abord, rouges sur le code de d66823b
- ld-engine inertia::when_everyone_votes_the_share_asked_is_m_on_every_electorate : pour tout W de 1 à 10 000 000, les cinq crans, G aux deux bornes de la participation habituelle, les quorums 1, 2 et W : quand les W votent, la part exigée des T′ = max(W, Q) votants est M, exactement ; la phrase d'avant (« la majorité M suffit », lue ⌈M·W⌉ « pour ») fausse sur 17 couples (W, cran), tous W ≤ 15, une voix de plus (B^W) ; vert sur le moteur inchangé, ses dents vues rouges par trois mutations (le plafond de la participation retiré, B^W mis dans la part, B^W retiré de R). - web/tests/unit/rules.test.ts : formula.reading épinglée en fr, en, es (« la part exigée est la majorité M ») ; aucune chaîne des catalogues ni du texte des composants ne dit que la majorité suffit ; la liste fermée de ce que l'écran dit de la pleine participation (formula.M, formula.reading, settings.reading) ; inertia.test.ts suit. 5 tests rouges sur 116. - tests/amorce.rs the_queue_names_the_starter_proposals_as_the_screen_does : les quatre raisons « Proposition d'amorce réécrite / ajoutée » (I1, E1, E2, E12), dans les constantes et dans les cinq lignes de file que T4 écrit sur une base comme la production après M8 ; celles de M6 inchangées. Rouge sur les constantes, et sur la file quand les constantes sont sautées. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -430,6 +430,104 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// Lot U3c (review of d66823b), `formula.reading`: « quand tout le monde vote, la part exigée est
|
||||
/// la majorité M ». On every W from 1 to 10 000 000 — the groups the server counts by their
|
||||
/// members (the test group: 2) and the web of trust it takes as true (`wot::PLAUSIBLE`, 100 to
|
||||
/// 10 000 000) — at the five levels, on both bounds of the quorum an author may set (in Monnaie
|
||||
/// libre and in the test group: 1, 2, W) and at both bounds of the usual participation (G at its
|
||||
/// lowest and at its highest): when the W vote, the voters judged are T′ = max(W, Q), never fewer
|
||||
/// than W, and the share asked of them is M_k, exactly.
|
||||
///
|
||||
/// The phrase of before, « quand tout le monde vote, la majorité M suffit », read as « ⌈M·W⌉
|
||||
/// « pour » pass », was false: R = B^W + M·W, and B^W adds one « pour » where M·W is a whole
|
||||
/// number, on 17 (W, level) pairs, every W up to 15 (W 4, level 2: 3 « pour » of 4, not 2; W 5,
|
||||
/// level 4: 4, not 3; W 10, level 4: 7, not 6) — none from W = 16 to 200, where it is checked.
|
||||
/// The new phrase says nothing of B^W: `formula.B.wot` does, in the web of trust only.
|
||||
#[test]
|
||||
fn when_everyone_votes_the_share_asked_is_m_on_every_electorate() {
|
||||
const LARGEST: u32 = 10_000_000;
|
||||
let params: Vec<(InertiaLevel, f64, FormulaParams)> = InertiaLevel::ALL
|
||||
.iter()
|
||||
.flat_map(|k| {
|
||||
[Participation::LOWEST, Participation::HIGHEST].map(|p| {
|
||||
let scale = Scale { electorate: 1, participation: p, quorums: [1; 5] };
|
||||
(*k, k.majority_pct() / 100.0, scale.params(*k, 1))
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
let mut cases = 0u64;
|
||||
for w in 1..=LARGEST {
|
||||
let ((lowest, highest), (lowest_essai, _)) = (quorum_bounds(w, false), quorum_bounds(w, true));
|
||||
// The share reads the voters judged only: W for every quorum up to W, the quorum above
|
||||
// it (2 on a web of 1, outside the test group).
|
||||
let judged = [lowest_essai, lowest, highest].map(|q| judged_voters(w, q));
|
||||
assert!(judged.iter().all(|v| *v >= w), "W {w}: {judged:?}");
|
||||
for (i, voters) in judged.iter().enumerate() {
|
||||
if judged[..i].contains(voters) {
|
||||
continue;
|
||||
}
|
||||
for (k, m, p) in ¶ms {
|
||||
let share = required_ratio(w, *voters, p);
|
||||
assert_eq!(share, *m, "W {w}, level {}, G {}, {voters} voters judged", k.get(), p.gradient);
|
||||
cases += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Every W, ten (level, G) each, and on a web of 1 the quorum of 2 besides.
|
||||
assert_eq!(cases, u64::from(LARGEST) * 10 + 10);
|
||||
// The share the evaluation gives (`Evaluation::required_ratio`), on every split of the W
|
||||
// voters, on the smallest electorates and on the web of trust.
|
||||
for w in (1..=40).chain([100, 101, 5707, 7224, LARGEST]) {
|
||||
let scale =
|
||||
Scale { electorate: w, participation: Participation::of(w, 50.0, None, 0).ratio, quorums: [2; 5] };
|
||||
for k in InertiaLevel::ALL {
|
||||
for q in [quorum_bounds(w, false).0, w] {
|
||||
for against in [0, w / 3, w / 2, w] {
|
||||
let tally = Tally { for_votes: w - against, against };
|
||||
let e = scale.evaluate(tally, k, q).expect("valid");
|
||||
assert_eq!(e.required_ratio, k.majority_pct() / 100.0, "W {w}, level {}, Q {q}", k.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// « La majorité M suffit » was false: the « pour » asked when the W vote, against ⌈M·W⌉.
|
||||
let mut more = Vec::new();
|
||||
for w in 1..=200u32 {
|
||||
for k in InertiaLevel::ALL {
|
||||
let p = Scale { electorate: w, participation: 0.1, quorums: [1; 5] }.params(k, 1);
|
||||
let asked = crate::threshold::wot_threshold(w, w, &p).expect("valid");
|
||||
let majority = (k.majority_pct() as u32 * w).div_ceil(100);
|
||||
if asked != majority {
|
||||
assert_eq!(asked, majority + 1, "W {w}, level {}: one « pour » more, B^W", k.get());
|
||||
assert_eq!(k.majority_pct() as u32 * w % 100, 0, "W {w}: M·W is a whole number");
|
||||
more.push((w, k.get()));
|
||||
}
|
||||
}
|
||||
}
|
||||
assert_eq!(
|
||||
more,
|
||||
[
|
||||
(2, 1),
|
||||
(2, 2),
|
||||
(4, 1),
|
||||
(4, 2),
|
||||
(5, 4),
|
||||
(6, 1),
|
||||
(6, 2),
|
||||
(8, 1),
|
||||
(8, 2),
|
||||
(10, 1),
|
||||
(10, 2),
|
||||
(10, 4),
|
||||
(12, 1),
|
||||
(12, 2),
|
||||
(14, 1),
|
||||
(14, 2),
|
||||
(15, 4)
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[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));
|
||||
|
||||
@@ -710,6 +710,62 @@ async fn production_after_m6c_gets_lot_t4_once_and_nothing_is_erased() {
|
||||
production_gets_lot_t4_once(production_after_m6c("m6c").await, 4).await;
|
||||
}
|
||||
|
||||
/// Lot U3c (review of d66823b): the reasons of the queue lines lot T4 writes become lines of the
|
||||
/// public commit of the texts when the first text enters into force. They name the seed proposals
|
||||
/// as the screen does, « Proposition d'amorce » (Yvv's word), never « Proposition seed ». The
|
||||
/// amendments of M6, applied in production (`seed_proposal_amend`), keep theirs: their lines are
|
||||
/// already in its queue.
|
||||
#[tokio::test]
|
||||
async fn the_queue_names_the_starter_proposals_as_the_screen_does() {
|
||||
const REWRITTEN: [(&str, &str); 3] = [
|
||||
("2026-11-03-i1-amorce", "Proposition d'amorce réécrite : I1, la Raison d'être (texte d'Yvv du 03/11)"),
|
||||
(
|
||||
"2026-11-03-e1-amorce",
|
||||
"Proposition d'amorce réécrite : E1, l'unicité du compte certifiant (texte d'Yvv du 03/11)",
|
||||
),
|
||||
(
|
||||
"2026-11-03-e2-amorce",
|
||||
"Proposition d'amorce réécrite : E2, la réciprocité des trois principes (texte d'Yvv du 03/11)",
|
||||
),
|
||||
];
|
||||
const ADDED: &str = "Proposition d'amorce ajoutée : E12, Responsabilité civique (texte d'Yvv du 03/11)";
|
||||
for (id, reason) in REWRITTEN {
|
||||
let a = ld_server::seed_amend::AMENDMENTS.iter().find(|a| a.id == id).expect(id);
|
||||
assert_eq!(a.reason, reason, "{id}");
|
||||
}
|
||||
let e12 = ld_server::article_amend::AMENDMENTS.iter().find(|a| a.id == ADD).expect(ADD);
|
||||
assert_eq!(e12.reason, ADDED);
|
||||
// The lines lot T4 writes on a base as production holds it after M8: Monnaie libre only.
|
||||
let base = production_after_m8("reasons").await;
|
||||
let last: i64 = count(&base, "SELECT IFNULL(MAX(id), 0) FROM outbox").await;
|
||||
let state = t4(&base).await;
|
||||
let written: Vec<(String, String)> = sqlx::query_as(
|
||||
"SELECT d.group_slug, o.reason FROM outbox o JOIN documents d ON d.id = o.doc_id WHERE o.id > ? ORDER BY o.id",
|
||||
)
|
||||
.bind(last)
|
||||
.fetch_all(&state.db)
|
||||
.await
|
||||
.expect("outbox");
|
||||
assert!(written.iter().all(|(g, _)| g == "monnaie-libre"), "{written:?}");
|
||||
let reasons: Vec<&str> = written.iter().map(|(_, r)| r.as_str()).collect();
|
||||
let between = format!("{ADDED}, entre E1 et E2");
|
||||
for line in REWRITTEN.iter().map(|(_, r)| *r).chain([between.as_str()]) {
|
||||
assert_eq!(reasons.iter().filter(|r| **r == line).count(), 1, "« {line} » in {reasons:?}");
|
||||
}
|
||||
assert_eq!(reasons.len(), 5, "and the retirement of the rules of operation: {reasons:?}");
|
||||
assert!(reasons.iter().all(|r| !r.contains("seed")), "{reasons:?}");
|
||||
// M6's, unchanged.
|
||||
let m6: Vec<&str> = ld_server::seed_proposal_amend::AMENDMENTS.iter().map(|a| a.reason).collect();
|
||||
assert_eq!(
|
||||
m6,
|
||||
[
|
||||
"Proposition seed : I1 sort de la vigueur",
|
||||
"Proposition seed : E1 sort de la vigueur",
|
||||
"Proposition seed : E2 sort de la vigueur"
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// After the retirement, the API refuses everything on them
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user