WIP lot V : le tampon câblé dans le serveur (migration 0010, simulation, discard-simulation), tests en cours de reprise — repris tel quel après la coupure du 02/11

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Yvv
2026-11-03 05:22:24 +01:00
co-authored by Claude Opus 5.5
parent ee37c3b4d5
commit 4b81a2e909
16 changed files with 1503 additions and 213 deletions
+12 -13
View File
@@ -586,6 +586,7 @@ pub async fn detail(
let my_vote = ballots::my_votes(&state, &user.id, &st.doc.id).await?.remove(&id);
let my_ballot = ballots::my_ballots(&state, &user.id, &st.doc.id).await?.remove(&id);
let seen = ballots::seen(p.revision, my_vote.as_ref(), my_ballot.as_ref());
let diff = match (&in_force, p.kind.as_str()) {
(Some(v), "text") => Some(edit::diff(&v.body, &p.body)),
_ => None,
@@ -602,11 +603,11 @@ pub async fn detail(
"mine": p.author_id.as_deref() == Some(user.id.as_str()),
"submittedAt": p.submitted_at, "enteredAt": p.entered_at, "confirmedAt": p.confirmed_at,
"seedMinimal": p.status == "in_force" && p.entered_at.is_none() && p.confirmed_at.is_none() && p.origin == "seed",
"tally": st.shown_tally(&p.id), "evaluation": eval, "votedAtInertia": voted_at,
"tally": st.shown_tally(&p.id), "tallyPending": st.pending_tally(&p.id),
"tallySimulated": st.simulated_tally(&p.id), "evaluation": eval, "votedAtInertia": voted_at,
"blocked": st.blocked.contains(&p.id), "votable": st.votable(&p),
"myVote": my_vote.as_ref().map(|v| &v.choice),
"myVoteStale": my_vote.as_ref().is_some_and(|v| v.revision != p.revision),
"myVoteChain": my_vote.as_ref().is_some_and(|v| v.source == "chain"), "myBallot": my_ballot,
"myVote": seen.choice, "myVoteState": seen.state, "myVoteStale": seen.stale,
"myVoteChain": seen.chain, "myBallot": my_ballot,
"translations": tr.get(&p.id).cloned().unwrap_or_default(),
},
"unit": { "id": unit.id, "code": unit.code, "inertia": docs::inertia_of(unit) },
@@ -633,8 +634,9 @@ pub struct VoteBody {
choice: Option<String>,
}
/// One vote on the server, in a managed group. Where the vote goes through the
/// chain (Monnaie libre), it is a ballot (`ballots::create`), never this.
/// One vote written by the server: in a managed group, or in simulation while
/// the chain is closed (ADR 0012). Where the vote goes through the chain and the
/// chain is open (Monnaie libre), it is a ballot (`ballots::create`), never this.
pub async fn vote(
State(state): State<AppState>,
User(user): User,
@@ -642,9 +644,7 @@ pub async fn vote(
Json(b): Json<VoteBody>,
) -> AppResult<Json<serde_json::Value>> {
let (p, st) = guarded(&state, &user, &id).await?;
if trustin_access::vote_way(&st.group.to_group()) == trustin_access::VoteWay::Chain {
return Err(AppError::Conflict("chain_only"));
}
let way = ballots::server_way(&state, &st.group)?;
if !matches!(p.status.as_str(), "open" | "in_force") {
return Err(AppError::Conflict("not_votable"));
}
@@ -659,13 +659,12 @@ pub async fn vote(
if !st.admits(&p, choice) {
return Err(AppError::Conflict("objection"));
}
if !docs::may_vote(&state, &user, &st.group).await? {
if !ballots::may_write(&state, &user, &st.group, way).await? {
return Err(AppError::Conflict("members_only"));
}
let cast = ballots::Cast { proposal_id: p.id.clone(), revision: p.revision, choice };
ballots::write_server_votes(&state, &user.id, &[cast]).await?;
let entered = adoption::run(&state, &st.doc.id).await?;
Ok(Json(json!({ "entered": entered })))
let entered = ballots::write(&state, &user, &st, way, &[cast]).await?;
Ok(Json(json!({ "entered": entered, "simulated": way == ballots::ServerWay::Simulated })))
}
// ---------------------------------------------------------------------------