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

//! The free cloud, both ways round. Every assertion is on applied output — the
//! grant the state file holds, the variant `ensure` answered, the words of a
//! refusal — never a round-trip. Measure, never assert: each claim's red twin
//! sits beside it.

use super::*;
use crate::{Resource, ServerId, ServerRole};
use monetize_product::{EntitlementFact, Growth, ProductError, TenantId, Usage};
use std::collections::BTreeMap;

const REF: &str = "gunnar/alice/2026-09-05";

/// A product that answers exactly what a test tells it to, and nothing else.
struct Box300 {
    servable: Mutex<Servable>,
}

impl Box300 {
    fn measured(servable_gib: u64, total_gib: u64) -> Arc<Box300> {
        Arc::new(Box300 {
            servable: Mutex::new(Servable::Measured { servable_bytes: servable_gib * GIB, total_bytes: total_gib * GIB, growth: Growth::Sealed("raid0, laid once".into()), ceilings: BTreeMap::new() }),
        })
    }
    fn unmeasured() -> Arc<Box300> {
        Arc::new(Box300 { servable: Mutex::new(Servable::Unmeasured("this gunnar has no Admin.Capacity RPC".into())) })
    }
    fn say(&self, s: Servable) {
        *self.servable.lock().unwrap() = s;
    }
}

impl Product for Box300 {
    fn facts(&self) -> monetize_product::ProductFacts {
        monetize_product::ProductFacts::new("gunnar", &[])
    }
    fn list_tenants(&self) -> Result<Vec<TenantId>, ProductError> {
        Ok(vec![])
    }
    fn read_usage(&self, _tenant: &TenantId) -> Result<Usage, ProductError> {
        Ok(Usage::default())
    }
    fn push_entitlement(&self, _fact: &EntitlementFact) -> Result<(), ProductError> {
        Ok(())
    }
    fn can_absorb(&self, _tenant: &TenantId, _caps: &BTreeMap<String, u64>) -> Result<monetize_product::Absorb, String> {
        Ok(monetize_product::Absorb::OnHand)
    }
    fn servable(&self) -> Servable {
        self.servable.lock().unwrap().clone()
    }
}

fn tmp(name: &str) -> PathBuf {
    let dir = std::env::temp_dir().join(format!("monetize-cloud-server-{}-{name}", std::process::id()));
    let _ = std::fs::remove_dir_all(&dir);
    dir.join("server-pool.json")
}

fn disk(gib: u64) -> Resource {
    Resource::Disk { server: ServerId("gunnar-se-sto1".into()), gib }
}

fn server(product: Arc<dyn Product>, pool_gib: u64) -> Server {
    Server::new(ServerConfig::new(product, pool_gib)).unwrap()
}

/// **`OnHand`, from the declared pool, idempotent, and remembered.** The grant
/// is read back from the FILE by a second `Server` — the deployment's restart —
/// which answers the same grant without touching the pool again.
#[test]
fn a_disk_the_pool_covers_is_on_hand_and_the_grant_survives_a_restart() {
    let path = tmp("onhand");
    let mut cfg = ServerConfig::new(Box300::measured(200, 300), 100);
    cfg.state_path = Some(path.clone());
    let s = Server::new(cfg.clone()).unwrap();
    assert_eq!(s.own_ensure(REF, &disk(10)).unwrap(), Ensured::OnHand { pool_free_after: 90 });
    assert_eq!(s.own_ensure(REF, &disk(10)).unwrap(), Ensured::OnHand { pool_free_after: 90 }, "idempotent on reference + resource");
    assert_eq!((s.sold_gib(), s.free_gib()), (10, 90), "asked twice, sold once");
    assert_eq!(s.own_ensure("gunnar/bob/2026-09-05", &disk(30)).unwrap(), Ensured::OnHand { pool_free_after: 60 });

    // The applied output: the file holds two OnHand grants and the sold count.
    let text = std::fs::read_to_string(&path).unwrap();
    assert!(text.contains("\"kind\": \"on_hand\""), "{text}");
    assert!(!text.contains("provisioned"), "nothing was bought: {text}");
    let again = Server::new(cfg).unwrap();
    assert_eq!(again.sold_gib(), 40, "the restart folds the grants back to the same number");
    assert_eq!(crate::ask::ensure(&again, REF, &disk(10), None).unwrap(), Ensured::OnHand { pool_free_after: 90 }, "the old grant, not a new one");
    assert_eq!(again.sold_gib(), 40);
    assert!(again.grants().iter().all(|(_, g)| matches!(g, Grant::OnHand { .. })), "{:?}", again.grants());
}

