Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/monetize

crates/monetize-product/src/guardian/rebuild.rs

//! **Rebuild the main from its twin — the guard** (Gunnar ca38566f, `gunnar-repair`
//! `rebuild::judge`, owner 2026-10-12: the twin is never promoted; a dead main is REBUILT:
//! its data is checked, reformatted only when it is destroyed, and filled from the twin).
//! Shared here by lane G11 (2026-10-21) so EVERY product's guardian — Gunnar's and Holger's,
//! linked and as signed guests — asks the same judgement before its plan destroys a main.
//! Pure, and in no product's types: each guardian builds a [`Seen`] from what its boxes
//! reported ([`seen_of`]) and its twin's own words ([`Twin`]).
//!
//! **What may destroy a main's data** is the provider deleting its data storage (the
//! runner's `Fence` step gives the old box back WHOLE), or a wipe medium. The guard sits in
//! front of both:
//!
//! * **R1 — evidence comes from where the data lives, never from silence.** Destroyed is
//!   the provider saying the box is GONE, the box itself answering `fresh`/`lost`, or the
//!   installer refusing its data set under the KEEP medium. A main that only does not
//!   report is not destroyed.
//! * **R2 — a silent box that stands is booted again first** (start it, or once from its
//!   keep medium): good data comes back as it was.
//! * **R3 — the twin can give it back**: it says it can, and it does not still hear a
//!   primary.
//! * **R4 — the main is not ahead**: a main that last reported a log head above the
//!   twin's holds entries the twin lacks; only a human accepting the loss goes on.
//! * **R5 — bound to what was seen**: a reformat names the storage it was judged on;
//!   a confirmation naming another storage is no confirmation.
//! * **R6 — auto-fix never reformats a standing disk.** It may lay a box that is GONE and
//!   fill a box that SAYS it is empty; a human decides the rest.
//!
//! **Over the reports** (what a guardian has): the provider's word is the main's
//! `BoxView::at_provider`, which the runner fills for a silent main monetize laid
//! (`active` / `stopped` / `gone`; empty: monetize laid no box there, or has no cloud to
//! ask — an operator's estate, whose kit asks the provider and runs this same judgement
//! with a typed confirmation before it destroys anything). The main's log head is its
//! last reported `data_head`. A guardian cannot boot a box again and has no typed
//! confirmation, so from the ONE box a main is laid again only once it is GONE
//! ([`new_server`]); the rest is a person's, by name.

use serde::{Deserialize, Serialize};

use super::{role, EstateView};

/// What the provider says about the main's server.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Provider {
    /// The server (or its data storage) is gone: 404.
    Gone,
    /// Stopped (or maintenance): it was not asked to run.
    Stopped,
    /// Started.
    Running,
    /// The provider could not be read, or said something else (in words).
    Unknown(String),
}

/// What the main itself last said, or that it says nothing.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Main {
    /// It does not answer (no report within the silence bound, no control plane).
    Silent,
    /// It answers: its `data` health word (`ok` / `fresh` / `lost` / `diverged`).
    Says { data: String },
}

/// A human's confirmation of a reformat: the data storage they read, typed back.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Confirm {
    /// Who (for the record).
    pub by: String,
    /// The storage uuid they confirmed destroying.
    pub storage: String,
    /// They accept losing what the twin never got (R4).
    #[serde(default)]
    pub accept_lost: bool,
}

/// **The twin, as the guard needs it** — in neither product's types (Gunnar's
/// `TwinReview`, Holger's `TwinStatus`, or the twin's health words).
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(default)]
pub struct Twin {
    /// Where it answers (for the words, and the plan's `twin`).
    pub addr: String,
    /// It still hears a primary (R3: a main it hears is not dead).
    pub hears_primary: bool,
    /// It can give the estate back now (R3).
    pub recharge_possible: bool,
    /// Its own words: why not, or how level it is.
    pub verdict: String,
    /// The highest log entry it holds, when the product has a log (R4); `None`: no log
    /// head to compare (Holger), never "ahead".
    pub holds: Option<u64>,
}

