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

nordisk/monetize

crates/monetize-cloud/src/lay.rs

//! **Laying a whole estate: the servers and disks of a private gunnar.**
//!
//! `private-gunnar-ops/PRIVATE-GUNNAR.md`: a company buys `<company>.gunnar.rs`
//! on gunnar.rs's front page, and the order is one more job in gunnar.rs's own
//! monetize queue. Its `laying` step is this call: the provider creates every
//! disk and every server of the estate (front, main, twin), stamps each with
//! [`crate::LABEL_REF`] = the order's reference, and answers what it laid.
//!
//! # The contract every implementation keeps
//!
//! * **Idempotent on the reference.** A resumed job calls `lay_estate` again
//!   with the same plan; a disk or server that already carries the reference
//!   and the same title is ADOPTED, never bought twice.
//! * **Journaled.** Each creation is written to the journal as an intent
//!   before the call and confirmed (with the provider's id) after it, in the
//!   grow journal's format, so the job's file says what exists.
//! * **Giving back is not here.** A failed job gives back everything under
//!   its reference with `Give::References` and proves it
//!   with `Listing::Under` — the same two calls a purged
//!   tenant goes through, so there is one teardown and not two.
//! * **What it laid is described exactly**, because the installer ADOPTS it
//!   (`cargo xtask estate install --laid <file>`): titles, plans, sizes, tiers,
//!   device addresses and both IPv4 addresses, per machine.

use std::collections::BTreeMap;

use crate::{CloudError, Zone};

/// The roles of an estate's machines, in the order they are laid.
pub const ROLES: [&str; 3] = ["front", "main", "twin"];

/// **The relay mark** (5.4): the label a one-machine plan carries when it lays ONE box of an
/// estate already laid, again (the estate guardian's `Lay` step, after the old box is fenced).
/// Its value is the machine's role, `main` or `twin`; the front is never laid again.
pub const RELAY_LABEL: &str = "relay";

/// The roles a relay may lay.
pub const RELAY_ROLES: [&str; 2] = ["main", "twin"];

/// **The added mark** (P3-FACTS, a plan change that adds machines): the label each machine of a
/// plan carries when a plan change lays it BESIDE an estate that stands (the Lake's `be-2`, its
/// backends going 1 → 2). Its value is the change's own name (`change-1`): the spend cap commits
/// the machines under `<reference>#<value>` ([`spend_key`]), never over what the estate's own lay
/// committed.
pub const ADDED_LABEL: &str = "added";

/// The step words this module writes to the journal.
pub mod step {
    /// The lay began (set-wide intent) or finished (confirmed).
    pub const LAY: &str = "lay";
    /// One disk; `volume` is its title, `detail` its provider id when confirmed.
    pub const DISK: &str = "lay-disk";
    /// One server; `volume` is its hostname, `detail` its provider id when confirmed.
    pub const SERVER: &str = "lay-server";
    /// A server's plan change; `volume` is its provider id, `detail` the plan.
    pub const PLAN: &str = "plan";
    /// One machine's CLONE of the release medium (no-dwarf step 4, 2026-10-08); `volume` is
    /// the clone's title ([`super::clone_title`]), `detail` the source medium on the intent
    /// and the clone's provider id when confirmed.
    pub const MEDIUM: &str = "lay-medium";
    /// That clone given back after the hand-off (`Change::ForgetMedia`);
    /// `volume` its title, `detail` its provider id, confirmed once it reads as gone.
    pub const MEDIUM_GONE: &str = "lay-medium-gone";
    /// One machine's metadata service turned OFF after the hand-off (its seed rode the
    /// `user_data`; 2026-10-09): `volume` its hostname, `detail` its provider id;
    /// `Failed` with the provider's words when it would not (the seed's bootstrap key is
    /// spent once the box joined, so a refusal is recorded, not fatal).
    pub const METADATA_OFF: &str = "lay-metadata-off";
    /// The release's medium storage IMPORTED from its object (2026-10-09): `volume` its title
    /// ([`super::MediaObject::storage_title`]), `detail` its provider id when confirmed. It is
    /// the RELEASE's (shared by every order's clones), never given back with an order.
    pub const MEDIUM_IMPORT: &str = "lay-medium-import";
    /// One laid machine booted once from a KEEP medium (F4, a sold estate upgraded in place,
    /// 2026-10-09): `volume` its hostname, `detail` its provider id; confirmed once it runs its
    /// upgraded disk again.
    pub const KEEP_BOOT: &str = "keep-boot";
    /// One laid machine's inbound firewall WRITTEN, per its role (stinky S7, 2026-10-10: a
    /// sold estate's servers were laid `"firewall": "on"` with no rules): `volume` its
    /// hostname, `detail` the rule count when confirmed, the provider's words when failed.
    pub const FIREWALL: &str = "lay-firewall";
    /// A sold estate's lost FRONT restored from its daily backup (S8, 2026-10-11): `volume`
    /// its hostname, `detail` the backup's provider id at the intent, the disk it was
    /// restored onto when confirmed.
    pub const RESTORE: &str = "restore-backup";
}

/// The label a release-medium clone carries beside the estate's reference (2026-10-09): a
/// clone takes no labels at the clone call, so it is labelled right after — the label search
/// and the orphan sweep find it by estate.
pub const CLONE_LABEL: &str = "release-clone";

/// **How long a whole keep boot may take** (audit 7, 2026-10-18): the keep medium imported and
/// cloned, the box stopped, the medium's run (the provider's own budget, 45 min at UpCloud),
/// the clone given back and the box started — each provider wait bounded on its own. The
/// shop's Upgrade call is held this long, and a front waits on it exactly as long: a front
/// that gave up first would report a failure while the shop still works. The one constant is
/// `monetize_product::KEEP_BOOT_WITHIN`, which the fronts read too (lane G9, A7 row 9).
pub use monetize_product::KEEP_BOOT_WITHIN;

/// **The title of a machine's release-medium clone**: the machine's title and
/// `-release-clone` — the suffix an orphan sweep finds a clone by when a crash left it
/// unrecorded.
pub fn clone_title(machine_title: &str) -> String {
    format!("{machine_title}{CLONE_SUFFIX}")
}

/// The suffix every release-medium clone's title ends with ([`clone_title`]).
pub const CLONE_SUFFIX: &str = "-release-clone";

/// Pure: **the release clones this journal recorded and has not seen given back** —
/// `(title, provider id)`, in the order they were confirmed. ONLY these are ever given back
/// (`Change::ForgetMedia`): a storage the journal did not record is never
/// touched, whatever its title says.
pub fn journaled_clones(records: &[crate::grow::GrowRecord]) -> Vec<(String, String)> {
    let mut out: Vec<(String, String)> = Vec::new();
    for r in records
        .iter()
        .filter(|r| r.mark == crate::grow::Mark::Confirmed && !r.detail.is_empty())
    {
        match r.step.as_str() {
            step::MEDIUM if !out.iter().any(|(_, id)| *id == r.detail) => {
                out.push((r.volume.clone(), r.detail.clone()))
            }
            step::MEDIUM_GONE => out.retain(|(_, id)| *id != r.detail),
            _ => {}
        }
    }
    out
}

