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

nordisk/monetize

crates/monetize-product/src/lib.rs

//! **The product as monetize serves it: its facts, and its estate over the pull channel.**
//!
//! monetize handles MANY products (gunnar.rs, holger.rs, ...) and knows none of them by name.
//! A product is two things here, and neither is a per-product adapter:
//!
//! * its **facts** ([`ProductFacts`]): its id, its meters, its words, its ticket and test
//!   buttons, sent by the product's own signed guest and read once by the host;
//! * its **estate** ([`pulled::Pulled`]): the newest report of the estate's main
//!   ([`pull::ProductReport`]) for every answer, and a signed order the main PULLS for every
//!   change. monetize never calls into an estate. [`Served`] holds the two side by side.
//!
//! ```text
//!   estate main ──report (pull channel)──▶ Pulled ──▶ monetize core
//!   estate main ◀──signed order, pulled─── Pulled ◀── monetize core
//! ```
//!
//! # Two directions, both facts
//!
//! * [`pulled::Pulled::read_usage`]: what a tenant is consuming, in the product's own units
//!   (gunnar: pack bytes, read-cache bytes, LFS bytes, tombstoned bytes, open-store RAM;
//!   holger: whatever holger meters), read from the main's report as [`Usage`], a flat bag of
//!   named meters, so core never has product-specific fields.
//! * [`pulled::Pulled::push_entitlement`]: the verdict, queued as a signed entitlement fact the
//!   main pulls and appends to its attestation log.
//!
//! # Growth
//!
//! A box grows through the v2 pull path only: a paid step is a signed grow go-ahead the box
//! pulls, and the box asks `Fleet.Ask{grow_set}` to size its own data disks (monetize-server
//! `estate_order::sold::grow_own`, journaled by `monetize_cloud::grow`). An order past the room
//! is queued with [`pulled::Pulled::queue_room`] (a signed `grow_room` order). There is no
//! appliance grow drive here.
//!
//! # monetize never blocks the product
//!
//! Counting is done **inside the product, always, open source**: a self-hoster wants the
//! numbers too. monetize only *reads* them. If monetize is down the product keeps serving on
//! its cached entitlement; the next report and the next pulled order catch up.

use std::collections::BTreeMap;

/// The registration seam: how a product gets into a server without the server
/// knowing its name. A build registers what it links; the server looks ids up.
pub mod factory;
pub use factory::{body_secret_placeholder, GuestSpec, HttpAuth, ProductBuild, ProductFactory, PULLED_NEVER_DIALLED};

/// **Reading a daily `*_today` counter out of a series of readings** — the fold
/// behind the console's SILENT and FRICTION lists. Pure; see the module doc for
/// why summing the readings is the wrong arithmetic and why it lives here.
pub mod flow;
pub mod guardian;
pub mod pull;
pub mod pulled;


/// A tenant, as the product names it. gunnar: the `Namespace` name (`team/sub`).
#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug, serde::Serialize, serde::Deserialize)]
#[derive(Default)]
pub struct TenantId(pub String);

/// **Whether the product will actually POLICE a cap on this meter.**
///
/// CPU and RAM exist as quota even where gunnar cannot cap them, and that gap is
/// narrowed without redoing the engine.
///
/// An order may cap any declared meter. But a cap on a meter nothing checks is a
/// promise nobody keeps, and the tenant cannot tell the difference from the
/// invoice. So the product says, per meter, which kind of promise it is — and
/// the console shows it beside the number rather than letting every cap look
/// like a guarantee.
///
/// This is the smallest honest way to let CPU and RAM be sold: they ARE bought,
/// with real money, and they DO change what the box can do — they are simply not
/// something a git server polices per namespace. Saying so is better than either
/// refusing to sell them or pretending they are enforced.
#[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
pub enum Enforcement {
    /// **The product refuses when over.** A cap here is a wall the tenant hits.
    /// gunnar: `pack_bytes` (receive-pack refused before the bytes),
    /// `cache_bytes` (the explode budget).
    Enforced,
    /// **Counted and reported, not policed.** The number is true and monetize
    /// can bill on it or a human can act on it, but nothing refuses. A cap here
    /// is a threshold, not a wall.
    Measured,
    /// **Neither counted nor policed BY THE PRODUCT — it is iron monetize
    /// bought.** CPU and RAM: a bigger plan at the provider, real money, real
    /// effect, and no per-namespace check anywhere in the product. The
    /// provider's invoice is the enforcement.
    Provisioned,
}

impl Enforcement {
    /// One line for the console, beside the cap.
    pub fn describe(self) -> &'static str {
        match self {
            Enforcement::Enforced => "the product refuses when over",
            Enforcement::Measured => "counted and reported; nothing refuses",
            Enforcement::Provisioned => "provisioned at the cloud provider; not policed by the product",
        }
    }

    /// Is a cap on this meter a wall the tenant will actually hit?
    pub fn is_a_wall(self) -> bool {
        matches!(self, Enforcement::Enforced)
    }
}

/// **What a cap on this meter is MADE OF** — and therefore what UNIT the price
/// list prices it in and what iron, if any, the transaction buys for it.
///
/// Three of the four are capacity: bytes and cores, priced per GiB or core
/// per month, and (for disk) bought from a cloud. The fourth, [`Backing::Units`],
/// is a COUNT that is nevertheless SOLD — a property, a seat, a
/// mailbox — priced flat per unit per month and buying nothing. It exists
/// because a meter with no backing at all costs nothing to promise, and a
/// product whose whole price is a count (a per-property price) would
/// otherwise open a checkout for zero.
///
/// A plan that sells bytes it never buys is the one bug here that silently
/// converts money into nothing. Since the plan catalogue went (2026-09-05), an
/// ORDER names caps and nothing else, and this is the field that turns a cap
/// into a purchase: `monetize::order::resources_for` translates the DELTA on a
/// disk-backed meter into a `Resource::Disk`, and `monetize::PriceList` prices
/// each backing's delta by its own unit.
///
/// It lives on the meter and not on the order because only the PRODUCT knows
/// what its meter is made of — that `pack_bytes` is block storage and `pushes`
/// is a count of events that costs nothing to promise.
#[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
pub enum Backing {
    /// **Bytes of block storage.** A delta here becomes a `Resource::Disk` of
    /// `ceil(bytes / 2^30)` GiB, and is priced per GiB per month
    /// (`disk_gib_month` on the price list).
    DiskBytes,
    /// **CPU, in millicores (1000 = one core).** Priced per core per month
    /// (`cpu_core_month`). monetize does NOT translate millicores into a
    /// provider's plan name (`2xCPU-4GB` is a name, and a table of names goes
    /// stale every time a provider renames a size), so a delta here buys no
    /// cloud resource of its own: the ceiling is the product's own measurement
    /// (`Pulled::can_absorb`) and the box's declared limit
    /// (`RoomLeft::ceilings`).
    CpuMillicores,
    /// **RAM, in bytes.** Priced per GiB per month (`ram_gib_month`); the same
    /// ceiling rule as [`Backing::CpuMillicores`].
    RamBytes,
    /// **A counted, sold unit that is not iron** — a property register's
    /// `fastigheter`, a seat, a mailbox. Priced flat per unit per month (`unit_month`, the
    /// delta times the price, no rounding), and it buys NO cloud resource:
    /// `resources_for` ignores it exactly as it ignores CPU and RAM. The
    /// ceiling is the product's own answer (`Pulled::can_absorb`).
    ///
    /// Without this a product priced per count had no way to be priced at all:
    /// a meter with no backing "costs nothing to promise", which is right for
    /// `pushes` and wrong for a property that is the whole subscription.
    Units,
}

