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/tests/release_for.rs

//! **`Give::References`: the tenant's whole footprint, given back.**
//!
//! `monetize::transaction::release_tenant` walks the LEDGER and destroys what
//! it finds. That is the right instrument for a teardown and it is blind to the
//! one class of resource that costs the most: an **orphan** — a disk the
//! provider still bills for that the ledger has no row for.
//! `monetize::inventory` names them and says why they matter:
//!
//! > an orphan — the cloud has it, the ledger does not — is money paid for
//! > something we forgot. It bills forever and nobody notices; found by hand
//! > with curl on 2026-09-05.
//!
//! A "super purge" that returned only what the ledger remembered would leave
//! exactly those behind, and they are the resources most likely to be there,
//! because a run that crashed between buying and recording is what makes one.
//!
//! # Why this is a trait method and not a fold in `monetize`
//!
//! Because the answer to *what belongs to this tenant here* is the provider's
//! and nobody else's. It is read off the `monetize_ref` label the provider
//! itself stamps, out of an account listing only the provider can take, and the
//! rows that must be spared — the box's own dataset, and anything with no label
//! at all — are distinctions only the provider draws. monetize core knows the
//! tenant's reference strings and nothing else about the account.
//!
//! The default implementation is written in terms of `inventory` and `destroy`,
//! both of which every provider already has to implement, so no provider is
//! forced to write anything and every one of them inherits its own `destroy` —
//! UpCloud's detach-then-delete, the sandbox's state file, the pool ledger each
//! of them forgets keys in. A provider that can do better in one API call
//! overrides it.
//!
//! **A default here is not the silence `Product::can_absorb` refuses to have.**
//! That method has none because every constant it could return is a lie about
//! capacity. This one's default is an exact implementation of the operation,
//! built from two required methods; the dangerous default would be `Ok(no rows)`,
//! and that is the one thing it cannot answer, because it asks the provider.

use monetize_cloud::{
    Cloud, CloudError, Ensured, Inventory, Owned, OwnedKind, Price, Resource, ResourceId,
    ResourceStatus, Zone,
};
use monetize_pay::Currency;
use std::sync::Mutex;

/// A provider holding a fixed account listing, recording every destroy.
struct Account {
    owned: Vec<Owned>,
    destroyed: Mutex<Vec<String>>,
    /// Ids whose destroy fails, by id.
    refuse: Vec<String>,
}

impl Account {
    fn new(owned: Vec<Owned>) -> Self {
        Account {
            owned,
            destroyed: Mutex::new(Vec::new()),
            refuse: Vec::new(),
        }
    }

    fn refusing(mut self, id: &str) -> Self {
        self.refuse.push(id.to_owned());
        self
    }

    fn destroyed(&self) -> Vec<String> {
        self.destroyed.lock().unwrap().clone()
    }
}

fn owned(id: &str, kind: OwnedKind, reference: Option<&str>) -> Owned {
    Owned {
        id: ResourceId {
            provider: "fake".into(),
            id: id.into(),
        },
        kind,
        gib: 10,
        zone: "se-sto1".into(),
        attached_to: None,
        reference: reference.map(str::to_owned),
        monthly_minor: Some(1_000),
        currency: Currency::Sek,
        provenance: "fake".into(),
        resource: None,
        backup: None,
    }
}

