aboutsummaryrefslogtreecommitdiffstats
path: root/src/remake/coordinator.rs
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-11-08 17:07:03 +0000
committertv <tv@krebsco.de>2026-11-08 19:28:22 +0100
commitf91604095e6f0978c61f66d0b937e36271637e6b (patch)
treee05cd2a21dd4122c3a8a3a450b3605b5a094416b /src/remake/coordinator.rs
parent5f69ecf6284a8061a2b8fae5b8f98d67a67dad5f (diff)
remake: no --follow-strict-remakes; a strict remake failing says who
A strict remake takes a majority of members whose --strict-keys take its manifest's signer: no node votes for one without, so --follow-strict-remakes (1173) goes. A node that doesn't vote still switches once the others commit, as with any remake. So that an operator can tell why one fails: a strict remake aborted for want of a majority (the voting period over, or a majority out of reach) says in its reason (GET /admin/remakes/{id}, and the coordinator's log) which members didn't vote, and that a member votes for one only with its manifest signed by a key of its --strict-keys. Each of those logs why itself (`Not voting for a strict remake`). The sim a_strict_remake_too_few_take: only a takes the manifest; the remake aborts, naming b and c.
Diffstat (limited to 'src/remake/coordinator.rs')
-rw-r--r--src/remake/coordinator.rs43
1 files changed, 41 insertions, 2 deletions
diff --git a/src/remake/coordinator.rs b/src/remake/coordinator.rs
index 314cd87..0d7a434 100644
--- a/src/remake/coordinator.rs
+++ b/src/remake/coordinator.rs
@@ -138,7 +138,10 @@ async fn step<H: Host>(host: &H, id: &str) -> Result<bool, StoreError> {
decide_abort(
host,
id,
- format!("no majority in the voting period: {tally}"),
+ format!(
+ "no majority in the voting period: {tally}{}",
+ strict_hint(id, &record, &register)
+ ),
)
.await?;
return Ok(true);
@@ -149,12 +152,48 @@ async fn step<H: Host>(host: &H, id: &str) -> Result<bool, StoreError> {
return Ok(true);
}
if tally.out_of_reach() {
- decide_abort(host, id, format!("a majority is out of reach: {tally}")).await?;
+ decide_abort(
+ host,
+ id,
+ format!(
+ "a majority is out of reach: {tally}{}",
+ strict_hint(id, &record, &register)
+ ),
+ )
+ .await?;
return Ok(true);
}
Ok(false)
}
+/// For a strict remake `id` without a majority: who didn't vote, and why they may not
+/// have (`vote::strict_taken`), for its operator to tell from the reason. Nothing for
+/// another kind, or if all voted.
+fn strict_hint(id: &str, record: &Record, register: &records::Register) -> String {
+ if record.proposal.kind != "strict" {
+ return String::new();
+ }
+ let silent: Vec<&str> = record
+ .proposal
+ .electorate
+ .iter()
+ .filter(|member| {
+ register
+ .get(member.as_str())
+ .is_none_or(|votes| !votes.iter().any(|vote| vote.remake == id))
+ })
+ .map(String::as_str)
+ .collect();
+ if silent.is_empty() {
+ return String::new();
+ }
+ format!(
+ "; a strict remake, not voted for by {}: a member votes for one only with its \
+ manifest signed by a key of its --strict-keys (or it was away)",
+ silent.join(", ")
+ )
+}
+
/// Why remake `id` must be aborted whatever its votes, if it must.
fn obstacle(id: &str, record: &Record, all: &[(String, Record)]) -> Option<String> {
if let Some(request) = &record.abort_requested {