/// **What to lay**: the estate's machines, each with its disks. The first disk
/// of a machine is its system disk (blank, `virtio:0`); the rest are data
/// disks in order (`virtio:1`, `virtio:2`, …).
#[derive(Clone, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
pub struct EstatePlan {
    /// The order's reference, `estate/<company>`: the label value on every resource.
    pub reference: String,
    pub company: String,
    pub zone: Zone,
    pub machines: Vec<MachinePlan>,
    /// **DEV-NARRATE's source** (lane N2, 2026-10-20): the ONE box's public IPv4. Set, every
    /// machine of the estate admits `udp/51822` (nordisk-narrate's port) FROM THIS ADDRESS ONLY,
    /// so the box that lays a test site hears its boot log; empty (the default, and always for a
    /// sold estate or a serving site) keeps it closed. Set by the shop for a site that says
    /// `[estates.<label>] narrate = true`, never by a provider.
    #[serde(default, skip_serializing_if = "String::is_empty")]
    pub narrate_from: String,
    /// **The product's shape the plan is checked against** (A4-FACTS): the roles its facts say an
    /// estate of it lays ([`PlanShape`]), set by the shop from the product's facts before the
    /// plan reaches a provider. `None` (an older shop's plan, a provider's own test): the shape
    /// before A4-FACTS — a plan that names a main or a twin is exactly one front, one main and
    /// one twin ([`EstatePlan::check`]).
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub shape: Option<PlanShape>,
}

/// **The roles an estate of a product may lay** (A4-FACTS; the product's `Facts.roles`, as the
/// cloud reads them): what [`EstatePlan::check`] holds a plan to instead of Gunnar's three
/// machines. A product whose twin is optional lays a front and a main alone, and a plan change
/// lays its twin beside them later.
#[derive(Clone, PartialEq, Eq, Debug, Default, serde::Serialize, serde::Deserialize)]
pub struct PlanShape {
    pub roles: Vec<ShapeRole>,
}

/// One role of a [`PlanShape`] (the product's `RoleFacts`, as the cloud reads it).
#[derive(Clone, PartialEq, Eq, Debug, Default, serde::Serialize, serde::Deserialize)]
pub struct ShapeRole {
    pub role: String,
    /// Laid n times: its first machine carries the role's name, the next ones `<role>-<n>`.
    #[serde(default)]
    pub many: bool,
    /// May be left out of the estate.
    #[serde(default)]
    pub optional: bool,
    /// **The ports this role opens to anyone, as the product declares them** (OX-WGDS5,
    /// 2026-10-29): what the cloud's firewall admits from any address on top of the estate's own
    /// lanes (the web, the relay, the installer's link). A product that declares none opens
    /// nothing more; the cloud names no product here, it reads this list.
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub open: Vec<OpenPort>,
}

/// **One port a role opens to any address** (a [`ShapeRole::open`] entry): `protocol` is `udp`
/// or `tcp`, `why` the product's words for it (the rule's comment).
#[derive(Clone, PartialEq, Eq, Debug, Default, serde::Serialize, serde::Deserialize)]
pub struct OpenPort {
    pub protocol: String,
    pub port: u16,
    #[serde(default, skip_serializing_if = "String::is_empty")]
    pub why: String,
}

impl PlanShape {
    /// Pure: **the ports the machine `machine` opens to anyone** — its role's
    /// [`ShapeRole::open`] (a numbered member's, its many-role's); none for a machine of no role.
    pub fn open_of(&self, machine: &str) -> Vec<OpenPort> {
        self.role_of(machine)
            .map(|r| r.open.clone())
            .unwrap_or_default()
    }

    /// Pure: **the role `machine` is laid as** — the role of that name, or the many-role
    /// `<role>` of a numbered member `<role>-<n>` (n ≥ 2).
    pub fn role_of(&self, machine: &str) -> Option<&ShapeRole> {
        self.roles.iter().find(|r| r.role == machine).or_else(|| {
            let (base, n) = machine.rsplit_once('-')?;
            n.parse::<u32>().ok().filter(|n| *n >= 2)?;
            self.roles.iter().find(|r| r.many && r.role == base)
        })
    }

    /// The roles, as a sentence lists them.
    fn names(&self) -> String {
        self.roles
            .iter()
            .map(|r| r.role.as_str())
            .collect::<Vec<_>>()
            .join(", ")
    }

    /// Pure: **why `plan`'s machines are not an estate of this shape**, or `None`. Every machine
    /// is one of the roles; a plan that lays the whole estate (no machine marked) lays every role
    /// that is not optional; a relay ([`RELAY_LABEL`]) lays ONE machine of its role; a plan
    /// change's machines ([`ADDED_LABEL`]) are each marked with the one change.
    pub fn refusal(&self, plan: &EstatePlan) -> Option<String> {
        if self.roles.is_empty() {
            return Some("the product's shape names no role: nothing is laid by it".into());
        }
        if let Some(m) = plan
            .machines
            .iter()
            .find(|m| self.role_of(&m.role).is_none())
        {
            return Some(format!(
                "the plan lays a {:?}, which is not a role of this product ({})",
                m.role,
                self.names()
            ));
        }
        let relays: Vec<&MachinePlan> = plan
            .machines
            .iter()
            .filter(|m| m.labels.contains_key(RELAY_LABEL))
            .collect();
        if !relays.is_empty() {
            let ok = plan.machines.len() == 1
                && relays[0].labels.get(RELAY_LABEL) == Some(&relays[0].role);
            return (!ok).then(|| format!("a relay lays ONE machine marked relay=<its role>; this plan marks {} of {} machine(s)", relays.len(), plan.machines.len()));
        }
        let added: Vec<&String> = plan
            .machines
            .iter()
            .filter_map(|m| m.labels.get(ADDED_LABEL))
            .collect();
        if !added.is_empty() {
            let one = added.len() == plan.machines.len()
                && added.iter().all(|a| *a == added[0] && !a.trim().is_empty());
            return (!one).then(|| format!("a plan that lays machines beside a standing estate marks every one {ADDED_LABEL}=<the change>; this plan marks {} of {}", added.len(), plan.machines.len()));
        }
        let laid: Vec<&str> = plan.machines.iter().map(|m| m.role.as_str()).collect();
        let missing: Vec<&str> = self
            .roles
            .iter()
            .filter(|r| !r.optional && !laid.contains(&r.role.as_str()))
            .map(|r| r.role.as_str())
            .collect();
        (!missing.is_empty()).then(|| {
            format!(
                "an estate of this product lays {}; this plan has no {}",
                self.names(),
                missing.join(", ")
            )
        })
    }
}

