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

nordisk/monetize

crates/monetize-server/src/estate_order/tests/paired_update.rs

//! **F7 (2026-10-20): a paired estate takes an estate update.** test3-holger-rs was adopted by
//! F5's re-check into `paired`; its main runs r6 and can never report (the report-when-fed fix is
//! in r10). The update was refused as "not delivered", and it could not be delivered without it.
//!
//! * **Paired takes the update**: an estate whose every box PAIRED (the installer's PASS) or
//!   that a re-check adopted takes the keep or live update of its boxes to the current release,
//!   its data kept — the way a delivered estate does. It is no delivery and sends no ready mail;
//!   the installed path (the main's report, the sale check, captured, ready) goes on after it.
//! * **An install still running is refused**, before any box is touched.
//! * **The capture deadline counts from the adoption** (and from the update's end), never while
//!   the update runs: an estate adopted near its deadline still captures after its update.
//! * Gunnar's and Holger's alike: one lifecycle. A delivered estate is unchanged.

use super::recheck::{answers, install_failed_on, site_of};
use super::*;

const MINUTE: u64 = 60_000;
const HOUR: u64 = 60 * MINUTE;
/// The site's cloud name.
pub(super) const SITE: &str = "gunnarrs";

/// A site's release (`r10`): its media, both keep media, and the live apps of both kinds.
pub(super) fn site_release(release: &str) -> EstateOrderConfig {
    release_cfg(release, true)
}

/// The estate's own client key, as the kit handed it to its front.
pub(super) fn front_key(kit: &FakeKit) -> monetize::Signer {
    let hexed = kit
        .handed
        .lock()
        .unwrap()
        .get(crate::custody::handoff::ESTATE_CLIENT)
        .cloned()
        .expect("the estate was handed its client key");
    let text = String::from_utf8(hexed).unwrap();
    let seed: Vec<u8> = (0..text.trim().len())
        .step_by(2)
        .map(|i| u8::from_str_radix(&text.trim()[i..i + 2], 16).unwrap())
        .collect();
    monetize::Signer::from_bytes(&seed.try_into().expect("32 bytes"))
}

pub(super) fn call(
    clock: &AtomicU64,
    key: &monetize::Signer,
    company: &str,
    name: &str,
    members: &[&str],
    gib: u64,
    carries: &str,
) -> (i64, Vec<u8>) {
    let at = clock.load(Ordering::SeqCst) as i64;
    let c = monetize::sign::EstateCall {
        v: 1,
        call: name.into(),
        estate: company.into(),
        members: members.iter().map(|m| m.to_string()).collect(),
        gib,
        at_unix_ms: at,
        carries: carries.into(),
    };
    (at, key.sign_estate_call(&c))
}

pub(super) fn take_as(
    state: &AppState,
    clock: &AtomicU64,
    key: &monetize::Signer,
    company: &str,
) -> update::TakeAnswer {
    let (at, sig) = call(clock, key, company, update::CALL_UPDATE_TAKE, &[], 0, "");
    update::take(state, company, at, &sig).expect("the front's take")
}

fn report_as(
    state: &AppState,
    clock: &AtomicU64,
    key: &monetize::Signer,
    company: &str,
    role: &str,
    outcome: &update::Outcome,
) {
    let carries = update::report_carries(outcome, "");
    let (at, sig) = call(
        clock,
        key,
        company,
        update::CALL_UPDATE_REPORT,
        &[role],
        0,
        &carries,
    );
    update::report(state, company, role, &carries, at, &sig)
        .unwrap_or_else(|e| panic!("{role}: {e:?}"));
}

/// The front, main and twin's keep path: the shop keep-boots `role`, the front re-admits it.
fn keep_upgrade(
    state: &AppState,
    clock: &AtomicU64,
    key: &monetize::Signer,
    company: &str,
    role: &str,
) -> Result<sold::UpgradeAnswer, sold::SoldRefusal> {
    let (at, sig) = call(clock, key, company, sold::CALL_UPGRADE, &[role], 0, "");
    sold::upgrade(state, company, role, false, at, &sig)
}

