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/adopt_kit.rs

//! **The ONE box ADOPTS a kit-installed operator site** (owner 2026-10-17): gunnar.rs was
//! installed by its kit; once adopted, the ONE box's `UpdateEstate` updates it — and nothing
//! of the shop lays, re-lays, fences, gives back or bills its machines.

use super::*;
use monetize_cloud::{Resource, ServerRole};
use monetize_guardian::World as _;

/// The operator site `gunnar-rs` (owned by `ignalina`, `terminable` so the adopted refusal is
/// the one a terminate meets), selling release `02` with media and live apps, and the KIT's
/// three machines standing at the provider with no label of the shop.
fn adopt_rig(name: &str) -> (Arc<AppState>, Arc<MockCloud>, Arc<AtomicU64>, PathBuf) {
    let dir = tmp(name);
    let pay = HoldPay::default();
    let cloud = Arc::new(MockCloud::new());
    let kit = FakeKit::new("PASS");
    let site =
        |vendor: &str, site: &str, owner: &str, operator_site: bool| crate::config::EstateConfig {
            product: "gunnar".into(),
            vendor: vendor.into(),
            site: site.into(),
            owner: owner.into(),
            operator_site,
            ..Default::default()
        };
    let reg = Registry::new()
        .sells("gunnar", pay.clone())
        .vendor(SitePay("sitepay-kunde"))
        .cloud(cloud.clone())
        .shared_product(shop_product())
        .home("gunnar", "gunnar-rs")
        .estate(
            "gunnar-rs",
            crate::config::EstateConfig {
                terminable: true,
                ..site("holdpay", "gunnar.rs", "ignalina", true)
            },
        )
        .estate(
            "kunde-se",
            site("sitepay-kunde", "kunde.se", "kunde", false),
        );
    let (state, clock, _mails, _) = build_in(
        reg,
        &dir,
        EstateOrderConfig {
            vendor: String::new(),
            apex: String::new(),
            ..release_cfg("02", true)
        },
        kit,
        None,
        None,
    );
    for id in ["kit-front", "kit-main", "kit-twin"] {
        cloud.live.lock().unwrap().insert(
            id.into(),
            Resource::Server {
                plan: "2xCPU-4GB".into(),
                zone: monetize_cloud::Zone("se-sto1".into()),
                role: ServerRole::Primary,
            },
        );
    }
    (state, cloud, clock, dir)
}

fn kit_machine(role: &str, server: &str, last: u8) -> monetize_cloud::lay::LaidMachine {
    monetize_cloud::lay::LaidMachine {
        role: role.into(),
        hostname: format!("{role}.gunnar.rs"),
        title: format!("gunnar {role}"),
        plan: "2xCPU-4GB".into(),
        server: server.into(),
        public_ipv4: format!("198.51.100.{last}"),
        utility_ipv4: format!("10.13.0.{last}"),
        disks: vec![monetize_cloud::lay::LaidDisk {
            title: format!("{role} system"),
            id: format!("{server}-disk0"),
            gib: 25,
            tier: "maxiops".into(),
            address: "virtio:0".into(),
        }],
        medium_clone: String::new(),
    }
}

fn ask(again: bool) -> adopt::AdoptAsk {
    adopt::AdoptAsk {
        estate: "gunnar-rs".into(),
        zone: String::new(),
        machines: vec![
            kit_machine("front", "kit-front", 1),
            kit_machine("main", "kit-main", 2),
            kit_machine("twin", "kit-twin", 3),
        ],
        release_of: BTreeMap::new(),
        again,
        by: "operator".into(),
    }
}