#[derive(Clone, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
pub struct MachinePlan {
    /// `front` | `main` | `twin`.
    pub role: String,
    pub hostname: String,
    pub title: String,
    pub plan: String,
    /// `disk,cdrom`: a blank system disk does not boot, so the box falls
    /// through to the installer medium the boot lane attaches.
    pub boot_order: String,
    /// Labels beyond the reference (`role`, the estate's own labels).
    #[serde(default)]
    pub labels: BTreeMap<String, String>,
    pub disks: Vec<DiskPlan>,
    /// **The machine's network interfaces** — what is laid AND what is
    /// priced: every public IPv4 address is a billed item at the provider
    /// (UpCloud `ipv4_address`, €3.51 a month in se-sto1, measured
    /// 2026-10-05). Absent in an older plan: a public and a utility IPv4,
    /// what every estate machine has always been laid with.
    #[serde(default = "default_interfaces")]
    pub interfaces: Vec<NetInterface>,
    /// **The seedless release medium this machine boots** (no operator machine in a sale,
    /// 2026-10-08): the provider's id of the release's medium storage for this role's kind.
    /// The lay CLONES it for this machine (journaled, [`step::MEDIUM`]) and attaches the clone
    /// as the cdrom; `Change::ForgetMedia` gives the journaled clones back.
    /// Empty: no medium (the kit attaches one).
    #[serde(default)]
    pub medium: String,
    /// **This machine's pairing seed**, given to it as the provider's `user_data` (the box
    /// reads it from the metadata on its first boot). It crosses the cloud guest's ABI with
    /// the plan, set only for the lay call (never kept with a run); redacted in `Debug`.
    #[serde(default, skip_serializing_if = "UserData::is_empty")]
    pub user_data: UserData,
    /// **The release medium as an OBJECT** (owner 2026-10-09: the release media live in
    /// Object Storage): when [`Self::medium`] is empty, the lay imports this object into the
    /// release's medium storage — once per release and zone, the provider fetching the bytes
    /// itself — and clones THAT for this machine as before.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub medium_object: Option<MediaObject>,
    /// **The release's TEMPLATE for this machine's kind** (owner 2026-10-12, option (a)): the
    /// provider's id of a template made from the seedless release medium's installed disk,
    /// before its first boot. Set: the lay CLONES it as the machine's system disk (disk 0)
    /// and boots that disk — no medium, no installer pass; the seed still reaches the box as
    /// its `user_data`. Empty: the medium path above.
    #[serde(default, skip_serializing_if = "String::is_empty")]
    pub template: String,
}

/// **One release medium in Object Storage**: where the provider fetches it, and what its
/// bytes must be. `url` may be presigned (a credential while it lives): `Debug` prints it
/// without its query.
#[derive(Clone, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
pub struct MediaObject {
    pub url: String,
    /// The bytes' sha256, hex: the import is refused unless the provider read exactly these.
    pub sha256: String,
    pub bytes: u64,
}

impl std::fmt::Debug for MediaObject {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "MediaObject({}?…, sha256 {}, {} bytes)",
            self.url.split('?').next().unwrap_or(""),
            self.sha256,
            self.bytes
        )
    }
}

impl MediaObject {
    /// The title of the release's medium storage for these bytes: `release-medium-<sha8>`.
    pub fn storage_title(&self) -> String {
        format!(
            "release-medium-{}",
            &self.sha256[..self.sha256.len().min(8)]
        )
    }
}

/// A machine's `user_data` (its pairing seed: a bootstrap key that joins one slot once).
/// `Debug` prints its length only.
#[derive(Clone, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
#[serde(transparent)]
pub struct UserData(pub String);

impl UserData {
    pub fn is_empty(&self) -> bool {
        self.0.is_empty()
    }
}

impl std::fmt::Debug for UserData {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "UserData({} bytes)", self.0.len())
    }
}

/// One network interface of a laid machine.
#[derive(Clone, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
pub struct NetInterface {
    /// `public` | `utility` | `private`.
    pub kind: String,
    /// `IPv4` | `IPv6`.
    pub family: String,
}

/// A public and a utility IPv4: what an estate machine is laid with.
pub fn default_interfaces() -> Vec<NetInterface> {
    vec![
        NetInterface {
            kind: "public".into(),
            family: "IPv4".into(),
        },
        NetInterface {
            kind: "utility".into(),
            family: "IPv4".into(),
        },
    ]
}

/// **A box behind its estate's front** (owner 2026-10-23, the lean estate: one public IPv4 per
/// estate): a utility IPv4 only. The front is the estate's hub — the WireGuard relay the boxes
/// pair and follow over, and their one way out — so nothing dials main or the twin from outside.
pub fn behind_the_front_interfaces() -> Vec<NetInterface> {
    vec![NetInterface {
        kind: "utility".into(),
        family: "IPv4".into(),
    }]
}

impl MachinePlan {
    /// **How many public IPv4 addresses this machine holds** — each one billed.
    pub fn public_ipv4(&self) -> u64 {
        self.interfaces
            .iter()
            .filter(|i| i.kind == "public" && i.family.eq_ignore_ascii_case("IPv4"))
            .count() as u64
    }
}

#[derive(Clone, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
pub struct DiskPlan {
    pub title: String,
    pub gib: u64,
    /// `standard` | `maxiops` | `hdd`.
    pub tier: String,
    #[serde(default)]
    pub labels: BTreeMap<String, String>,
}

/// **What was laid**, exactly as the provider reports it.
#[derive(Clone, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
pub struct LaidEstate {
    pub reference: String,
    pub company: String,
    pub zone: Zone,
    pub machines: Vec<LaidMachine>,
    /// **DEV-NARRATE's source** (lane N2, 2026-10-20): the ONE box's public IPv4. Set, every
    /// machine of the estate admits `udp/51822` (nordisk-narrate's port) FROM THIS ADDRESS ONLY,
    /// so the box that laid a test site hears its boot log; empty (the default, and always for a
    /// sold estate or a serving site) keeps it closed. Set by the shop for a site that says
    /// `[estates.<label>] narrate = true`, never by a provider.
    #[serde(default, skip_serializing_if = "String::is_empty")]
    pub narrate_from: String,
    /// **The product's shape as the shop reads it NOW** ([`EstatePlan::shape`]; OX-WGDS5): every
    /// firewall written again from this record opens each machine's declared ports
    /// ([`PlanShape::open_of`]) and no other. The shop sets it before each write (an estate laid
    /// before a product declared a port gets it on its next update); `None` opens none.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub shape: Option<PlanShape>,
}

#[derive(Clone, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
pub struct LaidMachine {
    pub role: String,
    pub hostname: String,
    pub title: String,
    pub plan: String,
    /// The provider's id for the server (an UpCloud server UUID).
    pub server: String,
    pub public_ipv4: String,
    pub utility_ipv4: String,
    pub disks: Vec<LaidDisk>,
    /// The clone of the release medium this machine boots ([`MachinePlan::medium`]); empty
    /// when none. Ejected and deleted once the estate is handed over.
    #[serde(default)]
    pub medium_clone: String,
}

#[derive(Clone, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
pub struct LaidDisk {
    pub title: String,
    /// The provider's id for the disk (an UpCloud storage UUID).
    pub id: String,
    pub gib: u64,
    pub tier: String,
    /// `virtio:0` is the system disk.
    pub address: String,
}

impl LaidEstate {
    pub fn machine(&self, role: &str) -> Option<&LaidMachine> {
        self.machines.iter().find(|m| m.role == role)
    }
}

impl LaidMachine {
    /// Every provider id the laid record names for this machine — the server first, then its
    /// disks in order, then the release clone — as laid (an UpCloud uuid is bare here).
    pub fn laid_ids(&self) -> Vec<&str> {
        let mut out = vec![self.server.as_str()];
        out.extend(self.disks.iter().map(|d| d.id.as_str()));
        out.push(self.medium_clone.as_str());
        out.retain(|i| !i.trim().is_empty());
        out
    }
}

/// **A laid id and a listed id are the same resource** when equal, or when the listed one is
/// the laid one under a kind prefix (`server/<uuid>`, `storage/<uuid>`): a provider lays with
/// its bare id and lists with its own scheme.
pub fn same_resource(listed: &str, laid: &str) -> bool {
    !laid.is_empty()
        && (listed == laid
            || listed
                .strip_suffix(laid)
                .is_some_and(|p| p.ends_with('/') && !p[..p.len() - 1].contains('/')))
}