#[allow(dead_code)]
impl Account {
    pub fn provider(&self) -> &'static str {
        "fake"
    }
    pub fn zones(&self) -> &[Zone] {
        &[]
    }
    pub fn price_resource(&self, _resource: &Resource) -> Result<Price, CloudError> {
        Err(CloudError::Unavailable("no price list".into()))
    }
    pub fn ensure(&self, _reference: &str, _resource: &Resource) -> Result<Ensured, CloudError> {
        Err(CloudError::Refused("this account never buys".into()))
    }
    pub fn destroy_id(&self, id: &ResourceId) -> Result<(), CloudError> {
        if self.refuse.contains(&id.id) {
            return Err(CloudError::Unavailable(format!("{} is wedged", id.id)));
        }
        self.destroyed.lock().unwrap().push(id.id.clone());
        Ok(())
    }
    pub fn status(&self, _id: &ResourceId) -> Result<ResourceStatus, CloudError> {
        Ok(ResourceStatus::Active)
    }
    pub fn list_owned(&self) -> Result<Inventory, CloudError> {
        Ok(Inventory {
            owned: self.owned.clone(),
            foreign: 3,
            currency: Currency::Sek,
        })
    }
}
#[allow(unused_variables, unreachable_code)]
impl monetize_cloud::cloud::Cloud for Account {
    fn manifest(&self) -> monetize_cloud::cloud::CloudManifest {
        monetize_cloud::cloud::CloudManifest { provider: self.provider().to_string(), zones: self.zones().to_vec(), settings: Vec::new(), grow_refused: Some(monetize_cloud::grow::unsupported(self.provider()).to_string()) }
    }
    fn catalog(&self, zone: &monetize_cloud::Zone) -> Result<monetize_cloud::cloud::Catalog, monetize_cloud::CloudError> {
        Err(monetize_cloud::lay::unsupported(self.provider()))
    }
    fn price(&self, what: &monetize_cloud::cloud::Priced<'_>) -> Result<monetize_cloud::Price, monetize_cloud::CloudError> {
        match what {
            monetize_cloud::cloud::Priced::Resource(r) => self.price_resource(r),
            monetize_cloud::cloud::Priced::Plan(p) => monetize_cloud::price_estate_by_rows(p, |r| self.price_resource(r), None),
            monetize_cloud::cloud::Priced::Grow(set) => Err(monetize_cloud::grow::unsupported(self.provider())),
        }
    }
    fn lay(&self, plan: &monetize_cloud::lay::EstatePlan, journal: &monetize_cloud::grow::Journal) -> Result<monetize_cloud::lay::LaidEstate, monetize_cloud::CloudError> {
        Err(monetize_cloud::lay::unsupported(self.provider()))
    }
    fn change(&self, change: &monetize_cloud::cloud::Change, journal: &monetize_cloud::grow::Journal) -> Result<monetize_cloud::cloud::Changed, monetize_cloud::CloudError> {
        Ok(match change {
            monetize_cloud::cloud::Change::Plan { server, plan, delta } => return Err(monetize_cloud::lay::unsupported_verb(self.provider(), "change_plan")),
            monetize_cloud::cloud::Change::Grow { set, estate } => return Err(monetize_cloud::grow::unsupported(self.provider())),
            monetize_cloud::cloud::Change::Settle { set } => return Err(monetize_cloud::grow::unsupported(self.provider())),
            monetize_cloud::cloud::Change::ForgetMedia { laid } => monetize_cloud::cloud::Changed::Forgotten(Vec::new()),
            monetize_cloud::cloud::Change::Ensure { reference, resource, room } => monetize_cloud::cloud::Changed::Ensured(self.ensure(reference, resource)?),
            monetize_cloud::cloud::Change::Retire { reference, machine } => monetize_cloud::cloud::Changed::Retired(monetize_cloud::cloud::retire_by_rows(self, reference, machine)?),
            monetize_cloud::cloud::Change::LabelAdopted { .. } => return Err(monetize_cloud::lay::unsupported_verb(self.provider(), "label_adopted")),
        })
    }
    fn boot(&self, boot: &monetize_cloud::cloud::Boot, journal: &monetize_cloud::grow::Journal) -> Result<monetize_cloud::cloud::Booted, monetize_cloud::CloudError> {
        Ok(match boot {
            monetize_cloud::cloud::Boot::Keep(k) => return Err(monetize_cloud::lay::unsupported_verb(self.provider(), "keep_boot")),
            monetize_cloud::cloud::Boot::Power { server, on } => return Err(monetize_cloud::lay::unsupported_verb(self.provider(), "set_power")),
            monetize_cloud::cloud::Boot::Restore(r) => return Err(monetize_cloud::lay::unsupported_verb(self.provider(), "restore_from_backup")),
        })
    }
    fn firewall(&self, laid: &monetize_cloud::lay::LaidEstate) -> Result<(), monetize_cloud::CloudError> {
        Err(monetize_cloud::lay::unsupported_verb(self.provider(), "set_estate_firewall"))
    }
    fn destroy(&self, what: &monetize_cloud::cloud::Give<'_>) -> Result<monetize_cloud::Reclaimed, monetize_cloud::CloudError> {
        match what {
            monetize_cloud::cloud::Give::References(refs) => Ok(monetize_cloud::release_for_by_rows(self.list_owned()?, refs, |id| self.destroy_id(id))),
            monetize_cloud::cloud::Give::Resource(id) => {
                self.destroy_id(id)?;
                Ok(monetize_cloud::Reclaimed { destroyed: vec![(*id).clone()], ..monetize_cloud::Reclaimed::default() })
            }
        }
    }
    fn inventory(&self, what: &monetize_cloud::cloud::Listing<'_>) -> Result<monetize_cloud::cloud::Listed, monetize_cloud::CloudError> {
        Ok(match what {
            monetize_cloud::cloud::Listing::Account => monetize_cloud::cloud::Listed::Account(self.list_owned()?),
            monetize_cloud::cloud::Listing::Set(set) => return Err(monetize_cloud::grow::unsupported(self.provider())),
            monetize_cloud::cloud::Listing::Status(id) => monetize_cloud::cloud::Listed::Status(self.status(id)?),
            monetize_cloud::cloud::Listing::Under(refs) => monetize_cloud::cloud::Listed::Under(monetize_cloud::cloud::under(self.list_owned()?, refs)),
            monetize_cloud::cloud::Listing::Room { delta, room } => monetize_cloud::cloud::Listed::Room(monetize_cloud::cloud::RoomLeft { serve: Ok(()), left_bytes: None, ceilings: Default::default() }),
            monetize_cloud::cloud::Listing::Admits(_plan) => monetize_cloud::cloud::Listed::Admitted,
        })
    }
}