/// Every box of `company` made to run an older release than the one sold (test3: r6).
pub(super) fn runs_older(state: &AppState, company: &str) {
    let _pen = state.estates.pen(company);
    let mut run = state.estates.get(company).unwrap();
    for role in update::ORDER {
        run.release_of.insert(role.into(), "r6-old".into());
    }
    save(state, &run).unwrap();
}

/// The update to the current release, twin and main and front, the main through its keep medium.
pub(super) fn update_every_box(
    state: &AppState,
    clock: &AtomicU64,
    key: &monetize::Signer,
    company: &str,
    estate: &str,
    app: &str,
) {
    let u = update::start(state, estate, "", "operator").expect("a paired estate takes the update");
    assert_eq!(u.release, format!("r10-{app}"), "{}", u.line());
    finish_every_box(state, clock, key, company, app);
}

/// The open update done box by box: twin live, main through its keep medium, front live.
pub(super) fn finish_every_box(
    state: &AppState,
    clock: &AtomicU64,
    key: &monetize::Signer,
    company: &str,
    app: &str,
) {
    // Its front keeps its installer material (F8): the front drives every box. A front that
    // never took it is the ONE box's to drive (F9, tests/custody_step.rs).
    {
        let _pen = state.estates.pen(company);
        let mut run = state.estates.get(company).unwrap();
        run.material_taken_at_ms = run.material_taken_at_ms.max(1);
        save(state, &run).unwrap();
    }
    let t = take_as(state, clock, key, company);
    assert!(t.open && t.role == "twin", "{t:?}");
    report_as(
        state,
        clock,
        key,
        company,
        "twin",
        &update::Outcome::Done("live".into()),
    );
    assert_eq!(take_as(state, clock, key, company).role, "main");
    let up = keep_upgrade(state, clock, key, company, "main")
        .expect("the shop keep-boots the main of a paired estate");
    assert!(up.upgraded, "{}", up.words);
    report_as(
        state,
        clock,
        key,
        company,
        "main",
        &update::Outcome::Done("keep".into()),
    );
    report_as(
        state,
        clock,
        key,
        company,
        "front",
        &update::Outcome::Done("live".into()),
    );
    let run = state.estates.get(company).unwrap();
    assert!(run.update.is_none(), "closed");
    assert_eq!(
        run.updates.last().unwrap().state,
        update::state::DONE,
        "{}",
        run.updates.last().unwrap().line()
    );
    assert_eq!(
        run.release_of.get("main").map(String::as_str),
        Some(format!("r10-{app}").as_str())
    );
}