/// **Refuse twin: a disk the pool cannot cover is refused, naming the
/// shortfall, and nothing is recorded.** Both the first sale that is too big
/// and the sale that would tip a nearly-sold pool over.
#[test]
fn refuse_twin_a_disk_larger_than_the_pool_is_refused_naming_the_shortfall() {
    let s = server(Box300::measured(200, 300), 25);
    let err = s.own_ensure(REF, &disk(30)).err().map(|e| e.to_string()).expect("30 > 25 must refuse");
    assert!(matches!(s.own_ensure(REF, &disk(30)), Err(CloudError::Refused(_))));
    assert!(err.contains("25 GiB declared") && err.contains("0 GiB already granted") && err.contains("short 5 GiB"), "{err}");
    assert!(err.contains("never buys"), "the refusal says what this provider will not do: {err}");
    assert_eq!(s.sold_gib(), 0, "a refusal records nothing");
    assert!(s.grants().is_empty());

    assert_eq!(s.own_ensure(REF, &disk(20)).unwrap(), Ensured::OnHand { pool_free_after: 5 });
    let err = s.own_ensure("gunnar/bob/2026-09-05", &disk(10)).err().map(|e| e.to_string()).unwrap();
    assert!(err.contains("20 GiB already granted") && err.contains("5 GiB free") && err.contains("short 5 GiB"), "{err}");
    assert_eq!(s.sold_gib(), 20);
}

/// **The product's measurement is a wall the declared pool cannot talk over,
/// and an unmeasured product is not a wall.** Declared 100, disk says 4 → a
/// 10 GiB grant is refused in the product's words; the product then says 50 →
/// granted; a product that cannot measure leaves the declaration to decide.
#[test]
fn the_products_measurement_walls_the_declared_pool_and_unmeasured_is_not_a_wall() {
    let product = Box300::measured(4, 300);
    let s = server(product.clone(), 100);
    let err = s.own_ensure(REF, &disk(10)).err().map(|e| e.to_string()).expect("the disk has 4 GiB; 10 must refuse");
    assert!(err.contains("gunnar measured") && err.contains("4.0 GiB more") && err.contains("short 6.0 GiB"), "{err}");
    assert!(err.contains("100 GiB, 100 GiB unsold"), "and says the declaration was not the reason: {err}");
    assert_eq!(s.sold_gib(), 0);

    product.say(Servable::Measured { servable_bytes: 50 * GIB, total_bytes: 300 * GIB, growth: Growth::Unknown(String::new()), ceilings: BTreeMap::new() });
    assert_eq!(s.own_ensure(REF, &disk(10)).unwrap(), Ensured::OnHand { pool_free_after: 90 });

    // Unmeasured: the live gunnar.rs appliance predates Admin.Capacity. Not a
    // full disk, not an empty one — the declared pool decides alone.
    let s = server(Box300::unmeasured(), 100);
    assert_eq!(s.own_ensure(REF, &disk(10)).unwrap(), Ensured::OnHand { pool_free_after: 90 });
    let s = server(Box300::unmeasured(), 5);
    assert!(matches!(s.own_ensure(REF, &disk(10)), Err(CloudError::Refused(_))), "the declaration still refuses on its own");
}

/// **This box is the only box.** A server or a plan change is refused, priced
/// nowhere, and never recorded — and the refusal says where such a plan belongs.
#[test]
fn refuse_twin_servers_and_plan_changes_are_refused_because_this_box_is_the_only_box() {
    let s = server(Box300::measured(200, 300), 100);
    let replica = Resource::Server { plan: "STARTER-2xCPU-4GB".into(), zone: Zone("se-sto1".into()), role: ServerRole::Replica };
    let resize = Resource::Compute { server: ServerId("gunnar-se-sto1".into()), plan: "4xCPU-8GB".into() };
    for r in [&replica, &resize] {
        let err = s.own_ensure(REF, r).err().map(|e| e.to_string()).expect("must refuse");
        assert!(err.contains("only box") && err.contains(&fingerprint(r)) && err.contains("needs a cloud that sells iron"), "{err}");
        assert!(matches!(s.own_price(r), Err(CloudError::Refused(_))), "and it has no price here");
    }
    assert!(s.grants().is_empty());
    assert!(matches!(s.own_ensure(REF, &disk(0)), Err(CloudError::Refused(_))), "a disk of 0 GiB");
    assert!(matches!(s.own_ensure("", &disk(1)), Err(CloudError::Refused(_))), "an empty reference");
}