impl Backing {
    /// The price-list key this backing is priced under, and the unit it means:
    /// `disk_gib_month` | `cpu_core_month` | `ram_gib_month` | `unit_month`.
    pub fn unit(self) -> &'static str {
        match self {
            Backing::DiskBytes => "disk_gib_month",
            Backing::CpuMillicores => "cpu_core_month",
            Backing::RamBytes => "ram_gib_month",
            Backing::Units => "unit_month",
        }
    }

    /// Every backing, in the order the price list is written in.
    pub const ALL: [Backing; 4] = [Backing::DiskBytes, Backing::CpuMillicores, Backing::RamBytes, Backing::Units];

    /// The backing a price-list key names, if any.
    pub fn from_unit(unit: &str) -> Option<Backing> {
        Backing::ALL.into_iter().find(|b| b.unit() == unit)
    }

    /// **Price `delta` of this backing at `minor_per_unit_month`, for one
    /// month**, in minor units. Integer arithmetic, rounded UP to the unit for
    /// bytes (a customer who asks for one byte over a GiB is sold the next GiB,
    /// which is what the provider sells us), exact per millicore for CPU
    /// (`minor × millicores / 1000`), and flat for a counted unit (`delta ×
    /// minor` — five properties cost five times one).
    pub fn price_month(self, delta: u64, minor_per_unit_month: u64) -> u64 {
        const GIB: u64 = 1 << 30;
        match self {
            Backing::DiskBytes | Backing::RamBytes => delta.div_ceil(GIB).saturating_mul(minor_per_unit_month),
            Backing::CpuMillicores => u64::try_from(u128::from(delta) * u128::from(minor_per_unit_month) / 1000).unwrap_or(u64::MAX),
            Backing::Units => delta.saturating_mul(minor_per_unit_month),
        }
    }
}

/// **WHERE a meter's number is even meaningful: per tenant, or per product.**
///
/// Disk is the whole game, and it is per tenant. CPU and RAM stay in the model,
/// but for gunnar they are not interesting per user — they are interesting per
/// product: a tenant does not own cores, the box does.
///
/// Orthogonal to [`Enforcement`], and the pair is what makes a fleet total
/// honest. `Enforcement` says whether a cap is a WALL. Scope says whether adding
/// one tenant's figure to another's produces anything at all:
///
/// * `pack_bytes` is per tenant, so nine tenants' pack bytes sum to the fleet's
///   pack bytes;
/// * `cpu_millicores` is per product, so nine tenants' CPU does not sum to
///   anything — nobody measured a tenant's cores, and adding nine numbers that
///   were never measured is arithmetic on nothing.
///
/// **It is the PRODUCT's call, not a global rule.** gunnar meters disk per tenant
/// and CPU per product; another product may legitimately meter CPU per user, or
/// disk only in total. [`ProductFacts::meters`] is already that seam, and nothing
/// outside a product decides which of its meters are per-tenant. `monetize::fleet`
/// sums only [`Scope::Tenant`], whatever a product declares.
#[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
pub enum Scope {
    /// **Measured, or sold, per TENANT.** A fleet figure is the sum over tenants.
    /// The default, because it is what a meter usually is.
    Tenant,
    /// **Measured, or bought, for the WHOLE PRODUCT.** It appears ONCE, on the
    /// product screen, and never in a per-tenant sum. A cap on it may still be
    /// sold — CPU and RAM are bought with real money — it simply is not a
    /// quantity any one tenant holds.
    Product,
}

impl Scope {
    /// May a figure for this meter be summed across tenants? Only [`Scope::Tenant`].
    pub fn sums_across_tenants(self) -> bool {
        matches!(self, Scope::Tenant)
    }

    /// `tenant` | `product` — the wire and JSON spelling, one writer.
    pub fn name(self) -> &'static str {
        match self {
            Scope::Tenant => "tenant",
            Scope::Product => "product",
        }
    }
}

/// **One meter a product declares**: its name, what it means, and whether a cap
/// on it is policed. An order may only cap a declared meter, and the console
/// shows [`Enforcement`] beside the number.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct Meter {
    /// The wire name, and the key in [`EntitlementFact::caps`] and [`Usage::meters`].
    pub name: &'static str,
    /// One line, the plugin's own words.
    pub meaning: &'static str,
    pub enforcement: Enforcement,
    /// **Does [`pulled::Pulled::read_usage`] carry a reading for this meter?**
    ///
    /// A meter exists to do one or both of two jobs: carry a usage READING, and
    /// accept a CAP. Most do both. Two kinds do not, and conflating them was
    /// caught by a test rather than by thinking:
    ///
    /// * `cpu_millicores`, `ram_bytes` — the product has no idea what was
    ///   bought; monetize does, from the order. Reporting `0` would read as "you
    ///   have none", which is worse than saying nothing.
    /// * `concurrent_transfers` — a cap the engine enforces instantly, with no
    ///   stored reading to bill from. The limit is real; the gauge does not exist.
    ///
    /// So a plugin's `read_usage` must report EXACTLY the meters with this set,
    /// which is an equality a test can hold in both directions: an undeclared
    /// reading is a leak, and a declared reading that stopped arriving is a
    /// silently emptied bill.
    pub reports_usage: bool,
    /// **What a cap on this meter is made of**, or `None` when a cap here costs
    /// nothing to grant (a count of pushes, a number of repositories the
    /// existing box already holds). `Some` is what makes an order on this meter
    /// a PURCHASE: the delta is priced by the backing's unit and, for disk,
    /// bought as iron. See [`Backing`].
    pub backing: Option<Backing>,
    /// **Whether this meter's number is per tenant or per product.** See
    /// [`Scope`]; it is what keeps a fleet total from adding up figures nobody
    /// measured per tenant.
    pub scope: Scope,
}

impl Meter {
    /// A meter that both reports a reading and may be capped — the common case.
    pub const fn new(name: &'static str, meaning: &'static str, enforcement: Enforcement) -> Meter {
        Meter { name, meaning, enforcement, reports_usage: true, backing: None, scope: Scope::Tenant }
    }