/// **The headline: everything under the tenant's references goes, including the
/// row the ledger never knew about — and nothing else does.**
///
/// The account here is deliberately mixed, because a release that destroyed the
/// whole account would pass any test that only checked the tenant's own rows
/// were gone.
#[test]
fn a_release_takes_the_tenants_rows_including_orphans_and_spares_everything_else() {
    let account = Account::new(vec![
        // The tenant's, and the ledger knows about this one.
        owned("disk-a", OwnedKind::Disk, Some("ord-1")),
        // The tenant's, from a second order.
        owned("srv-b", OwnedKind::Server, Some("ord-2")),
        // **The orphan.** Bought under one of the tenant's references and never
        // recorded — the class `release_tenant` can never reach, and the one
        // that bills for ever.
        owned("disk-orphan", OwnedKind::Disk, Some("ord-2")),
        // Somebody else's tenant.
        owned("disk-other", OwnedKind::Disk, Some("ord-99")),
        // **The box's own data set.** The `server` provider reports one of these
        // for the iron monetize itself runs on; destroying it is destroying the
        // deployment.
        owned("this-box", OwnedKind::Dataset, Some("ord-1")),
        // Labelled with nothing at all: not monetize's, whoever's it is.
        owned("stranger", OwnedKind::Disk, None),
    ]);

    let released = monetize_cloud::ask::release_for(&account, &["ord-1".to_owned(), "ord-2".to_owned()])
        .expect("the release runs");

    let mut gone = account.destroyed();
    gone.sort();
    assert_eq!(
        gone,
        vec!["disk-a".to_owned(), "disk-orphan".to_owned(), "srv-b".to_owned()],
        "asserted on what DESTROY was actually called with, not on the returned list — a release \
         that reported ids it never handed back would be green on the report"
    );
    let mut reported: Vec<String> = released.destroyed.iter().map(|id| id.id.clone()).collect();
    reported.sort();
    assert_eq!(reported, gone, "the report and the calls must agree");
    assert!(released.failures.is_empty(), "{released:?}");
    assert_eq!(
        released.skipped_other_tenants, 1,
        "one row belonged to another tenant and must be COUNTED, not silently ignored: a release \
         that scoped itself wrongly and a release that found nothing look identical otherwise"
    );
    assert_eq!(
        released.spared_datasets, 1,
        "the box's own data set must be spared and said so; a silent skip would be one line \
         between an operator and the deployment"
    );
}

/// **A reference nobody bought under releases nothing**, and that is a success.
///
/// A tenant served entirely from the pool owns no provider-side resource at
/// all, and a super purge of one must not look like a failure.
#[test]
fn a_tenant_with_nothing_at_the_provider_releases_nothing_and_that_is_not_an_error() {
    let account = Account::new(vec![owned("disk-other", OwnedKind::Disk, Some("ord-99"))]);
    let released = monetize_cloud::ask::release_for(&account, &["ord-1".to_owned()])
        .expect("the release runs");
    assert!(released.destroyed.is_empty());
    assert!(released.failures.is_empty());
    assert_eq!(released.skipped_other_tenants, 1);
    assert!(
        account.destroyed().is_empty(),
        "nothing of anybody's may be destroyed by a release that matched nothing"
    );
}

/// **An empty reference list destroys NOTHING.**
///
/// The single most dangerous input this method can be given: a caller whose
/// ledger read failed, or whose tenant turns out to have no orders. An
/// implementation that read "no filter" as "everything" would empty the whole
/// account — every tenant's, and the box's own — on a caller's bad day.
#[test]
fn an_empty_reference_list_is_not_a_wildcard() {
    let account = Account::new(vec![
        owned("disk-a", OwnedKind::Disk, Some("ord-1")),
        owned("disk-other", OwnedKind::Disk, Some("ord-99")),
        owned("this-box", OwnedKind::Dataset, Some("ord-1")),
    ]);
    let released = monetize_cloud::ask::release_for(&account, &[]).expect("the release runs");
    assert!(
        account.destroyed().is_empty(),
        "an empty reference list destroyed {:?}; it must be read as `nothing matches`, never as \
         `no filter`",
        account.destroyed()
    );
    assert!(released.destroyed.is_empty());
}