/// The spend cap's key for a lay: `<reference>` for an estate, `<reference>#<role>` for a
/// relay ([`RELAY_LABEL`]) — so a relay never settles over what its estate's lay committed,
/// and the fence of that box gives back exactly its own month — and `<reference>#<change>` for
/// the machines a plan change adds, every one marked [`ADDED_LABEL`] `= <change>`.
pub fn spend_key(plan: &EstatePlan) -> String {
    let added: Vec<&String> = plan
        .machines
        .iter()
        .filter_map(|m| m.labels.get(ADDED_LABEL))
        .collect();
    if !added.is_empty()
        && added.len() == plan.machines.len()
        && added.iter().all(|a| *a == added[0] && !a.trim().is_empty())
    {
        return relay_key(&plan.reference, added[0]);
    }
    match plan.machines.as_slice() {
        [m] => match m.labels.get(RELAY_LABEL) {
            Some(role) => relay_key(&plan.reference, role),
            None => plan.reference.clone(),
        },
        _ => plan.reference.clone(),
    }
}

/// `<reference>#<role>`: the relay of `role` in the estate's spend.
pub fn relay_key(reference: &str, role: &str) -> String {
    format!("{reference}#{role}")
}

impl EstatePlan {
    /// **Refuse a plan that is not an estate**, before anything is bought:
    /// a bad company name, a reference that is not `estate/<company>`, not the
    /// product's own shape when the plan names one ([`EstatePlan::shape`],
    /// [`PlanShape::refusal`]); without one, not
    /// exactly one front, one main and one twin (or ONE main or twin marked
    /// [`RELAY_LABEL`] — a relay; or, for a product guest's own lifecycle that names none of
    /// those roles, each of its own roles once), a machine with no system
    /// disk, a disk of 0 GiB, or two resources with one title (the title is
    /// what an adopting retry matches on).
    pub fn check(&self) -> Result<(), CloudError> {
        if let Some(why) = company_name_refusal(&self.company) {
            return Err(CloudError::Refused(why));
        }
        if self.reference != reference_of(&self.company) {
            return Err(CloudError::Refused(format!(
                "the estate's reference must be {:?}, not {:?}: it is the label every resource carries",
                reference_of(&self.company),
                self.reference
            )));
        }
        // **The product's own shape** (A4-FACTS), when the shop named it: its roles, never the
        // three machines below.
        if let Some(shape) = &self.shape {
            if let Some(why) = shape.refusal(self) {
                return Err(CloudError::Refused(why));
            }
            let mut roles: Vec<&str> = self.machines.iter().map(|m| m.role.as_str()).collect();
            roles.sort_unstable();
            if let Some(w) = roles.windows(2).find(|w| w[0] == w[1]) {
                return Err(CloudError::Refused(format!(
                    "the role {:?} is laid twice; an estate lays each of its roles once",
                    w[0]
                )));
            }
            return self.check_machines();
        }
        let marks: Vec<(&str, &str)> = self
            .machines
            .iter()
            .filter_map(|m| {
                m.labels
                    .get(RELAY_LABEL)
                    .map(|r| (m.role.as_str(), r.as_str()))
            })
            .collect();
        if !marks.is_empty() {
            let ok = self.machines.len() == 1
                && marks.len() == 1
                && marks[0].0 == marks[0].1
                && RELAY_ROLES.contains(&marks[0].0);
            if !ok {
                return Err(CloudError::Refused(format!("a relay lays ONE main or twin marked relay=<its role>; this plan marks {marks:?} over {} machine(s)", self.machines.len())));
            }
        }
        let mut roles: Vec<&str> = self.machines.iter().map(|m| m.role.as_str()).collect();
        roles.sort_unstable();
        let mut want = ROLES.to_vec();
        want.sort_unstable();
        // The relay roles ([`RELAY_ROLES`], the ones the guardian lays again) come as the core's
        // three-machine shape: a plan that names a main or a twin is exactly one front, one main
        // and one twin ([`ROLES`]), so a relay that lost its mark is never laid as an estate.
        // Any other plan names the product's own roles (A17; the Lake's `front`, `ingestor`,
        // `be`, `fe`, `graph`, with `be-2`, … for a role laid n times): each is laid once.
        let relay_shaped = roles.iter().any(|r| RELAY_ROLES.contains(r));
        if marks.is_empty() && relay_shaped && roles != want {
            return Err(CloudError::Refused(format!(
                "an estate is exactly one front, one main and one twin; this plan has {roles:?}"
            )));
        }
        if marks.is_empty() && !relay_shaped {
            if roles.is_empty() {
                return Err(CloudError::Refused(
                    "an estate has at least one machine; this plan has none".into(),
                ));
            }
            if let Some(w) = roles.windows(2).find(|w| w[0] == w[1]) {
                return Err(CloudError::Refused(format!(
                    "the role {:?} is laid twice; an estate lays each of its roles once",
                    w[0]
                )));
            }
        }
        self.check_machines()
    }

    /// Every machine has a system disk, a plan, a hostname and a title; no disk is 0 GiB; no two
    /// resources share a title.
    fn check_machines(&self) -> Result<(), CloudError> {
        let mut titles = std::collections::BTreeSet::new();
        for m in &self.machines {
            if m.disks.is_empty() {
                return Err(CloudError::Refused(format!(
                    "{} has no system disk",
                    m.role
                )));
            }
            if m.plan.is_empty() || m.hostname.is_empty() || m.title.is_empty() {
                return Err(CloudError::Refused(format!(
                    "{} needs a plan, a hostname and a title",
                    m.role
                )));
            }
            if !titles.insert(m.title.clone()) {
                return Err(CloudError::Refused(format!(
                    "two resources are titled {:?}",
                    m.title
                )));
            }
            for d in &m.disks {
                if d.gib == 0 {
                    return Err(CloudError::Refused(format!(
                        "{}: the disk {:?} is 0 GiB",
                        m.role, d.title
                    )));
                }
                if !titles.insert(d.title.clone()) {
                    return Err(CloudError::Refused(format!(
                        "two resources are titled {:?}",
                        d.title
                    )));
                }
            }
        }
        Ok(())
    }

    /// Every disk of the plan, with its machine.
    pub fn disks(&self) -> impl Iterator<Item = (&MachinePlan, usize, &DiskPlan)> {
        self.machines
            .iter()
            .flat_map(|m| m.disks.iter().enumerate().map(move |(i, d)| (m, i, d)))
    }
}

/// The order's reference for a company: `estate/<company>`.
pub fn reference_of(company: &str) -> String {
    format!("estate/{company}")
}

/// Names no company may take: the serving estate's own names, the ONE `monetize` (OM-12) and
/// the test estates'. A product's own name is the shop's to refuse, from the products it links
/// (monetize-server `estate_order::product_name_refusal`, MONETIZE-FIX): the cloud names none.
pub const RESERVED: [&str; 4] = ["www", "git", "monetize", "blue"];