    /// A meter that may be CAPPED but carries no reading. See
    /// [`Meter::reports_usage`] for the two kinds and why `0` is not a truthful
    /// substitute.
    pub const fn cap_only(name: &'static str, meaning: &'static str, enforcement: Enforcement) -> Meter {
        Meter { name, meaning, enforcement, reports_usage: false, backing: None, scope: Scope::Tenant }
    }

    /// The same meter, declaring what a cap on it is MADE OF — and so what an
    /// order on it buys and what the price list prices it by. A
    /// [`Enforcement::Provisioned`] meter without one is a cap monetize can
    /// neither price nor buy; gunnar's own test holds its list to that.
    pub const fn with_backing(self, backing: Backing) -> Meter {
        Meter { backing: Some(backing), ..self }
    }

    /// The same meter, measured for the WHOLE PRODUCT rather than per tenant —
    /// so a fleet total leaves it out instead of adding up figures nobody took.
    /// See [`Scope`].
    pub const fn per_product(self) -> Meter {
        Meter { scope: Scope::Product, ..self }
    }
}

/// **What a product says it could still serve, or why it could not say.**
///
/// The other half of the oversell number: `sum(what has been sold) − servable`.
/// Without it monetize could sell ten tenants 10 GiB each on a box with 55 GiB
/// servable and nothing would object until the sixth push.
///
/// The two arms are the distinction `BASE-MODEL.md` rule 3 turns on and the same
/// one [`pulled::Pulled::can_absorb`] already makes: **an unanswered capacity question
/// is a measurement that did not happen, not a full disk and not an empty one.**
/// The live gunnar.rs appliance predates gunnar's `Admin.Capacity` RPC and
/// answers [`Servable::Unmeasured`] today; a fleet total that turned that into a
/// zero would report every deployment as catastrophically oversold, and one that
/// turned it into infinity would report every deployment as fine. Neither is a
/// measurement.
/// **Can this product's store grow while it runs?** Carried on
/// [`Servable::Measured`] beside the bytes, because the two answer different
/// questions and an operator acts differently on each: a full box that can grow
/// wants a disk; a full box that is SEALED wants its set enlarged by an operator
/// maintenance operation (the volumes it already has, grown in place) or a fresh
/// install onto a larger one, and a disk bought for it is billed and invisible.
///
/// Each arm carries the PRODUCT's own remediation sentence (gunnar's
/// `remediation()`), so the console renders what the product said rather than a
/// paraphrase of it — `UI.md` screen 2's `⚠ cannot grow` badge holds exactly
/// that paragraph.
#[derive(Clone, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
pub enum Growth {
    /// Cannot gain capacity while it runs, and nothing on the machine can give
    /// it any. `GROWTH_SEALED` on gunnar's wire.
    Sealed(String),
    /// The product will not grow the set itself, but an operator can, out of
    /// band. `GROWTH_OPERATOR_ONLY`.
    OperatorOnly(String),
    /// The product could not tell. NOT a promise that it can grow.
    Unknown(String),
    /// **Grows at its next restart** once every member volume has been
    /// enlarged to one equal size (gunnar's `GROWTH_AT_RESTART`, 2026-09-14).
    /// Still no hot-plug: a disk ATTACHED to it is as invisible as under
    /// `Sealed`; what helps is the outside actor enlarging the members
    /// (`monetize_cloud::grow`).
    AtRestart(String),
    /// **Grows without a restart**: the product drains, unmounts and grows into
    /// enlarged members while its control plane stays up (gunnar's
    /// `GROWTH_AT_RUNTIME`).
    AtRuntime(String),
}

impl Growth {
    /// The wire word: `sealed` | `operator_only` | `unknown` | `at_restart` |
    /// `at_runtime`.
    pub fn name(&self) -> &'static str {
        match self {
            Growth::Sealed(_) => "sealed",
            Growth::OperatorOnly(_) => "operator_only",
            Growth::Unknown(_) => "unknown",
            Growth::AtRestart(_) => "at_restart",
            Growth::AtRuntime(_) => "at_runtime",
        }
    }

    /// The product's own sentence about what to do.
    pub fn detail(&self) -> &str {
        match self {
            Growth::Sealed(s) | Growth::OperatorOnly(s) | Growth::Unknown(s) | Growth::AtRestart(s) | Growth::AtRuntime(s) => s,
        }
    }
}

#[derive(Clone, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
pub enum Servable {
    /// The product measured its own store.
    Measured {
        /// **Bytes that may still be handed to tenants**, beyond what they already
        /// hold, after whatever reserve the product keeps for itself. gunnar's
        /// `servable_bytes` (`free − disk floor`) — never its `free_bytes`, which
        /// includes a reserve monetize must not sell.
        servable_bytes: u64,
        /// The whole store, reserve and all. `used + servable_bytes` is what the
        /// fleet could grow to; `total_bytes` is larger than that by the reserve
        /// and by anything on the filesystem that is not tenant data. 0 when the
        /// product measures what is left but not what there is in total.
        total_bytes: u64,
        /// **Whether the store can EVER hold more than `total_bytes`.** A
        /// measurement of the box, independent of the bytes: gunnar's
        /// `Admin.Capacity` answers it as `growth`, and it is the field that
        /// stops "buy another disk" being the reflex answer to a full fleet.
        growth: Growth,
        /// **The box's MEASURED ceilings on product-scope meters**, by the
        /// product's own meter name: gunnar's `cpu_millicores_total` and
        /// `ram_bytes_total` (Admin.Capacity, since gunnar 744bf72d) land here
        /// as `cpu_millicores` / `ram_bytes`. A meter ABSENT here was not
        /// measured — the wire spells that as an absent field, never as 0, and
        /// a reader must never take a 0 as a ceiling. An order that would
        /// raise a product-scope cap past a number here is refused by name;
        /// the box's DECLARED ceilings (`RoomLeft::ceilings`) carry only
        /// for a meter this map does not hold.
        ceilings: BTreeMap<String, u64>,
    },
    /// **It could not be asked, or would not say — which is not zero.** The
    /// string names WHICH: an RPC the deployed build predates, a control plane
    /// that is down, a plugin that was never wired.
    Unmeasured(String),
}

/// One named meter. Names are the product's, documented in its plugin crate, e.g.
/// `pack_bytes`, `cache_bytes`, `lfs_bytes`, `tombstoned_bytes`, `open_store_ram_bytes`,
/// `pushes`, `anonymous_reads`. Values are the product's units, usually bytes or counts.
#[derive(Clone, PartialEq, Eq, Debug, Default, serde::Serialize, serde::Deserialize)]
pub struct Usage {
    pub meters: BTreeMap<String, u64>,
    pub measured_at_unix_ms: u64,
}