/// **One wedged resource does not stop the others, and is named.**
///
/// The opposite choice from `release_tenant`, which stops at the first failure
/// so a retry finds the rest still held. It can afford that: every id it walks
/// is in the ledger, so nothing is forgotten by stopping. Here the list is the
/// PROVIDER's, and the rows are exactly the ones the ledger may not know —
/// stopping would leave the remaining orphans billing with nothing to remind
/// anybody they exist. So the walk finishes, and what is left is named.
#[test]
fn a_wedged_resource_is_named_and_the_rest_still_go() {
    let account = Account::new(vec![
        owned("disk-a", OwnedKind::Disk, Some("ord-1")),
        owned("disk-wedged", OwnedKind::Disk, Some("ord-1")),
        owned("disk-c", OwnedKind::Disk, Some("ord-1")),
    ])
    .refusing("disk-wedged");

    let released = monetize_cloud::ask::release_for(&account, &["ord-1".to_owned()])
        .expect("a partial release still reports");

    let mut gone = account.destroyed();
    gone.sort();
    assert_eq!(
        gone,
        vec!["disk-a".to_owned(), "disk-c".to_owned()],
        "the walk stopped at the wedged row and left the rest billing"
    );
    assert_eq!(released.failures.len(), 1, "{released:?}");
    assert!(
        released.failures[0].contains("disk-wedged"),
        "the failure must NAME what the provider still holds, or nobody can go and free it: \
         {released:?}"
    );
    assert!(
        released.failures[0].contains("wedged"),
        "and it must carry the provider's own words: {released:?}"
    );
}