/// Zero price with a currency, destroy a no-op that is `Ok` for anything,
/// status that knows the data set and nothing else, no ceiling because nothing
/// can be spent.
#[test]
fn price_is_zero_destroy_is_a_no_op_status_knows_only_the_data_set_and_there_is_no_ceiling() {
    let s = server(Box300::measured(200, 300), 100);
    assert_eq!(s.own_price(&disk(10)).unwrap(), Price { minor_per_month: 0, currency: Currency::Eur });
    let mut sek = ServerConfig::new(Box300::measured(200, 300), 100);
    sek.currency = Currency::Sek;
    assert_eq!(crate::Cloud::price(&Server::new(sek).unwrap(), &crate::cloud::Priced::Resource(&disk(10))).unwrap().currency, Currency::Sek, "zero is a number OF something");

    let dataset = ResourceId { provider: "server".into(), id: "dataset:gunnar".into() };
    assert_eq!(s.own_status(&dataset).unwrap(), ResourceStatus::Active);
    let stranger = ResourceId { provider: "server".into(), id: "storage/abc".into() };
    assert!(matches!(s.own_status(&stranger), Err(CloudError::UnknownResource(_))), "never a silent Gone");
    let elsewhere = ResourceId { provider: "upcloud".into(), id: "dataset:gunnar".into() };
    assert!(matches!(s.own_status(&elsewhere), Err(CloudError::UnknownResource(_))), "the provider is part of the identity");

    s.own_ensure(REF, &disk(10)).unwrap();
    s.own_destroy(&dataset).unwrap();
    s.own_destroy(&stranger).unwrap();
    s.own_destroy(&dataset).unwrap();
    assert_eq!(s.sold_gib(), 10, "on-hand iron is not returned to anyone: destroy moved nothing");
    assert_eq!(s.own_status(&dataset).unwrap(), ResourceStatus::Active, "the box is still there after a destroy");
    assert_eq!(Cloud::manifest(&s).zones, vec![Zone("this-box".into())]);
}

/// **The panel shows the box itself.** One `dataset` row sized from the
/// product's `total_bytes`, `provenance: server`, attached to the product, no
/// reference, priced at zero; the declared pool stands in when the product
/// could not measure; nothing is foreign.
#[test]
fn inventory_lists_the_data_set_as_this_box_with_provenance_server() {
    let s = server(Box300::measured(200, 300), 100);
    let inv = crate::ask::account(&s).unwrap();
    assert_eq!(inv.owned.len(), 1, "{inv:?}");
    let o = &inv.owned[0];
    assert_eq!((o.id.provider.as_str(), o.id.id.as_str(), o.kind, o.kind.name()), ("server", "dataset:gunnar", OwnedKind::Dataset, "dataset"));
    assert_eq!(o.gib, 300, "the product's total_bytes, not the pool");
    assert_eq!((o.attached_to.as_deref(), o.reference.as_deref(), o.provenance.as_str()), (Some("gunnar"), None, "server"));
    assert_eq!((o.monthly_minor, o.currency, o.zone.as_str()), (Some(0), Currency::Eur, "this-box"));
    assert_eq!((inv.foreign, inv.currency), (0, Currency::Eur));

    let s = server(Box300::unmeasured(), 100);
    assert_eq!(crate::ask::account(&s).unwrap().owned[0].gib, 100, "unmeasured: the declared pool is the only size there is");
}