/// Everything the guard reads. Built by each caller from its own channels.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Seen {
    pub provider: Provider,
    pub main: Main,
    /// The highest log head the main ever reported (`data_head`), if known.
    #[serde(default)]
    pub main_head: Option<u64>,
    /// The main was already booted again (started, or once from its keep medium) since it
    /// went silent, and stays silent (R2).
    #[serde(default)]
    pub booted_again: bool,
    /// The installer, booted with the keep medium, refused the main's data set (its words).
    #[serde(default)]
    pub installer_refused: Option<String>,
    /// The twin; `None` when no twin answered.
    pub twin: Option<Twin>,
    /// The main's data storage as the provider lists it (R5); empty when unknown.
    #[serde(default)]
    pub data_storage: String,
    /// A human's confirmation, if one was given.
    #[serde(default)]
    pub confirm: Option<Confirm>,
    /// The caller is the estate's auto-fix (no human in the loop).
    #[serde(default)]
    pub auto: bool,
}

/// What to do, in words a person reads.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", tag = "do")]
pub enum Rebuild {
    /// The main serves its data: nothing is rebuilt.
    Healthy { why: String },
    /// The server is stopped: start it. It keeps its disk and rejoins as it was.
    StartIt { why: String },
    /// The server runs and is silent: boot it once more (keep medium) and look again.
    BootAgain { why: String },
    /// The main answers with no data set: fill it from the twin. Nothing is destroyed.
    Fill { twin: String, why: String },
    /// The main is gone at the provider: lay it anew, then fill it. Nothing is left to destroy.
    LayAgain { twin: String, why: String },
    /// The main's data is destroyed: give its storage `storage` back (reformat), lay it
    /// anew, fill it from `twin`.
    Reformat {
        storage: String,
        twin: String,
        why: String,
    },
    /// Not without a person, or not at all: why, by name.
    Refused { code: String, why: String },
}

impl Rebuild {
    /// The one word for logs and health (`healthy`, `start_it`, …).
    pub fn word(&self) -> &'static str {
        match self {
            Rebuild::Healthy { .. } => "healthy",
            Rebuild::StartIt { .. } => "start_it",
            Rebuild::BootAgain { .. } => "boot_again",
            Rebuild::Fill { .. } => "fill",
            Rebuild::LayAgain { .. } => "lay_again",
            Rebuild::Reformat { .. } => "reformat",
            Rebuild::Refused { .. } => "refused",
        }
    }

    /// Does this step destroy anything at the main?
    pub fn destroys(&self) -> bool {
        matches!(self, Rebuild::Reformat { .. })
    }

    /// The words.
    pub fn why(&self) -> &str {
        match self {
            Rebuild::Healthy { why }
            | Rebuild::StartIt { why }
            | Rebuild::BootAgain { why }
            | Rebuild::Fill { why, .. }
            | Rebuild::LayAgain { why, .. }
            | Rebuild::Reformat { why, .. }
            | Rebuild::Refused { why, .. } => why,
        }
    }
}

/// Refusal codes, stable for callers and tests (Gunnar's).
pub mod code {
    /// No twin answered: nothing to rebuild from.
    pub const NO_TWIN: &str = "no_twin";
    /// The twin still hears a primary: a second main would be split brain.
    pub const TWIN_HEARS_A_MAIN: &str = "twin_hears_a_main";
    /// The twin cannot give everything back now (its own verdict).
    pub const TWIN_NOT_READY: &str = "twin_not_ready";
    /// The main holds entries the twin lacks (R4).
    pub const MAIN_AHEAD: &str = "main_ahead";
    /// The main reports a diverged data set.
    pub const DIVERGED: &str = "diverged";
    /// A standing disk is reformatted only by a person (R6).
    pub const AUTO_NEVER_REFORMATS: &str = "auto_never_reformats";
    /// A person must confirm by typing the storage back (R5).
    pub const CONFIRM_STORAGE: &str = "confirm_storage";
    /// The provider's word is not one the guard can act on.
    pub const PROVIDER_UNKNOWN: &str = "provider_unknown";
}