/// ★ **RED MAP-SEG 4c (coordinator 2026-10-26): the adopt step LABELS the machines it adopts**
/// with the run's `monetize_ref`, so they join the labelled listing (no MISSING alarm) — labels
/// only. A cloud that cannot label (an older guest) still adopts, and says so.
#[test]
fn an_adoption_labels_the_kit_machines_as_the_estates() {
    let (state, cloud, _clock, _dir) = adopt_rig("adopt-labels");
    assert_eq!(
        cloud.live_under("estate/gunnarrs"),
        0,
        "the kit's machines carry no label of the shop"
    );
    let a = adopt::adopt(&state, &ask(false)).expect("adopted");
    assert_eq!(
        cloud.live_under("estate/gunnarrs"),
        3,
        "front, main and twin carry estate/gunnarrs now"
    );
    let note = &a.run.history.last().unwrap().note;
    assert!(note.contains("labelled estate/gunnarrs"), "{note}");
    assert_eq!(
        cloud.live.lock().unwrap().len(),
        3,
        "nothing was bought or removed"
    );
}

/// The client key the front is fed, as the signer it signs with.
fn fed_key(feed: &BTreeMap<String, String>) -> monetize::Signer {
    let hex = feed[&adopt::client_key_feed()].trim().to_string();
    let seed: Vec<u8> = (0..hex.len())
        .step_by(2)
        .map(|i| u8::from_str_radix(&hex[i..i + 2], 16).unwrap())
        .collect();
    monetize::Signer::from_bytes(&seed.try_into().expect("32 bytes"))
}

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