/// A resource already gone at the provider counts as released. A super purge is
/// retried, and the second run finds the first run's work done.
#[test]
fn a_resource_already_gone_counts_as_released() {
    struct Vanished(Account);
    #[allow(dead_code)]
    impl Vanished {
        pub fn provider(&self) -> &'static str {
            self.0.provider()
        }
        pub fn zones(&self) -> &[Zone] {
            self.0.zones()
        }
        pub fn price_resource(&self, r: &Resource) -> Result<Price, CloudError> {
            monetize_cloud::Cloud::price(&self.0, &monetize_cloud::cloud::Priced::Resource(r))
        }
        pub fn ensure(&self, reference: &str, r: &Resource) -> Result<Ensured, CloudError> {
            self.0.ensure(reference, r)
        }
        pub fn destroy_id(&self, id: &ResourceId) -> Result<(), CloudError> {
            Err(CloudError::UnknownResource(id.clone()))
        }
        pub fn status(&self, id: &ResourceId) -> Result<ResourceStatus, CloudError> {
            self.0.status(id)
        }
        pub fn list_owned(&self) -> Result<Inventory, CloudError> {
            monetize_cloud::ask::account(&self.0)
        }
    }
    #[allow(unused_variables, unreachable_code)]
    impl monetize_cloud::cloud::Cloud for Vanished {
        fn manifest(&self) -> monetize_cloud::cloud::CloudManifest {
            monetize_cloud::cloud::CloudManifest { provider: self.provider().to_string(), zones: self.zones().to_vec(), settings: Vec::new(), grow_refused: Some(monetize_cloud::grow::unsupported(self.provider()).to_string()) }
        }
        fn catalog(&self, zone: &monetize_cloud::Zone) -> Result<monetize_cloud::cloud::Catalog, monetize_cloud::CloudError> {
            Err(monetize_cloud::lay::unsupported(self.provider()))
        }
        fn price(&self, what: &monetize_cloud::cloud::Priced<'_>) -> Result<monetize_cloud::Price, monetize_cloud::CloudError> {
            match what {
                monetize_cloud::cloud::Priced::Resource(r) => self.price_resource(r),
                monetize_cloud::cloud::Priced::Plan(p) => monetize_cloud::price_estate_by_rows(p, |r| self.price_resource(r), None),
                monetize_cloud::cloud::Priced::Grow(set) => Err(monetize_cloud::grow::unsupported(self.provider())),
            }
        }
        fn lay(&self, plan: &monetize_cloud::lay::EstatePlan, journal: &monetize_cloud::grow::Journal) -> Result<monetize_cloud::lay::LaidEstate, monetize_cloud::CloudError> {
            Err(monetize_cloud::lay::unsupported(self.provider()))
        }
        fn change(&self, change: &monetize_cloud::cloud::Change, journal: &monetize_cloud::grow::Journal) -> Result<monetize_cloud::cloud::Changed, monetize_cloud::CloudError> {
            Ok(match change {
                monetize_cloud::cloud::Change::Plan { server, plan, delta } => return Err(monetize_cloud::lay::unsupported_verb(self.provider(), "change_plan")),
                monetize_cloud::cloud::Change::Grow { set, estate } => return Err(monetize_cloud::grow::unsupported(self.provider())),
                monetize_cloud::cloud::Change::Settle { set } => return Err(monetize_cloud::grow::unsupported(self.provider())),
                monetize_cloud::cloud::Change::ForgetMedia { laid } => monetize_cloud::cloud::Changed::Forgotten(Vec::new()),
                monetize_cloud::cloud::Change::Ensure { reference, resource, room } => monetize_cloud::cloud::Changed::Ensured(self.ensure(reference, resource)?),
                monetize_cloud::cloud::Change::Retire { reference, machine } => monetize_cloud::cloud::Changed::Retired(monetize_cloud::cloud::retire_by_rows(self, reference, machine)?),
                monetize_cloud::cloud::Change::LabelAdopted { .. } => return Err(monetize_cloud::lay::unsupported_verb(self.provider(), "label_adopted")),
            })
        }
        fn boot(&self, boot: &monetize_cloud::cloud::Boot, journal: &monetize_cloud::grow::Journal) -> Result<monetize_cloud::cloud::Booted, monetize_cloud::CloudError> {
            Ok(match boot {
                monetize_cloud::cloud::Boot::Keep(k) => return Err(monetize_cloud::lay::unsupported_verb(self.provider(), "keep_boot")),
                monetize_cloud::cloud::Boot::Power { server, on } => return Err(monetize_cloud::lay::unsupported_verb(self.provider(), "set_power")),
                monetize_cloud::cloud::Boot::Restore(r) => return Err(monetize_cloud::lay::unsupported_verb(self.provider(), "restore_from_backup")),
            })
        }
        fn firewall(&self, laid: &monetize_cloud::lay::LaidEstate) -> Result<(), monetize_cloud::CloudError> {
            Err(monetize_cloud::lay::unsupported_verb(self.provider(), "set_estate_firewall"))
        }
        fn destroy(&self, what: &monetize_cloud::cloud::Give<'_>) -> Result<monetize_cloud::Reclaimed, monetize_cloud::CloudError> {
            match what {
                monetize_cloud::cloud::Give::References(refs) => Ok(monetize_cloud::release_for_by_rows(self.list_owned()?, refs, |id| self.destroy_id(id))),
                monetize_cloud::cloud::Give::Resource(id) => {
                    self.destroy_id(id)?;
                    Ok(monetize_cloud::Reclaimed { destroyed: vec![(*id).clone()], ..monetize_cloud::Reclaimed::default() })
                }
            }
        }
        fn inventory(&self, what: &monetize_cloud::cloud::Listing<'_>) -> Result<monetize_cloud::cloud::Listed, monetize_cloud::CloudError> {
            Ok(match what {
                monetize_cloud::cloud::Listing::Account => monetize_cloud::cloud::Listed::Account(self.list_owned()?),
                monetize_cloud::cloud::Listing::Set(set) => return Err(monetize_cloud::grow::unsupported(self.provider())),
                monetize_cloud::cloud::Listing::Status(id) => monetize_cloud::cloud::Listed::Status(self.status(id)?),
                monetize_cloud::cloud::Listing::Under(refs) => monetize_cloud::cloud::Listed::Under(monetize_cloud::cloud::under(self.list_owned()?, refs)),
                monetize_cloud::cloud::Listing::Room { delta, room } => monetize_cloud::cloud::Listed::Room(monetize_cloud::cloud::RoomLeft { serve: Ok(()), left_bytes: None, ceilings: Default::default() }),
                monetize_cloud::cloud::Listing::Admits(_plan) => monetize_cloud::cloud::Listed::Admitted,
            })
        }
    }

    let account = Vanished(Account::new(vec![owned(
        "disk-a",
        OwnedKind::Disk,
        Some("ord-1"),
    )]));
    let released = monetize_cloud::ask::release_for(&account, &["ord-1".to_owned()])
        .expect("the release runs");
    assert_eq!(
        released.destroyed.len(),
        1,
        "a resource the provider no longer has IS released; reporting it as a failure would make \
         every retry of a super purge look broken: {released:?}"
    );
    assert!(released.failures.is_empty(), "{released:?}");
}