/// **Why `name` cannot be a company's estate name**, or `None` when it can.
///
/// The kit's instance rule (`private-gunnar-ops/xtask/src/instance.rs`), so
/// an order monetize accepts is an instance the kit can install: lowercase
/// letters and digits, starting with a letter, at most 16 characters; not a
/// name the serving estate uses, and not `test<N>` (the test estates').
pub fn company_name_refusal(name: &str) -> Option<String> {
    if name.is_empty() || name.len() > 16 {
        return Some(format!(
            "a company name is 1 to 16 characters; {name:?} is {}",
            name.len()
        ));
    }
    if !name.starts_with(|c: char| c.is_ascii_lowercase()) {
        return Some(format!(
            "a company name starts with a lowercase letter: {name:?}"
        ));
    }
    if !name
        .chars()
        .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit())
    {
        return Some(format!(
            "a company name is lowercase letters and digits only: {name:?}"
        ));
    }
    if RESERVED.contains(&name) {
        return Some(format!("{name:?} is reserved"));
    }
    if name
        .strip_prefix("test")
        .is_some_and(|rest| rest.chars().all(|c| c.is_ascii_digit()))
    {
        return Some(format!("{name:?} is reserved for the test estates"));
    }
    None
}

/// **One plan the provider sells**, as the provider describes it: what a
/// buyer sizes a machine from (cores, memory) and what it costs a month. Read
/// from the provider's own list (`Cloud::catalog`),
/// never from a constant.
#[derive(Clone, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
pub struct PlanSpec {
    pub name: String,
    pub cores: u32,
    pub memory_mib: u64,
    /// What the server alone costs a month at this plan (disks apart).
    pub price: crate::Price,
}

/// **The twin's size follows main's** (owner, 2026-10-04): the buyer never
/// sizes the twin. Its CPU and memory are an eighth of main's, never below
/// one core and 4 GiB (main 64 cores → twin 8 cores); the smallest plan in
/// the provider's list that meets both is chosen. `None`: no plan does.
///
/// **The floor is 4 GiB** (owner, 2026-10-13; it was 2): on a 2 GB twin
/// gunnar-server itself warns "this box cannot afford the handle-cache floor
/// … that is the 2 GB box's collapse" (`gunnar::limits`). It is the floor on
/// every size, for every NEW choice of a twin; a twin already laid smaller
/// keeps its plan until its estate's plan is changed.
pub const TWIN_FRACTION: u32 = 8;
pub const TWIN_FLOOR_CORES: u32 = 1;
pub const TWIN_FLOOR_MEMORY_MIB: u64 = 4096;

pub fn twin_plan_for<'a>(catalog: &'a [PlanSpec], main: &PlanSpec) -> Option<&'a PlanSpec> {
    twin_plan_floored(catalog, main, TWIN_FLOOR_MEMORY_MIB)
}

/// [`twin_plan_for`] over a product's OWN memory floor (owner 2026-10-23, the lean Holger
/// estate: holger-server's twin runs in 1 GB; the 4 GiB floor is gunnar-server's). The eighth of
/// main and the one-core floor are everyone's.
pub fn twin_plan_floored<'a>(
    catalog: &'a [PlanSpec],
    main: &PlanSpec,
    floor_memory_mib: u64,
) -> Option<&'a PlanSpec> {
    let cores = main.cores.div_ceil(TWIN_FRACTION).max(TWIN_FLOOR_CORES);
    let memory = (main.memory_mib / TWIN_FRACTION as u64).max(floor_memory_mib);
    catalog
        .iter()
        .filter(|p| p.cores >= cores && p.memory_mib >= memory)
        .min_by_key(|p| {
            (
                p.cores,
                p.memory_mib,
                p.price.minor_per_month,
                p.name.clone(),
            )
        })
}

/// The refusal every default answers with: this provider cannot lay an estate.
pub fn unsupported(provider: &str) -> CloudError {
    CloudError::Refused(format!(
        "the {provider} cloud cannot lay an estate: it has no `lay_estate` implementation. Nothing was bought"
    ))
}

/// **One laid machine upgraded IN PLACE** (F4, 2026-10-09): booted once from a clone of the
/// release's KEEP medium (`korp.reinstall=keep`, `install_then=poweroff`): the ESP and rootfs
/// are rewritten, `/data` is kept byte for byte, and the box powers off; then it boots its disk
/// again (`Boot::Keep`). The medium is a storage id, or the object the
/// provider imports it from.
#[derive(Clone, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
pub struct KeepBoot {
    /// The estate's reference (`estate/<company>`): the clone is labelled with it.
    pub reference: String,
    pub zone: Zone,
    pub machine: LaidMachine,
    #[serde(default)]
    pub medium: String,
    #[serde(default)]
    pub medium_object: Option<MediaObject>,
}

/// **A sold estate's lost FRONT restored from its provider backup** (S8, 2026-10-11;
/// `Boot::Restore`). The front was laid with the provider's own
/// daily backup of its one disk (S6, `simple_backup "0300,dailies"`); its disk holds the
/// estate's own keys, so a restore — unlike a re-lay from the release medium — needs nothing
/// trusted again. `machine` is the front AS LAID (its server, its disk); `plan` is the run's
/// front plan, what a server made again is made from when the old one is gone.
#[derive(Clone, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
pub struct RestoreFromBackup {
    /// The estate's reference (`estate/<company>`): what a disk or server made again is
    /// labelled with.
    pub reference: String,
    pub zone: Zone,
    pub machine: LaidMachine,
    pub plan: MachinePlan,
    /// [`EstatePlan::narrate_from`]: the estate's firewall written again after a restore keeps
    /// DEV-NARRATE open from this address (empty: closed).
    #[serde(default, skip_serializing_if = "String::is_empty")]
    pub narrate_from: String,
    /// [`LaidEstate::shape`]: the firewall written again after a restore opens the ports the
    /// product declares (`None`: none).
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub shape: Option<PlanShape>,
}

/// What `Boot::Restore` did.
#[derive(Clone, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
pub struct Restored {
    /// The machine as it stands now.
    pub machine: LaidMachine,
    /// The backup's provider id.
    pub backup: String,
    /// When the backup was taken (Unix ms; `0` when the provider did not say).
    pub backup_created_ms: u64,
    /// `true`: the server or its disk was MADE AGAIN (a new server, new addresses — DNS and
    /// the estate's firewall follow); `false`: restored in place, the same server and disk.
    pub moved: bool,
}