fn refused(code: &str, why: impl Into<String>) -> Rebuild {
    Rebuild::Refused {
        code: code.into(),
        why: why.into(),
    }
}

/// **R3 + R4**: may the twin fill a new main now? `Ok(twin address)`, or the refusal.
fn twin_may_fill(s: &Seen) -> Result<String, Rebuild> {
    let Some(t) = s.twin.as_ref() else {
        return Err(refused(code::NO_TWIN, "no twin answered: there is nothing to rebuild the main from — a human looks first (provider console, last receipts)"));
    };
    if t.hears_primary {
        return Err(refused(
            code::TWIN_HEARS_A_MAIN,
            format!("the twin at {} still hears its primary: a main it can hear is not dead — a rebuild beside it would be a second writable main", t.addr),
        ));
    }
    if !t.recharge_possible {
        return Err(refused(
            code::TWIN_NOT_READY,
            format!(
                "the twin at {} cannot give the estate back now: {}",
                t.addr, t.verdict
            ),
        ));
    }
    let accepted = s.confirm.as_ref().is_some_and(|c| c.accept_lost);
    if let (Some(head), Some(twin_has)) = (s.main_head, t.holds) {
        if head > twin_has && !accepted {
            return Err(refused(
                code::MAIN_AHEAD,
                format!("the main last reported log head {head}, the twin holds {twin_has}: {} entr(y/ies) exist only on the main's disk — a person accepts that loss (accept_lost), or the main's disk is brought back", head - twin_has),
            ));
        }
    }
    Ok(t.addr.clone())
}

/// **The guard**: what may be done about the main, given what was seen (Gunnar's
/// `rebuild::judge`, rule for rule).
pub fn judge(s: &Seen) -> Rebuild {
    match &s.main {
        Main::Says { data } => match data.as_str() {
            "ok" => Rebuild::Healthy { why: "the main answers and reports its data whole: nothing is rebuilt".into() },
            "diverged" => refused(code::DIVERGED, "the main reports its data diverged from the twin's log: a person compares the two before either is touched"),
            // The box itself says it holds no data set (R1): fill it, nothing to destroy.
            "fresh" | "lost" => match twin_may_fill(s) {
                Ok(twin) => Rebuild::Fill { twin, why: format!("the main answers and reports data={data}: it is filled from the twin, nothing on it is destroyed") },
                Err(r) => r,
            },
            other => refused(code::PROVIDER_UNKNOWN, format!("the main reports data={other:?}, a word the guard does not act on")),
        },
        Main::Silent => match &s.provider {
            Provider::Stopped => Rebuild::StartIt { why: "the main is stopped at the provider: start it — it keeps its disk and rejoins as it was; nothing is rebuilt".into() },
            Provider::Gone => match twin_may_fill(s) {
                Ok(twin) => Rebuild::LayAgain { twin, why: "the main is GONE at the provider: it is laid anew from its medium and filled from the twin (nothing of it is left to destroy)".into() },
                Err(r) => r,
            },
            Provider::Unknown(w) => refused(code::PROVIDER_UNKNOWN, format!("the provider's word on the main is not known ({w}): nothing is destroyed on a guess")),
            Provider::Running if !s.booted_again && s.installer_refused.is_none() => {
                Rebuild::BootAgain { why: "the main runs at the provider and is silent: it is booted once more from its keep medium — a disk with its data comes back as it was; only a box that stays silent is judged again".into() }
            }
            Provider::Running => {
                let twin = match twin_may_fill(s) {
                    Ok(t) => t,
                    Err(r) => return r,
                };
                if s.auto {
                    return refused(code::AUTO_NEVER_REFORMATS, "the main's server stands with its disk: auto-fix never reformats a standing disk — a person confirms it (the kit, the ONE box, or the console)");
                }
                let evidence = match &s.installer_refused {
                    Some(w) => format!("the installer refused its data set under the keep medium ({w})"),
                    None => "it was booted again and stays silent".to_string(),
                };
                match s.confirm.as_ref() {
                    Some(c) if !s.data_storage.is_empty() && c.storage.trim() == s.data_storage => Rebuild::Reformat {
                        storage: s.data_storage.clone(),
                        twin,
                        why: format!("the main's data is destroyed: {evidence}; {} confirmed storage {} — it is given back, the main laid anew and filled from the twin", c.by, s.data_storage),
                    },
                    Some(c) => refused(code::CONFIRM_STORAGE, format!("the confirmation names storage {:?}, the main's data storage is {:?}: nothing is destroyed", c.storage, s.data_storage)),
                    None => refused(code::CONFIRM_STORAGE, format!("the main's data looks destroyed ({evidence}); a person confirms by typing its data storage back: {}", if s.data_storage.is_empty() { "(unknown)" } else { &s.data_storage })),
                }
            }
        },
    }
}