/// **An inventory that cannot be taken is an ERROR, never an empty release.**
///
/// The one answer that must not be optimistic. "I could not look" and "there is
/// nothing there" would be the same green, and a caller would record a tenant's
/// iron as returned while the provider went on billing for it.
#[test]
fn an_unreadable_inventory_is_refused_rather_than_reported_as_nothing_to_do() {
    struct Blind;
    #[allow(dead_code)]
    impl Blind {
        pub fn provider(&self) -> &'static str {
            "blind"
        }
        pub fn zones(&self) -> &[Zone] {
            &[]
        }
        pub fn price_resource(&self, _r: &Resource) -> Result<Price, CloudError> {
            Err(CloudError::Unavailable("no".into()))
        }
        pub fn ensure(&self, _reference: &str, _r: &Resource) -> Result<Ensured, CloudError> {
            Err(CloudError::Refused("no".into()))
        }
        pub fn destroy_id(&self, _id: &ResourceId) -> Result<(), CloudError> {
            panic!("destroy must not be reached when the account could not be listed");
        }
        pub fn status(&self, _id: &ResourceId) -> Result<ResourceStatus, CloudError> {
            Ok(ResourceStatus::Active)
        }
        pub fn list_owned(&self) -> Result<Inventory, CloudError> {
            Err(CloudError::Unavailable("the API is down".into()))
        }
    }
    #[allow(unused_variables, unreachable_code)]
    impl monetize_cloud::cloud::Cloud for Blind {
        fn manifest(&self) -> monetize_cloud::cloud::CloudManifest {
            monetize_cloud::cloud::CloudManifest { provider: self.provider().to_string(), zones: self.zones().to_vec(), settings: Vec::new(), grow_refused: Some(monetize_cloud::grow::unsupported(self.provider()).to_string()) }
        }
        fn catalog(&self, zone: &monetize_cloud::Zone) -> Result<monetize_cloud::cloud::Catalog, monetize_cloud::CloudError> {
            Err(monetize_cloud::lay::unsupported(self.provider()))
        }
        fn price(&self, what: &monetize_cloud::cloud::Priced<'_>) -> Result<monetize_cloud::Price, monetize_cloud::CloudError> {
            match what {
                monetize_cloud::cloud::Priced::Resource(r) => self.price_resource(r),
                monetize_cloud::cloud::Priced::Plan(p) => monetize_cloud::price_estate_by_rows(p, |r| self.price_resource(r), None),
                monetize_cloud::cloud::Priced::Grow(set) => Err(monetize_cloud::grow::unsupported(self.provider())),
            }
        }
        fn lay(&self, plan: &monetize_cloud::lay::EstatePlan, journal: &monetize_cloud::grow::Journal) -> Result<monetize_cloud::lay::LaidEstate, monetize_cloud::CloudError> {
            Err(monetize_cloud::lay::unsupported(self.provider()))
        }
        fn change(&self, change: &monetize_cloud::cloud::Change, journal: &monetize_cloud::grow::Journal) -> Result<monetize_cloud::cloud::Changed, monetize_cloud::CloudError> {
            Ok(match change {
                monetize_cloud::cloud::Change::Plan { server, plan, delta } => return Err(monetize_cloud::lay::unsupported_verb(self.provider(), "change_plan")),
                monetize_cloud::cloud::Change::Grow { set, estate } => return Err(monetize_cloud::grow::unsupported(self.provider())),
                monetize_cloud::cloud::Change::Settle { set } => return Err(monetize_cloud::grow::unsupported(self.provider())),
                monetize_cloud::cloud::Change::ForgetMedia { laid } => monetize_cloud::cloud::Changed::Forgotten(Vec::new()),
                monetize_cloud::cloud::Change::Ensure { reference, resource, room } => monetize_cloud::cloud::Changed::Ensured(self.ensure(reference, resource)?),
                monetize_cloud::cloud::Change::Retire { reference, machine } => monetize_cloud::cloud::Changed::Retired(monetize_cloud::cloud::retire_by_rows(self, reference, machine)?),
                monetize_cloud::cloud::Change::LabelAdopted { .. } => return Err(monetize_cloud::lay::unsupported_verb(self.provider(), "label_adopted")),
            })
        }
        fn boot(&self, boot: &monetize_cloud::cloud::Boot, journal: &monetize_cloud::grow::Journal) -> Result<monetize_cloud::cloud::Booted, monetize_cloud::CloudError> {
            Ok(match boot {
                monetize_cloud::cloud::Boot::Keep(k) => return Err(monetize_cloud::lay::unsupported_verb(self.provider(), "keep_boot")),
                monetize_cloud::cloud::Boot::Power { server, on } => return Err(monetize_cloud::lay::unsupported_verb(self.provider(), "set_power")),
                monetize_cloud::cloud::Boot::Restore(r) => return Err(monetize_cloud::lay::unsupported_verb(self.provider(), "restore_from_backup")),
            })
        }
        fn firewall(&self, laid: &monetize_cloud::lay::LaidEstate) -> Result<(), monetize_cloud::CloudError> {
            Err(monetize_cloud::lay::unsupported_verb(self.provider(), "set_estate_firewall"))
        }
        fn destroy(&self, what: &monetize_cloud::cloud::Give<'_>) -> Result<monetize_cloud::Reclaimed, monetize_cloud::CloudError> {
            match what {
                monetize_cloud::cloud::Give::References(refs) => Ok(monetize_cloud::release_for_by_rows(self.list_owned()?, refs, |id| self.destroy_id(id))),
                monetize_cloud::cloud::Give::Resource(id) => {
                    self.destroy_id(id)?;
                    Ok(monetize_cloud::Reclaimed { destroyed: vec![(*id).clone()], ..monetize_cloud::Reclaimed::default() })
                }
            }
        }
        fn inventory(&self, what: &monetize_cloud::cloud::Listing<'_>) -> Result<monetize_cloud::cloud::Listed, monetize_cloud::CloudError> {
            Ok(match what {
                monetize_cloud::cloud::Listing::Account => monetize_cloud::cloud::Listed::Account(self.list_owned()?),
                monetize_cloud::cloud::Listing::Set(set) => return Err(monetize_cloud::grow::unsupported(self.provider())),
                monetize_cloud::cloud::Listing::Status(id) => monetize_cloud::cloud::Listed::Status(self.status(id)?),
                monetize_cloud::cloud::Listing::Under(refs) => monetize_cloud::cloud::Listed::Under(monetize_cloud::cloud::under(self.list_owned()?, refs)),
                monetize_cloud::cloud::Listing::Room { delta, room } => monetize_cloud::cloud::Listed::Room(monetize_cloud::cloud::RoomLeft { serve: Ok(()), left_bytes: None, ceilings: Default::default() }),
                monetize_cloud::cloud::Listing::Admits(_plan) => monetize_cloud::cloud::Listed::Admitted,
            })
        }
    }

    let err = monetize_cloud::ask::release_for(&Blind, &["ord-1".to_owned()])
        .expect_err("an unreadable account must not answer `nothing to release`");
    assert!(
        format!("{err}").contains("the API is down"),
        "the provider's own words must survive: {err}"
    );
}