/// ★ **Adopt, then update**: a kit-installed site is refused what it is not (an undeclared site,
/// a machine the provider does not hold), adopted ONCE with its three machines read back, its
/// client key minted through the custody and answered once in the front's feed lines — which
/// open `SoldEstate.UpdateTake` — and the operator's update then runs over it like a laid one.
#[test]
fn a_kit_installed_site_is_adopted_once_and_its_front_takes_the_operators_update() {
    let (state, cloud, clock, dir) = adopt_rig("adopt-update");
    // Refused by name before anything is minted.
    let not_ours = adopt::adopt(
        &state,
        &adopt::AdoptAsk {
            estate: "kunde-se".into(),
            ..ask(false)
        },
    )
    .unwrap_err();
    assert!(
        matches!(&not_ours, StartRefusal::NotSold(w) if w.contains("not a declared operator site")),
        "{not_ours:?}"
    );
    let mut ghost = ask(false);
    ghost.machines[1].server = "kit-ghost".into();
    assert!(
        matches!(adopt::adopt(&state, &ghost).unwrap_err(), StartRefusal::NotFound(w) if w.contains("kit-ghost"))
    );
    let mut two = ask(false);
    two.machines.pop();
    assert!(
        matches!(adopt::adopt(&state, &two).unwrap_err(), StartRefusal::BadName(w) if w.contains("no twin"))
    );
    assert!(
        state.estates.get("gunnarrs").is_none(),
        "nothing was written by a refusal"
    );

    let a = adopt::adopt(&state, &ask(false)).expect("adopted");
    let run = &a.run;
    assert_eq!(
        (
            run.company.as_str(),
            run.estate.as_str(),
            run.account.as_str(),
            run.phase.as_str()
        ),
        ("gunnarrs", "gunnar-rs", "ignalina", phase::CAPTURED)
    );
    assert!(
        run.site_order && run.adopted_at_ms > 0 && run.told && run.handle.is_none(),
        "a delivered site run, adopted, nobody mailed, nothing held"
    );
    assert_eq!(
        run.quote.monthly.minor + run.amount.minor,
        0,
        "the shop bills nothing of it"
    );
    assert_eq!(
        run.laid
            .as_ref()
            .unwrap()
            .machines
            .iter()
            .map(|m| (m.role.as_str(), m.server.as_str()))
            .collect::<Vec<_>>(),
        vec![
            ("front", "kit-front"),
            ("main", "kit-main"),
            ("twin", "kit-twin")
        ]
    );
    assert!(run.history.last().unwrap().note.starts_with(adopt::ADOPTED));
    // The feed lines: the shop door names the run's company, the key is the run's.
    let door: toml::Table = toml::from_str(&a.feed[adopt::FEED_SHOP_DOOR]).unwrap();
    assert_eq!(
        (door["company"].as_str(), door["client_key_secret"].as_str()),
        (Some("gunnarrs"), Some("shop_client_key"))
    );
    assert_eq!(
        door["door"].as_str().map(str::to_string),
        state.estates.cfg.as_ref().unwrap().shop_door().ok()
    );
    let key = fed_key(&a.feed);
    let public: String = key
        .public_key()
        .iter()
        .map(|b| format!("{b:02x}"))
        .collect();
    assert_eq!(
        run.client_key, public,
        "the run verifies the key the front is fed"
    );
    // Never kept: not in custody, not in the run file, not in the Debug.
    let seed = a.feed[&adopt::client_key_feed()].clone();
    assert!(
        !state.estates.custody.as_ref().unwrap().holds("gunnarrs"),
        "the custody forgot the seed: the shop keeps the public half alone"
    );
    let file = std::fs::read_to_string(dir.join("estate-orders/gunnarrs/run.json")).unwrap();
    assert!(!file.contains(&seed) && !format!("{a:?}").contains(&seed));
    assert!(
        !dir.join("estate-orders/gunnarrs/adopt-handoff").exists(),
        "the hand-off is gone"
    );

    // Once: again is refused by name, unless `again` — which mints a NEW key.
    assert!(
        matches!(adopt::adopt(&state, &ask(false)).unwrap_err(), StartRefusal::Taken(w) if w.contains("adopted already"))
    );
    assert!(
        matches!(
            site::start_site(
                &state,
                "gunnar-rs",
                "ops@example.invalid",
                &EstateSizes::default()
            ),
            Err(StartRefusal::Taken(_))
        ),
        "nor is it ordered over"
    );
    let again = adopt::adopt(&state, &ask(true)).expect("adopted again");
    assert_ne!(again.run.client_key, run.client_key, "a new client key");
    assert_eq!(again.run.ordered_at_ms, run.ordered_at_ms, "the same run");
    let old_key = key;
    let key = fed_key(&again.feed);
    let (at, sig) = signed_by(&clock, &old_key, update::CALL_UPDATE_TAKE, &[], 0);
    assert_eq!(
        update::take(&state, "gunnarrs", at, &sig).unwrap_err(),
        sold::SoldRefusal::NotSigned,
        "the earlier key opens nothing any more"
    );

    // Its front carries the taker (it asks; nothing is open yet): the update goes twin first.
    let (at, sig) = signed_by(&clock, &key, update::CALL_UPDATE_TAKE, &[], 0);
    assert!(
        !update::take(&state, "gunnarrs", at, &sig)
            .expect("the front's look")
            .open
    );
    // The operator's update runs over it: the release is unknown on every box, so all of them.
    let u = update::start(&state, "gunnar-rs", "", "operator").expect("an adopted site is updated");
    assert_eq!(u.release, "02-gunnar");
    let (at, sig) = signed_by(&clock, &key, update::CALL_UPDATE_TAKE, &[], 0);
    let t = update::take(&state, "gunnarrs", at, &sig)
        .expect("the front's take, signed with the fed key");
    assert!(t.open && t.role == "twin" && t.app.is_some(), "{t:?}");
    // The keep path of the box whose turn it is: the shop keep-boots the KIT's twin by its id.
    let (at, sig) = signed_by(&clock, &key, sold::CALL_UPGRADE, &["twin"], 0);
    let up = sold::upgrade(&state, "gunnarrs", "twin", false, at, &sig).expect("upgraded");
    assert!(up.upgraded, "{}", up.words);
    assert_eq!(
        cloud
            .keep_booted
            .lock()
            .unwrap()
            .last()
            .map(|(s, _)| s.as_str()),
        Some("kit-twin")
    );
    // A box whose turn it is NOT (the front's daily look) is refused by name (F-U-02).
    let (at, sig) = signed_by(&clock, &key, sold::CALL_UPGRADE, &["main"], 0);
    let idle = sold::upgrade(&state, "gunnarrs", "main", false, at, &sig).unwrap_err();
    assert!(
        matches!(&idle, sold::SoldRefusal::Refused(w) if w.contains("no update is open for") && w.contains("twin's turn")),
        "{idle:?}"
    );
    assert_eq!(cloud.keep_booted.lock().unwrap().len(), 1);
}