/// Refuse twin at construction: an empty zone, and a state file that is there
/// but does not parse — a provider that guessed over it could grant twice.
#[test]
fn refuse_twin_an_empty_zone_or_a_corrupt_state_file_cannot_start() {
    let mut blank = ServerConfig::new(Box300::measured(200, 300), 100);
    blank.zone = Zone("  ".into());
    assert!(matches!(Server::new(blank), Err(CloudError::Refused(_))));

    let path = tmp("corrupt");
    std::fs::create_dir_all(path.parent().unwrap()).unwrap();
    std::fs::write(&path, b"{ not json").unwrap();
    let mut corrupt = ServerConfig::new(Box300::measured(200, 300), 100);
    corrupt.state_path = Some(path);
    let err = Server::new(corrupt).err().map(|e| e.to_string()).expect("must refuse");
    assert!(err.contains("does not parse") && err.contains("refusing to start"), "{err}");

    // A missing file is the first start, not an error.
    let mut fresh = ServerConfig::new(Box300::measured(200, 300), 100);
    fresh.state_path = Some(tmp("fresh"));
    assert!(Server::new(fresh).is_ok());
    assert_eq!(CONFIG_ENVS, &[] as &[&str], "no credential: there is no account");
}

/// **The quote's question is the grant's question, without the grant.**
/// `can_serve` refuses exactly what `ensure` would — the declared pool, then
/// the product's measurement — in the same words, and records nothing; and it
/// answers the box's declared ceilings on what no tenant owns.
#[test]
fn can_serve_answers_like_ensure_would_without_granting_and_declares_its_ceilings() {
    let product = Box300::measured(200, 300);
    let mut cfg = ServerConfig::new(product.clone(), 25);
    cfg.ceilings = [("cpu_millicores".to_string(), 4000u64), ("ram_bytes".to_string(), 8 * GIB)].into();
    let s = Server::new(cfg).unwrap();
    assert_eq!(s.own_can_serve(&[disk(10), disk(5)]), Ok(()), "15 of 25");
    assert_eq!(s.own_can_serve(&[]), Ok(()), "an order that buys no iron is served");
    let err = s.own_can_serve(&[disk(30)]).unwrap_err();
    assert!(err.contains("25 GiB declared") && err.contains("short 5 GiB"), "{err}");
    // Two disks that each fit and together do not: refused as a whole.
    let err = s.own_can_serve(&[disk(20), disk(10)]).unwrap_err();
    assert!(err.contains("short 5 GiB"), "{err}");
    assert_eq!((s.sold_gib(), s.grants().len()), (0, 0), "a quote grants NOTHING");

    // The measurement walls it the same way.
    product.say(Servable::Measured { servable_bytes: 4 * GIB, total_bytes: 300 * GIB, growth: Growth::Unknown(String::new()), ceilings: BTreeMap::new() });
    let err = s.own_can_serve(&[disk(10)]).unwrap_err();
    assert!(err.contains("gunnar measured") && err.contains("short 6.0 GiB"), "{err}");
    assert!(matches!(s.own_ensure(REF, &disk(10)), Err(CloudError::Refused(_))), "and ensure agrees");
    // A server or a plan change is refused by name here too.
    let err = s.own_can_serve(&[Resource::Compute { server: ServerId("this-box".into()), plan: "2xCPU-4GB".into() }]).unwrap_err();
    assert!(err.contains("only box"), "{err}");

    // The declared ceilings, by the product's own meter names.
    assert_eq!(s.own_ceilings(), [("cpu_millicores".to_string(), 4000u64), ("ram_bytes".to_string(), 8 * GIB)].into());
    assert!(crate::ask::ceilings(&server(Box300::unmeasured(), 100)).is_empty(), "nothing declared, nothing refused on their account");
}

/// **The real `[clouds.server]` build**, so the refusal below is the one a
/// deployment gets at start and not a function called in isolation.
fn build_block(product: Arc<dyn Product>, toml_ish: serde_json::Value, dir: &std::path::Path) -> Result<(), String> {
    let products = [product];
    build(&crate::factory::CloudBuild {
        name: "server",
        data_dir: dir,
        settings: monetize_pay::factory::Settings(&toml_ish),
        ceiling: None,
        products: &products,
    })
    .map(|_| ())
}