/// The refusal of an estate verb a provider does not implement.
pub fn unsupported_verb(provider: &str, verb: &str) -> CloudError {
    CloudError::Refused(format!(
        "the {provider} cloud has no `{verb}`: nothing was changed"
    ))
}

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

    /// ★ **Only the clones the journal recorded are ever given back** (no-dwarf step 4,
    /// 2026-10-08): a confirmed clone is listed until a confirmed give-back of the same id; an
    /// intent with no id, a failed clone and a storage the journal never named are not.
    #[test]
    fn only_the_journaled_clones_are_given_back() {
        use crate::grow::{GrowRecord, Mark};
        let rec = |step: &str, mark: Mark, title: &str, id: &str| {
            let mut r = GrowRecord::set_wide(1, 1, step, mark, id.to_string());
            r.volume = title.to_string();
            r
        };
        let front = clone_title("acme-front");
        assert!(front.ends_with(CLONE_SUFFIX));
        let records = vec![
            rec(step::MEDIUM, Mark::Intent, &front, "01-release-front"),
            rec(step::MEDIUM, Mark::Confirmed, &front, "c-front"),
            rec(
                step::MEDIUM,
                Mark::Intent,
                "acme-main-release-clone",
                "01-release-gunnar",
            ),
            rec(
                step::MEDIUM,
                Mark::Failed,
                "acme-main-release-clone",
                "QUOTA",
            ),
            rec(
                step::MEDIUM,
                Mark::Confirmed,
                "acme-twin-release-clone",
                "c-twin",
            ),
            rec(
                step::MEDIUM,
                Mark::Confirmed,
                "acme-twin-release-clone",
                "c-twin",
            ),
        ];
        assert_eq!(
            journaled_clones(&records),
            vec![
                (front.clone(), "c-front".to_string()),
                ("acme-twin-release-clone".to_string(), "c-twin".to_string())
            ]
        );
        let mut after = records.clone();
        after.push(rec(step::MEDIUM_GONE, Mark::Intent, &front, "c-front"));
        assert_eq!(
            journaled_clones(&after).len(),
            2,
            "an intent to give back is not a give-back"
        );
        after.push(rec(step::MEDIUM_GONE, Mark::Confirmed, &front, "c-front"));
        assert_eq!(
            journaled_clones(&after),
            vec![("acme-twin-release-clone".to_string(), "c-twin".to_string())]
        );
    }

    /// ★ **The machines a plan change adds are their own spend** (P3-FACTS): a plan of machines
    /// every one marked [`ADDED_LABEL`] `= change-1` is committed under `<reference>#change-1`,
    /// never under the estate's own key (its lay's month); a plan that marks only some, or marks
    /// them with two changes, is the estate's (and its check refuses nothing new).
    #[test]
    fn the_machines_a_plan_change_adds_are_spent_under_the_change_never_over_the_estate() {
        let mut p = plan("acme");
        assert_eq!(spend_key(&p), "estate/acme");
        p.machines.retain(|m| m.role == "front");
        p.machines[0].role = "be-2".into();
        p.machines[0]
            .labels
            .insert(ADDED_LABEL.into(), "change-1".into());
        assert_eq!(spend_key(&p), "estate/acme#change-1");
        assert!(p.check().is_ok(), "{:?}", p.check());
        let mut two = p.clone();
        let mut more = two.machines[0].clone();
        (more.role, more.title, more.hostname) =
            ("be-3".into(), "be-3 acme".into(), "be-3-acme".into());
        more.disks[0].title = "be-3 system".into();
        two.machines.push(more.clone());
        assert_eq!(
            spend_key(&two),
            "estate/acme#change-1",
            "every machine of the one change"
        );
        two.machines[1]
            .labels
            .insert(ADDED_LABEL.into(), "change-2".into());
        assert_eq!(
            spend_key(&two),
            "estate/acme",
            "two changes' marks: not one change's spend"
        );
        two.machines[1].labels.remove(ADDED_LABEL);
        assert_eq!(
            spend_key(&two),
            "estate/acme",
            "a machine unmarked: the estate's"
        );
    }

    pub(crate) fn plan(company: &str) -> EstatePlan {
        let m = |role: &str, disks: Vec<(&str, u64)>| MachinePlan {
            role: role.into(),
            hostname: format!("gunnar-{role}-{company}"),
            title: format!("gunnar {role}-{company}"),
            plan: "STARTER-1xCPU-2GB".into(),
            boot_order: "disk,cdrom".into(),
            labels: BTreeMap::from([("role".to_string(), role.to_string())]),
            interfaces: default_interfaces(),
            medium: String::new(),
            medium_object: None,
            template: String::new(),
            user_data: UserData::default(),
            disks: disks
                .into_iter()
                .map(|(t, gib)| DiskPlan {
                    title: format!("gunnar-{role}-{company} {t}"),
                    gib,
                    tier: "standard".into(),
                    labels: BTreeMap::new(),
                })
                .collect(),
        };
        EstatePlan {
            reference: reference_of(company),
            company: company.into(),
            zone: Zone("se-sto1".into()),
            machines: vec![
                m("front", vec![("system", 10)]),
                m("main", vec![("system", 10), ("data 1", 10)]),
                m("twin", vec![("system", 10), ("data", 20)]),
            ],
            narrate_from: String::new(),
            shape: None,
        }
    }

    #[test]
    fn a_company_name_is_the_kits_instance_rule() {
        for ok in ["acme", "a", "acme2", "abcdefghijklmnop"] {
            assert_eq!(company_name_refusal(ok), None, "{ok}");
        }
        for bad in [
            "",
            "Acme",
            "2acme",
            "acme-corp",
            "acme.rs",
            "abcdefghijklmnopq",
            "www",
            "git",
            "monetize",
            "test2",
            "test",
            "blue",
        ] {
            assert!(company_name_refusal(bad).is_some(), "{bad} must be refused");
        }
    }

    /// OM-12 (ONE monetize): the core's own names are never sold as a company (a product's own
    /// name is the shop's to refuse, from what it links: monetize-server `product_name_refusal`).
    #[test]
    fn the_cores_own_names_are_not_for_sale() {
        for name in ["monetize", "www", "git"] {
            assert!(
                company_name_refusal(name).is_some(),
                "{name} is the core's own name and must be refused"
            );
        }
    }

    /// **A product guest's own estate is laid by its own roles** (A17, the echo bench): a
    /// lifecycle that names its own roles (`box`) is an estate when each role is laid once. A
    /// plan that names a relay role (main or twin) is still exactly front, main and twin.
    #[test]
    fn a_product_guests_own_roles_are_an_estate_when_each_is_laid_once() {
        let mut p = plan("acme");
        p.machines.truncate(1);
        p.machines[0].role = "box".into();
        assert!(p.check().is_ok(), "{:?}", p.check());
        let mut two = p.clone();
        two.machines.push(two.machines[0].clone());
        two.machines[1].title = "another".into();
        for d in two.machines[1].disks.iter_mut() {
            d.title = format!("{} 2", d.title);
        }
        assert!(
            matches!(two.check(), Err(CloudError::Refused(w)) if w.contains("twice")),
            "a role laid twice is not an estate"
        );
        let mut mixed = plan("acme");
        mixed.machines[2].role = "box".into();
        assert!(
            matches!(mixed.check(), Err(CloudError::Refused(w)) if w.contains("one front, one main and one twin")),
            "the relay roles stay exactly three"
        );
        let mut none = plan("acme");
        none.machines.clear();
        assert!(
            matches!(none.check(), Err(CloudError::Refused(_))),
            "no machine is no estate"
        );
    }

    /// **A relay lays ONE gunnar box of an estate already laid, again** (5.4, the guardian): the
    /// one machine carries `relay=<its role>`; a front, two machines, or an unmarked one-machine
    /// plan is still not an estate.
    #[test]
    fn a_relay_plan_is_one_main_or_twin_marked_as_a_relay() {
        let one = |role: &str, mark: Option<&str>| {
            let mut p = plan("acme");
            p.machines.retain(|m| m.role == role);
            if let Some(r) = mark {
                p.machines[0].labels.insert(RELAY_LABEL.into(), r.into());
            }
            p
        };
        assert!(one("twin", Some("twin")).check().is_ok());
        assert!(one("main", Some("main")).check().is_ok());
        assert!(
            matches!(one("twin", None).check(), Err(CloudError::Refused(w)) if w.contains("one twin")),
            "unmarked: not an estate"
        );
        assert!(
            matches!(one("front", Some("front")).check(), Err(CloudError::Refused(w)) if w.contains("relay")),
            "the front is never laid again"
        );
        assert!(
            matches!(
                one("twin", Some("main")).check(),
                Err(CloudError::Refused(_))
            ),
            "the mark names the machine's own role"
        );
        let mut two = plan("acme");
        two.machines.retain(|m| m.role != "front");
        two.machines[0]
            .labels
            .insert(RELAY_LABEL.into(), "main".into());
        assert!(
            matches!(two.check(), Err(CloudError::Refused(_))),
            "a relay is one box"
        );
        let mut whole = plan("acme");
        whole.machines[2]
            .labels
            .insert(RELAY_LABEL.into(), "twin".into());
        assert!(
            matches!(whole.check(), Err(CloudError::Refused(w)) if w.contains("relay")),
            "a whole estate carries no relay mark"
        );
    }

    #[test]
    fn a_plan_is_exactly_front_main_and_twin_each_with_a_system_disk() {
        assert!(plan("acme").check().is_ok());
        let mut p = plan("acme");
        p.machines.pop();
        assert!(matches!(p.check(), Err(CloudError::Refused(w)) if w.contains("one twin")));
        let mut p = plan("acme");
        p.machines[1].disks.clear();
        assert!(matches!(p.check(), Err(CloudError::Refused(w)) if w.contains("no system disk")));
        let mut p = plan("acme");
        p.machines[2].disks[1].gib = 0;
        assert!(matches!(p.check(), Err(CloudError::Refused(w)) if w.contains("0 GiB")));
        let mut p = plan("acme");
        p.reference = "estate/other".into();
        assert!(matches!(p.check(), Err(CloudError::Refused(w)) if w.contains("label")));
        let mut p = plan("acme");
        p.machines[2].disks[1].title = p.machines[1].disks[1].title.clone();
        assert!(matches!(p.check(), Err(CloudError::Refused(w)) if w.contains("two resources")));
        // A product's own roles beside a front (the Lake): laid; a role laid twice under one
        // name is refused; a plan with a main or a twin is Gunnar's three, as ever.
        let lake = |roles: &[&str]| {
            let mut p = plan("acme");
            let m = p.machines[0].clone();
            p.machines = roles
                .iter()
                .map(|r| MachinePlan {
                    role: r.to_string(),
                    hostname: format!("lake-{r}-acme"),
                    title: format!("Lake {r} acme"),
                    disks: vec![DiskPlan {
                        title: format!("lake-{r}-acme system"),
                        gib: 20,
                        tier: "standard".into(),
                        labels: Default::default(),
                    }],
                    ..m.clone()
                })
                .collect();
            p
        };
        assert!(
            lake(&["front", "ingestor", "be", "be-2", "fe"])
                .check()
                .is_ok(),
            "{:?}",
            lake(&["front", "ingestor", "be", "be-2", "fe"]).check()
        );
        assert!(
            matches!(lake(&["front", "be", "be"]).check(), Err(CloudError::Refused(w)) if w.contains("laid twice"))
        );
        assert!(
            matches!(lake(&["front", "main", "be"]).check(), Err(CloudError::Refused(w)) if w.contains("one twin"))
        );
    }

    /// ★ **A plan that carries its product's shape is held to THAT shape** (A4-FACTS): the
    /// roles its facts name, one of them optional (a Gunnar-shaped estate whose twin may be left
    /// out), another laid n times; never Gunnar's three. A twin laid beside a standing estate by a
    /// plan change (`added=change-1`) is an estate's machine of this shape too. Without a shape,
    /// the rule before (exactly a front, a main and a twin) stands.
    #[test]
    fn a_shaped_plan_is_held_to_the_products_roles_not_gunnars_three() {
        let shape = PlanShape {
            roles: vec![
                ShapeRole {
                    role: "front".into(),
                    ..ShapeRole::default()
                },
                ShapeRole {
                    role: "main".into(),
                    ..ShapeRole::default()
                },
                ShapeRole {
                    role: "twin".into(),
                    optional: true,
                    ..ShapeRole::default()
                },
                ShapeRole {
                    role: "node".into(),
                    many: true,
                    optional: true,
                    ..ShapeRole::default()
                },
            ],
        };
        let shaped = |keep: &[&str]| {
            let mut p = plan("acme");
            p.machines.retain(|m| keep.contains(&m.role.as_str()));
            p.shape = Some(shape.clone());
            p
        };
        let mut twinless = plan("acme");
        twinless.machines.retain(|m| m.role != "twin");
        assert!(
            matches!(twinless.check(), Err(CloudError::Refused(w)) if w.contains("one twin")),
            "no shape: Gunnar's three, as before"
        );
        assert!(
            shaped(&["front", "main"]).check().is_ok(),
            "the twin is optional: {:?}",
            shaped(&["front", "main"]).check()
        );
        assert!(shaped(&["front", "main", "twin"]).check().is_ok());
        assert!(
            matches!(shaped(&["front", "twin"]).check(), Err(CloudError::Refused(w)) if w.contains("this plan has no main")),
            "main is not optional"
        );
        // A node laid n times; a role the product does not name is refused by name.
        let mut nodes = shaped(&["front", "main"]);
        for r in ["node", "node-2"] {
            let mut m = nodes.machines[1].clone();
            m.role = r.into();
            m.title = format!("{r} acme");
            m.disks
                .iter_mut()
                .for_each(|d| d.title = format!("{r} {}", d.title));
            nodes.machines.push(m);
        }
        assert!(nodes.check().is_ok(), "{:?}", nodes.check());
        let mut odd = nodes.clone();
        odd.machines[3].role = "gate".into();
        assert!(
            matches!(odd.check(), Err(CloudError::Refused(w)) if w.contains("\"gate\", which is not a role of this product (front, main, twin, node)")),
            "{:?}",
            odd.check()
        );
        let mut twice = nodes.clone();
        twice.machines[3].role = "node".into();
        assert!(matches!(twice.check(), Err(CloudError::Refused(w)) if w.contains("laid twice")));
        // The twin laid beside a standing estate: one machine, marked with the one change.
        let mut add = shaped(&["twin"]);
        add.machines[0]
            .labels
            .insert(ADDED_LABEL.into(), "change-1".into());
        assert!(add.check().is_ok(), "{:?}", add.check());
        let mut half = shaped(&["main", "twin"]);
        half.machines[1]
            .labels
            .insert(ADDED_LABEL.into(), "change-1".into());
        assert!(
            matches!(half.check(), Err(CloudError::Refused(w)) if w.contains("marks every one added")),
            "{:?}",
            half.check()
        );
        assert!(
            matches!(shaped(&["twin"]).check(), Err(CloudError::Refused(w)) if w.contains("no front, main")),
            "an unmarked twin alone is no estate"
        );
        // A relay of the shape: ONE machine marked with its own role.
        let mut relay = shaped(&["main"]);
        relay.machines[0]
            .labels
            .insert(RELAY_LABEL.into(), "main".into());
        assert!(relay.check().is_ok());
        relay.machines[0]
            .labels
            .insert(RELAY_LABEL.into(), "twin".into());
        assert!(matches!(relay.check(), Err(CloudError::Refused(w)) if w.contains("relay")));
        // On the wire: absent when unnamed (an older provider reads the plan as before).
        assert!(serde_json::to_value(plan("acme"))
            .unwrap()
            .get("shape")
            .is_none());
        let wire = serde_json::to_value(shaped(&["front", "main"])).unwrap();
        assert_eq!(
            wire["shape"]["roles"][2],
            serde_json::json!({"role": "twin", "many": false, "optional": true})
        );
        assert_eq!(
            serde_json::from_value::<EstatePlan>(wire).unwrap(),
            shaped(&["front", "main"])
        );
    }

    fn spec(name: &str, cores: u32, gib: u64) -> PlanSpec {
        PlanSpec {
            name: name.into(),
            cores,
            memory_mib: gib * 1024,
            price: crate::Price {
                minor_per_month: cores as u64 * 1000,
                currency: monetize_pay::Currency::Eur,
            },
        }
    }

    #[test]
    fn the_twin_is_an_eighth_of_main_never_below_one_core_and_4_gib() {
        let catalog = vec![
            spec("1x2", 1, 2),
            spec("1x4", 1, 4),
            spec("2x4", 2, 4),
            spec("4x8", 4, 8),
            spec("8x32", 8, 32),
            spec("16x64", 16, 64),
            spec("64x256", 64, 256),
        ];
        let twin = |main: &str| {
            twin_plan_for(&catalog, catalog.iter().find(|p| p.name == main).unwrap())
                .map(|p| p.name.clone())
        };
        assert_eq!(
            twin("64x256").as_deref(),
            Some("8x32"),
            "main 64 cores → twin 8 cores"
        );
        assert_eq!(
            twin("16x64").as_deref(),
            Some("4x8"),
            "at least 2 cores AND 8 GiB: the 2x4 has too little memory"
        );
        assert_eq!(
            twin("4x8").as_deref(),
            Some("1x4"),
            "the floor: one core, 4 GiB"
        );
        assert_eq!(
            twin("1x2").as_deref(),
            Some("1x4"),
            "the floor holds under the smallest main too"
        );
        assert_eq!(
            twin_plan_for(&catalog[..3], &spec("64x256", 64, 256)),
            None,
            "no plan is big enough: refused, never a smaller twin"
        );
    }

    /// ★ **No twin under 4 GiB** (owner, 2026-10-13): on a 2 GB box gunnar-server itself says
    /// "this box cannot afford the handle-cache floor … that is the 2 GB box's collapse"
    /// (`gunnar::limits`), so 4 GiB is the twin's floor whatever main is — the smallest main
    /// gets the one-core 4 GiB plan, never the 2 GiB one the provider also sells.
    #[test]
    fn no_twin_is_ever_laid_under_4_gib() {
        let catalog = vec![
            spec("1x1", 1, 1),
            spec("1x2", 1, 2),
            spec("1x4", 1, 4),
            spec("2x4", 2, 4),
            spec("4x8", 4, 8),
            spec("4x16", 4, 16),
            spec("8x32", 8, 32),
            spec("16x64", 16, 64),
            spec("64x256", 64, 256),
        ];
        for main in &catalog {
            let twin =
                twin_plan_for(&catalog, main).unwrap_or_else(|| panic!("a twin for {}", main.name));
            assert!(
                twin.memory_mib >= 4 * 1024,
                "the twin of a {} main is {} ({} MiB): under the 4 GiB floor",
                main.name,
                twin.name,
                twin.memory_mib
            );
        }
        let twin = |main: &str| {
            twin_plan_for(&catalog, catalog.iter().find(|p| p.name == main).unwrap())
                .map(|p| p.name.clone())
        };
        assert_eq!(
            twin("4x8").as_deref(),
            Some("1x4"),
            "Mini's main: one core is enough, 4 GiB is the floor"
        );
        assert_eq!(twin("4x16").as_deref(), Some("1x4"));
        assert_eq!(
            twin("64x256").as_deref(),
            Some("8x32"),
            "a big main's twin is still an eighth of it"
        );
        assert_eq!(TWIN_FLOOR_MEMORY_MIB, 4 * 1024);
        // A provider that sells nothing with 4 GiB is refused: never a 2 GiB twin instead.
        assert_eq!(twin_plan_for(&catalog[..2], &spec("4x8", 4, 8)), None);
    }

    /// ★ **A product names its own twin floor** (owner 2026-10-23, the lean Holger estate): the
    /// 4 GiB floor is Gunnar's (gunnar-server's handle cache); Holger's twin is a 1-core 1 GB
    /// box under a 1-core 2 GB main. The floor is the product's; the eighth of main is everyone's.
    #[test]
    fn a_product_names_its_own_twin_floor() {
        let catalog = vec![
            spec("1x1", 1, 1),
            spec("1x2", 1, 2),
            spec("1x4", 1, 4),
            spec("2x4", 2, 4),
            spec("4x8", 4, 8),
            spec("8x32", 8, 32),
            spec("16x64", 16, 64),
        ];
        let twin = |main: &str, floor: u64| {
            twin_plan_floored(
                &catalog,
                catalog.iter().find(|p| p.name == main).unwrap(),
                floor,
            )
            .map(|p| p.name.clone())
        };
        assert_eq!(
            twin("1x2", 1024).as_deref(),
            Some("1x1"),
            "Holger's Mini: a 1 GB twin under a 2 GB main"
        );
        assert_eq!(
            twin("16x64", 1024).as_deref(),
            Some("4x8"),
            "an eighth of main still holds over a low floor"
        );
        assert_eq!(
            twin("1x2", TWIN_FLOOR_MEMORY_MIB).as_deref(),
            Some("1x4"),
            "Gunnar's floor: unchanged"
        );
        for main in &catalog {
            assert_eq!(
                twin_plan_floored(&catalog, main, TWIN_FLOOR_MEMORY_MIB),
                twin_plan_for(&catalog, main),
                "twin_plan_for is Gunnar's floor"
            );
        }
    }

    #[test]
    fn the_disks_walk_every_machine_in_order() {
        let p = plan("acme");
        let walked: Vec<(String, usize)> = p.disks().map(|(m, i, _)| (m.role.clone(), i)).collect();
        assert_eq!(
            walked,
            vec![
                ("front".into(), 0),
                ("main".into(), 0),
                ("main".into(), 1),
                ("twin".into(), 0),
                ("twin".into(), 1)
            ]
        );
    }

    /// ★ **A machine opens what its role declares, and nothing else** (OX-WGDS5): the front of a
    /// product that declares udp/51830 opens it, its other roles and a product that declares
    /// nothing open none; a numbered member opens its many-role's ports.
    #[test]
    fn a_machine_opens_the_ports_its_role_declares() {
        let wg = OpenPort {
            protocol: "udp".into(),
            port: 51830,
            why: "a downstream's handshake".into(),
        };
        let role = |r: &str, open: Vec<OpenPort>| ShapeRole {
            role: r.into(),
            open,
            ..ShapeRole::default()
        };
        let declares = PlanShape {
            roles: vec![
                role("front", vec![wg.clone()]),
                role("main", vec![]),
                role("twin", vec![]),
            ],
        };
        assert_eq!(declares.open_of("front"), vec![wg.clone()]);
        assert!(
            declares.open_of("main").is_empty()
                && declares.open_of("twin").is_empty()
                && declares.open_of("nope").is_empty()
        );
        let plain = PlanShape {
            roles: vec![
                role("front", vec![]),
                role("main", vec![]),
                role("twin", vec![]),
            ],
        };
        assert!(
            plain.open_of("front").is_empty(),
            "declared nothing, opens nothing"
        );
        let many = PlanShape {
            roles: vec![ShapeRole {
                many: true,
                ..role("be", vec![wg.clone()])
            }],
        };
        assert_eq!(many.open_of("be-2"), vec![wg.clone()]);
        // The wire: an older shop's shape (no `open`) reads as none, and none is not written.
        let old: ShapeRole = serde_json::from_str(r#"{"role":"front"}"#).unwrap();
        assert!(old.open.is_empty());
        assert_eq!(
            serde_json::to_string(&old).unwrap(),
            r#"{"role":"front","many":false,"optional":false}"#
        );
    }
}