/// ★ **Nothing of the shop lays, re-lays, fences, gives back or bills an adopted site**: its tick
/// lays nothing and lapses nothing; `Recharge`, `RestoreFront` and `Terminate` are refused by
/// name; the guardian sees no laid machine (it waits for the operator's kit, as before); the
/// site's join record keeps keying its boxes; its Sites row reads ready and offers no Terminate;
/// the front's daily upgrade look is refused by name while no update is open.
#[test]
fn an_adopted_site_is_never_laid_given_back_or_billed_by_the_shop() {
    let (state, cloud, clock, _dir) = adopt_rig("adopt-never");
    let a = adopt::adopt(&state, &ask(false)).expect("adopted");
    let key = fed_key(&a.feed);
    let before = cloud.calls.lock().unwrap().len();
    for days in [0u64, 2, 400] {
        clock.store(a.run.captured_at_ms + days * 86_400_000, Ordering::SeqCst);
        drive(&state, "gunnarrs");
        let run = state.estates.get("gunnarrs").unwrap();
        assert_eq!(run.phase, phase::CAPTURED, "day {days}: {:#?}", run.history);
        assert!(run.change.is_none() && run.renewal.is_none());
    }
    assert!(
        cloud.lays().is_empty()
            && cloud.destroys().is_empty()
            && cloud.keep_booted.lock().unwrap().is_empty(),
        "the tick laid, gave back and booted nothing"
    );
    let _ = before;
    // Refused by name.
    let (at, sig) = signed_by(&clock, &key, sold::CALL_RECHARGE, &["main"], 0);
    let c = monetize::sign::EstateCall {
        v: 1,
        call: sold::CALL_RECHARGE.into(),
        estate: "gunnarrs".into(),
        members: vec!["main".into()],
        gib: 0,
        at_unix_ms: at,
        carries: "seed".into(),
    };
    let _ = sig;
    let r = sold::recharge(
        &state,
        "gunnarrs",
        "main",
        "seed",
        false,
        at,
        &key.sign_estate_call(&c),
    )
    .unwrap_err();
    assert!(
        matches!(&r, sold::SoldRefusal::Refused(w) if w.contains("adopted") && w.contains("kit")),
        "{r:?}"
    );
    let restore = sold::restore_front(&state, "gunnarrs", sold::RestoreBy::Operator("op".into()))
        .unwrap_err();
    assert!(
        matches!(&restore, sold::SoldRefusal::Refused(w) if w.contains("adopted")),
        "{restore:?}"
    );
    let t = terminate::terminate(&state, None, "gunnar-rs", "gunnar-rs").unwrap_err();
    assert!(
        matches!(&t, StartRefusal::NotNow(w) if w.contains("adopted")),
        "{t:?}"
    );
    assert_eq!(
        state.estates.get("gunnarrs").unwrap().phase,
        phase::CAPTURED,
        "nothing was ended"
    );
    // The front's daily look, no update open: refused by name, nothing booted (F-U-02).
    let (at, sig) = signed_by(&clock, &key, sold::CALL_UPGRADE, &["twin"], 0);
    let idle = sold::upgrade(&state, "gunnarrs", "twin", false, at, &sig).unwrap_err();
    assert!(
        matches!(&idle, sold::SoldRefusal::Refused(w) if w.contains("no update is open for")),
        "{idle:?}"
    );
    // The guardian: no laid machine of it (the operator's kit fences and lays), its restatement as before.
    let world = crate::fleet::ShopWorld { state: &state };
    assert!(
        world.laid_machine("gunnar-rs", "main").is_none()
            && world.laid_machine("gunnarrs", "twin").is_none()
    );
    // The join record keeps keying its boxes.
    assert!(
        !site::laid_since(&state, "gunnar-rs", 0),
        "an adopted site is not the shop's to key"
    );
    // Its Sites row: ready once its join record holds its bearer (M-FOLLOW 1).
    assert_eq!(
        site::sites(&state)
            .into_iter()
            .find(|s| s.estate == "gunnar-rs")
            .unwrap()
            .state,
        site::UNJOINED,
        "no join record taken yet"
    );
    state
        .fleet
        .know_bearer("gunnar-rs-joined-digest", "gunnar-rs");
    let row = site::sites(&state)
        .into_iter()
        .find(|s| s.estate == "gunnar-rs")
        .unwrap();
    assert_eq!(row.state, "ready", "{row:?}");
    assert!(
        row.words.contains("adopted") && !row.terminable && !row.why_not.is_empty(),
        "{row:?}"
    );
    assert!(
        cloud.lays().is_empty() && cloud.destroys().is_empty(),
        "still nothing laid or given back"
    );
}