/// (r) **A `pool_gib` bigger than the disk the product measures is REFUSED AT
/// START, by name, with both numbers in it.**
///
/// The live front declared `pool_gib = 100` against a store the product
/// measures at 37.8 GiB (2026-09-18). Nothing broke, because `covers` overlays
/// the measurement on every grant — which is exactly what made it invisible:
/// the only moment anybody would have found out is when the 38th GiB was sold,
/// and until then every number derived from the declaration alone was a lie
/// about the box by nearly 3×.
///
/// Asserted on the REFUSAL and on its numbers, not on a bool: a start that
/// failed with "bad config" would be the same silence in a louder costume. The
/// twins are the three answers that must still start — a declaration inside the
/// disk, a product that measures what is left but not the total, and a product
/// that did not measure at all.
#[test]
fn a_declared_pool_bigger_than_the_measured_disk_refuses_to_start_and_names_both_numbers() {
    let dir = std::env::temp_dir().join(format!("monetize-cloud-server-pool-{}", std::process::id()));
    let _ = std::fs::remove_dir_all(&dir);
    std::fs::create_dir_all(&dir).unwrap();

    // The front: 100 GiB declared, 37 GiB measured.
    let err = build_block(Box300::measured(30, 37), serde_json::json!({ "pool_gib": 100 }), &dir).expect_err("a pool of disk that does not exist must not start");
    assert!(err.starts_with("[clouds.server]"), "{err}");
    assert!(err.contains("pool_gib = 100"), "the refusal does not name what was declared: {err}");
    assert!(err.contains("37.0 GiB"), "the refusal does not name what was measured: {err}");
    assert!(err.contains("63.0 GiB") && err.contains("does not exist"), "the refusal does not name the shortfall: {err}");

    // A declaration the disk can hold starts, and so does one exactly equal to
    // it — the boundary is "more than the disk", not "as much as".
    assert!(build_block(Box300::measured(30, 300), serde_json::json!({ "pool_gib": 100 }), &dir).is_ok());
    assert!(build_block(Box300::measured(0, 100), serde_json::json!({ "pool_gib": 100 }), &dir).is_ok(), "a full box may still have declared its own size");

    // **A product that did not measure is a warning, never a refusal**: a box
    // that is rebooting at start has not contradicted anybody, and taking the
    // deployment down over it would turn silence into a verdict.
    assert!(build_block(Box300::unmeasured(), serde_json::json!({ "pool_gib": 100 }), &dir).is_ok());
    // Nor does a product that reports what is LEFT but not the TOTAL: a
    // `total_bytes` of 0 is a measurement that did not happen, not a disk of no
    // size, and reading it as one would refuse every such deployment at start.
    let partial = Box300::measured(10, 300);
    partial.say(Servable::Measured { servable_bytes: 10 * GIB, total_bytes: 0, growth: Growth::Sealed("x".into()), ceilings: BTreeMap::new() });
    assert!(build_block(partial, serde_json::json!({ "pool_gib": 100 }), &dir).is_ok());
    let _ = std::fs::remove_dir_all(&dir);
}

/// ★ **g-dk-market / g-currencies: a box whose data set GROWS under a capped
/// cloud covers past its declared pool and past its current measurement — by
/// exactly the reach the grower answers, and not a byte more.**
///
/// MEASURED on the release-3 mock 2026-09-29: `default_cloud = "server"` with
/// `pool_gib = 37` on a 4×10 GiB set that the grow batcher enlarges through the
/// capped upcloud guest — after 30 GiB sold, every further 10 GiB order was
/// withheld, even though the set could grow to cover it. The reach is the
/// grower's answer ([`GrowReach`]); none answered is the old wall, unchanged.
#[test]
fn a_set_that_grows_under_its_cap_covers_past_its_pool_and_its_measurement_by_its_reach() {
    let product = Box300::measured(38, 40);
    let reach = GrowReach::default();
    let mut cfg = ServerConfig::new(product.clone(), 37);
    cfg.reach = reach.clone();
    let s = Server::new(cfg).unwrap();
    s.own_ensure("gunnar/functest/1", &disk(30)).unwrap();
    // The 30 GiB are written: the box measures 8 GiB left.
    product.say(Servable::Measured { servable_bytes: 8 * GIB, total_bytes: 40 * GIB, growth: Growth::AtRuntime("grows in place".into()), ceilings: BTreeMap::new() });
    let e = s.own_can_serve(&[disk(10)]).unwrap_err();
    assert!(e.contains("7 GiB free"), "no grower answered: the declared pool is the wall, as before: {e}");

    // The grower says the set can still grow by 216 GiB under the ceiling.
    reach.answer_with(Arc::new(|product: &str| (product == "gunnar").then_some(216 * GIB)));
    s.own_can_serve(&[disk(10)]).expect("the reach covers the pool's 7 GiB and the disk's 8 GiB");
    match s.own_ensure("gunnar/dk/1", &disk(10)).unwrap() {
        Ensured::OnHand { pool_free_after } => assert_eq!(pool_free_after, 37 + 216 - 30 - 10, "the grant is counted against the reach, not beside it"),
        other => panic!("{other:?}"),
    }
    let e = s.own_can_serve(&[disk(300)]).unwrap_err();
    assert!(e.contains("server cloud"), "past the reach is refused by the same wall: {e}");

    // A grower that cannot answer (no set, no ceiling, a sealed box) is no reach.
    reach.answer_with(Arc::new(|_: &str| None));
    assert!(s.own_can_serve(&[disk(10)]).is_err(), "an unanswered reach is not room");
}