/// **One page of a BATCHED usage read** — [`pulled::Pulled::read_usage_page`]'s
/// question (2026-10-06).
///
/// The poller used to ask [`pulled::Pulled::read_usage`] once per tenant per tick: at
/// 1,000 tenants that was ~25–30 s of 1.4 cores on gunnar's main every five
/// minutes, linear in tenants, and at ~10,000 the whole period. A page answers
/// many tenants in ONE call, and `changed_since` lets the product answer only
/// the tenants whose usage moved — so a quiet estate costs a few calls, not one
/// per tenant.
///
/// **Who a listed tenant IS** ([`pulled::Pulled::tenant_owners`], 2026-10-08): the product's own
/// word for the namespace's owner, so a SELLING shop can give every PERSON's account a fact
/// (no account there is "no fact = uncapped") and leave the SELLER's own alone.
///
/// Every field defaults, and neither side denies unknown fields.
#[derive(Clone, PartialEq, Eq, Debug, Default, serde::Serialize, serde::Deserialize)]
#[serde(default)]
pub struct TenantOwner {
    pub tenant: TenantId,
    /// The tenant is a PERSON's own account (gunnar: `owner_kind` USER). False for a group,
    /// a clan, a machine's, or a namespace of no principal.
    pub person: bool,
    /// The owner holds a SERVER-scope grant at the product (the operator — the seller): the
    /// shop's own, never a customer. Only the product says so; nothing a customer does sets it.
    pub seller: bool,
}

/// Every field defaults, and neither side denies unknown fields: an older and a
/// newer build of either side read each other's JSON.
#[derive(Clone, PartialEq, Eq, Debug, Default, serde::Serialize, serde::Deserialize)]
pub struct UsagePageReq {
    /// `""` asks for the first page; otherwise the previous page's
    /// [`UsagePage::next_page_token`], unchanged.
    #[serde(default)]
    pub page_token: String,
    /// At most this many rows; 0 = the product's own default.
    #[serde(default)]
    pub page_size: u32,
    /// `""` = every tenant. Otherwise a [`UsagePage::cursor`] an earlier read
    /// answered: only the tenants whose usage CHANGED since. A product that can
    /// no longer honour it (it restarted, the day turned) answers every tenant
    /// and says so with [`UsagePage::changed_only`] = false.
    #[serde(default)]
    pub changed_since: String,
}

/// One tenant's row in a [`UsagePage`].
#[derive(Clone, PartialEq, Eq, Debug, Default, serde::Serialize, serde::Deserialize)]
pub struct TenantUsage {
    pub tenant: TenantId,
    pub usage: Usage,
}

/// **How long a whole keep boot may take** (audit 7, 2026-10-18; lane G9, A7 row 9): 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. The shop holds its Upgrade
/// call this long (`monetize_cloud::lay::KEEP_BOOT_WITHIN`), and every product's front waits on
/// it exactly as long (`monetize_embed::fleet::KEEP_BOOT_WITHIN`, Gunnar b74ca801): a front
/// that gave up first would report a failure while the shop still works. ONE constant, here,
/// because the shop's cloud crate and the fronts' embed crate both stand on this one.
pub const KEEP_BOOT_WITHIN: std::time::Duration = std::time::Duration::from_secs(120 * 60);

/// **The mark of a sold estate's tenant on the ONE ledger** (5.4, ONE monetize, G16): a
/// private Gunnar runs no monetize of its own, so its tenants are accounts of product `gunnar`
/// in the ONE ledger as `@<estate>/<namespace>`. `@` is no character of a gunnar namespace
/// (`gunnar-core` sandbox rules), so no tenant of the product's own estate reads as one.
pub const ESTATE_MARK: char = '@';

impl TenantId {
    /// `namespace` on the sold estate `estate`, as the ONE ledger keys it: `@<estate>/<namespace>`.
    pub fn on_estate(estate: &str, namespace: &str) -> Self {
        TenantId(format!("{ESTATE_MARK}{estate}/{namespace}"))
    }

    /// `(estate, namespace)` when this is a sold estate's tenant ([`Self::on_estate`]); `None`
    /// for a tenant of the product's own estate.
    pub fn estate(&self) -> Option<(&str, &str)> {
        let (estate, namespace) = self.0.strip_prefix(ESTATE_MARK)?.split_once('/')?;
        (!estate.is_empty() && !namespace.is_empty()).then_some((estate, namespace))
    }
}

/// **One page of a batched usage read** — [`pulled::Pulled::read_usage_page`]'s
/// answer. See [`UsagePageReq`].
#[derive(Clone, PartialEq, Eq, Debug, Default, serde::Serialize, serde::Deserialize)]
pub struct UsagePage {
    /// The readings, in the product's order. MAY include names that
    /// [`pulled::Pulled::list_tenants`] does not list (a product's own machine
    /// accounts); a caller keys by tenant and ignores the rest.
    #[serde(default)]
    pub rows: Vec<TenantUsage>,
    /// `""` = this was the last page.
    #[serde(default)]
    pub next_page_token: String,
    /// What to pass as [`UsagePageReq::changed_since`] next tick. The same on
    /// every page of one read: it is taken when the read BEGAN, so a change that
    /// lands while the pages are fetched is answered again next time rather than
    /// lost.
    #[serde(default)]
    pub cursor: String,
    /// True when `changed_since` was honoured and `rows` holds only the tenants
    /// that changed; false when every tenant is in the read.
    #[serde(default)]
    pub changed_only: bool,
    /// When the product took the reading this page answers from.
    #[serde(default)]
    pub as_of_unix_ms: u64,
}

/// **One tenant's FLOW for one UTC day** — what MOVED, as against what is held.
///
/// Every number in [`Usage`] is a STOCK reading: it answers *how much is on the
/// product's disk*, and it reads exactly the same for a customer who worked
/// this morning and one who last touched the product in March. monetize could
/// therefore bill nine tenants correctly and not know that six of them had
/// stopped. This is the other kind, and it is what the SILENT and FRICTION
/// lists are made of.
///
/// **The meters are the product's own names**, exactly as [`Usage::meters`] is,
/// and for the same reason: core has never held a product-specific field and
/// must not gain one here. gunnar fills `bytes_in`, `bytes_out`, `pushes`,
/// `fetches`, `lfs_bytes_in`, `lfs_bytes_out`, `refused`, its three
/// `refused_*` reasons, `auth_failures`, `distinct_principals` and the two
/// `last_*_unix_ms` timestamps. Another product will fill something else, and
/// nothing here needs to know.
///
/// **A day with no traffic has NO ROW.** Not a row of zeroes: the caller knows
/// which days it asked for, and zero-filling would put a bar of height zero
/// beside a bar that means *not counted*.
#[derive(Clone, PartialEq, Eq, Debug, Default, serde::Serialize, serde::Deserialize)]
pub struct ActivityDay {
    /// Midnight UTC of the day, in Unix milliseconds.
    pub day_unix_ms: u64,
    /// The product's own meter names. See the type's doc.
    pub meters: BTreeMap<String, u64>,
}

