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_material.rs

//! **F8 (2026-10-21): a paired estate's front takes its installer material.** test3-holger-rs
//! was adopted into `paired` (F5) and F7 let it take the update to r10 — which failed on its
//! twin: "its live door did not answer: no installer material is kept in holger's primary …
//! repository monetize-backup yet". The material reaches the primary only through the front's
//! `SoldEstate.TakeMaterial`, and that was refused until the estate was captured; a paired
//! estate is captured only once its main reports, and its main could report only after the
//! update. A circle.
//!
//! * **Every box PAIRED takes the material**: an estate in `paired` (its installer's PASS, or
//!   adopted on a re-check) takes its own installer material, the way a delivered one does —
//!   the material is the estate's, its payment is held. The hand-off still forgets it only
//!   once the estate is delivered.
//! * **An install still running is refused it**, and so is every phase before `paired`.
//! * Gunnar's and Holger's alike: both fronts ask as soon as their primary answers.

use super::paired_update::{
    call, finish_every_box, front_key, runs_older, site_release, take_as, SITE,
};
use super::recheck::{answers, install_failed_on};
use super::*;

const MINUTE: u64 = 60_000;
const HOUR: u64 = 60 * MINUTE;

/// The primary's `monetize-backup` repository as the front keeps the material there: the
/// front takes it from the shop, keeps it, and calls `done` (holger-estate-front
/// `shop_door::keep_material`, gunnar-front `recharge::take_kept`).
#[derive(Default)]
struct PrimarySlot(Mutex<Option<Vec<u8>>>);

impl PrimarySlot {
    fn keep(
        &self,
        state: &AppState,
        clock: &AtomicU64,
        key: &monetize::Signer,
        company: &str,
    ) -> Result<String, sold::SoldRefusal> {
        let take = |done: bool| {
            let (at, sig) = call(
                clock,
                key,
                company,
                sold::CALL_TAKE_MATERIAL,
                &[],
                u64::from(done),
                "",
            );
            sold::take_material(state, company, done, at, &sig)
        };
        let taken = take(false)?;
        assert!(!taken.bundle.is_empty(), "{}", taken.words);
        *self.0.lock().unwrap() = Some(taken.bundle);
        Ok(take(true)?.words)
    }

    /// The twin's live door: it answers only once the primary keeps the material.
    fn twin_door(&self) -> Result<(), String> {
        self.0.lock().unwrap().as_ref().map(|_| ()).ok_or_else(|| {
            "no installer material is kept in the primary's repository monetize-backup yet"
                .to_string()
        })
    }
}

/// The operator site whose install was stopped while its installer ran, then adopted on a
/// re-check (F7's test3-holger-rs), its pairing's late bundle sealed into the custody with the
/// rest (the shop's pairing writes it on its late PASS; the take-back seals it whatever the
/// outcome).
fn adopted(name: &str) -> (Arc<AppState>, Arc<MockCloud>, Arc<FakeKit>, Arc<AtomicU64>) {
    let (state, cloud, kit, _mails, clock) = install_failed_on(name, true, site_release("r10"));
    let custody = state.estates.custody.as_ref().unwrap();
    let dir = state.estates.run_dir(SITE).unwrap().join("late-pass");
    let h = custody.hand_off(SITE, &dir).unwrap();
    let back = h.dir().join(crate::custody::handoff::RETURN_DIR);
    std::fs::create_dir_all(&back).unwrap();
    std::fs::write(
        back.join(crate::custody::handoff::INSTALLER_MATERIAL),
        "{\"v\":\"installer-material/1\"}",
    )
    .unwrap();
    custody.take_back(SITE, &h).unwrap();
    drop(h);
    // Its boxes run r6, whose main never reports (its report-when-fed fix is in r10).
    runs_older(&state, SITE);
    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);
    let run = state.estates.get(SITE).unwrap();
    assert_eq!(
        run.phase,
        phase::PAIRED,
        "its main never reports on r6: {:#?}",
        run.history
    );
    (state, cloud, kit, clock)
}