/// ★ **Past both walls, QUEUED where the set grows later** (round 5.3, owner
/// 2026-10-08: "the customer must not know about a cap! it must be queued and
/// resized later"). The grower says the set is resized by its batcher
/// ([`GrowReach::queues`]): an order past the pool and the measurement is
/// GRANTED (recorded against the pool, nothing free after it) instead of
/// refused, and the next one is too — the batcher grows the set for what is
/// queued. With nobody answering `queues`, the wall refuses as before.
#[test]
fn past_both_walls_a_grant_is_queued_where_the_set_grows_later() {
    let product = Box300::measured(38, 40);
    let reach = GrowReach::default();
    let mut cfg = ServerConfig::new(product.clone(), 37);
    cfg.reach = reach.clone();
    let s = Server::new(cfg).unwrap();
    s.own_ensure("gunnar/functest/1", &disk(30)).unwrap();
    product.say(Servable::Measured { servable_bytes: 8 * GIB, total_bytes: 40 * GIB, growth: Growth::AtRuntime("grows in place".into()), ceilings: BTreeMap::new() });
    // The spend cap leaves no reach at all.
    reach.answer_with(Arc::new(|_: &str| Some(0)));
    assert!(s.own_can_serve(&[disk(500)]).is_err(), "nobody says the set grows later: the wall refuses, as before");

    reach.queue_with(Arc::new(|product: &str| product == "gunnar"));
    s.own_can_serve(&[disk(500)]).expect("past both walls, queued for the resize");
    match s.own_ensure("gunnar/past/1", &disk(500)).expect("granted, queued") {
        Ensured::OnHand { pool_free_after } => assert_eq!(pool_free_after, 0, "a queued grant leaves nothing free"),
        other => panic!("{other:?}"),
    }
    match s.own_ensure("gunnar/past/2", &disk(10)).expect("the next order is queued too") {
        Ensured::OnHand { pool_free_after } => assert_eq!(pool_free_after, 0),
        other => panic!("{other:?}"),
    }
    assert_eq!(s.own_room_left_bytes(), Some(0), "nothing left on hand while the queue waits");
    // Another product's set is not resized by this grower: the wall stands.
    reach.queue_with(Arc::new(|_: &str| false));
    assert!(s.own_can_serve(&[disk(10)]).is_err());
}

