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

nordisk/monetize

xtask/src/drill.rs

//! `cargo xtask drill [--live --owner-yes]`: the 5.4 resilience drill.
//!
//! By default it runs the guardian functional tests on the mock cloud
//! (mock-upcloud plus the pulling gunnar mock). These are the tests CI runs:
//! a twin dies → case → button → new box laid → the main pulls adopt_twin → done;
//! main data lost → case → button → the main pulls recharge_from_twin, then
//! restate_entitlements (a signed snapshot) → done.
//!
//! `--live` is the owner's opt-in drill on real UpCloud (owner, 2026-10-10):
//! it may touch only boxes it creates itself, tagged [`tag`]; it never touches
//! gunnar.rs production, holger or the build box; it deletes everything
//! it created even when it fails; it never prints tokens; and its receipt goes
//! to `~/runs/repair-2026-10-10/`. The owner must say yes before the first live
//! run, so the live path refuses unless `--owner-yes` is given. It also refuses
//! until it is built: today it only names its plan.

/// What `cargo xtask drill` was asked to do.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Drill {
    /// The mock drill: the guardian functional tests (the default).
    Mock,
    /// The live drill on real UpCloud; needs `--owner-yes`.
    Live,
}

/// Parse the arguments after `drill`.
pub fn parse(args: &[String]) -> Result<Drill, String> {
    let (mut live, mut yes) = (false, false);
    for a in args {
        match a.as_str() {
            "--live" => live = true,
            "--owner-yes" => yes = true,
            other => return Err(format!("unknown flag `{other}` (usage: drill [--live --owner-yes])")),
        }
    }
    match (live, yes) {
        (false, false) => Ok(Drill::Mock),
        (false, true) => Err("`--owner-yes` means nothing without `--live`".into()),
        (true, false) => Err("the live drill runs on real UpCloud and needs the owner's yes first: add `--owner-yes` only after he has said so".into()),
        (true, true) => Ok(Drill::Live),
    }
}

/// The tag every box a live drill creates carries, e.g. `drill-r5-4-2026-10-10`.
pub fn tag(date: &str) -> String {
    format!("drill-r5-4-{date}")
}

/// The cargo invocation the mock drill runs, as arguments to `cargo`.
pub fn mock_command() -> Vec<&'static str> {
    vec!["test", "-p", "monetize-server", "--lib", "estate_order::tests::guardian"]
}

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

    fn a(v: &[&str]) -> Vec<String> {
        v.iter().map(|s| s.to_string()).collect()
    }

    /// ★ Off by default: a bare `drill` is the mock.
    #[test]
    fn a_bare_drill_is_the_mock() {
        assert_eq!(parse(&a(&[])), Ok(Drill::Mock));
    }

    /// ★ Live without the owner's yes is refused, naming why.
    #[test]
    fn live_without_the_owners_yes_is_refused() {
        let e = parse(&a(&["--live"])).unwrap_err();
        assert!(e.contains("owner"), "{e}");
        assert_eq!(parse(&a(&["--live", "--owner-yes"])), Ok(Drill::Live));
        assert_eq!(parse(&a(&["--owner-yes", "--live"])), Ok(Drill::Live));
    }

    /// `--owner-yes` alone is meaningless, and an unknown flag is refused.
    #[test]
    fn stray_flags_are_refused() {
        assert!(parse(&a(&["--owner-yes"])).is_err());
        assert!(parse(&a(&["--yes"])).is_err());
    }

    /// The live drill's tag, the one marker the cleanup is allowed to delete by.
    #[test]
    fn the_tag_names_the_drill_and_its_date() {
        assert_eq!(tag("2026-10-10"), "drill-r5-4-2026-10-10");
    }

    /// The mock drill runs the guardian functional tests in monetize-server.
    #[test]
    fn the_mock_drill_runs_the_guardian_functional_tests() {
        let c = mock_command();
        assert_eq!(&c[..3], &["test", "-p", "monetize-server"]);
        assert!(c.contains(&"estate_order::tests::guardian"), "{c:?}");
    }
}