v3 : moteur des objections « redit » / « contredit » et du circuit des coquilles

- ld-engine/objection.rs : la dernière arrivée (jamais une version en vigueur)
  n'est plus votable ; un cycle à la fois (réponse de l'auteur, puis lever ou
  réitérer) ; levée seule 3 jours après la réponse ; une révision de l'autre
  côté répond aussi ; l'administrateur lève, ne réitère jamais.
- ld-engine/correction.rs : qui applique une coquille et quand (celui qui
  répond de la version l'applique, les autres attendent 3 jours au plus),
  l'auteur garde la main hors vigueur, deux coquilles en attente : la seconde
  devient caduque.
- trustin-access : answers_typo, lifts_objection, reiterates_objection,
  reads_message.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Yvv
2026-10-31 01:13:52 +01:00
co-authored by Claude Opus 5.5
parent 1a35d0f521
commit 70dbd712c8
4 changed files with 737 additions and 0 deletions
+58
View File
@@ -150,6 +150,33 @@ pub fn manages_access(identity: &Identity) -> bool {
identity.admin
}
/// Who answers a typo fix someone else wrote on a version (2026-10-30, L13):
/// its author; for a text without author (a starting text), an administrator of
/// trustinWallet. Whoever answers for a version also applies their own fix at once.
pub fn answers_typo(identity: &Identity, version_author: Option<&str>) -> bool {
match version_author {
Some(author) => identity.id == author,
None => identity.admin,
}
}
/// Who lifts an objection (2026-10-30, L12): the person who raised it, or an
/// administrator of trustinWallet.
pub fn lifts_objection(identity: &Identity, objector: &str) -> bool {
identity.id == objector || identity.admin
}
/// Who reiterates an objection: only the person who raised it — an
/// administrator lifts, never speaks for someone else.
pub fn reiterates_objection(identity: &Identity, objector: &str) -> bool {
identity.id == objector
}
/// A message of trustinWallet is read by its recipient only.
pub fn reads_message(identity: &Identity, recipient: &str) -> bool {
identity.id == recipient
}
/// The groups a fresh deployment starts with. Ordered as displayed.
pub fn default_groups() -> Vec<Group> {
let g = |slug: &str, name: &str, parent: Option<&str>, policy, hidden| Group {
@@ -251,6 +278,37 @@ mod tests {
assert!(!asks_ballot(&invited, &essai, true), "no chain there: the test group stays easy to try");
}
#[test]
fn the_author_answers_a_typo_and_an_administrator_for_a_starting_text() {
let chiara = Identity { id: "chiara".into(), ..who(IdentityKind::Invite, false, false) };
let admin = Identity { id: "yvv".into(), ..who(IdentityKind::G1, true, true) };
let other = Identity { id: "luna".into(), ..who(IdentityKind::G1, true, false) };
assert!(answers_typo(&chiara, Some("chiara")));
assert!(!answers_typo(&other, Some("chiara")));
assert!(!answers_typo(&admin, Some("chiara")), "an administrator never answers for an author");
assert!(answers_typo(&admin, None), "a starting text has no author: an administrator answers");
assert!(!answers_typo(&other, None));
}
#[test]
fn the_objector_lifts_or_reiterates_an_administrator_only_lifts() {
let objector = Identity { id: "luna".into(), ..who(IdentityKind::G1, true, false) };
let admin = Identity { id: "yvv".into(), ..who(IdentityKind::G1, true, true) };
let other = Identity { id: "chiara".into(), ..who(IdentityKind::Invite, false, false) };
assert!(lifts_objection(&objector, "luna") && reiterates_objection(&objector, "luna"));
assert!(lifts_objection(&admin, "luna"));
assert!(!reiterates_objection(&admin, "luna"));
assert!(!lifts_objection(&other, "luna") && !reiterates_objection(&other, "luna"));
}
#[test]
fn a_message_is_read_by_its_recipient_only() {
let admin = Identity { id: "yvv".into(), ..who(IdentityKind::G1, true, true) };
let luna = Identity { id: "luna".into(), ..who(IdentityKind::G1, true, false) };
assert!(reads_message(&luna, "luna"));
assert!(!reads_message(&admin, "luna"), "not even an administrator");
}
#[test]
fn no_atelier_du_canal() {
assert!(default_groups().iter().all(|g| !g.slug.contains("atelier")));