/// ★ **An adopted paired estate takes an estate update, then goes to ready once its main
/// reports** — adopted near its capture deadline, which it outlives: the same boxes, nothing
/// given back, no ready mail before its main reports, one after.
#[test]
fn an_adopted_paired_estate_is_updated_then_ready_once_its_main_reports_even_near_its_deadline() {
    let (state, cloud, kit, mails, clock) =
        install_failed_on("f7-adopted", true, site_release("r10"));
    let key = front_key(&kit);
    runs_older(&state, SITE);
    // A failed install is no estate to update.
    match update::start(&state, "gunnar-rs", "", "operator") {
        Err(sold::SoldRefusal::Refused(w)) => assert!(w.contains("not delivered (failed)"), "{w}"),
        other => panic!("{other:?}"),
    }
    // Adopted 23 h 30 min after its lay: half an hour before the deadline the lay set.
    clock.fetch_add(22 * HOUR + 29 * MINUTE, Ordering::SeqCst);
    answers(&kit, true);
    assert_eq!(
        settle(&state, "gunnar-rs", RECHECK).expect("adopted").phase,
        phase::PAIRED
    );
    crate::estate_order::drive(&state, SITE);
    assert_eq!(
        state.estates.get(SITE).unwrap().phase,
        phase::PAIRED,
        "its main never reports on r6"
    );
    assert_eq!(
        state.estates.get(SITE).unwrap().client_key,
        key.public_key()
            .iter()
            .map(|b| format!("{b:02x}"))
            .collect::<String>(),
        "the client key its stopped install never wrote is named again"
    );
    assert!(
        !take_as(&state, &clock, &key, SITE).open,
        "the front has asked before: nothing open"
    );
    let keep_before = cloud.keep_booted.lock().unwrap().len();

    let u =
        update::start(&state, "gunnar-rs", "", "operator").expect("the operator's update is taken");
    assert_eq!(u.release, "r10-gunnar", "{}", u.line());
    // The update runs past the lay's deadline: the boxes are never given back meanwhile.
    clock.fetch_add(2 * HOUR, Ordering::SeqCst);
    crate::estate_order::drive(&state, SITE);
    let run = state.estates.get(SITE).unwrap();
    assert_eq!(run.phase, phase::PAIRED, "{:#?}", run.history);
    assert_eq!(
        run.update.as_ref().map(|u| u.release.clone()),
        Some(u.release.clone()),
        "the update still runs"
    );
    finish_every_box(&state, &clock, &key, SITE, "gunnar");
    assert_eq!(
        cloud.keep_booted.lock().unwrap().len(),
        keep_before + 1,
        "the main, from the keep medium, its data kept"
    );

    // No delivery and no ready mail from the update: it waits for its main's report.
    clock.fetch_add(2 * HOUR, Ordering::SeqCst);
    crate::estate_order::drive(&state, SITE);
    let run = state.estates.get(SITE).unwrap();
    assert_eq!(run.phase, phase::PAIRED, "{:#?}", run.history);
    assert_eq!(mails.count("estate.ready"), 0, "{:?}", mails.kinds());

    // The main on r10 reports: the sale check, captured, ready, its mail once.
    let g = state.fleet.guardian().unwrap();
    g.store
        .record_report("gunnar-rs", "main", state.now(), &running("main"))
        .unwrap();
    crate::estate_order::drive(&state, SITE);
    let run = state.estates.get(SITE).unwrap();
    assert_eq!(run.phase, phase::CAPTURED, "{:#?}", run.history);
    assert_eq!(site_of(&state).state, "ready");
    assert_eq!(mails.count("estate.ready"), 1, "{:?}", mails.kinds());
    assert_eq!(cloud.lays().len(), 1, "no new machine was laid");
    assert!(
        cloud.destroys().is_empty(),
        "nothing was given back: {:?}",
        cloud.destroys()
    );
}

/// ★ **An installing estate whose boxes are not paired is still refused**, before any box is
/// touched; its install then goes on.
#[test]
fn an_installing_estate_is_refused_an_update() {
    let (state, pay, cloud, kit, _dir, _clock, _mails) =
        site_rig_with_cfg("f7-installing", true, site_release("r10"));
    let run = site::start_site(
        &state,
        "gunnar-rs",
        "ops@example.org",
        &EstateSizes::default(),
    )
    .unwrap();
    pay.authorize(&run.handle.clone().unwrap().id);
    kit.install_gate.arm();
    let s = state.clone();
    let driver = std::thread::spawn(move || super::drive(&s, SITE));
    kit.install_gate.reached();
    assert_eq!(state.estates.get(SITE).unwrap().phase, phase::INSTALLING);
    let calls_before = cloud.calls.lock().unwrap().len();
    for r in [
        update::start(&state, "gunnar-rs", "", "operator"),
        update::start_front_first(&state, "gunnar-rs", "", "operator"),
    ] {
        match r {
            Err(sold::SoldRefusal::Refused(w)) => assert!(
                w.contains("not delivered (installing)") && w.contains("still running"),
                "{w}"
            ),
            other => panic!("{other:?}"),
        }
    }
    assert_eq!(
        cloud.calls.lock().unwrap().len(),
        calls_before,
        "no box was touched, not even its firewall"
    );
    assert!(state.estates.get(SITE).unwrap().update.is_none());
    kit.install_gate.open();
    driver.join().unwrap();
    assert_eq!(
        state.estates.get(SITE).unwrap().phase,
        phase::CAPTURED,
        "its install went on"
    );
}