// ---------------------------------------------------------------------------------------
// Over the reports: what a guardian has.
// ---------------------------------------------------------------------------------------

/// The provider's word on a box (`BoxView::at_provider`), as the guard reads it. Empty is
/// "not asked" — never a guess.
pub fn provider_of(word: &str) -> Provider {
    match word.trim() {
        "gone" => Provider::Gone,
        "stopped" | "maintenance" => Provider::Stopped,
        "active" | "started" | "running" | "provisioning" => Provider::Running,
        "" => Provider::Unknown("the provider was not asked".into()),
        other => Provider::Unknown(other.into()),
    }
}

/// The main as the guard reads its reports: silent, or its `data` word (a restore under
/// way is `lost`, Gunnar's `main_of`), and the last log head it reported (`data_head`,
/// kept from its last report while it is silent).
pub fn main_of(v: &EstateView) -> (Main, Option<u64>) {
    let b = v.get(role::MAIN);
    let head = b.and_then(|b| b.word("data_head").trim().parse().ok());
    if v.silent(role::MAIN) {
        return (Main::Silent, head);
    }
    let data = match b.map(|b| b.word("data")).unwrap_or("") {
        "restoring" => "lost",
        w => w,
    };
    (Main::Says { data: data.into() }, head)
}

/// The twin's address as a plan names it (its `serves_at`, else where it was laid).
pub fn twin_addr(v: &EstateView) -> String {
    v.get(role::TWIN)
        .map(|b| {
            if b.word("serves_at").is_empty() {
                b.laid_ipv4.clone()
            } else {
                b.word("serves_at").to_string()
            }
        })
        .unwrap_or_default()
}

/// **The guard's [`Seen`] over an estate's reports.** `twin` is the product's reading of its
/// twin (`None` when no twin reports); the guardian has no confirmation and cannot boot a box.
pub fn seen_of(v: &EstateView, twin: Option<Twin>, auto: bool) -> Seen {
    let (main, main_head) = main_of(v);
    let provider = provider_of(
        v.get(role::MAIN)
            .map(|b| b.at_provider.as_str())
            .unwrap_or(""),
    );
    Seen {
        provider,
        main,
        main_head,
        booted_again: false,
        installer_refused: None,
        twin,
        data_storage: String::new(),
        confirm: None,
        auto,
    }
}

/// **May auto-fix lay the main again?** Only where the guard says [`Rebuild::LayAgain`]:
/// the main is GONE at the provider and the twin can give it back (R1, R3, R4, R6).
pub fn auto_new_server(v: &EstateView, twin: Option<Twin>) -> Rebuild {
    judge(&seen_of(v, twin, true))
}