/// **A tenant's kept activity, and how far back the product remembers.**
///
/// The second half is not decoration. An empty `days` means one of two opposite
/// things — *nothing happened* or *you asked about a time the product no longer
/// remembers* — and only `retained_days` can tell them apart. A console that
/// drew an empty series as "silent" without it would report every tenant on a
/// freshly installed box as a lapsed customer.
#[derive(Clone, PartialEq, Eq, Debug, Default, serde::Serialize, serde::Deserialize)]
pub struct Activity {
    /// Oldest first. Days with no traffic are absent.
    pub days: Vec<ActivityDay>,
    /// How many days the product keeps. `0` when it would not say.
    pub retained_days: u64,
}

/// The verdict monetize pushes back. Product-agnostic; the plugin maps it to the
/// product's own enum (gunnar: `EntitlementState`).
#[derive(Clone, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
pub struct EntitlementFact {
    pub tenant: TenantId,
    /// **The ORDER this verdict came from** — the ledger reference
    /// (`<product>/<tenant>/<date>+<caps>`) for a purchase, an operator's own
    /// label (or nothing) for a hand-set fact. The field is still called `plan`
    /// on the wire and in the signed canonical form (`monetize_embed::signing`),
    /// because gunnar verifies that form and a renamed field would invalidate
    /// every signature a deployed gunnar checks; the CONTENT is an order
    /// reference since the plan catalogue went on 2026-09-05.
    pub plan: String,
    pub state: State,
    pub paid_until_unix_ms: Option<u64>,
    /// Per-meter caps the product enforces itself (gunnar: `pack_quota_bytes`,
    /// `explode_budget_bytes`). Absent = product default.
    pub caps: BTreeMap<String, u64>,
    /// `operator` | `payment:<vendor>:<reference>` — lands in the product's attestation log.
    pub source: String,
    /// Ed25519 over the canonical JSON of the fields above, by monetize-server's key.
    /// **The V1 form** — it does NOT cover [`EntitlementFact::issued_unix_ms`], and
    /// that is deliberate: it is the signature an appliance built before
    /// 2026-09-17 computes, and it must keep verifying there for ever. See
    /// [`Self::issued_signature`].
    pub signature: Vec<u8>,
    /// **When monetize issued this verdict — the field that makes a fact good
    /// ONCE.**
    ///
    /// Without it an `EntitlementFact` is replayable for ever: capture a `Paid`
    /// fact, wait for the tenant to lapse, push the captured bytes back, and the
    /// signature still verifies because it is a real signature. `paid_until`
    /// cannot tell the two apart — a lapse keeps the date and moves the ladder —
    /// so the only thing that can is something MONOTONIC inside the signed form.
    /// A product refuses a fact that is not newer than the one it holds
    /// (gunnar: `EntitlementSlot::set`).
    ///
    /// Unix milliseconds, and not a sequence number, for the same reason
    /// [`crate`]'s sibling [`monetize_embed::signing::Snapshot`] chose one: a
    /// clock needs no durable per-tenant counter on monetize's side, so a
    /// restored ledger cannot rewind one and mint facts every appliance in the
    /// field then refuses for ever. One idea in this system, not two.
    ///
    /// `None` is a fact signed before this field existed. It stays legal, and a
    /// product accepts it — until that product has seen ONE stamped fact for the
    /// tenant, after which the unstamped form is a downgrade and is refused.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub issued_unix_ms: Option<u64>,
    /// **The V2 signature**: Ed25519 over the canonical JSON of every field
    /// above INCLUDING `issued_unix_ms`
    /// (`monetize_embed::signing::fact_message_issued`).
    ///
    /// Two signatures and not one, because a fact has to be readable by two
    /// generations of appliance at once. An appliance that predates this field
    /// reads only fields 1-7 off the wire, computes the V1 form, and checks
    /// [`Self::signature`] — so it accepts a stamped fact unchanged, and a
    /// paying customer on an un-upgraded box loses nothing. An appliance that
    /// knows the field checks BOTH, so the issue time is signed and cannot be
    /// added, moved or bumped by whoever relays the fact.
    ///
    /// Empty exactly when `issued_unix_ms` is `None`; neither is legal without
    /// the other.
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub issued_signature: Vec<u8>,
}

/// The ladder. Numbers (grace/retention days) are the deployment's policy
/// (`monetize::Policy`), not the enum's.
#[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
pub enum State {
    Free,
    Paid,
    Grace,
    Suspended,
    Retention,
}

#[derive(Clone, Debug)]
pub enum ProductError {
    /// The product's control plane refused (auth, unknown tenant).
    Refused(String),
    /// Product unreachable. Retryable; nothing was written.
    Unavailable(String),
}

impl std::fmt::Display for ProductError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            ProductError::Refused(r) => write!(f, "product refused: {r}"),
            ProductError::Unavailable(r) => write!(f, "product unavailable: {r}"),
        }
    }
}
impl std::error::Error for ProductError {}

/// **Where the room for a target comes from, as the product measured it** —
/// what [`pulled::Pulled::can_absorb`] answers when it can serve.
///
/// A yes is two different facts, and the difference is money. gunnar's
/// `Admin.Capacity` answers a `verdict` and a `growth`, and the three readings
/// of them a seller can act on are: there is room already (buy nothing), there
/// is no room but a bought disk will be used (buy, and somebody has to extend a
/// filesystem), and there is no room and a bought disk will NOT be used (refuse).
/// The third is a refusal and never reaches this type. Before this type existed
/// a yes was `Ok(())` and the cloud was asked to `ensure` a disk regardless. With
/// the front's configuration (`pool_reserve_gib` covering the whole data set)
/// UpCloud's pool arithmetic answers "short" for every order, so every yes
/// bought and attached a disk to an appliance that had just said it had room —
/// read from the code and the config, not from a bill.
///
/// The transaction turns it into `monetize_cloud::Room` for the provider, so
/// the cloud is told what the product measured and never has to know which
/// product that was.
#[derive(Clone, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
pub enum Absorb {
    /// **Served out of what the product already has.** Nothing may be bought
    /// for it: a disk bought on top of this answer is billed and not needed.
    OnHand,
    /// **Short today, and bought iron becomes usable** — by the hand `note`
    /// names, in the product's own words (gunnar: an operator extends the
    /// filesystem out of band, and gunnar measures the larger one on its next
    /// look). Carried onto the purchase row so the ledger records that
    /// somebody still has a job to do after the disk is attached.
    WithIron { note: String },
}

/// **What a metered verb answered** — see [`pulled::Pulled::metered_door`]. Three
/// arms, because "nobody could ask" is neither a paid build nor a community
/// one, and a deployment that sells against any of the last two is selling
/// something it cannot deliver.
#[derive(Clone, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
pub enum MeteredDoor {
    /// **The box answered a metered verb.** It has the paid services compiled
    /// in; the string is whatever the plugin wants to record about the proof
    /// (the verb it called, the build it saw).
    Enforcing(String),
    /// **The box answered `UNIMPLEMENTED`.** A community build. Every paid
    /// signup against it will take the money and fail to grant the
    /// entitlement, so a deployment that sells is misconfigured, loudly.
    Absent(String),
    /// **Nobody could ask** — the box is down, the plugin has no probe, nothing
    /// has run yet. NOT a yes and NOT a no; carries which.
    Unproven(String),
}