/// ★ **L1 — THE POOL FOLLOWS THE SET AS IT GROWS** (owner, test2 2026-10-05:
/// "500 GiB cannot"). `pool_gib = 37` is what the operator declared on a
/// 4 × 10 GiB set (37 GiB measured then). The grow batcher has since grown
/// the set to 120 GiB through the capped provider, and the 30 GiB sold are
/// written (80 GiB measured left). The declared side was `37 + reach − sold`
/// for ever — read once at install, the growth already bought never added —
/// so the box refused 50 GiB it had. It is now the declaration plus what the
/// set has grown by since it was made (re-read at every ask, the size it was
/// declared against kept in the state file), plus the reach.
#[test]
fn the_pool_follows_the_set_as_it_grows_not_the_size_it_was_declared_at() {
    let path = tmp("follows");
    let product = Box300::measured(37, 37);
    let reach = GrowReach::default();
    let mut cfg = ServerConfig::new(product.clone(), 37);
    cfg.reach = reach.clone();
    cfg.state_path = Some(path.clone());
    let s = Server::new(cfg.clone()).unwrap();
    s.own_ensure("gunnar/functest/1", &disk(30)).unwrap();

    // The batcher grows the set to 120 GiB; the 30 GiB sold are written.
    product.say(Servable::Measured { servable_bytes: 80 * GIB, total_bytes: 120 * GIB, growth: Growth::AtRuntime("grows in place".into()), ceilings: BTreeMap::new() });
    s.own_can_serve(&[disk(50)]).expect("37 declared + 83 grown − 30 sold = 90 GiB free, and the disk measures 80");
    match s.own_ensure("gunnar/dk/1", &disk(50)).unwrap() {
        Ensured::OnHand { pool_free_after } => assert_eq!(pool_free_after, 37 + 83 - 30 - 50),
        other => panic!("{other:?}"),
    }
    // A restart keeps the size the declaration was made against.
    let again = Server::new(cfg.clone()).unwrap();
    crate::ask::can_serve(&again, &[disk(10)], None).expect("the growth is still counted after a restart");
    let e = crate::ask::can_serve(&again, &[disk(41)], None).unwrap_err();
    assert!(e.contains("37 GiB declared") && e.contains("83 GiB the set has grown by") && e.contains("40 GiB free"), "{e}");

    // The reach adds on top: what the ceiling can still buy.
    reach.answer_with(Arc::new(|product: &str| (product == "gunnar").then_some(100 * GIB)));
    crate::ask::can_serve(&again, &[disk(120)], None).expect("40 free + 100 the set can still grow by");

    // A NEW declaration is measured against the set as it is when it is made.
    let mut raised = cfg;
    raised.pool_gib = 150;
    let raised = Server::new(raised).unwrap();
    reach.answer_with(Arc::new(|_: &str| None));
    let e = crate::ask::can_serve(&raised, &[disk(71)], None).unwrap_err();
    assert!(e.contains("150 GiB declared") && e.contains("70 GiB free") && !e.contains("grown by"), "{e}");
    let _ = std::fs::remove_dir_all(path.parent().unwrap());
}

/// ★ **cov-10 (estate-box 2026-10-06): the server cloud names its room left**
/// — the two walls `covers` checks, as a size: 37 declared − 30 sold = 7 GiB
/// on a disk that measures 35 GiB servable; the disk's 3 GiB when it is the
/// smaller. What a greyed offer's "Larger than the room left here (N)" says.
#[test]
fn the_server_cloud_names_its_room_left_as_the_smaller_wall() {
    let product = Box300::measured(37, 35);
    let s = Server::new(ServerConfig::new(product.clone(), 37)).unwrap();
    s.own_ensure("gunnar/functest/1", &disk(30)).unwrap();
    assert_eq!(s.own_room_left_bytes(), Some(7 * GIB), "the pool: 37 declared − 30 sold");
    assert!(s.own_can_serve(&[disk(10)]).unwrap_err().contains("7 GiB free"));
    product.say(Servable::Measured { servable_bytes: 3 * GIB, total_bytes: 37 * GIB, growth: Growth::Unknown("x".into()), ceilings: BTreeMap::new() });
    assert_eq!(s.own_room_left_bytes(), Some(3 * GIB), "the disk, when it is the smaller wall");
}

/// R5a: the server cloud grants the ONE box's own disk. A panic that once held
/// its pool must not make every later order and every room reading panic: a
/// grant is persisted before the call answers, so the pool a panic leaves is a
/// pool that was written.
#[test]
fn a_poisoned_pool_still_grants_and_answers() {
    let s = server(Box300::measured(200, 300), 100);
    let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
        let _held = s.state.lock();
        panic!("a panic while the pool is held");
    }));
    assert!(s.state.is_poisoned());
    assert_eq!(s.own_ensure(REF, &disk(10)).expect("a grant after the panic"), Ensured::OnHand { pool_free_after: 90 });
    assert_eq!(s.sold_gib(), 10);
    assert_eq!(s.grants().len(), 1);
    assert!(s.own_can_serve(&[disk(10)]).is_ok());
    assert!(s.own_room_left_bytes().is_some());
}