/// **May a person's New server run?** `Ok` with the guard's words, or why not. Monetize
/// laid the main (the provider's word is filled): only a main GONE at the provider is laid
/// again — a standing one is started or booted again first, and a standing disk is
/// reformatted only on a typed confirmation, which the ONE box's button does not carry
/// (the kit's `estate rebuild main` does). Monetize laid no main there (an operator's
/// estate; no provider word): the twin's half of the guard decides here, and the operator's
/// kit asks the provider and confirms before it destroys anything.
pub fn new_server(v: &EstateView, twin: Option<Twin>) -> Result<String, String> {
    let mut s = seen_of(v, twin, false);
    let operator = v
        .get(role::MAIN)
        .is_none_or(|b| b.at_provider.trim().is_empty());
    if operator && s.main == Main::Silent {
        s.provider = Provider::Gone;
    }
    match judge(&s) {
        Rebuild::LayAgain { why, .. } if operator => Ok(format!("{why} — on this estate the operator's kit asks the provider and confirms before anything is destroyed")),
        Rebuild::LayAgain { why, .. } => Ok(why),
        Rebuild::Fill { why, .. } => Err(format!("{why}: press Recharge from twin — New server would destroy a box that answers")),
        other => Err(other.why().to_string()),
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::guardian::BoxView;

    const STORAGE: &str = "01aa-data";
    const TWIN_AT: &str = "10.0.0.3:50051";

    fn twin(hears: bool, possible: bool, holds: u64) -> Twin {
        Twin {
            addr: TWIN_AT.into(),
            hears_primary: hears,
            recharge_possible: possible,
            verdict: if possible {
                "level".into()
            } else {
                "behind its primary".into()
            },
            holds: Some(holds),
        }
    }

    fn seen(provider: Provider, main: Main) -> Seen {
        Seen {
            provider,
            main,
            main_head: Some(100),
            booted_again: false,
            installer_refused: None,
            twin: Some(twin(false, true, 100)),
            data_storage: STORAGE.into(),
            confirm: None,
            auto: false,
        }
    }

    fn says(d: &str) -> Main {
        Main::Says { data: d.into() }
    }

    fn me(storage: &str) -> Option<Confirm> {
        Some(Confirm {
            by: "rickard".into(),
            storage: storage.into(),
            accept_lost: false,
        })
    }

    /// R1: a main that answers with its data whole is never touched, whatever else is seen.
    #[test]
    fn a_main_with_its_data_is_never_rebuilt() {
        for p in [Provider::Running, Provider::Gone, Provider::Stopped] {
            let mut s = seen(p, says("ok"));
            s.confirm = me(STORAGE);
            assert_eq!(judge(&s).word(), "healthy");
            assert!(!judge(&s).destroys());
        }
    }

    /// R1 + R2: silence is never enough. A stopped box is started, a running one booted again.
    #[test]
    fn silence_alone_destroys_nothing() {
        assert_eq!(
            judge(&seen(Provider::Stopped, Main::Silent)).word(),
            "start_it"
        );
        let mut s = seen(Provider::Running, Main::Silent);
        s.confirm = me(STORAGE);
        assert_eq!(
            judge(&s).word(),
            "boot_again",
            "even a confirmation does not skip booting it again"
        );
        assert_eq!(
            judge(&seen(Provider::Unknown("timeout".into()), Main::Silent)).word(),
            "refused"
        );
    }

    /// R1: a main that SAYS it is empty is filled; nothing is destroyed. Auto-fix may.
    #[test]
    fn a_main_that_says_it_is_empty_is_filled_and_auto_may() {
        for d in ["fresh", "lost"] {
            let mut s = seen(Provider::Running, says(d));
            s.auto = true;
            match judge(&s) {
                Rebuild::Fill { twin, .. } => assert_eq!(twin, TWIN_AT),
                other => panic!("{d}: {other:?}"),
            }
        }
        assert_eq!(
            judge(&seen(Provider::Running, says("diverged"))).word(),
            "refused"
        );
    }

    /// A box gone at the provider is laid again; auto-fix may, nothing is left to destroy.
    #[test]
    fn a_gone_main_is_laid_again_and_auto_may() {
        let mut s = seen(Provider::Gone, Main::Silent);
        s.auto = true;
        assert_eq!(judge(&s).word(), "lay_again");
    }

    /// R6 + R5: a standing disk is reformatted only by a person who types its storage back.
    #[test]
    fn a_standing_disk_is_reformatted_only_on_a_typed_confirmation() {
        let mut s = seen(Provider::Running, Main::Silent);
        s.booted_again = true;
        let r = judge(&s);
        assert!(
            matches!(&r, Rebuild::Refused { code, .. } if code == code::CONFIRM_STORAGE),
            "{r:?}"
        );
        assert!(
            r.why().contains(STORAGE),
            "the person is told which storage: {r:?}"
        );
        s.confirm = me("01bb-other");
        assert!(
            matches!(judge(&s), Rebuild::Refused { code, .. } if code == code::CONFIRM_STORAGE),
            "another storage's name is no confirmation"
        );
        s.confirm = me(STORAGE);
        match judge(&s) {
            Rebuild::Reformat { storage, twin, .. } => {
                assert_eq!((storage.as_str(), twin.as_str()), (STORAGE, TWIN_AT))
            }
            other => panic!("{other:?}"),
        }
        s.auto = true;
        assert!(
            matches!(judge(&s), Rebuild::Refused { code, .. } if code == code::AUTO_NEVER_REFORMATS),
            "auto-fix never, even confirmed"
        );
        let mut i = seen(Provider::Running, Main::Silent);
        i.installer_refused = Some("member 1 of 2 is missing".into());
        i.confirm = me(STORAGE);
        assert_eq!(
            judge(&i).word(),
            "reformat",
            "the installer's refusal of the data set is evidence too"
        );
        i.data_storage.clear();
        assert_eq!(
            judge(&i).word(),
            "refused",
            "an unknown storage cannot be confirmed"
        );
    }

    /// R3: no twin, a twin that still hears a primary, or a twin not ready — nothing moves.
    #[test]
    fn the_twin_must_be_able_to_give_it_back() {
        for (t, want) in [
            (None, code::NO_TWIN),
            (Some(twin(true, true, 100)), code::TWIN_HEARS_A_MAIN),
            (Some(twin(false, false, 90)), code::TWIN_NOT_READY),
        ] {
            for (p, m) in [
                (Provider::Gone, Main::Silent),
                (Provider::Running, says("fresh")),
            ] {
                let mut s = seen(p, m);
                s.twin = t.clone();
                assert!(
                    matches!(judge(&s), Rebuild::Refused { ref code, .. } if code == want),
                    "{want}: {:?}",
                    judge(&s)
                );
            }
        }
    }

    /// R4: a main that reported more than the twin holds is refused until a person accepts the loss.
    #[test]
    fn a_main_ahead_of_its_twin_needs_a_person_to_accept_the_loss() {
        let mut s = seen(Provider::Gone, Main::Silent);
        s.main_head = Some(120);
        let r = judge(&s);
        assert!(
            matches!(&r, Rebuild::Refused { code, .. } if code == code::MAIN_AHEAD),
            "{r:?}"
        );
        assert!(r.why().contains("20 entr"), "{r:?}");
        s.confirm = Some(Confirm {
            by: "rickard".into(),
            storage: String::new(),
            accept_lost: true,
        });
        assert_eq!(judge(&s).word(), "lay_again");
        s.main_head = None;
        s.confirm = None;
        assert_eq!(
            judge(&s).word(),
            "lay_again",
            "an unknown head is not 'ahead'"
        );
        s.main_head = Some(120);
        s.twin.as_mut().unwrap().holds = None;
        assert_eq!(
            judge(&s).word(),
            "lay_again",
            "a twin with no log head to compare (Holger) is not 'behind'"
        );
    }

    /// Only Reformat destroys (the JSON shape is held by the guests' parity tests, which
    /// carry a JSON codec; this crate has serde alone).
    #[test]
    fn only_a_reformat_destroys() {
        assert!(Rebuild::Reformat {
            storage: STORAGE.into(),
            twin: "t".into(),
            why: "w".into()
        }
        .destroys());
        for r in [
            Rebuild::Fill {
                twin: "t".into(),
                why: String::new(),
            },
            Rebuild::LayAgain {
                twin: "t".into(),
                why: String::new(),
            },
            Rebuild::BootAgain { why: String::new() },
            Rebuild::StartIt { why: String::new() },
        ] {
            assert!(!r.destroys());
        }
    }

    // --- over the reports -----------------------------------------------------------------

    const NOW: u64 = 1_800_000_000_000;

    fn estate(main_heard_ago: u64, main_words: &[(&str, &str)], at_provider: &str) -> EstateView {
        let health = main_words
            .iter()
            .map(|(k, v)| (k.to_string(), v.to_string()))
            .collect();
        let main = BoxView {
            role: "main".into(),
            heard_unix_ms: NOW - main_heard_ago,
            health,
            at_provider: at_provider.into(),
            ..Default::default()
        };
        EstateView {
            product: "p".into(),
            instance: "acme".into(),
            now_unix_ms: NOW,
            silent_after_ms: 90_000,
            boxes: [("main".to_string(), main)].into_iter().collect(),
        }
    }

    fn blind() -> Option<Twin> {
        Some(Twin {
            addr: TWIN_AT.into(),
            hears_primary: false,
            recharge_possible: true,
            verdict: "level".into(),
            holds: None,
        })
    }

    /// The provider's word, read: empty is never a guess.
    #[test]
    fn the_providers_word_is_read_and_empty_is_not_asked() {
        assert_eq!(provider_of("gone"), Provider::Gone);
        assert_eq!(provider_of("stopped"), Provider::Stopped);
        assert_eq!(provider_of("active"), Provider::Running);
        assert!(matches!(provider_of(""), Provider::Unknown(_)));
        assert!(matches!(
            provider_of("unknown (timeout)"),
            Provider::Unknown(_)
        ));
    }

    /// The main off its reports: silent keeps its last `data_head`; restoring reads as lost.
    #[test]
    fn the_main_is_read_off_its_reports() {
        assert_eq!(
            main_of(&estate(
                600_000,
                &[("data", "ok"), ("data_head", "900")],
                ""
            )),
            (Main::Silent, Some(900))
        );
        assert_eq!(
            main_of(&estate(1_000, &[("data", "restoring")], "")),
            (says("lost"), None)
        );
        assert_eq!(
            main_of(&estate(1_000, &[("data", "fresh"), ("data_head", "0")], "")),
            (says("fresh"), Some(0))
        );
    }

    /// ★ **Row 5's gap, closed for every guardian**: silence alone never lays a main again by
    /// auto-fix. Only a main GONE at the provider is (R1, R6); a standing one is booted
    /// again first (R2); an unasked provider is no evidence.
    #[test]
    fn auto_fix_lays_a_silent_main_again_only_when_the_provider_says_it_is_gone() {
        assert_eq!(
            auto_new_server(&estate(600_000, &[], "gone"), blind()).word(),
            "lay_again"
        );
        assert_eq!(
            auto_new_server(&estate(600_000, &[], "active"), blind()).word(),
            "boot_again"
        );
        assert_eq!(
            auto_new_server(&estate(600_000, &[], "stopped"), blind()).word(),
            "start_it"
        );
        assert_eq!(
            auto_new_server(&estate(600_000, &[], ""), blind()).word(),
            "refused",
            "not asked is not gone"
        );
        assert_eq!(
            auto_new_server(&estate(600_000, &[], "gone"), None).word(),
            "refused",
            "no twin"
        );
    }

    /// A person's New server: a gone main is laid again, a standing one is not (no typed
    /// confirmation on the button), an answering one is recharged instead, and on an
    /// operator's estate (no provider word) the twin's half decides and the kit confirms.
    #[test]
    fn a_persons_new_server_destroys_no_standing_disk() {
        assert!(new_server(&estate(600_000, &[], "gone"), blind()).is_ok());
        let standing = new_server(&estate(600_000, &[], "active"), blind()).unwrap_err();
        assert!(standing.contains("booted once more"), "{standing}");
        let answers =
            new_server(&estate(1_000, &[("data", "fresh")], "active"), blind()).unwrap_err();
        assert!(answers.contains("Recharge from twin"), "{answers}");
        assert!(
            new_server(&estate(1_000, &[("data", "ok")], "active"), blind()).is_err(),
            "a healthy main is never laid again"
        );
        let operator = new_server(&estate(600_000, &[], ""), blind()).unwrap();
        assert!(operator.contains("operator's kit"), "{operator}");
        let mut hears = blind();
        hears.as_mut().unwrap().hears_primary = true;
        assert!(
            new_server(&estate(600_000, &[], ""), hears).is_err(),
            "the twin still hears its main: not on an operator's estate either"
        );
    }
}