/// **The pointer forms forward it**, so an `Arc<UpCloud>` that overrides
/// `release_for` in one API call is not silently handed the default.
///
/// The forwarding macro is the whole reason this can go wrong quietly: a
/// method added to the trait and not to the macro compiles, ships, and takes
/// the default through every `Arc` and `Box` in the server — which is every
/// provider the registry builds.
#[test]
fn arc_and_box_forward_to_an_overriding_impl() {
    struct OneCall;
    #[allow(dead_code)]
    impl OneCall {
        pub fn provider(&self) -> &'static str {
            "one-call"
        }
        pub fn zones(&self) -> &[Zone] {
            &[]
        }
        pub fn price_resource(&self, _r: &Resource) -> Result<Price, CloudError> {
            Err(CloudError::Unavailable("no".into()))
        }
        pub fn ensure(&self, _reference: &str, _r: &Resource) -> Result<Ensured, CloudError> {
            Err(CloudError::Refused("no".into()))
        }
        pub fn destroy_id(&self, _id: &ResourceId) -> Result<(), CloudError> {
            Ok(())
        }
        pub fn status(&self, _id: &ResourceId) -> Result<ResourceStatus, CloudError> {
            Ok(ResourceStatus::Active)
        }
        pub fn list_owned(&self) -> Result<Inventory, CloudError> {
            panic!("an overriding release_for must not fall back to the inventory default");
        }
        pub fn release_for(
            &self,
            _references: &[String],
        ) -> Result<monetize_cloud::Reclaimed, CloudError> {
            Ok(monetize_cloud::Reclaimed {
                destroyed: vec![ResourceId {
                    provider: "one-call".into(),
                    id: "everything".into(),
                }],
                failures: Vec::new(),
                skipped_other_tenants: 0,
                spared_datasets: 0,
            })
        }
    }
    #[allow(unused_variables, unreachable_code)]
    impl monetize_cloud::cloud::Cloud for OneCall {
        fn manifest(&self) -> monetize_cloud::cloud::CloudManifest {
            monetize_cloud::cloud::CloudManifest { provider: self.provider().to_string(), zones: self.zones().to_vec(), settings: Vec::new(), grow_refused: Some(monetize_cloud::grow::unsupported(self.provider()).to_string()) }
        }
        fn catalog(&self, zone: &monetize_cloud::Zone) -> Result<monetize_cloud::cloud::Catalog, monetize_cloud::CloudError> {
            Err(monetize_cloud::lay::unsupported(self.provider()))
        }
        fn price(&self, what: &monetize_cloud::cloud::Priced<'_>) -> Result<monetize_cloud::Price, monetize_cloud::CloudError> {
            match what {
                monetize_cloud::cloud::Priced::Resource(r) => self.price_resource(r),
                monetize_cloud::cloud::Priced::Plan(p) => monetize_cloud::price_estate_by_rows(p, |r| self.price_resource(r), None),
                monetize_cloud::cloud::Priced::Grow(set) => Err(monetize_cloud::grow::unsupported(self.provider())),
            }
        }
        fn lay(&self, plan: &monetize_cloud::lay::EstatePlan, journal: &monetize_cloud::grow::Journal) -> Result<monetize_cloud::lay::LaidEstate, monetize_cloud::CloudError> {
            Err(monetize_cloud::lay::unsupported(self.provider()))
        }
        fn change(&self, change: &monetize_cloud::cloud::Change, journal: &monetize_cloud::grow::Journal) -> Result<monetize_cloud::cloud::Changed, monetize_cloud::CloudError> {
            Ok(match change {
                monetize_cloud::cloud::Change::Plan { server, plan, delta } => return Err(monetize_cloud::lay::unsupported_verb(self.provider(), "change_plan")),
                monetize_cloud::cloud::Change::Grow { set, estate } => return Err(monetize_cloud::grow::unsupported(self.provider())),
                monetize_cloud::cloud::Change::Settle { set } => return Err(monetize_cloud::grow::unsupported(self.provider())),
                monetize_cloud::cloud::Change::ForgetMedia { laid } => monetize_cloud::cloud::Changed::Forgotten(Vec::new()),
                monetize_cloud::cloud::Change::Ensure { reference, resource, room } => monetize_cloud::cloud::Changed::Ensured(self.ensure(reference, resource)?),
                monetize_cloud::cloud::Change::Retire { reference, machine } => monetize_cloud::cloud::Changed::Retired(monetize_cloud::cloud::retire_by_rows(self, reference, machine)?),
                monetize_cloud::cloud::Change::LabelAdopted { .. } => return Err(monetize_cloud::lay::unsupported_verb(self.provider(), "label_adopted")),
            })
        }
        fn boot(&self, boot: &monetize_cloud::cloud::Boot, journal: &monetize_cloud::grow::Journal) -> Result<monetize_cloud::cloud::Booted, monetize_cloud::CloudError> {
            Ok(match boot {
                monetize_cloud::cloud::Boot::Keep(k) => return Err(monetize_cloud::lay::unsupported_verb(self.provider(), "keep_boot")),
                monetize_cloud::cloud::Boot::Power { server, on } => return Err(monetize_cloud::lay::unsupported_verb(self.provider(), "set_power")),
                monetize_cloud::cloud::Boot::Restore(r) => return Err(monetize_cloud::lay::unsupported_verb(self.provider(), "restore_from_backup")),
            })
        }
        fn firewall(&self, laid: &monetize_cloud::lay::LaidEstate) -> Result<(), monetize_cloud::CloudError> {
            Err(monetize_cloud::lay::unsupported_verb(self.provider(), "set_estate_firewall"))
        }
        fn destroy(&self, what: &monetize_cloud::cloud::Give<'_>) -> Result<monetize_cloud::Reclaimed, monetize_cloud::CloudError> {
            match what {
                monetize_cloud::cloud::Give::References(refs) => self.release_for(refs),
                monetize_cloud::cloud::Give::Resource(id) => {
                    self.destroy_id(id)?;
                    Ok(monetize_cloud::Reclaimed { destroyed: vec![(*id).clone()], ..monetize_cloud::Reclaimed::default() })
                }
            }
        }
        fn inventory(&self, what: &monetize_cloud::cloud::Listing<'_>) -> Result<monetize_cloud::cloud::Listed, monetize_cloud::CloudError> {
            Ok(match what {
                monetize_cloud::cloud::Listing::Account => monetize_cloud::cloud::Listed::Account(self.list_owned()?),
                monetize_cloud::cloud::Listing::Set(set) => return Err(monetize_cloud::grow::unsupported(self.provider())),
                monetize_cloud::cloud::Listing::Status(id) => monetize_cloud::cloud::Listed::Status(self.status(id)?),
                monetize_cloud::cloud::Listing::Under(refs) => monetize_cloud::cloud::Listed::Under(monetize_cloud::cloud::under(self.list_owned()?, refs)),
                monetize_cloud::cloud::Listing::Room { delta, room } => monetize_cloud::cloud::Listed::Room(monetize_cloud::cloud::RoomLeft { serve: Ok(()), left_bytes: None, ceilings: Default::default() }),
                monetize_cloud::cloud::Listing::Admits(_plan) => monetize_cloud::cloud::Listed::Admitted,
            })
        }
    }

    let boxed: Box<dyn Cloud> = Box::new(OneCall);
    assert_eq!(
        monetize_cloud::ask::release_for(&boxed, &["ord-1".into()]).expect("boxed").destroyed[0].id,
        "everything",
        "a Box took the default instead of the impl's own release_for"
    );
    let shared = std::sync::Arc::new(OneCall);
    assert_eq!(
        shared.release_for(&["ord-1".into()]).expect("arc").destroyed[0].id,
        "everything",
        "an Arc took the default instead of the impl's own release_for"
    );
}