/// ★ **M-FOLLOW 1 (prod 2026-10-26)**: an adopted site's ONLY standing here is its join record
/// (`joined/<site>.json`, its ONE bearer). Prod's `site list` said gunnar-rs `ready` while its
/// record was put aside and gunnar.rs's shop answered 503. A site whose shop cannot answer
/// never reads `ready`: not while no join record holds its bearer, nor once that bearer is
/// revoked or expired. The record taken (the guardian's real path) makes it `ready` again.
#[test]
fn an_adopted_site_reads_ready_only_while_its_join_record_holds_its_bearer() {
    let (state, _cloud, clock, dir) = adopt_rig("adopt-standing");
    adopt::adopt(&state, &ask(false)).expect("adopted");
    let row = || {
        site::sites(&state)
            .into_iter()
            .find(|s| s.estate == "gunnar-rs")
            .unwrap()
    };

    // Adopted, but no join record is taken: its shop cannot answer.
    let r = row();
    assert_eq!(r.state, site::UNJOINED, "{r:?}");
    assert!(
        r.words.contains("joined/gunnar-rs.json") && r.words.contains("cannot answer"),
        "{r:?}"
    );
    let run = state.estates.get("gunnarrs").unwrap();
    let (st, words, _) = site::operator_view_held(
        &state,
        &run,
        run.operator_view_as("gunnar.rs", &site::words_for(&state, "gunnar-rs")),
    );
    assert_eq!(
        st,
        site::UNJOINED,
        "`site status` reads the same as `site list`: {words}"
    );

    // The guardian takes its join record (the real path): its bearer opens, it reads ready.
    let joined = dir.join("joined");
    std::fs::create_dir_all(&joined).unwrap();
    let now = clock.load(Ordering::SeqCst);
    let bearer = "gunnar-rs-site-bearer-0123456789abcdef";
    let record = serde_json::json!({
        "estate": "gunnar-rs", "product": "gunnar", "box_name": "main",
        "enrolled_unix_ms": now - 1_000, "bearer_sha256": crate::fleet::bearer_digest(bearer), "bearer_until_unix_ms": now + 86_400_000,
    });
    std::fs::write(joined.join("gunnar-rs.json"), record.to_string()).unwrap();
    let lines = crate::fleet::take_joined(&state, &joined);
    assert!(lines.iter().all(|l| !l.contains("REFUSED")), "{lines:#?}");
    assert_eq!(
        state.fleet.estate_of_bearer_at(bearer, now).as_deref(),
        Some("gunnar-rs")
    );
    let r = row();
    assert_eq!(r.state, "ready", "{r:?}");
    assert!(r.words.contains("adopted"), "{r:?}");

    // Its bearer expires: its shop cannot answer, and the list says so.
    clock.store(now + 2 * 86_400_000, Ordering::SeqCst);
    let r = row();
    assert_eq!(r.state, site::UNJOINED, "{r:?}");
    assert!(r.words.contains("expired"), "{r:?}");

    // The operator revokes it: the same.
    clock.store(now, Ordering::SeqCst);
    state.fleet.revoke_bearer("gunnar-rs", now);
    let r = row();
    assert_eq!(r.state, site::UNJOINED, "{r:?}");
    assert!(r.words.contains("revoked"), "{r:?}");
}