/// ★ **An adopted paired estate's front takes its material, the primary keeps it, and the
/// update to the current release then goes through all three boxes** — the custody keeps its
/// copy until the estate is delivered.
#[test]
fn an_adopted_paired_estate_takes_its_material_then_its_update_goes_through_every_box() {
    let (state, cloud, kit, clock) = adopted("f8-adopted");
    let key = front_key(&kit);
    let slot = PrimarySlot::default();
    assert!(slot.twin_door().is_err());

    let said = slot
        .keep(&state, &clock, &key, SITE)
        .expect("a paired estate's front takes its material");
    assert!(said.contains("took its installer material"), "{said}");
    assert_eq!(
        slot.0.lock().unwrap().as_deref(),
        Some(&b"{\"v\":\"installer-material/1\"}"[..])
    );
    let run = state.estates.get(SITE).unwrap();
    assert!(run.material_taken_at_ms > 0, "{:#?}", run.history);
    assert_eq!(
        run.phase,
        phase::PAIRED,
        "taking the material is no delivery"
    );
    assert!(
        state.estates.custody.as_ref().unwrap().holds(SITE),
        "the shop forgets it only at the hand-off of a delivered estate"
    );

    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());
    slot.twin_door()
        .expect("the twin's live door answers: the primary keeps the material");
    finish_every_box(&state, &clock, &key, SITE, "gunnar");
    assert_eq!(
        cloud.keep_booted.lock().unwrap().len(),
        keep_before + 1,
        "the main, from its keep medium"
    );
    let run = state.estates.get(SITE).unwrap();
    assert_eq!(
        run.updates.last().unwrap().state,
        update::state::DONE,
        "{}",
        run.updates.last().unwrap().line()
    );
    assert!(
        ["front", "main", "twin"].iter().all(|r| run
            .release_of
            .get(*r)
            .is_some_and(|x| x.starts_with("r10-"))),
        "{:?}",
        run.release_of
    );
}

/// ★ **Holger, one lifecycle**: a Holger estate paired by its installer's PASS, no box reported
/// yet, takes its material into its primary and then its update through all three boxes.
#[test]
fn a_paired_holger_estate_takes_its_material_then_its_update() {
    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("f8-holger-paired", c, "PASS");
    r.kit.silent.store(true, Ordering::SeqCst);
    r.kit.bundles.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");
    let slot = PrimarySlot::default();
    slot.keep(&r.state, &r.clock, &key, "acme")
        .expect("a paired Holger estate's front takes its material");
    assert!(!take_as(&r.state, &r.clock, &key, "acme").open);
    let u =
        update::start(&r.state, "acme", "", "operator").expect("a paired estate takes the update");
    assert_eq!(u.release, "r10-holger", "{}", u.line());
    slot.twin_door().expect("the twin's live door answers");
    finish_every_box(&r.state, &r.clock, &key, "acme", "holger");
    assert_eq!(
        r.cloud.keep_booted.lock().unwrap().last().unwrap().1,
        "r10-holger-keep"
    );
}

/// ★ **An installing estate is still refused its material** — its boxes are not all PAIRED;
/// its install then goes on.
#[test]
fn an_installing_estate_is_refused_its_material() {
    let (state, pay, _cloud, kit, _dir, clock, _mails) =
        site_rig_with_cfg("f8-installing", true, site_release("r10"));
    kit.bundles.store(true, Ordering::SeqCst);
    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);
    // The estate's own client key, as the hand-off holds it while the installer runs.
    let hexed = std::fs::read_to_string(
        state
            .estates
            .run_dir(SITE)
            .unwrap()
            .join("handoff")
            .join(crate::custody::handoff::ESTATE_CLIENT),
    )
    .expect("the hand-off holds the client key");
    let seed: Vec<u8> = (0..hexed.trim().len())
        .step_by(2)
        .map(|i| u8::from_str_radix(&hexed.trim()[i..i + 2], 16).unwrap())
        .collect();
    let key = monetize::Signer::from_bytes(&seed.try_into().expect("32 bytes"));
    // Named on the run as an adopted one is (F7), so the refusal is the phase's, not the key's.
    let _pen = state.estates.pen(SITE);
    let mut run = state.estates.get(SITE).unwrap();
    run.client_key = key
        .public_key()
        .iter()
        .map(|b| format!("{b:02x}"))
        .collect();
    save(&state, &run).unwrap();
    drop(_pen);
    for done in [false, true] {
        let (at, sig) = call(
            &clock,
            &key,
            SITE,
            sold::CALL_TAKE_MATERIAL,
            &[],
            u64::from(done),
            "",
        );
        match sold::take_material(&state, SITE, done, at, &sig) {
            Err(sold::SoldRefusal::Refused(w)) => assert!(
                w.contains("not delivered (installing)") && w.contains("PAIRED"),
                "{w}"
            ),
            other => panic!("{:?}", other.map(|a| a.words)),
        }
    }
    assert_eq!(state.estates.get(SITE).unwrap().material_taken_at_ms, 0);
    kit.install_gate.open();
    driver.join().unwrap();
    assert_eq!(
        state.estates.get(SITE).unwrap().phase,
        phase::CAPTURED,
        "its install went on"
    );
}