forked from yvv/decision
v3 : sur une version retenue, « contre » et retrait oui, « pour » non (moteur, serveur, chaîne)
Décision d'Yvv du 01/11/2026 : « Retirer ou "contre" ». Le moteur dit la règle (objection::may_cast) ; le vote direct, le vote groupé et le bulletin de la chaîne l'appliquent par DocState::admits : « pour » 409 objection, « contre » et retrait acceptés, une ligne refusée refuse tout le vote. Un bulletin scellé avant l'objection compte tel qu'il est signé ; une version retenue n'entre jamais. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -90,7 +90,7 @@ pub struct BallotBody {
|
||||
author: Option<String>,
|
||||
}
|
||||
|
||||
/// A line checked against the document: a votable version, its revision now.
|
||||
/// A line checked against the document: a version open to its choice, its revision now.
|
||||
pub(crate) struct Cast {
|
||||
pub proposal_id: String,
|
||||
pub revision: i64,
|
||||
@@ -105,7 +105,9 @@ fn parse_choice(raw: Option<&str>) -> AppResult<Choice> {
|
||||
}
|
||||
|
||||
/// The lines of a vote, read against the document: at least one, each version
|
||||
/// once, each one of this document and votable.
|
||||
/// once, each one of this document and open to its choice — a version held back
|
||||
/// by an objection takes against or a withdrawal, never a *for* (`DocState::admits`).
|
||||
/// One line refused refuses them all.
|
||||
fn casts(st: &DocState, lines: &[LineBody]) -> AppResult<Vec<Cast>> {
|
||||
if lines.is_empty() || lines.len() > MAX_LINES {
|
||||
return Err(AppError::Invalid("lines"));
|
||||
@@ -120,11 +122,13 @@ fn casts(st: &DocState, lines: &[LineBody]) -> AppResult<Vec<Cast>> {
|
||||
if !matches!(p.status.as_str(), "open" | "in_force") {
|
||||
return Err(AppError::Conflict("not_votable"));
|
||||
}
|
||||
// Held back by an objection: no new vote, on the server or on the chain (ADR 0010).
|
||||
if !st.votable(p) {
|
||||
let choice = parse_choice(l.choice.as_deref())?;
|
||||
// Held back by an objection: no new *for*, on the server or on the chain
|
||||
// (ADR 0010, Yvv 2026-11-01: « Retirer ou "contre" »).
|
||||
if !st.admits(p, choice) {
|
||||
return Err(AppError::Conflict("objection"));
|
||||
}
|
||||
out.push(Cast { proposal_id: p.id.clone(), revision: p.revision, choice: parse_choice(l.choice.as_deref())? });
|
||||
out.push(Cast { proposal_id: p.id.clone(), revision: p.revision, choice });
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
@@ -600,7 +604,9 @@ async fn refuse(state: &AppState, row: &BallotRow, found: &Found, reason: &str)
|
||||
}
|
||||
|
||||
/// The ballot counts: written for the identity of the signer, line by line,
|
||||
/// where no more recent ballot of that identity decided already.
|
||||
/// where no more recent ballot of that identity decided already. A ballot sealed
|
||||
/// before an objection counts as it was signed — a *for* too, as a vote already
|
||||
/// cast —, and the version held back still cannot enter (ADR 0010).
|
||||
async fn confirm(state: &AppState, row: &BallotRow, found: &Found, index: u32) -> AppResult<()> {
|
||||
let voter = format!("idty:{index}");
|
||||
let position = found.comment.position;
|
||||
|
||||
Reference in New Issue
Block a user