impl MeteredDoor {
    /// `enforcing` | `absent` | `unproven` — the wire, JSON and log spelling,
    /// one writer.
    pub fn name(&self) -> &'static str {
        match self {
            MeteredDoor::Enforcing(_) => "enforcing",
            MeteredDoor::Absent(_) => "absent",
            MeteredDoor::Unproven(_) => "unproven",
        }
    }
    /// The plugin's own words about the answer.
    pub fn detail(&self) -> &str {
        match self {
            MeteredDoor::Enforcing(s) | MeteredDoor::Absent(s) | MeteredDoor::Unproven(s) => s,
        }
    }
    /// **Has the paid door been PROVEN to be there?** False for `Unproven`,
    /// which is the whole point of the arm: a deployment may not call itself
    /// healthy on the strength of a question nobody answered.
    pub fn proven(&self) -> bool {
        matches!(self, MeteredDoor::Enforcing(_))
    }
}

/// **One TEST BUTTON a product declares** (owner 2026-10-11): the operator's
/// console renders one button per declared action and monetize runs it through
/// the SAME sign-up / estate-order path a customer takes, on a vendor in test
/// mode only. Data, never markup: the console stays a thin renderer and the
/// product never ships HTML into it.
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct TestAction {
    /// Stable within the product (`hosted-10gb`); what the console sends back.
    pub id: String,
    /// What the button reads (`Test: 10 GB hosted`).
    pub label: String,
    pub kind: TestActionKind,
}

/// **What a test button buys** — the two things a stranger can buy today.
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum TestActionKind {
    /// A hosted account with this much room, at the plan the sign-up page
    /// offers for it NOW (`Signup.Plans`) — the product names the room, never a
    /// priced id, because the price is the shop's.
    HostedPlan { cap_gib: u64 },
    /// A private instance from the estate-order ladder, at these sizes (empty:
    /// the ladder's own default for that field).
    PrivateInstance {
        #[serde(default)]
        front_plan: String,
        #[serde(default)]
        main_plan: String,
        #[serde(default)]
        main_data_gib: u64,
    },
}

/// **What is fixed of a product, as DATA** (A32-13): its manifest's id and meters and its facts'
/// tickets, test buttons and guardian. The host reads it ONCE ([`pulled::Pulled::facts`]) and holds it
/// in the product's slot, as the Cloud and Payment slots hold their manifests; every reader reads
/// the slot ([`Served`]), never the product.
#[derive(Clone, Debug, PartialEq, Eq, Default)]
pub struct ProductFacts {
    /// `gunnar`, `holger`: the block's id. Also the first segment of every payment reference.
    pub id: &'static str,
    /// The meters this product declares — the UI and the order form read this, so an order
    /// can only cap a meter that exists, and the console can say whether a cap on it is
    /// policed ([`Enforcement`]).
    pub meters: &'static [Meter],
    /// **Its main mints actor tickets** (the facts' `tickets`): a console may offer
    /// self-service renewal. False means there is nothing that could tell a customer from a
    /// stranger, so the route must not exist rather than existing and refusing. The HOST checks
    /// each ticket. Reported on the wire as `ProductInfo.tenant_renewal`.
    pub tickets: bool,
    /// **The test buttons this product offers its operator** ([`TestAction`]). Empty: the
    /// console shows none for it.
    pub test_actions: Vec<TestAction>,
    /// **It brings an estate guardian** ([`guardian::EstateGuardian`], 5.4): how its estates are
    /// diagnosed and repaired. The host holds the guardian beside the slot.
    pub guardian: bool,
    /// `Some(why)`: the facts could not be read yet (a guest that could not answer); its
    /// tickets, test buttons and guardian are off, and the host asks again on the next look.
    pub unread: Option<String>,
}

impl ProductFacts {
    /// A product `id` with `meters` and nothing else: no tickets, no buttons, no guardian.
    pub fn new(id: &'static str, meters: &'static [Meter]) -> ProductFacts {
        ProductFacts { id, meters, ..Default::default() }
    }

    /// **These facts, interned**: one `'static` copy per distinct answer (the meter tables are
    /// interned the same way), so a slot hands out `&'static` facts that outlive a replaced
    /// guest, and reading the same answer again leaks nothing.
    pub fn leak(self) -> &'static ProductFacts {
        static HELD: std::sync::Mutex<Vec<&'static ProductFacts>> = std::sync::Mutex::new(Vec::new());
        let mut held = HELD.lock().unwrap_or_else(|e| e.into_inner());
        if let Some(f) = held.iter().find(|f| ***f == self) {
            return f;
        }
        let f: &'static ProductFacts = Box::leak(Box::new(self));
        held.push(f);
        f
    }
}

/// **A product as the host serves it**: its facts, read once, beside the product's estate verbs.
/// Copy, like `monetize_pay::Vendor`: the slot's facts (`&'static`) and the product. Every
/// reader of an id, a meter table, the ticket fact or the test buttons reads them here; the
/// estate verbs are the product's ([`std::ops::Deref`]).
#[derive(Clone, Copy)]
pub struct Served<'a> {
    facts: &'a ProductFacts,
    product: &'a pulled::Pulled,
    guardian: Option<&'a dyn guardian::EstateGuardian>,
    /// **The room a quote sells against**, when the host decides it (the queue past the room,
    /// monetize-server `grow::can_absorb_queued`); `None`: the product's own answer.
    absorb: Option<AbsorbFn<'a>>,
}

/// The host's own answer to a quote's room ([`Served::with_room`]).
pub type AbsorbFn<'a> = &'a (dyn Fn(&TenantId, &BTreeMap<String, u64>) -> Result<Absorb, String> + Sync);