/// ★ **A delivered estate is unchanged**: its update runs as before, it stays captured, and no
/// ready mail is sent again.
#[test]
fn a_delivered_estate_is_updated_as_before() {
    let r = delivered("f7-delivered", release_cfg("r10", true));
    let key = client_of(&r, "acme");
    runs_older(&r.state, "acme");
    let ready = r.mails.count("estate.ready");
    assert!(!take(&r, &key).unwrap().open);
    update_every_box(&r.state, &r.clock, &key, "acme", "acme", "gunnar");
    drive(&r.state, "acme");
    let run = r.state.estates.get("acme").unwrap();
    assert_eq!(run.phase, phase::CAPTURED, "{:#?}", run.history);
    assert_eq!(r.mails.count("estate.ready"), ready, "no second ready mail");
}

/// ★ **Holger, one lifecycle**: a Holger estate paired by its installer's PASS (every box PAIRED)
/// whose boxes have not reported takes the update, its main keep-booted from `holger-keep`, and
/// is captured once they report.
#[test]
fn a_paired_holger_estate_takes_the_update_then_is_captured_once_it_reports() {
    let mut c = EstateOrderConfig {
        lifecycle: "holger".into(),
        ..cfg()
    };
    c.media = [
        ("front", "front"),
        ("holger-front", "holger-front"),
        ("holger", "holger"),
        ("holger-keep", "holger-keep"),
        ("holger-front-keep", "holger-front-keep"),
    ]
    .into_iter()
    .map(|(k, v)| (k.to_string(), format!("r10-{v}")))
    .collect();
    for kind in ["holger", "holger-front"] {
        c.live_apps.insert(
            kind.into(),
            update::LiveRecord {
                base: "b".repeat(64),
                app: "a".repeat(64),
                app_len: 9,
                sig: "5".repeat(128),
                ..Default::default()
            },
        );
        c.media_objects.insert(
            format!("{kind}{}", update::LIVE_SUFFIX),
            monetize_cloud::lay::MediaObject {
                url: format!("https://objsto.example/release/{kind}/r10.live-app.znippy"),
                sha256: "c".repeat(64),
                bytes: 9,
            },
        );
    }
    let r = rig_with("f7-holger-paired", c, "PASS");
    r.kit.silent.store(true, Ordering::SeqCst);
    start_for(
        &r.state,
        "acme",
        "buyer@example.org",
        "",
        "alice",
        &EstateSizes::default(),
    )
    .unwrap();
    r.pay.authorize(&handle_of(&r, "acme"));
    crate::estate_order::drive(&r.state, "acme");
    assert_eq!(
        r.state.estates.get("acme").unwrap().phase,
        phase::PAIRED,
        "no box reported yet"
    );
    let key = client_of(&r, "acme");
    runs_older(&r.state, "acme");
    assert!(!take(&r, &key).unwrap().open);
    let keep_before = r.cloud.keep_booted.lock().unwrap().len();
    update_every_box(&r.state, &r.clock, &key, "acme", "acme", "holger");
    let booted = r.cloud.keep_booted.lock().unwrap().clone();
    assert_eq!(booted.len(), keep_before + 1);
    assert_eq!(
        booted.last().unwrap().1,
        "r10-holger-keep",
        "Holger's main from Holger's keep medium"
    );
    let run = r.state.estates.get("acme").unwrap();
    assert_eq!(
        run.phase,
        phase::PAIRED,
        "the update is no delivery: {:#?}",
        run.history
    );
    assert_eq!(r.mails.count("estate.ready"), 0);
    r.clock.fetch_add(MINUTE, Ordering::SeqCst);
    drive(&r.state, "acme");
    let run = r.state.estates.get("acme").unwrap();
    assert_eq!(run.phase, phase::CAPTURED, "{:#?}", run.history);
    assert_eq!(r.mails.count("estate.ready"), 1);
}