impl<'a> Served<'a> {
    /// `product` with the facts the host holds for it, and no guardian.
    pub fn new(facts: &'a ProductFacts, product: &'a pulled::Pulled) -> Served<'a> {
        Served { facts, product, guardian: None, absorb: None }
    }
    /// **The same, the quote's room decided by the host** (MONETIZE-FIX step 3, replacing the
    /// `Growing` wrapper): `absorb` answers whether an order fits.
    pub fn with_room(mut self, absorb: AbsorbFn<'a>) -> Served<'a> {
        self.absorb = Some(absorb);
        self
    }
    /// **Whether `caps` fit for `tenant`**: the host's room when it decides it, else the product's.
    pub fn can_absorb(&self, tenant: &TenantId, caps: &BTreeMap<String, u64>) -> Result<Absorb, String> {
        match self.absorb {
            Some(f) => f(tenant, caps),
            None => self.product.can_absorb(tenant, caps),
        }
    }
    /// The same, with the estate guardian the host holds beside it.
    pub fn with_guardian(mut self, guardian: Option<&'a dyn guardian::EstateGuardian>) -> Served<'a> {
        self.guardian = guardian;
        self
    }
    /// **The estate guardian that answers for it** (5.4): how its estates are diagnosed and
    /// repaired. `None`: monetize repairs none of its estates.
    pub fn guardian(&self) -> Option<&'a dyn guardian::EstateGuardian> {
        self.guardian
    }
    /// The block's id.
    pub fn id(&self) -> &'static str {
        self.facts.id
    }
    /// The declared meters.
    pub fn meters(&self) -> &'static [Meter] {
        self.facts.meters
    }
    /// Its main mints actor tickets ([`ProductFacts::tickets`]).
    pub fn tenant_renewal(&self) -> bool {
        self.facts.tickets
    }
    /// Its operator's test buttons.
    pub fn test_actions(&self) -> &'a [TestAction] {
        &self.facts.test_actions
    }
    /// Everything the host holds of it.
    pub fn facts(&self) -> &'a ProductFacts {
        self.facts
    }
    /// The product's estate verbs, without the facts.
    pub fn product(&self) -> &'a pulled::Pulled {
        self.product
    }
}

impl<'a> std::ops::Deref for Served<'a> {
    type Target = pulled::Pulled;
    fn deref(&self) -> &Self::Target {
        self.product
    }
}

impl std::fmt::Debug for Served<'_> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Served").field("id", &self.facts.id).finish()
    }
}

/// **What the host says about a ticket somebody presented** (`monetize_server::site_ticket`).
///
/// Not a `bool` and not a `Result<(), ProductError>`: a refusal is a normal,
/// expected answer that carries a SENTENCE — the words a caller may show — and
/// a `ProductError` means something else entirely (the product's control plane
/// is down, the question could not be asked). Conflating the two would let an
/// unreachable appliance read as a refused ticket, or worse, the reverse.
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum ActorVerdict {
    /// The ticket verifies, names this tenant, this product and this verb, is
    /// inside its life, and has not been spent before.
    Allowed,
    /// Refused, and why — in words a customer surface may show. A verifier must
    /// not name the ticket's own tenant back to whoever asked about another
    /// one; see `monetize_embed::verify_ticket`.
    Refused(String),
}

/// **"Add users"** (owner, 2026-10-04): `count` users, each holding
/// `room_bytes` of the tenant's room. `count` 1 is ONE named user (`name`);
/// more is that many SEATS with no names, filled later (an invite, an
/// import). `room_bytes` 0 is the product's own default. The rules a person
/// types against (the range, the names, the room's floor) are the PRODUCT's,
/// which every door shares.
#[derive(Clone, PartialEq, Eq, Debug, Default, serde::Serialize, serde::Deserialize)]
#[serde(default)]
pub struct UsersAsk {
    pub count: u32,
    pub room_bytes: u64,
    #[serde(skip_serializing_if = "String::is_empty")]
    pub name: String,
    /// Where the named user's invite is mailed; empty: nowhere. Not part of
    /// the ask's identity ([`UsersAsk::fingerprint`]).
    #[serde(skip_serializing_if = "String::is_empty")]
    pub email: String,
    /// The console the invite is redeemed at.
    #[serde(skip_serializing_if = "String::is_empty")]
    pub invite_origin: String,
    /// **The named user as the ledger knows them** (G13, 5.4): an opaque id
    /// (`monetize::ledger::principal_id`), never the name. Empty for seats.
    #[serde(skip_serializing_if = "String::is_empty")]
    pub principal_id: String,
}

impl UsersAsk {
    /// **What a ticket for this ask covers** — the "caps" an `order` actor
    /// ticket is minted over and compared against, so a ticket vouches for
    /// exactly this number of users at exactly this room, and no other.
    pub fn ticket_caps(&self) -> BTreeMap<String, u64> {
        [("users".to_owned(), u64::from(self.count.max(1))), ("user_room_bytes".to_owned(), self.room_bytes)].into_iter().collect()
    }

    /// A short, stable fingerprint for a reference: `2x10737418240` or
    /// `1x10737418240:p-<id>` — the named user by [`UsersAsk::principal_id`],
    /// never by name (G13).
    pub fn fingerprint(&self) -> String {
        match self.principal_id.as_str() {
            "" => format!("{}x{}", self.count.max(1), self.room_bytes),
            id => format!("{}x{}:{id}", self.count.max(1), self.room_bytes),
        }
    }

    /// **The ask as the ledger may hold it**: no name, no address — the
    /// named user only by [`UsersAsk::principal_id`].
    pub fn without_names(&self) -> UsersAsk {
        UsersAsk { name: String::new(), email: String::new(), ..self.clone() }
    }

    /// One named user (the name, or its id, is known).
    pub fn is_named(&self) -> bool {
        !self.name.is_empty() || !self.principal_id.is_empty()
    }
}

/// The product's answer to [`pulled::Pulled::users_fit`] / [`pulled::Pulled::add_users`].
#[derive(Clone, PartialEq, Eq, Debug, Default, serde::Serialize, serde::Deserialize)]
#[serde(default)]
pub struct UsersAnswer {
    /// The named user, once created; empty for seats and for a dry run.
    pub principal: String,
    /// Bytes each user holds, as the product resolved it (its default applied).
    pub room_bytes: u64,
    /// What the ask needs of the tenant's room: count × room.
    pub need_bytes: u64,
    /// The tenant's room neither stored nor promised, before the ask. `None`:
    /// nothing caps the tenant there.
    pub unused_bytes: Option<u64>,
    /// How much is missing; 0 = it fits.
    pub shortfall_bytes: u64,
    /// The tenant's seats after this: every seat, and the open ones.
    pub seats: u32,
    pub seats_open: u32,
    /// Nothing was written: a dry run, or the retry of a granted order.
    pub unchanged: bool,
    /// The new named user's invite secret, on the answer that created the
    /// user (and never again): what a `<console>/login?invite=` link carries.
    #[serde(skip_serializing_if = "String::is_empty")]
    pub invite_secret: String,
}

/// What [`pulled::Pulled::purge_tenant`] did.
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Purged {
    pub tenant: TenantId,
    /// What the product removed, in its own words and its own units — "5 of 5
    /// stores, 1.2 GiB". Free text because every product counts different
    /// things and a shared schema would force each of them to lie a little.
    pub detail: String,
    /// Bytes the product says it reclaimed. `None` means it does not count
    /// them, which is not the same as zero and must not be rendered as it.
    pub bytes_reclaimed: Option<u64>,
    /// **Is the tenant still locked out of the product?**
    ///
    /// `true` is a real and expected outcome — an operator may have suspended
    /// the account separately, and the purge correctly refuses to lift a
    /// lockout it did not take — but it is also what a purge that could not
    /// unlock reports, and either way the tenant cannot use what is left.
    /// Carried so a super purge can say so rather than infer it.
    pub still_locked: bool,
    /// One line per thing the product could not remove. Non-empty means the
    /// tenant's data is PARTIALLY there, and the caller must not go on to
    /// destroy the iron it is sitting on.
    pub failures: Vec<String>,
}

/// **Bytes in the largest unit that still leaves a digit before the decimal
/// point**, by integer arithmetic: `10.0 GiB`, `64.0 MiB`, `999 B`.
///
/// One writer, because there used to be three and every one of them carried the
/// same bug. Each divided by 1 GiB unconditionally and printed one decimal, so
/// anything under a gibibyte rendered `0.0 GiB` — and `0.0` beside a quantity is
/// read as *there is none*:
///
/// * the console (then a Plans page) showed a real 64 MiB
///   `pack_bytes` cap as `0.0 GiB`, which an operator reads as "no quota";
/// * the old catalogue validator said *"sells pack_bytes = 10.0 GiB but
///   its resources buy only 10.0 GiB — short 0.0 GiB"*, a sentence in which
///   every number is wrong in the direction of "nothing is the matter";
/// * `products/gunnar`'s capacity refusals said "short 0.0 GiB" for the same
///   reason.
///
/// It lives here because this is the crate that already owns the meter
/// vocabulary ([`Meter`], [`Backing::DiskBytes`]) and the only one every side
/// can depend on: core, the product plugins, and the browser console alike. It
/// pulls in nothing (this crate is `serde` and nothing else), so the wasm
/// bundle pays a few hundred bytes for a formatter it was carrying anyway.
///
/// Truncates, never rounds up: a reading must not appear to cross a cap it has
/// not crossed.
pub fn bytes(n: u64) -> String {
    const KIB: u64 = 1 << 10;
    const MIB: u64 = 1 << 20;
    const GIB: u64 = 1 << 30;
    const TIB: u64 = 1 << 40;
    let (unit, per) = match n {
        n if n >= TIB => ("TiB", TIB),
        n if n >= GIB => ("GiB", GIB),
        n if n >= MIB => ("MiB", MIB),
        n if n >= KIB => ("KiB", KIB),
        // Under a kibibyte there is nothing to scale to, and `0.0 KiB` would be
        // the same lie one unit down. Bytes are exact and short.
        n => return format!("{n} B"),
    };
    format!("{}.{} {unit}", n / per, ((n % per) * 10) / per)
}

#[cfg(test)]
mod backing_tests {
    use super::Backing;

    /// **A byte over the GiB is sold the next GiB, and a millicore is priced
    /// as a thousandth of a core.** Distinct non-zero numbers: 2200 öre per
    /// GiB-month, 90 GiB + 1 byte, 1500 millicores at 30 000.
    #[test]
    fn prices_round_bytes_up_to_the_unit_and_cpu_by_the_millicore() {
        const GIB: u64 = 1 << 30;
        assert_eq!(Backing::DiskBytes.price_month(90 * GIB, 2200), 198_000, "90 GiB × 22.00 SEK");
        assert_eq!(Backing::DiskBytes.price_month(90 * GIB + 1, 2200), 200_200, "one byte over is the 91st GiB");
        assert_eq!(Backing::DiskBytes.price_month(0, 2200), 0);
        assert_eq!(Backing::RamBytes.price_month(3 * GIB, 700), 2100);
        assert_eq!(Backing::CpuMillicores.price_month(1500, 30_000), 45_000, "1.5 cores at 300.00");
        assert_eq!(Backing::CpuMillicores.price_month(1, 30_000), 30, "one millicore is not free and not a core");
        // A counted unit is flat: five properties at 200.00 SEK-month are 1000.00,
        // and one is 200.00 — no rounding, no thousandths. This is the arm a per-property
        // subscription rides on; before it existed a property priced to zero.
        assert_eq!(Backing::Units.price_month(5, 20_000), 100_000, "5 × 200.00");
        assert_eq!(Backing::Units.price_month(1, 20_000), 20_000);
        assert_eq!(Backing::Units.price_month(0, 20_000), 0);
        assert_eq!(Backing::Units.unit(), "unit_month");
        // A zero unit price is FREE, whatever the delta: the open-source list.
        for b in Backing::ALL {
            assert_eq!(b.price_month(u64::MAX / 4, 0), 0, "{b:?}");
        }
        // The unit names are the price list's keys, both ways.
        for b in Backing::ALL {
            assert_eq!(Backing::from_unit(b.unit()), Some(b));
        }
        assert_eq!(Backing::from_unit("moon_month"), None);
    }
}

#[cfg(test)]
mod bytes_tests {
    use super::bytes;

    /// **A quantity that exists never renders as zero.** RED before this
    /// function existed: `0.0 GiB` for every value under 2^30, in three separate
    /// copies of the same six lines.
    #[test]
    fn only_a_genuine_zero_reads_as_zero() {
        for n in [1u64, 512, 1 << 20, 67_108_864, (1 << 30) - 1] {
            let s = bytes(n);
            assert!(!s.starts_with("0.0 ") && !s.starts_with("0 "), "{n} bytes rendered as {s:?}, which reads as nothing");
        }
        assert_eq!(bytes(0), "0 B");
    }

    #[test]
    fn the_unit_scales_and_the_value_truncates() {
        assert_eq!(bytes(999), "999 B");
        assert_eq!(bytes(1536), "1.5 KiB");
        assert_eq!(bytes(67_108_864), "64.0 MiB");
        assert_eq!(bytes((1 << 30) - 1), "1023.9 MiB", "truncates, never rounds up past the cap");
        assert_eq!(bytes(10 << 30), "10.0 GiB");
        assert_eq!(bytes(3 << 40), "3.0 TiB");
        assert_eq!(bytes(u64::MAX), "16777215.9 TiB", "no overflow at the top of the range");
    }
}

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

    /// ★ G16: a sold estate's tenant is `@<estate>/<namespace>` on the ONE ledger and reads
    /// back whole (a namespace keeps its own slashes); a tenant of the product's own estate,
    /// nested or not, never reads as one, and a half-mark is no estate.
    #[test]
    fn a_sold_estates_tenant_carries_the_estate_in_its_path_and_no_own_tenant_reads_as_one() {
        let t = TenantId::on_estate("acme", "bob");
        assert_eq!(t.0, "@acme/bob");
        assert_eq!(t.estate(), Some(("acme", "bob")));
        assert_eq!(TenantId::on_estate("acme", "team/sub").estate(), Some(("acme", "team/sub")));
        for own in ["bob", "team/sub", "acme/bob", "", "@acme", "@/bob", "@acme/", "x@acme/bob"] {
            assert_eq!(TenantId(own.into()).estate(), None, "{own:?}");
        }
    }
}