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

nordisk/monetize

crates/monetize-server/src/registry.rs

//! **The registry**: which products, vendors and clouds are linked into THIS
//! server, chosen by the config file. Everything else looks providers up here by
//! name — a `Purchase.Start` naming a vendor that is not here is `NotFound`, never
//! a silently substituted one.
//!
//! # The registration seam — the registry knows no vendor's name
//!
//! The open server must run for people sharing their servers for free, with the
//! paid vendors and clouds in a private repository. So
//! there is no `match name { "<vendor>" => … }` here. A server is constructed
//! with a [`Linked`] set — a list of `monetize_pay::factory::VendorFactory` and
//! `monetize_cloud::factory::CloudFactory` values — and `from_config_with`
//! looks every enabled `[vendors.<name>]` / `[clouds.<name>]` up in that list.
//! [`Linked::open`] is what this crate's own binary links: the impls behind its
//! cargo features (`invoice`, `sandbox`, and `server` always). The vetra build
//! is a different binary in the private repo that starts from `Linked::open()`
//! and registers the paid factories on top through each impl crate's
//! `register`; it never edits this file.
//!
//! What every cloud gets, whoever linked it: the spend ceiling, or the
//! `CannotSpend` guard when its factory says it cannot spend. That wrap is
//! not a factory's choice — see [`build_cloud`].

use std::path::Path;
use std::sync::Arc;

use monetize_cloud::factory::{CloudBuild, CloudFactory};
use monetize_cloud::cloud::{Boot, Booted, Catalog, Change, Changed, CloudManifest, Give, Listed, Listing, Priced, RoomLeft};
use monetize_cloud::{Cloud, CloudError, Price, Reclaimed, Zone};
use monetize_pay::factory::{Settings, SignupBuild, SignupFactory, VendorBuild, VendorFactory};
use monetize_pay::{Payment, Vendor};
use monetize_product::pulled::Pulled;
use monetize_product::{ProductBuild, ProductFactory, ProductFacts, Served};

use crate::config::Config;
use crate::hot::{HotCloud, HotVendor, Slots};

/// **The vendors and clouds THIS binary can build**, by name. What
/// `from_config_with` looks configured names up in, and what `--install`
/// reads the secret environment names from.
#[derive(Clone, Debug, Default)]
pub struct Linked {
    /// **The product plugins THIS binary can build**, by `[products.<id>]`.
    /// The open build registers NONE: every plugin — gunnar, holger, a
    /// deployment's own — reaches the server by registration, exactly as a
    /// paid vendor does, so this file names no product.
    pub products: Vec<ProductFactory>,
    pub vendors: Vec<VendorFactory>,
    pub clouds: Vec<CloudFactory>,
    /// **What turns a PAID SIGNUP into a customer** — at most one, because a
    /// deployment creates its customers in exactly one way. `None` is the open
    /// build's answer and means [`crate::signup::LoudSink`]: the seam runs, the
    /// refusals are real, and nothing is created.
    ///
    /// The vetra build registers the real one exactly as it registers a paid
    /// vendor — `monetize_pay_impl::register(&mut l.vendors)` has a sibling
    /// that fills this slot — so this file still names no private crate.
    pub signup: Option<SignupFactory>,
    /// **What turns the ledger into a tax return** — at most one, because a
    /// deployment files in one jurisdiction. `None` is the open build's answer:
    /// a `[tax]` that declares one ([`monetize::tax::Declared`]), else
    /// [`monetize::PerCountry`] — each buyer country's totals, from the
    /// country, rate and tax every payment row carries (the vendor's).
    pub tax: Option<monetize::TaxFactory>,
    /// **The DNS writers THIS binary can build**, by `[estate_orders] dns`.
    /// The open build registers none (`file:<path>` is the mock's recording
    /// file, built in); an account's writer (Cloudflare) is registered by the
    /// private build, the way a paid cloud is.
    pub dns: Vec<monetize_cloud::dns::DnsFactory>,
    /// **The installers THIS binary can build**, by `[estate_orders] pairing`
    /// (no operator machine in a sale, 2026-10-08): the shop pairs a sold
    /// estate's three machines itself. The open build registers none — the
    /// pairing links Skidbladnir's nordisk-pairing, which this workspace never
    /// does — and the builds that link `monetize-shop-pairing` (the solo monetize
    /// box, gunnar's front) register `shop-pairing`.
    pub installers: Vec<InstallerFactory>,
    /// **The estate guardians THIS binary links**, by product id (5.4): a product plugin
    /// answers its own through `Product::guardian`; a product served as a WASM guest cannot,
    /// so the build that links its native crate registers its guardian here.
    pub guardians: Vec<GuardianLink>,
    /// **Where this box keeps the sites the operator adds and edits in the application**
    /// (`SiteBook`, 2026-10-14). The open build links none (its sites are its config file's);
    /// the ONE box's `monetize-admin` links its own store.
    pub site_book: Option<crate::site_book::SiteBookLink>,
}

/// **A product's estate guardian a build links** ([`Linked::guardian`]).
#[derive(Clone, Copy)]
pub struct GuardianLink {
    pub product: &'static str,
    pub guardian: &'static dyn monetize_product::guardian::EstateGuardian,
}

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

/// **An installer a build links**: a name and how it is built for one shop.
#[derive(Clone, Copy)]
pub struct InstallerFactory {
    pub name: &'static str,
    pub build: fn(&InstallerBuild<'_>) -> Result<std::sync::Arc<dyn crate::estate_order::Installer>, String>,
}

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

/// What a linked installer is built from: the shop's order config and its data root.
pub struct InstallerBuild<'a> {
    pub orders: &'a crate::estate_order::EstateOrderConfig,
    pub data_dir: &'a std::path::Path,
}

impl Linked {
    /// **The open set** — every impl this crate's features compile in. The
    /// `server` cloud is not behind a feature of this crate: it is in
    /// `monetize-cloud`'s default, and a server with no cloud that can serve
    /// its own box would be an odd open-source deployment.
    pub fn open() -> Linked {
        let mut l = Linked::default();
        #[cfg(feature = "invoice")]
        l.vendor(monetize_pay::invoice::FACTORY);
        #[cfg(feature = "sandbox")]
        l.vendor(monetize_pay::sandbox::FACTORY);
        l.cloud(monetize_cloud::server::FACTORY);
        #[cfg(feature = "sandbox")]
        l.cloud(monetize_cloud::sandbox::FACTORY);
        l
    }

    /// Register a product plugin, replacing one of the same name
    /// (`monetize_product::factory::register`).
    /// **The guest factories a config asks for**, registered after the linked
    /// set: every `[products.<id>]` with a `guest = "<path>.wasm"` key gets the
    /// guest host's factory under that id. Registration replaces, so a `guest =`
    /// key takes an id over from a linked plugin of the same name — a deployment
    /// moves one product at a time and the binary needs no change. The open
    /// server, which links no product, meters a guest exactly this way.
    pub fn with_guests_from(mut self, cfg: &Config) -> Linked {
        for (id, p) in &cfg.products {
            if p.guest.is_some() {
                self.product(monetize_product_wasm::factory(monetize_product_wasm::leak_id(id)));
            }
        }
        // **A cloud guest the same way**: `[clouds.<name>] guest = …` takes the
        // name over from a linked cloud of that name, and the open server,
        // which links no paid cloud, buys through a signed module.
        for (name, c) in &cfg.clouds {
            if c.settings.get("guest").is_some() {
                self.cloud(monetize_cloud_wasm::factory(crate::hot::intern_name(name)));
            }
        }
        self
    }

    /// Register a product plugin, replacing one of the same name; the estate guardian its
    /// factory brings is linked beside it ([`Linked::guardian`]).
    pub fn product(&mut self, f: ProductFactory) -> &mut Self {
        if let Some(g) = f.guardian {
            self.guardian(f.name, g);
        }
        monetize_product::factory::register(&mut self.products, f);
        self
    }
    /// Register a vendor, replacing one of the same name (`factory::register`).
    pub fn vendor(&mut self, f: VendorFactory) -> &mut Self {
        monetize_pay::factory::register(&mut self.vendors, f);
        self
    }
    /// Register a cloud, replacing one of the same name.
    pub fn cloud(&mut self, f: CloudFactory) -> &mut Self {
        monetize_cloud::factory::register(&mut self.clouds, f);
        self
    }
    /// Register a DNS writer, replacing one of the same name.
    pub fn dns_writer(&mut self, f: monetize_cloud::dns::DnsFactory) -> &mut Self {
        monetize_cloud::dns::register(&mut self.dns, f);
        self
    }
    /// Register an installer; a second one under the same name replaces the first.
    pub fn installer(&mut self, f: InstallerFactory) -> &mut Self {
        self.installers.retain(|g| g.name != f.name);
        self.installers.push(f);
        self
    }
    /// Register a product's estate guardian; a second one for the same product replaces the first.
    pub fn guardian(&mut self, product: &'static str, guardian: &'static dyn monetize_product::guardian::EstateGuardian) -> &mut Self {
        self.guardians.retain(|g| g.product != product);
        self.guardians.push(GuardianLink { product, guardian });
        self
    }
    /// The installer named `name`, if this build links it.
    pub fn find_installer(&self, name: &str) -> Option<&InstallerFactory> {
        self.installers.iter().find(|f| f.name == name)
    }
    /// Every installer this build links, by name.
    pub fn installer_names(&self) -> Vec<&'static str> {
        self.installers.iter().map(|f| f.name).collect()
    }
    /// The DNS writer named `name`, if this build links it.
    pub fn find_dns(&self, name: &str) -> Option<&monetize_cloud::dns::DnsFactory> {
        self.dns.iter().find(|f| f.name == name)
    }
    /// Every DNS writer this build links, by name.
    pub fn dns_names(&self) -> Vec<&'static str> {
        self.dns.iter().map(|f| f.name).collect()
    }
    /// **Register the signup sink, replacing whatever was there.** The slot is
    /// singular and so is this: registering twice is a build that linked two
    /// ways of creating a customer, and the last one wins — the same rule
    /// `factory::register` applies to a name, stated where "both run" cannot be
    /// read into it.
    pub fn signup(&mut self, f: SignupFactory) -> &mut Self {
        self.signup = Some(f);
        self
    }

    /// **Register the jurisdiction's tax report, replacing whatever was there.**
    /// One slot, last writer wins — the rule [`Linked::signup`] states, for the
    /// same reason: two returns filed for one period is not a richer deployment,
    /// it is a wrong one.
    pub fn tax(&mut self, f: monetize::TaxFactory) -> &mut Self {
        self.tax = Some(f);
        self
    }

    /// The report this build will use, built from `[tax]`. No factory and no
    /// declared jurisdiction is [`monetize::PerCountry`]: the totals per buyer
    /// country, each payment's country, rate and tax the vendor's.
    pub fn build_tax(&self, data_dir: &Path, settings: &serde_json::Value) -> Result<Arc<dyn monetize::TaxReport>, String> {
        match &self.tax {
            Some(f) => (f.build)(&monetize::tax::TaxBuild { name: f.name, data_dir, settings: Settings(settings) }),
            // No implementation linked: the jurisdiction `[tax]` DECLARES
            // (`jurisdiction`, `rate_percent`) fills monetize's own folds;
            // declaring none (the ONE box, owner 2026-10-11: Stripe decides per
            // country, each payment is a row) totals per buyer country.
            None => match monetize::tax::Declared::from_settings(settings)? {
                Some(d) => Ok(Arc::new(d)),
                None => Ok(Arc::new(monetize::PerCountry::default())),
            },
        }
    }

    /// The sink this build will use, built from `[signups.<product>]`. `None` from the
    /// factory's own refusal is an error naming the block; no factory at all is
    /// [`crate::signup::LoudSink`], the open build's working default.
    pub fn build_signup_sink(&self, data_dir: &Path, settings: &serde_json::Value) -> Result<crate::signup::Sink, String> {
        match &self.signup {
            Some(f) => (f.build)(&SignupBuild { name: f.name, data_dir, settings: Settings(settings) }),
            None => Ok(Arc::new(crate::signup::NoSink)),
        }
    }

    /// Every environment name the SIGNUP SINK reads, for `--install`'s 0600 env
    /// file. Empty when no sink is linked.
    pub fn signup_config_envs(&self) -> Vec<&'static str> {
        self.signup.iter().flat_map(|f| f.config_envs.iter().copied()).collect()
    }

    pub fn find_product(&self, name: &str) -> Option<&ProductFactory> {
        monetize_product::factory::find(&self.products, name)
    }
    pub fn product_names(&self) -> Vec<&'static str> {
        self.products.iter().map(|f| f.name).collect()
    }
    pub fn find_vendor(&self, name: &str) -> Option<&VendorFactory> {
        self.vendors.iter().find(|f| f.name == name)
    }
    pub fn find_cloud(&self, name: &str) -> Option<&CloudFactory> {
        self.clouds.iter().find(|f| f.name == name)
    }
    pub fn vendor_names(&self) -> Vec<&'static str> {
        self.vendors.iter().map(|f| f.name).collect()
    }
    pub fn cloud_names(&self) -> Vec<&'static str> {
        self.clouds.iter().map(|f| f.name).collect()
    }

    /// Every environment name a vendor LINKED INTO THIS BINARY reads,
    /// assembled from each factory's own `config_envs`. `--install` forwards
    /// these from the operator's environment into the 0600 `EnvironmentFile=`,
    /// so a vendor that is linked cannot be forgotten (its own list is the
    /// source) and one that is NOT linked cannot put a name into the unit —
    /// the same rule `build_vendor` enforces for `enabled`.
    ///
    /// A name with the `{INSTANCE}` placeholder is per vendor INSTANCE and is
    /// not here: [`Linked::vendor_instance_envs`] expands it for the instances
    /// a config holds.
    pub fn vendor_config_envs(&self) -> Vec<&'static str> {
        self.vendors.iter().flat_map(|f| f.config_envs.iter().copied()).filter(|n| !n.contains(monetize_pay::instance::INSTANCE_PLACEHOLDER)).collect()
    }

    /// The per-instance names (`MONETIZE_STRIPE_{INSTANCE}_SECRET_KEY`), once
    /// for every ENABLED `[vendors.<kind>.<instance>]` in `cfg` whose kind is
    /// linked: `MONETIZE_STRIPE_GUNNAR_SECRET_KEY`, `MONETIZE_STRIPE_HOLGER_…`.
    pub fn vendor_instance_envs(&self, cfg: &Config) -> Vec<String> {
        let mut out = Vec::new();
        for (id, _) in cfg.vendors.iter().filter(|(_, v)| v.enabled) {
            let Ok((kind, instance)) = monetize_pay::instance::split(id) else { continue };
            let Some(f) = self.find_vendor(kind) else { continue };
            for t in f.config_envs.iter().filter(|n| n.contains(monetize_pay::instance::INSTANCE_PLACEHOLDER)) {
                let name = monetize_pay::instance::expand_env(t, instance);
                if !out.contains(&name) {
                    out.push(name);
                }
            }
        }
        out
    }

    /// Every environment name a CLOUD linked into this binary reads. Same rule
    /// and same reason: the credential lives in the operator's environment, not
    /// in the 0644 toml, so the install has to carry it into the 0600 env file
    /// or the service starts with no way to reach the provider.
    pub fn cloud_config_envs(&self) -> Vec<&'static str> {
        self.clouds.iter().flat_map(|f| f.config_envs.iter().copied()).collect()
    }
}

/// **How many products may be ADDED while the server runs** — guests whose
/// `[products.<id>]` block appeared after the start. Preallocated, append-only
/// and never emptied, which is what lets [`Registry::products`] hand out plain
/// `monetize_product::Served<'_>` borrows while a writer adds one: a slot, once filled, is
/// never written again. A replacement does not take a slot (it swaps inside
/// its [`HotGuest`]); only a new id does. A deployment that meets the bound
/// gets a named refusal and restarts, which re-counts from zero.
pub const LIVE_PRODUCT_SLOTS: usize = 64;

/// **One product as the host holds it** (A32-13): the product's estate verbs, its facts READ
/// ONCE (asked again only while they are unread, and when its guest is replaced live), and the
/// guardian that answers for it. Every reader takes it as a [`Served`] handle.
pub struct HotProduct {
    product: Arc<Pulled>,
    /// **The block's id, where it serves a plugin of another id** (G15: `[products.gunnar-test2]
    /// plugin = "gunnar"`): what the ledger, the prices, the vendor route and the signing key key
    /// on. `None`: the plugin's own id.
    served_as: Option<&'static str>,
    facts: std::sync::RwLock<&'static ProductFacts>,
    /// The guardian this build links for the product ([`Linked::guardian`]), which answers first.
    linked_guardian: Option<&'static dyn monetize_product::guardian::EstateGuardian>,
    /// The guest that serves it, whose own guardian answers when its facts declare one.
    guest: Option<Arc<monetize_product_wasm::HotGuest>>,
}

impl HotProduct {
    /// `product`, its facts read now.
    pub fn new(product: Arc<Pulled>) -> HotProduct {
        Self::served_as(product, None)
    }
    /// `product`, its facts read now, served under `id` when one is named ([`HotProduct::served_as`]).
    pub fn served_as(product: Arc<Pulled>, id: Option<&'static str>) -> HotProduct {
        let facts = std::sync::RwLock::new(Self::read(&product, id));
        HotProduct { product, served_as: id, facts, linked_guardian: None, guest: None }
    }
    /// The product's facts, under the block's id when it serves another plugin's.
    fn read(product: &Pulled, id: Option<&'static str>) -> &'static ProductFacts {
        let f = product.facts();
        match id {
            Some(id) => ProductFacts { id, ..f }.leak(),
            None => f.leak(),
        }
    }
    fn guarded_by(mut self, linked: Option<&'static dyn monetize_product::guardian::EstateGuardian>, guest: Option<Arc<monetize_product_wasm::HotGuest>>) -> HotProduct {
        self.linked_guardian = linked;
        self.guest = guest;
        self
    }
    /// The facts held; unread ones are asked again (the guest could not answer at its load).
    pub fn facts(&self) -> &'static ProductFacts {
        let held = *self.facts.read().unwrap_or_else(|e| e.into_inner());
        if held.unread.is_none() {
            return held;
        }
        let now = Self::read(&self.product, self.served_as);
        if now.unread.is_some() {
            return held;
        }
        *self.facts.write().unwrap_or_else(|e| e.into_inner()) = now;
        now
    }
    /// Read the facts again (its guest was replaced live).
    fn reread(&self) {
        *self.facts.write().unwrap_or_else(|e| e.into_inner()) = Self::read(&self.product, self.served_as);
    }
    /// The product's estate verbs, shared.
    pub fn handle(&self) -> &Arc<Pulled> {
        &self.product
    }
    /// **The guardian that answers for this product**: the one the build links, else the serving
    /// guest's own when its facts declare one.
    pub fn guardian(&self) -> Option<&dyn monetize_product::guardian::EstateGuardian> {
        if let Some(g) = self.linked_guardian {
            return Some(g);
        }
        if !self.facts().guardian {
            return None;
        }
        self.guest.as_deref().and_then(|g| g.guardian())
    }
    /// This product as every reader takes it.
    pub fn served(&self) -> Served<'_> {
        Served::new(self.facts(), self.product.as_ref()).with_guardian(self.guardian())
    }
}

/// The append-only slots [`LIVE_PRODUCT_SLOTS`] describes.
struct LiveSlots(Box<[std::sync::OnceLock<HotProduct>]>);

impl Default for LiveSlots {
    fn default() -> Self {
        LiveSlots((0..LIVE_PRODUCT_SLOTS).map(|_| std::sync::OnceLock::new()).collect())
    }
}

impl LiveSlots {
    fn iter(&self) -> impl Iterator<Item = &HotProduct> {
        self.0.iter().map_while(|s| s.get())
    }
    /// Fill the first empty slot. Callers hold [`Registry::guests`]'s lock, so
    /// there is one writer and the first empty slot is really empty.
    fn push(&self, p: HotProduct) -> Result<(), HotProduct> {
        match self.0.iter().find(|s| s.get().is_none()) {
            Some(slot) => slot.set(p),
            None => Err(p),
        }
    }
}

/// **What a live guest reload did with one `[products.<id>]` block** — one
/// line of the operator's log. A refusal names its clause and keeps whatever
/// was serving.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum GuestChange {
    /// A new product id, served from now on.
    Added { id: String, digest: String, signer: Option<String> },
    /// The serving guest was replaced; calls in flight finished on the old one.
    Replaced(monetize_product_wasm::Replaced),
    /// Refused by name: the serving guest (if any) is untouched.
    Refused { id: String, clause: &'static str, why: String },
}

impl std::fmt::Display for GuestChange {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            GuestChange::Added { id, digest, signer } => {
                write!(f, "guest ADDED [products.{id}] blake3 {digest}{}", signer.as_deref().map(|s| format!(", signed by {s}")).unwrap_or_default())
            }
            GuestChange::Replaced(r) => write!(
                f,
                "guest REPLACED [products.{}] blake3 {} → {}{}; calls in flight finish on the old one",
                r.id,
                r.from_digest,
                r.to_digest,
                r.signer.as_deref().map(|s| format!(", signed by {s}")).unwrap_or_default()
            ),
            GuestChange::Refused { id, clause, why } => write!(f, "guest REFUSED [{clause}] [products.{id}]: {why} — whatever served before still serves"),
        }
    }
}


#[derive(Default)]
pub struct Registry {
    /// Shared, not boxed: the `server` cloud holds the product it measures
    /// through, so a product handle has two owners in a deployment on its own iron.
    products: Vec<HotProduct>,
    /// Products ADDED while the server runs ([`Registry::apply_guests`]).
    live: LiveSlots,
    /// **Every product a guest serves**, by id — at start and added live — so a
    /// changed block can be swapped into the one it names. The lock also
    /// serialises [`Registry::apply_guests`]: one writer at a time.
    guests: std::sync::Mutex<std::collections::BTreeMap<String, Arc<monetize_product_wasm::HotGuest>>>,
    /// **What the start refused** and skipped, by name: a guest that did not
    /// pass its gates, or a product this deployment may not serve unpriced.
    /// One bad block never stops the server (it used to: the front's
    /// refused plugin stopped its whole monetize at the next boot).
    start_refused: Vec<GuestChange>,
    /// The vendor guests the start refused and skipped, by name — the same
    /// rule: one bad vendor block never stops the server.
    vendor_start_refused: Vec<ProviderChange>,
    /// Every vendor and every cloud in a slot that can be swapped, retired or
    /// added while the server runs ([`crate::hot`]).
    vendors: Slots<HotVendor>,
    clouds: Slots<HotCloud>,
    default_cloud: std::sync::RwLock<Option<String>>,
    /// The factories this server was built with, kept so a block changed
    /// live is rebuilt through the same seam as at start.
    linked: Linked,
    /// Serialises [`Registry::apply_providers`]: one writer at a time.
    providers: std::sync::Mutex<()>,
    /// **Every vendor a guest serves**, by name: a changed guest block is
    /// replaced INSIDE it (`HotVendor::replace` — the host's memory for the
    /// vendor shared with the build it replaces), not rebuilt from nothing.
    vendor_guests: std::sync::Mutex<std::collections::BTreeMap<String, Arc<monetize_product_wasm::HotVendor>>>,
    /// **The `server` cloud's grow slot** (g-dk-market, round 5.3): whether a
    /// grant past its walls is queued, filled once the state exists
    /// ([`crate::grow::arm`]). Shared with every `server` this
    /// registry builds, at start and live.
    pub grow_reach: monetize_cloud::server::GrowReach,
    /// **product → vendor instance** (`[products.<id>] vendor`): the one
    /// account a product's money goes to. A product with no route takes no
    /// money — there is no "the only vendor" fallback (5.4, G3).
    routes: std::sync::RwLock<std::collections::BTreeMap<String, String>>,
    /// **estate → its site's route** (`[estates.<estate>]`): see [`Registry::route_for`].
    estates: std::sync::RwLock<std::collections::BTreeMap<String, crate::config::EstateConfig>>,
    /// **product → its own estate** (`[products.<id>] instance`).
    homes: std::sync::RwLock<std::collections::BTreeMap<String, String>>,
    /// **The tickets spent at each site**, by `<estate>\u{0}<nonce>` (ST-3): a site's ticket
    /// checked under its own key ([`crate::site_ticket::may_act`]) is honoured once. In
    /// memory, the lifetime the product's own ring has.
    site_nonces: monetize_embed::SeenNonces,
    /// **Each site's ticket key as its ESTATE delivered it** (ST-3, 2026-10-11: the owner's
    /// install notice carries the elected primary's `ticket_pubkey`; [`crate::fleet::take_joined`]
    /// hands it here). Kept apart from `[estates]`, which a config reload replaces: an
    /// operator's `[estates.<e>] appliance_key` is a PIN over it ([`Registry::appliance_key_of`]).
    delivered_keys: std::sync::RwLock<std::collections::BTreeMap<String, String>>,
    /// The (estate, key) refusals already said, so a refused key is said once, not every tick.
    refused_keys: std::sync::RwLock<std::collections::BTreeSet<String>>,
    /// **A LINKED product's own ticket key** (A32-13: a ticket is the host's, never the
    /// product's): `(key, pinned)`, the block's `appliance_key` (pinned: the operator's word
    /// wins) or the key the product's HOME estate delivered ([`Registry::deliver_product_key`]).
    /// A guest-served product's key is its block's ([`monetize_product_wasm::GuestProduct::appliance_key`]).
    product_keys: std::sync::RwLock<std::collections::BTreeMap<String, (String, bool)>>,
    /// The tickets spent for a LINKED product, by `<product>\u{0}<nonce>` (a guest-served
    /// product's are spent in its host memory).
    product_nonces: monetize_embed::SeenNonces,
    /// How many presented tickets the host checked, per product ([`Registry::tickets_checked`]).
    tickets_checked: std::sync::Mutex<std::collections::BTreeMap<String, u64>>,
    /// **The guardian every pulled product reads and queues on** (5.4, G1): filled when the
    /// server opens its fleet store; shared with [`crate::fleet::FleetState`].
    pull_slot: monetize_guardian::pulled::GuardianSlot,
}

/// The guardian this build links for `[products.<id>]` (by its id, else by the plugin it names).
fn linked_guardian(linked: &Linked, id: &str, p: &crate::config::ProductConfig) -> Option<&'static dyn monetize_product::guardian::EstateGuardian> {
    let plugin = p.plugin.as_deref().unwrap_or(id);
    linked.guardians.iter().find(|g| g.product == id).or_else(|| linked.guardians.iter().find(|g| g.product == plugin)).map(|g| g.guardian)
}

/// `[products.<id>] instance` wraps the plugin: monetize never dials that estate.
fn pulled(id: &str, p: &crate::config::ProductConfig, facts: monetize_product::pulled::FactsSource, slot: &monetize_guardian::pulled::GuardianSlot) -> Arc<Pulled> {
    match p.instance.as_deref() {
        Some(instance) => {
            // The box that serves the tenants is the product's primary (its lifecycle's first
            // data role: `main` for Gunnar and Holger, the Lake's own), asked at each read
            // because a guest's lifecycle is taken while the server runs.
            let kind = p.plugin.clone().unwrap_or_else(|| id.to_string());
            let primary: monetize_guardian::pulled::Primary = Arc::new(move || crate::estate_order::lifecycle::find(&kind).map(|l| l.primary_role()).unwrap_or_else(|| monetize_product::guardian::role::MAIN.to_string()));
            Arc::new(monetize_guardian::pulled::pulled(facts, instance, slot.clone(), monetize_guardian::pulled::system_clock(), primary))
        }
        None => Arc::new(Pulled::new(facts, Arc::new(monetize_product::pulled::Unplaced(monetize_product_wasm::leak_id(id))))),
    }
}

/// The serving guest's facts, asked each time (a guest replaced live answers anew).
fn guest_facts(hot: &Arc<monetize_product_wasm::HotGuest>) -> monetize_product::pulled::FactsSource {
    let hot = Arc::clone(hot);
    Arc::new(move || hot.facts())
}

/// **What a registry built in code takes as a product**: a served product, or a test double
/// served from its own main.
pub trait IntoServed {
    fn into_served(self) -> Arc<Pulled>;
}

impl IntoServed for Pulled {
    fn into_served(self) -> Arc<Pulled> {
        Arc::new(self)
    }
}

impl IntoServed for Arc<Pulled> {
    fn into_served(self) -> Arc<Pulled> {
        self
    }
}

#[cfg(any(test, feature = "testing"))]
impl IntoServed for monetize::testing::MockProduct {
    fn into_served(self) -> Arc<Pulled> {
        Arc::new(self.pulled().clone())
    }
}

#[cfg(any(test, feature = "testing"))]
impl IntoServed for Arc<monetize::testing::MockProduct> {
    fn into_served(self) -> Arc<Pulled> {
        Arc::new(self.pulled().clone())
    }
}

/// **Why an order cannot be routed to a vendor instance** ([`Registry::route_for_coded`],
/// RA3-3): the name a caller decides by; the words are for people.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RouteRefusal {
    /// `[estates.<e>]` sells another product (`[estate-other-product]`).
    OtherProduct,
    /// The product is sold per site and the order names no estate (`[estate-unnamed]`).
    Unnamed,
    /// The estate is not declared (`[estate-undeclared]`).
    Undeclared,
    /// The product names no vendor route of its own.
    Unrouted,
}

impl Registry {
    pub fn new() -> Self {
        Self::default()
    }

    /// The slot the pulled products' guardian is set in (shared with the fleet state).
    pub fn pull_slot(&self) -> monetize_guardian::pulled::GuardianSlot {
        self.pull_slot.clone()
    }
    pub fn product(mut self, p: impl IntoServed) -> Self {
        self.products.push(HotProduct::new(p.into_served()));
        self
    }
    /// `p` served under the block id `id` (G15: a plugin of another id).
    pub fn product_as(mut self, p: impl IntoServed, id: &'static str) -> Self {
        self.products.push(HotProduct::served_as(p.into_served(), Some(id)));
        self
    }
    /// The served product `p`, shared with the caller.
    pub fn served_product(mut self, p: Arc<Pulled>) -> Self {
        self.products.push(HotProduct::new(p));
        self
    }
    /// The same as [`Self::product`]: `p` is a handle the caller keeps.
    pub fn shared_product(self, p: impl IntoServed) -> Self {
        self.product(p)
    }
    /// **`p`'s facts served as production serves every product** (A32-13 step 2): under
    /// `Pulled`, its reads answered from the report of estate `instance`'s main and its changes
    /// queued as signed orders the main pulls. In a test,
    /// [`crate::testing::estate::FakeEstate`] is that main.
    pub fn pulled_product(mut self, p: impl IntoServed, instance: &str) -> Self {
        let facts = p.into_served();
        let served = Arc::new(monetize_guardian::pulled::pulled(Arc::new(move || facts.facts()), instance, self.pull_slot.clone(), monetize_guardian::pulled::system_clock(), monetize_guardian::pulled::main_primary()));
        self.products.push(HotProduct::new(served));
        self
    }
    pub fn vendor(self, v: impl Payment + 'static) -> Self {
        self.boxed_vendor(Box::new(v))
    }
    pub fn cloud(self, c: impl Cloud + 'static) -> Self {
        self.boxed_cloud(Box::new(c))
    }
    /// A registry built in CODE with more than [`crate::hot::PROVIDER_SLOTS`]
    /// vendors is a programming error and panics; a CONFIG that names too many
    /// is refused by [`Registry::from_config`] instead.
    pub fn boxed_vendor(self, v: Box<dyn Payment>) -> Self {
        self.sourced_vendor(v, String::new()).unwrap_or_else(|e| panic!("{e}"))
    }
    /// As [`Registry::boxed_vendor`], for a cloud.
    pub fn boxed_cloud(self, c: Box<dyn Cloud>) -> Self {
        self.sourced_cloud(c, String::new()).unwrap_or_else(|e| panic!("{e}"))
    }
    /// A vendor built from a config block whose fingerprint is `source` — what
    /// a live reload compares against. A provider handed in by code has an
    /// empty source and is never retired by a config change. Past the slots:
    /// an error naming them, never a panic (stinky monetize-server #23).
    fn sourced_vendor(self, v: Box<dyn Payment>, source: String) -> anyhow::Result<Self> {
        if self.vendors.push(Arc::new(HotVendor::serving(v, source))).is_err() {
            anyhow::bail!("more than {} vendors in one registry; this server holds at most {0} [vendors.…] blocks", crate::hot::PROVIDER_SLOTS);
        }
        Ok(self)
    }
    /// A cloud built in CODE inside its spend cap: the slot holds the cap's shared ledger,
    /// so the console, the lay gate and the reconcile read it as they read a configured one.
    pub fn cloud_capped<C: Cloud + 'static>(self, c: monetize_cloud::cap::SpendCap<C>) -> Self {
        let cap = c.cap();
        self.sourced_cloud_in(Box::new(c), Some(cap), String::new()).unwrap_or_else(|e| panic!("{e}"))
    }
    fn sourced_cloud(self, c: Box<dyn Cloud>, source: String) -> anyhow::Result<Self> {
        self.sourced_cloud_in(c, None, source)
    }
    fn sourced_cloud_in(self, c: Box<dyn Cloud>, cap: Option<Arc<monetize_cloud::cap::Cap>>, source: String) -> anyhow::Result<Self> {
        if self.clouds.push(Arc::new(HotCloud::serving(c, cap, source))).is_err() {
            anyhow::bail!("more than {} clouds in one registry; this server holds at most {0} [clouds.…] blocks", crate::hot::PROVIDER_SLOTS);
        }
        Ok(self)
    }
    /// Link `v` and route `product`'s money through it — a code-built
    /// registry's `[vendors.…]` block plus `[products.<id>] vendor` in one.
    pub fn sells(self, product: &str, v: impl Payment + 'static) -> Self {
        let name = v.manifest().vendor;
        self.vendor(v).route(product, &name)
    }
    /// Route `product`'s money through the vendor instance `vendor`.
    pub fn route(mut self, product: &str, vendor: &str) -> Self {
        self.routes.get_mut().unwrap_or_else(|e| e.into_inner()).insert(product.to_owned(), vendor.to_owned());
        self
    }
    /// What this build links (the DNS writers a site's zone may name, built again live by
    /// [`crate::estate_order::EstateOrders::follow_site_dns`]).
    pub fn linked(&self) -> &Linked {
        &self.linked
    }
    /// Hold `linked` as what this build links (a test's book, installers, guardians).
    pub fn with_linked(mut self, linked: Linked) -> Self {
        self.linked = linked;
        self
    }
    /// Take the config's `[products.<id>] vendor` routes, replacing the ones held.
    pub fn set_routes(&self, cfg: &Config) {
        *self.routes.write().unwrap_or_else(|e| e.into_inner()) = cfg.vendor_routes();
        *self.estates.write().unwrap_or_else(|e| e.into_inner()) = cfg.estates.clone();
        *self.homes.write().unwrap_or_else(|e| e.into_inner()) = cfg.products.iter().filter_map(|(id, p)| p.instance.clone().map(|i| (id.clone(), i))).collect();
    }
    /// The vendor instance `product` bills through, if it names one.
    pub fn route_of(&self, product: &str) -> Option<String> {
        self.routes.read().unwrap_or_else(|e| e.into_inner()).get(product).cloned()
    }
    /// **The vendor instance an order of `product` from `estate` bills through** (ONE
    /// monetize, several sites of one product). A product with `[estates]` declared routes
    /// ONLY per estate: no estate, an estate not declared, or one declared for another
    /// product is refused by name — never another site's account. A product with none
    /// declared routes by its own `[products.<id>] vendor`, as before.
    /// **Each site's own vendor instance** (`[estates.<label>] vendor`), by label.
    pub fn estate_vendors(&self) -> std::collections::BTreeMap<String, String> {
        self.estates.read().unwrap_or_else(|e| e.into_inner()).iter().map(|(id, e)| (id.clone(), e.vendor.clone())).collect()
    }
    pub fn route_for(&self, product: &str, estate: Option<&str>) -> Result<String, String> {
        self.route_for_coded(product, estate).map_err(|(_, why)| why)
    }
    /// [`Self::route_for`], its refusal named by a [`RouteRefusal`] beside its words (RA3-3:
    /// a caller decides by the name, never by matching the words).
    pub fn route_for_coded(&self, product: &str, estate: Option<&str>) -> Result<String, (RouteRefusal, String)> {
        let estates = self.estates.read().unwrap_or_else(|e| e.into_inner());
        if let Some(e) = estate.and_then(|id| estates.get(id)).filter(|e| e.product != product) {
            return Err((RouteRefusal::OtherProduct, format!("REFUSED [estate-other-product] [estates.{}] sells {:?}, not {product}", estate.unwrap_or_default(), e.product)));
        }
        if !estates.values().any(|e| e.product == product) {
            drop(estates);
            return self.route_of(product).ok_or_else(|| (RouteRefusal::Unrouted, self.route_said(product)));
        }
        let Some(estate) = estate else {
            return Err((
                RouteRefusal::Unnamed,
                format!(
                    "REFUSED [estate-unnamed] {product} is sold for several sites here ([estates]: {}) and this order says by nothing which estate it is for — it is never billed through another site's account",
                    estates.iter().filter(|(_, e)| e.product == product).map(|(id, _)| id.as_str()).collect::<Vec<_>>().join(", ")
                ),
            ));
        };
        match estates.get(estate) {
            Some(e) if e.product == product => Ok(e.vendor.clone()),
            Some(e) => Err((RouteRefusal::OtherProduct, format!("REFUSED [estate-other-product] [estates.{estate}] sells {:?}, not {product}", e.product))),
            None => Err((RouteRefusal::Undeclared, format!("REFUSED [estate-undeclared] {estate} is not a site this monetize sells {product} for (no [estates.{estate}]): nothing is billed for it"))),
        }
    }
    /// **The estate a tenant of `product` belongs to**: `@<estate>/<ns>`'s estate, else the
    /// product's own (`[products.<id>] instance`); `None` when the product names none.
    pub fn estate_of_tenant(&self, product: &str, tenant: &str) -> Option<String> {
        match monetize_product::TenantId(tenant.to_owned()).estate() {
            Some((estate, _)) => Some(estate.to_owned()),
            None => self.home_of(product),
        }
    }
    /// **The estate whose own account `vendor` is, for `product`** (`[estates.<estate>]
    /// vendor`): one account per site, so the instance a signup's money went through names
    /// the site it began on. `None`: no declared site of `product` bills through it.
    pub fn estate_of_vendor(&self, product: &str, vendor: &str) -> Option<String> {
        let estates = self.estates.read().unwrap_or_else(|e| e.into_inner());
        estates.iter().find(|(_, e)| e.product == product && e.vendor == vendor).map(|(id, _)| id.clone())
    }
    /// **The calling site's own instance of the vendor KIND `kind`** (`[estates.<estate>]
    /// vendor` = `stripe/gunnar-test2` for `stripe`): a console names the kind, the rows the
    /// instance's webhook keeps name the instance. `None`: no such estate, or its instance is
    /// of another kind — another site's instance is never answered.
    pub fn instance_of_kind(&self, estate: &str, kind: &str) -> Option<String> {
        let estates = self.estates.read().unwrap_or_else(|e| e.into_inner());
        let e = estates.get(estate)?;
        let (k, _) = e.vendor.split_once('/')?;
        (k == kind).then(|| e.vendor.clone())
    }
    /// **`estate`'s own main's ticket key**: the operator's PIN (`[estates.<estate>]
    /// appliance_key`) when set, else the key the estate itself delivered
    /// ([`Registry::deliver_appliance_key`]) — only for an estate declared here.
    pub fn appliance_key_of(&self, estate: &str) -> Option<String> {
        let estates = self.estates.read().unwrap_or_else(|e| e.into_inner());
        let declared = estates.get(estate)?;
        let pin = declared.appliance_key.trim();
        if !pin.is_empty() {
            return Some(pin.to_owned());
        }
        self.delivered_keys.read().unwrap_or_else(|e| e.into_inner()).get(estate).cloned()
    }
    /// **A site's ticket key, as its estate delivered it** (ST-3): `product`'s estate
    /// `estate` names `key` (64 hex) through its enrolled owner's notice. `Ok(Some(line))`:
    /// taken (new or changed); `Ok(None)`: nothing new, or a refusal already said; `Err`: refused
    /// by name, once — an estate not declared here (or declared for another product), a key that
    /// is no ed25519 key, or one the operator's pin contradicts (the pin wins).
    pub fn deliver_appliance_key(&self, product: &str, estate: &str, key: &str) -> Result<Option<String>, String> {
        let key = key.trim().to_ascii_lowercase();
        let refuse = |why: String| -> Result<Option<String>, String> {
            if self.refused_keys.write().unwrap_or_else(|e| e.into_inner()).insert(format!("{estate}\u{0}{key}\u{0}{why}")) {
                Err(why)
            } else {
                Ok(None)
            }
        };
        if crate::site_ticket::key_of(&key).is_none() {
            return refuse(format!("REFUSED [site-key-shape] {estate} delivered a ticket key that is not an ed25519 public key (64 hex): nothing taken"));
        }
        let pin = {
            let estates = self.estates.read().unwrap_or_else(|e| e.into_inner());
            match estates.get(estate) {
                None => return refuse(format!("REFUSED [site-key-undeclared] {estate} delivered its ticket key, and no [estates.{estate}] is declared here: a site this monetize does not sell for checks no ticket")),
                Some(e) if e.product != product => return refuse(format!("REFUSED [site-key-other-product] {estate} delivered a {product} ticket key; [estates.{estate}] sells {:?}", e.product)),
                Some(e) => e.appliance_key.trim().to_ascii_lowercase(),
            }
        };
        if !pin.is_empty() && pin != key {
            return refuse(format!("REFUSED [site-key-pinned] {estate} delivered a ticket key other than the operator's pin ([estates.{estate}] appliance_key): the pin is kept, the delivered key is not taken"));
        }
        let mut held = self.delivered_keys.write().unwrap_or_else(|e| e.into_inner());
        if held.get(estate) == Some(&key) {
            return Ok(None);
        }
        held.insert(estate.to_owned(), key.clone());
        Ok(Some(format!("{estate}'s appliance key is the one its estate delivered ({}…){}", &key[..12], if pin.is_empty() { "" } else { ", as pinned" })))
    }
    /// `estate`'s own order terms (`[estates.<estate>.orders]`), when declared.
    pub fn orders_of(&self, estate: &str) -> Option<crate::config::SiteOrders> {
        self.estates.read().unwrap_or_else(|e| e.into_inner()).get(estate).map(|e| e.orders.clone())
    }
    /// The product `estate` sells (`[estates.<estate>] product`), when declared.
    pub fn product_of_estate(&self, estate: &str) -> Option<String> {
        self.estates.read().unwrap_or_else(|e| e.into_inner()).get(estate).map(|e| e.product.clone()).filter(|p| !p.is_empty())
    }
    /// Whether `product` is sold for declared sites here (`[estates]` naming it).
    pub fn sold_per_site(&self, product: &str) -> bool {
        self.estates.read().unwrap_or_else(|e| e.into_inner()).values().any(|e| e.product == product)
    }
    /// **The sites `product` is sold as** (`[estates.<estate>]` naming it): `(estate, site)`,
    /// in estate order; the site is the estate's host (empty when it names none).
    pub fn sites_of(&self, product: &str) -> Vec<(String, String)> {
        self.estates.read().unwrap_or_else(|e| e.into_inner()).iter().filter(|(_, e)| e.product == product).map(|(id, e)| (id.clone(), e.site.trim().to_owned())).collect()
    }
    /// **Spend `nonce` at `estate`**: `true` the first time, `false` ever after.
    pub fn spend_site_nonce(&self, estate: &str, nonce: &str) -> bool {
        self.site_nonces.admit(&format!("{estate}\u{0}{nonce}"))
    }
    /// **`product` checks its buyers' tickets under `hex`**, as its block's `appliance_key`
    /// does: a code-built registry's (a test's) `[products.<id>] appliance_key`. Pinned.
    pub fn ticket_key(self, product: &str, hex: &str) -> Self {
        self.product_keys.write().unwrap_or_else(|e| e.into_inner()).insert(product.to_owned(), (hex.trim().to_ascii_lowercase(), true));
        self
    }
    /// **The key `product`'s own tickets are checked under** (no site key decides them): a
    /// guest's block key; a linked product's block key, else the key its home estate delivered.
    pub fn product_ticket_key(&self, product: &str) -> Option<String> {
        if let Some(guest) = self.guest(product) {
            return guest.appliance_key().map(str::to_owned);
        }
        self.product_keys.read().unwrap_or_else(|e| e.into_inner()).get(product).map(|(k, _)| k.clone())
    }
    /// **The key `product`'s HOME estate delivered** (its owner's install notice, ST-3), taken
    /// for the product's own tickets, so no operator copies it into `[products.<id>]
    /// appliance_key`. `Ok(line)`: taken. Refused by name: a guest-served product (its block's
    /// key decides, as the guest's own host did), a key that is no ed25519 key, or a product
    /// whose block pins one (the operator's word wins).
    pub fn deliver_product_key(&self, product: &str, hex: &str) -> Result<String, String> {
        if self.guest(product).is_some() {
            return Err(format!("{product} takes no delivered appliance key"));
        }
        let key = hex.trim().to_ascii_lowercase();
        if crate::site_ticket::key_of(&key).is_none() {
            return Err(format!("an appliance key is 64 hex characters of an ed25519 public key, not {} characters", key.len()));
        }
        let mut keys = self.product_keys.write().unwrap_or_else(|e| e.into_inner());
        if keys.get(product).is_some_and(|(_, pinned)| *pinned) {
            return Err(format!("[products.{product}] appliance_key pins its ticket key: the delivered key is not taken"));
        }
        keys.insert(product.to_owned(), (key, false));
        Ok(format!("{product} checks its buyers' tickets under the key its main delivered"))
    }
    /// **Spend `nonce` for the linked `product`**: `true` the first time, `false` ever after.
    pub fn spend_product_nonce(&self, product: &str, nonce: &str) -> bool {
        self.product_nonces.admit(&format!("{product}\u{0}{nonce}"))
    }
    /// A presented ticket of `product` reached the host's check.
    pub(crate) fn ticket_checked(&self, product: &str) {
        *self.tickets_checked.lock().unwrap_or_else(|e| e.into_inner()).entry(product.to_owned()).or_default() += 1;
    }
    /// **How many presented tickets of `product` the host checked**: a call that carried none
    /// asked nothing.
    pub fn tickets_checked(&self, product: &str) -> u64 {
        self.tickets_checked.lock().unwrap_or_else(|e| e.into_inner()).get(product).copied().unwrap_or(0)
    }
    /// The feed no longer declares `id` (a test's feed change).
    #[cfg(test)]
    pub(crate) fn forget_estate(&self, id: &str) {
        self.estates.write().unwrap_or_else(|p| p.into_inner()).remove(id);
    }
    /// The feed declares `id` again (a test's feed change).
    #[cfg(test)]
    pub(crate) fn declare_estate(&self, id: &str, e: crate::config::EstateConfig) {
        self.estates.write().unwrap_or_else(|p| p.into_inner()).insert(id.to_owned(), e);
    }
    /// The declared estates, replacing the ones held (from `[estates]` and each product's
    /// `instance`, its own estate).
    pub fn estate(self, id: &str, e: crate::config::EstateConfig) -> Self {
        self.estates.write().unwrap_or_else(|p| p.into_inner()).insert(id.to_owned(), e);
        self
    }
    /// `product`'s own estate, if it names one.
    pub fn home_of(&self, product: &str) -> Option<String> {
        self.homes.read().unwrap_or_else(|e| e.into_inner()).get(product).cloned()
    }
    /// **The admin of `estate` when it is one of the operator's OWN sites**
    /// (`[estates.<estate>] operator_site = true`): its declared owner; `None` for any other.
    pub fn operator_admin_of(&self, estate: &str) -> Option<String> {
        self.estates.read().unwrap_or_else(|e| e.into_inner()).get(estate).filter(|e| e.operator_site).map(|e| e.owner.trim().to_owned()).filter(|o| !o.is_empty())
    }
    /// **Is `estate` one of the operator's OWN sites** (`[estates.<estate>] operator_site = true`)?
    /// `false` for an undeclared (sold) estate or a customer's declared one (HENRIK-1).
    pub fn operator_site(&self, estate: &str) -> bool {
        self.estates.read().unwrap_or_else(|e| e.into_inner()).get(estate).is_some_and(|e| e.operator_site)
    }
    /// **Whether `estate`'s own estate is narrated** (`[estates.<estate>] narrate = true`, lane N2).
    pub fn narrates(&self, estate: &str) -> bool {
        self.estates.read().unwrap_or_else(|e| e.into_inner()).get(estate).is_some_and(|e| e.narrate && e.operator_site)
    }
    /// **Does `estate`'s OWN estate ship an off-site copy** (lane O1)? `[estates.<label>] offsite`
    /// on an operator's own site.
    pub fn offsite(&self, estate: &str) -> bool {
        self.estates.read().unwrap_or_else(|e| e.into_inner()).get(estate).is_some_and(|e| e.offsite && e.operator_site)
    }
    /// **Is `estate`'s public face on** (TASKS-12)? `[estates.<label>] public_site` on an
    /// operator's own site (gunnar.rs, holger.rs).
    pub fn public_site(&self, estate: &str) -> bool {
        self.estates.read().unwrap_or_else(|e| e.into_inner()).get(estate).is_some_and(|e| e.public_site && e.operator_site)
    }
    /// **Is `estate`'s CLI hole open to the internet** (RA1-14)? `[estates.<label>]
    /// cli_hole_public` on an operator's own site (gunnar.rs).
    pub fn cli_hole_public(&self, estate: &str) -> bool {
        self.estates.read().unwrap_or_else(|e| e.into_inner()).get(estate).is_some_and(|e| e.cli_hole_public && e.operator_site)
    }
    /// **The public repositories of `estate`'s own estate** (NIGHT-PUBREPO): `[estates.<label>]
    /// public_repositories` on an operator's own site (holger.rs: `crates`, `npm`); none for
    /// any other estate.
    pub fn public_repositories(&self, estate: &str) -> Vec<String> {
        self.estates.read().unwrap_or_else(|e| e.into_inner()).get(estate).filter(|e| e.operator_site).map(|e| e.public_repositories.clone()).unwrap_or_default()
    }
    /// **The host a site's customers come from and return to** (`[estates.<label>] site`,
    /// `test3.holger.rs`); `None` for an undeclared site or one that names none (A16).
    pub fn site_of_estate(&self, estate: &str) -> Option<String> {
        self.estates.read().unwrap_or_else(|e| e.into_inner()).get(estate.trim()).map(|e| e.site.trim().to_owned()).filter(|s| !s.is_empty())
    }
    /// **Every declared site** (`[estates]`): `(label, its config)`, in label order.
    pub fn declared_estates(&self) -> Vec<(String, crate::config::EstateConfig)> {
        self.estates.read().unwrap_or_else(|e| e.into_inner()).iter().map(|(k, v)| (k.clone(), v.clone())).collect()
    }
    /// **Where `estate`'s customers pay and renew** (NP-3): its `account_url`, else
    /// `https://<site>/`; `None` when it names neither (the box's `[nonpay] account_url`).
    pub fn account_url_of(&self, estate: &str) -> Option<String> {
        let estates = self.estates.read().unwrap_or_else(|e| e.into_inner());
        let e = estates.get(estate)?;
        let own = e.account_url.trim();
        if !own.is_empty() {
            return Some(own.to_owned());
        }
        let site = e.site.trim();
        (!site.is_empty()).then(|| format!("https://{site}/"))
    }
    /// `estate`'s declared site (`[estates.<estate>] site`), when it names one.
    pub fn site_of(&self, estate: &str) -> Option<String> {
        self.estates.read().unwrap_or_else(|e| e.into_inner()).get(estate).map(|e| e.site.trim().to_owned()).filter(|s| !s.is_empty())
    }
    /// [`Registry::home`] on a registry already shared (tests).
    pub fn declare_home(&self, product: &str, estate: &str) {
        self.homes.write().unwrap_or_else(|p| p.into_inner()).insert(product.to_owned(), estate.to_owned());
    }
    /// `product`'s own estate (`[products.<id>] instance`).
    pub fn home(self, product: &str, estate: &str) -> Self {
        self.homes.write().unwrap_or_else(|p| p.into_inner()).insert(product.to_owned(), estate.to_owned());
        self
    }
    /// `product`'s route in the config's words, for a refusal:
    /// `[products.gunnar] vendor = "stripe/gunnar"`, or that it names none.
    pub fn route_said(&self, product: &str) -> String {
        match self.route_of(product) {
            Some(v) => format!("[products.{product}] vendor = {v:?}"),
            None => format!("[products.{product}] names no `vendor`"),
        }
    }
    pub fn with_default_cloud(mut self, name: Option<String>) -> Self {
        *self.default_cloud.get_mut().unwrap_or_else(|e| e.into_inner()) = name;
        self
    }

    /// Every product: those built at start, then those added live, in the
    /// order they arrived.
    pub fn products(&self) -> impl Iterator<Item = Served<'_>> {
        self.slots().map(HotProduct::served)
    }
    /// Every product's slot: those built at start, then those added live.
    pub fn slots(&self) -> impl Iterator<Item = &HotProduct> {
        self.products.iter().chain(self.live.iter())
    }
    /// The slot of the product `id`.
    pub fn slot(&self, id: &str) -> Option<&HotProduct> {
        self.slots().find(|p| p.facts().id == id)
    }
    /// **The guardian that answers for `product`** ([`HotProduct::guardian`]).
    pub fn guardian_of(&self, product: &str) -> Option<&dyn monetize_product::guardian::EstateGuardian> {
        self.slot(product).and_then(HotProduct::guardian)
    }
    /// Every vendor serving now — a vendor retired live is not listed.
    pub fn vendors(&self) -> impl Iterator<Item = Vendor<'_>> {
        self.vendors.iter().filter(|v| v.is_serving()).map(|v| v.vendor())
    }
    /// Every cloud serving now — a cloud retired live is not listed.
    pub fn clouds(&self) -> impl Iterator<Item = &HotCloud> {
        self.clouds.iter().filter(|c| c.is_serving()).map(|c| c.as_ref())
    }

    pub fn find_product(&self, id: &str) -> Option<Served<'_>> {
        self.slot(id).map(HotProduct::served)
    }
    /// The shared handle, for a cloud that measures through the product.
    pub fn product_handle(&self, id: &str) -> Option<Arc<Pulled>> {
        self.slot(id).map(|p| Arc::clone(&p.product))
    }
    /// By name — and `none` is always found: it is the vendor of every attempt
    /// that moved no money (`monetize::NO_VENDOR`), so a free attempt's row can
    /// be resumed, reconciled or abandoned by the same code as a paid one. It
    /// declines every reserve, so naming it on a priced purchase is a refusal
    /// with the reason on the row, never a sale.
    pub fn find_vendor(&self, name: &str) -> Option<Vendor<'_>> {
        if name == monetize::NO_VENDOR {
            return Some(monetize::no_vendor());
        }
        self.vendors().find(|v| v.name == name)
    }
    /// The vendor a purchase of `product` at `price` goes through: none at
    /// price 0 (the free path calls nobody), else the instance `product` is
    /// routed to (`[products.<id>] vendor`). A `requested` vendor must BE that
    /// instance — one product's money never reaches another's account. `None`
    /// when the product has no route, the route serves nothing now, or the
    /// request names another; the transaction then refuses by name
    /// (`monetize::Refusal::NoVendor`) with nothing written. `market` is the
    /// buyer's: an instance that does not serve it takes nothing.
    pub fn vendor_for(&self, price: &monetize_pay::Money, market: monetize_pay::Market, product: &str, requested: &str) -> Option<Vendor<'_>> {
        self.vendor_for_estate(price, market, product, None, requested)
    }
    /// [`Registry::vendor_for`] for an order of `tenant`: routed by the tenant's estate
    /// ([`Registry::estate_of_tenant`]).
    pub fn vendor_for_tenant(&self, price: &monetize_pay::Money, market: monetize_pay::Market, product: &str, tenant: &str, requested: &str) -> Option<Vendor<'_>> {
        let estate = self.estate_of_tenant(product, tenant);
        self.vendor_for_estate(price, market, product, estate.as_deref(), requested)
    }
    /// [`Registry::vendor_for`] for an order from `estate` ([`Registry::route_for`]).
    pub fn vendor_for_estate(&self, price: &monetize_pay::Money, market: monetize_pay::Market, product: &str, estate: Option<&str>, requested: &str) -> Option<Vendor<'_>> {
        if price.minor == 0 {
            return Some(monetize::no_vendor());
        }
        let route = self.route_for(product, estate).ok()?;
        // A console names the vendor KIND (`stripe`); the route names the INSTANCE
        // (`stripe/gunnar-test2`, one account per site). The kind matches its instance;
        // any other name is never substituted.
        let kind = route.split_once('/').map_or(route.as_str(), |(k, _)| k);
        if !requested.is_empty() && requested != route && requested != kind {
            return None;
        }
        self.find_vendor(&route).filter(|v| v.markets.contains(&market))
    }
    /// Every linked cloud whose ACCOUNT changed since the last ask (CODE RED audit 5): its
    /// name, and `Some(words)` exhausted or `None` working again.
    pub fn account_changes(&self) -> Vec<(String, Option<String>)> {
        self.clouds.iter().filter_map(|c| c.account_change().map(|a| (c.name().to_string(), a))).collect()
    }

    pub fn find_cloud(&self, name: &str) -> Option<&HotCloud> {
        self.clouds().find(|c| c.name() == name)
    }

    /// The cloud the transaction provisions on: the configured default, else the
    /// only one linked, else [`NoCloud`] — which refuses to ensure anything, so a
    /// plan with resources fails honestly instead of being "provisioned" nowhere.
    pub fn cloud_for_transactions(&self) -> &dyn Cloud {
        match self.transaction_slot() {
            Some(c) => c,
            None => &NoCloud,
        }
    }

    /// The slot [`Registry::cloud_for_transactions`] resolves to — its cap and name with it —
    /// or `None` where that is [`NoCloud`].
    pub fn transaction_slot(&self) -> Option<&HotCloud> {
        let default = self.default_cloud.read().unwrap_or_else(|e| e.into_inner()).clone();
        if let Some(c) = default.as_deref().and_then(|name| self.find_cloud(name)) {
            return Some(c);
        }
        let mut serving = self.clouds();
        match (serving.next(), serving.next()) {
            (Some(one), None) => Some(one),
            _ => None,
        }
    }

    /// The provider [`Registry::cloud_for_transactions`] resolves to, or `None`
    /// when that is [`NoCloud`]. What the console marks as the cloud a sale
    /// spends at — the resolved answer, not `default_cloud`'s string, because a
    /// name that matches nothing linked is not where the money goes.
    pub fn default_cloud_name(&self) -> Option<&'static str> {
        // The same two rules, by name: comparing trait-object addresses would
        // call a zero-sized linked cloud and `&NoCloud` the same object.
        let default = self.default_cloud.read().unwrap_or_else(|e| e.into_inner()).clone();
        if let Some(c) = default.as_deref().and_then(|name| self.find_cloud(name)) {
            return Some(c.name());
        }
        let mut serving = self.clouds();
        match (serving.next(), serving.next()) {
            (Some(one), None) => Some(one.name()),
            _ => None,
        }
    }

    /// The vendor guests the start refused and skipped, by name.
    pub fn vendor_start_refusals(&self) -> &[ProviderChange] {
        &self.vendor_start_refused
    }
    /// What the start refused and skipped, by name (see [`Registry::from_config_with`]).
    pub fn start_refusals(&self) -> &[GuestChange] {
        &self.start_refused
    }
    pub fn product_ids(&self) -> Vec<&'static str> {
        self.products().map(|p| p.id()).collect()
    }
    pub fn vendor_names(&self) -> Vec<&'static str> {
        self.vendors.iter().filter(|v| v.is_serving()).map(|v| v.name()).collect()
    }
    pub fn cloud_names(&self) -> Vec<&'static str> {
        self.clouds().map(|c| c.name()).collect()
    }

    /// [`Registry::from_config_with`] over the open set this binary links.
    pub fn from_config(cfg: &Config) -> anyhow::Result<Self> {
        Self::from_config_with(cfg, &Linked::open())
    }

    /// Build from the config file: every `[products.<id>]` becomes a plugin, every
    /// `enabled = true` vendor / cloud is constructed by its factory in `linked` —
    /// and a vendor that is enabled but not linked into this binary is an error
    /// at start, not a 404 at purchase.
    pub fn from_config_with(cfg: &Config, linked: &Linked) -> anyhow::Result<Self> {
        let base = linked.clone();
        let linked = &linked.clone().with_guests_from(cfg);
        let mut reg = Registry::new().with_default_cloud(cfg.default_cloud.clone());
        reg.linked = base;
        reg.set_routes(cfg);
        for (id, p) in &cfg.products {
            if let Some(why) = cfg.unpriced(id) {
                reg.start_refused.push(GuestChange::Refused { id: id.clone(), clause: "product-no-prices", why });
                continue;
            }
            // A guest is held where it can be replaced live; a linked plugin as it always was.
            if let Some(spec) = guest_spec(p) {
                // A guest that does not pass its gates is SKIPPED by name, never
                // fatal: the rest of the deployment serves, and the watcher
                // takes it in when a good module or block arrives.
                let hot = match monetize_product_wasm::HotGuest::build(&product_build(id, p, Some(&spec))) {
                    Ok(h) => Arc::new(h),
                    Err(e) => {
                        reg.start_refused.push(GuestChange::Refused { id: id.clone(), clause: "guest-refused-at-start", why: e });
                        continue;
                    }
                };
                reg.guests.get_mut().unwrap_or_else(|e| e.into_inner()).insert(id.clone(), Arc::clone(&hot));
                let served = pulled(id, p, guest_facts(&hot), &reg.pull_slot);
                reg.products.push(HotProduct::new(served).guarded_by(linked_guardian(linked, id, p), Some(hot)));
            } else {
                let (built, served_as) = build_product(id, p, linked)?;
                let served = pulled(id, p, built, &reg.pull_slot);
                reg.products.push(HotProduct::served_as(served, served_as).guarded_by(linked_guardian(linked, id, p), None));
                if let Some(key) = linked_ticket_key(id, p)? {
                    reg = reg.ticket_key(id, &key);
                }
            }
        }
        for (name, v) in cfg.vendors.iter().filter(|(_, v)| v.enabled) {
            let source = vendor_source(name, v);
            // A vendor guest is held where it can be replaced in place; a
            // linked vendor is built by its factory as it always was.
            if let Some(spec) = vendor_guest_spec(cfg, v) {
                let settings = settings_json(&v.settings)?;
                let b = monetize_product_wasm::VendorGuestBuild { name, spec: &spec, settings: &settings };
                let hot = match monetize_product_wasm::HotVendor::build(&b) {
                    Ok(h) => Arc::new(h),
                    Err(e) => {
                        reg.vendor_start_refused.push(ProviderChange::Refused { list: "vendors", name: name.clone(), clause: "vendor-guest-refused-at-start", why: e });
                        continue;
                    }
                };
                reg.vendor_guests.get_mut().unwrap_or_else(|e| e.into_inner()).insert(name.clone(), Arc::clone(&hot));
                if reg.vendors.push(Arc::new(HotVendor::serving_guest(hot, source))).is_err() {
                    anyhow::bail!("more than {} vendors in one registry", crate::hot::PROVIDER_SLOTS);
                }
                continue;
            }
            reg = reg.sourced_vendor(build_vendor(name, cfg, v, linked)?, source)?;
        }
        for (name, c) in cfg.clouds.iter().filter(|(_, c)| c.enabled) {
            let source = cloud_source(name, c);
            // A cloud GUEST that does not load (a dead token at its start-path
            // proof, a module not vouched for) is SKIPPED by name like a vendor
            // guest — the rest of the deployment serves and the watcher takes it
            // in when a good block arrives. A LINKED cloud keeps its fatal start,
            // and a guest block with no `cap` is refused as ever (below the gate).
            let is_guest = c.settings.get("guest").is_some();
            // A cloud that serves a PRODUCT not linked yet (`[clouds.server] product =
            // "gunnar"`, whose plugin self-registers after monetize serves) is
            // skipped by name at start: the watcher builds it once the product is
            // in. Its absence is said, never the end of the server.
            if let Some(product) = c.settings.get("product").and_then(|p| p.as_str()) {
                if !reg.products.iter().any(|p| p.facts().id == product) {
                    reg.vendor_start_refused.push(ProviderChange::Refused {
                        list: "clouds",
                        name: name.clone(),
                        clause: "cloud-product-not-linked-at-start",
                        why: format!("product = {product:?}, but no [products.{product}] is linked yet — it is taken in when the product registers"),
                    });
                    continue;
                }
            }
            let handles: Vec<Arc<Pulled>> = reg.products.iter().map(|p| Arc::clone(&p.product)).collect();
            let (cloud, cap) = match build_cloud(name, cfg, c, linked, &handles, &reg.grow_reach) {
                Ok(cl) => cl,
                Err(e) if is_guest && c.cap.is_some() => {
                    reg.vendor_start_refused.push(ProviderChange::Refused { list: "clouds", name: name.clone(), clause: "cloud-guest-refused-at-start", why: format!("{e:#}") });
                    continue;
                }
                Err(e) => return Err(e),
            };
            reg = reg.sourced_cloud_in(cloud, cap, source)?;
        }
        Ok(reg)
    }
}

impl Registry {
    /// **Apply the config's guest blocks to the RUNNING registry** — no
    /// restart. For every `[products.<id>]` with a `guest =` key:
    ///
    /// * **unchanged** (same module bytes, same block): nothing, and no line;
    /// * **known id, changed block**: [`HotGuest::replace`] — every gate a start
    ///   passes, compiled before the swap; calls in flight finish on the old one;
    /// * **new id**: built as at start, then `admit_new(id)` (the caller's
    ///   chance to install what else the product needs — its price list — and
    ///   refuse), then published in a [`LIVE_PRODUCT_SLOTS`] slot.
    ///
    /// Everything a guest may reach (grants, `http_hosts`, `guest_grpc_hosts`)
    /// is read from `cfg`, the file the operator wrote; a module can only be
    /// refused against it. Every refusal keeps what was serving and is
    /// returned by name. An id a LINKED plugin serves is not taken over live,
    /// and a block that disappeared is not retired live: both are restarts,
    /// said so.
    ///
    /// [`HotGuest::replace`]: monetize_product_wasm::HotGuest::replace
    pub fn apply_guests(&self, cfg: &Config, admit_new: &mut dyn FnMut(&str) -> Result<(), String>) -> Vec<GuestChange> {
        let mut guests = self.guests.lock().unwrap_or_else(|e| e.into_inner());
        self.set_routes(cfg);
        let mut out = Vec::new();
        let refused = |id: &str, clause: &'static str, why: String| GuestChange::Refused { id: id.to_string(), clause, why };
        for (id, p) in &cfg.products {
            let Some(spec) = guest_spec(p) else { continue };
            let b = product_build(id, p, Some(&spec));
            if let Some(hot) = guests.get(id) {
                match hot.is_current_for(&b) {
                    Ok(true) => {}
                    Ok(false) => out.push(match hot.replace(&b) {
                        Ok(r) => {
                            // Its facts are read again: the replacement's meters and facts.
                            if let Some(slot) = self.slot(id) {
                                slot.reread();
                            }
                            GuestChange::Replaced(r)
                        }
                        Err(e) => refused(id, "guest-refused", e),
                    }),
                    Err(e) => out.push(refused(id, "guest-unreadable", e)),
                }
                continue;
            }
            if self.products().any(|x| x.id() == id) {
                out.push(refused(
                    id,
                    "guest-over-linked",
                    "is served by a plugin linked into this binary; moving a product from a linked plugin to a guest takes a restart".into(),
                ));
                continue;
            }
            if let Some(why) = cfg.unpriced(id) {
                out.push(GuestChange::Refused { id: id.clone(), clause: "product-no-prices", why });
                continue;
            }
            if self.live.iter().count() >= LIVE_PRODUCT_SLOTS {
                out.push(refused(id, "guest-slots-full", format!("{LIVE_PRODUCT_SLOTS} products were already added since the start; restart to add more")));
                continue;
            }
            let hot = match monetize_product_wasm::HotGuest::build(&b) {
                Ok(h) => Arc::new(h),
                Err(e) => {
                    out.push(refused(id, "guest-refused", e));
                    continue;
                }
            };
            if let Err(e) = admit_new(id) {
                out.push(refused(id, "guest-terms-refused", e));
                continue;
            }
            let (digest, signer) = {
                let g = hot.current();
                (g.digest_hex().to_string(), g.signer().map(str::to_string))
            };
            let slot = HotProduct::new(pulled(id, p, guest_facts(&hot), &self.pull_slot)).guarded_by(linked_guardian(&self.linked, id, p), Some(Arc::clone(&hot)));
            if self.live.push(slot).is_err() {
                out.push(refused(id, "guest-slots-full", format!("{LIVE_PRODUCT_SLOTS} products were already added since the start; restart to add more")));
                continue;
            }
            guests.insert(id.clone(), hot);
            out.push(GuestChange::Added { id: id.clone(), digest, signer });
        }
        for id in guests.keys() {
            if !cfg.products.get(id).is_some_and(|p| p.guest.is_some()) {
                out.push(refused(id, "guest-removal-needs-restart", "its guest block is gone from the config; it keeps serving until the next restart".into()));
            }
        }
        out
    }


    /// The guest serving `[products.<id>]` now, when a guest serves it.
    /// The ids of every product a guest serves now.
    pub fn guest_ids(&self) -> Vec<String> {
        self.guests.lock().unwrap_or_else(|e| e.into_inner()).keys().cloned().collect()
    }

    pub fn guest(&self, id: &str) -> Option<Arc<monetize_product_wasm::GuestProduct>> {
        self.guests.lock().unwrap_or_else(|e| e.into_inner()).get(id).map(|h| h.current())
    }
}

/// **What a live reload did with one `[vendors.<name>]` or `[clouds.<name>]`
/// block** — one line of the operator's journal. A refusal names its clause
/// and keeps whatever was serving.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ProviderChange {
    /// A name that was not serving is served from now on.
    Added { list: &'static str, name: String, detail: String },
    /// The serving provider was replaced; calls in flight finished on the old one.
    Replaced { list: &'static str, name: String, detail: String },
    /// `enabled = false`, or the block is gone: nothing new goes through it.
    Retired { list: &'static str, name: String },
    /// Refused by name: what served (if anything) still serves.
    Refused { list: &'static str, name: String, clause: &'static str, why: String },
    /// `default_cloud` moved.
    DefaultCloud { from: Option<String>, to: Option<String> },
}

fn singular(list: &str) -> &'static str {
    if list == "vendors" {
        "vendor"
    } else {
        "cloud"
    }
}

impl std::fmt::Display for ProviderChange {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            ProviderChange::Added { list, name, detail } => write!(f, "{} ADDED {} {detail}", singular(list), block_name(list, name)),
            ProviderChange::Replaced { list, name, detail } => {
                write!(f, "{} REPLACED {} {detail}; calls in flight finish on the old one", singular(list), block_name(list, name))
            }
            ProviderChange::Retired { list, name } => {
                write!(f, "{} RETIRED {}: disabled or removed from the config; nothing new goes through it", singular(list), block_name(list, name))
            }
            ProviderChange::Refused { list, name, clause, why } => {
                write!(f, "{} REFUSED [{clause}] {}: {why} — whatever served before still serves", singular(list), block_name(list, name))
            }
            ProviderChange::DefaultCloud { from, to } => write!(f, "default_cloud {:?} → {:?}", from.as_deref().unwrap_or("(none)"), to.as_deref().unwrap_or("(none)")),
        }
    }
}

/// `[vendors.stripe.gunnar]` for the vendor instance `stripe/gunnar`; a cloud's
/// `[clouds.upcloud]` as written.
fn block_name(list: &str, name: &str) -> String {
    format!("[{list}.{}]", name.replace('/', "."))
}

/// **A block's identity for a live reload**: every key of the block and the
/// bytes of every file a `guest =` key names (the module, its `.p7s`, its
/// roots). Two equal sources build the same provider; hashed, so a secret in a
/// block is never kept as text.
fn block_source(list: &str, name: &str, block: &serde_json::Value, guest: Option<&Path>, roots: &[std::path::PathBuf]) -> String {
    let mut text = format!("{list}|{name}|{block}");
    let file = |p: &Path| std::fs::read(p).map(|b| monetize_product_wasm::digest_hex(&b)).unwrap_or_else(|e| format!("unreadable:{e}"));
    if let Some(module) = guest {
        let mut sig = module.as_os_str().to_owned();
        sig.push(".p7s");
        text.push_str(&format!("|{}|{}", file(module), file(Path::new(&sig))));
    }
    for r in roots {
        text.push_str(&format!("|{}", file(r)));
    }
    monetize_product_wasm::digest_hex(text.as_bytes())
}

fn vendor_source(name: &str, v: &crate::config::VendorConfig) -> String {
    block_source("vendors", name, &serde_json::to_value(v).unwrap_or_default(), v.guest.as_deref(), &v.guest_trust_roots)
}

/// A cloud's guest keys live in the block's own table (the cloud guest host
/// reads them there), not in typed fields.
fn cloud_guest_files(c: &crate::config::CloudConfig) -> (Option<std::path::PathBuf>, Vec<std::path::PathBuf>) {
    let module = c.settings.get("guest").and_then(|v| v.as_str()).map(std::path::PathBuf::from);
    let roots = c.settings.get("guest_trust_roots").and_then(|v| v.as_array()).map(|a| a.iter().filter_map(|r| r.as_str().map(std::path::PathBuf::from)).collect()).unwrap_or_default();
    (module, roots)
}

fn cloud_source(name: &str, c: &crate::config::CloudConfig) -> String {
    let (module, roots) = cloud_guest_files(c);
    block_source("clouds", name, &serde_json::to_value(c).unwrap_or_default(), module.as_deref(), &roots)
}

/// What the journal says a provider was built from: the guest module's digest
/// when the block names one, else the linked factory.
fn built_from(guest: Option<&Path>) -> String {
    match guest {
        Some(m) => match std::fs::read(m) {
            Ok(b) => format!("guest {} blake3 {}", m.display(), monetize_product_wasm::digest_hex(&b)),
            Err(_) => format!("guest {}", m.display()),
        },
        None => "linked".to_string(),
    }
}

impl Registry {
    /// **Apply the config's `[vendors.*]` and `[clouds.*]` blocks to the
    /// RUNNING registry** — no restart. Per block:
    ///
    /// * **unchanged** (same keys, same guest files): nothing, and no line;
    /// * **changed**: rebuilt through the same factory as at start, off the
    ///   serving path; any refusal keeps what serves;
    /// * **new name**: built and added;
    /// * **`enabled = false`, or the block is gone**: retired — no new call
    ///   reaches it, and it is no longer listed. A provider handed in by code
    ///   (no block) is never retired by a config.
    ///
    /// **A cloud is published only after its spend ceiling is reconciled.**
    /// The replacement is built unwrapped; then the slot is quiesced (every
    /// `ensure` in flight finishes, new ones wait), the provider is wrapped in
    /// its `SpendCap` — whose total is read from its file only now — and the
    /// cap is corrected from `spent(provider, currency)`, the LEDGER's total.
    /// A cap that cannot be reconciled refuses the swap. Only then is it
    /// published. The guest never sees any of it.
    pub fn apply_providers(&self, cfg: &Config, spent: &dyn Fn(&str, monetize_pay::Currency) -> Result<u64, String>) -> Vec<ProviderChange> {
        let _one = self.providers.lock().unwrap_or_else(|e| e.into_inner());
        let mut out = Vec::new();
        let linked = self.linked.clone().with_guests_from(cfg);
        self.set_routes(cfg);

        // ── vendors ──
        for (name, v) in &cfg.vendors {
            let source = vendor_source(name, v);
            let slot = self.vendors.iter().find(|h| h.name() == name.as_str()).cloned();
            let refused = |clause: &'static str, why: String| ProviderChange::Refused { list: "vendors", name: name.clone(), clause, why };
            if !v.enabled {
                if let Some(h) = slot.filter(|h| h.is_serving()) {
                    h.retire(source);
                    out.push(ProviderChange::Retired { list: "vendors", name: name.clone() });
                }
                continue;
            }
            if slot.as_ref().is_some_and(|h| h.is_serving() && h.source() == source) {
                continue;
            }
            // **A vendor guest is replaced INSIDE its HotVendor** (the money
            // lane's `replace`: every gate a start passes, the host's memory
            // for the vendor shared with the build it replaces); the slot then
            // points at it. A new guest block builds a new one.
            if let Some(spec) = vendor_guest_spec(cfg, v) {
                let settings = match settings_json(&v.settings) {
                    Ok(s) => s,
                    Err(e) => {
                        out.push(refused("vendor-settings", e.to_string()));
                        continue;
                    }
                };
                let b = monetize_product_wasm::VendorGuestBuild { name, spec: &spec, settings: &settings };
                let mut guests = self.vendor_guests.lock().unwrap_or_else(|e| e.into_inner());
                let (hot, detail) = match guests.get(name).cloned() {
                    Some(hot) => match hot.replace(&b) {
                        Ok(r) => (hot, format!("guest blake3 {} → {}{}", r.from_digest, r.to_digest, r.signer.map(|s| format!(", signed by {s}")).unwrap_or_default())),
                        Err(e) => {
                            out.push(refused("vendor-guest-refused", e));
                            continue;
                        }
                    },
                    None => match monetize_product_wasm::HotVendor::build(&b) {
                        Ok(h) => {
                            let h = Arc::new(h);
                            let d = {
                                let g = h.current();
                                format!("guest blake3 {}{}", g.digest_hex(), g.signer().map(|s| format!(", signed by {s}")).unwrap_or_default())
                            };
                            (h, d)
                        }
                        Err(e) => {
                            out.push(refused("vendor-guest-refused", e));
                            continue;
                        }
                    },
                };
                guests.insert(name.clone(), Arc::clone(&hot));
                match slot {
                    Some(h) => {
                        let was = h.is_serving();
                        let markets = hot.markets();
                        h.publish(hot, markets, source);
                        out.push(if was { ProviderChange::Replaced { list: "vendors", name: name.clone(), detail } } else { ProviderChange::Added { list: "vendors", name: name.clone(), detail } });
                    }
                    None => match self.vendors.push(Arc::new(HotVendor::serving_guest(hot, source))) {
                        Ok(()) => out.push(ProviderChange::Added { list: "vendors", name: name.clone(), detail }),
                        Err(_) => out.push(refused("vendor-slots-full", format!("{} vendors are already held; restart to add more", crate::hot::PROVIDER_SLOTS))),
                    },
                }
                continue;
            }
            self.vendor_guests.lock().unwrap_or_else(|e| e.into_inner()).remove(name.as_str());
            let built = match build_vendor(name, cfg, v, &linked) {
                Ok(b) => b,
                Err(e) => {
                    out.push(refused("vendor-refused", format!("{e:#}")));
                    continue;
                }
            };
            let manifest = built.manifest();
            if manifest.vendor != name.as_str() {
                out.push(refused("vendor-wrong-name", format!("the block built a vendor that calls itself `{}`", manifest.vendor)));
                continue;
            }
            let detail = built_from(v.guest.as_deref());
            match slot {
                Some(h) => {
                    let was = h.is_serving();
                    h.publish(Arc::from(built), &manifest.markets, source);
                    out.push(if was { ProviderChange::Replaced { list: "vendors", name: name.clone(), detail } } else { ProviderChange::Added { list: "vendors", name: name.clone(), detail } });
                }
                None => match self.vendors.push(Arc::new(HotVendor::serving(built, source))) {
                    Ok(()) => out.push(ProviderChange::Added { list: "vendors", name: name.clone(), detail }),
                    Err(_) => out.push(refused("vendor-slots-full", format!("{} vendors are already held; restart to add more", crate::hot::PROVIDER_SLOTS))),
                },
            }
        }
        for h in self.vendors.iter().filter(|h| h.is_serving() && !h.source().is_empty()) {
            if !cfg.vendors.contains_key(h.name()) {
                h.retire(String::new());
                out.push(ProviderChange::Retired { list: "vendors", name: h.name().to_string() });
            }
        }

        // ── clouds ──
        let products: Vec<Arc<Pulled>> = self.slots().map(|p| Arc::clone(&p.product)).collect();
        for (name, c) in &cfg.clouds {
            let source = cloud_source(name, c);
            let slot = self.clouds.iter().find(|h| h.name() == name.as_str()).cloned();
            let refused = |clause: &'static str, why: String| ProviderChange::Refused { list: "clouds", name: name.clone(), clause, why };
            if !c.enabled {
                if let Some(h) = slot.filter(|h| h.is_serving()) {
                    let _q = h.quiesce();
                    h.retire(source);
                    out.push(ProviderChange::Retired { list: "clouds", name: name.clone() });
                }
                continue;
            }
            if slot.as_ref().is_some_and(|h| h.is_serving() && h.source() == source) {
                continue;
            }
            // Built OFF the serving path: a guest is compiled and proves its
            // credential here, while the old provider keeps buying.
            let (inner, ceiling) = match build_cloud_bare(name, cfg, c, &linked, &products, &self.grow_reach) {
                Ok(b) => b,
                Err(e) => {
                    out.push(refused("cloud-refused", format!("{e:#}")));
                    continue;
                }
            };
            let calls_itself = inner.manifest().provider;
            if calls_itself != name.as_str() {
                out.push(refused("cloud-wrong-name", format!("the block built a cloud that calls itself `{calls_itself}`")));
                continue;
            }
            let detail = built_from(cloud_guest_files(c).0.as_deref());
            // Quiesce, wrap, reconcile, publish — in that order, under the gate.
            let gate_of = slot.clone();
            let quiet = gate_of.as_ref().map(|h| h.quiesce());
            let (capped, cap) = match wrap_cloud(name, &cfg.data_dir, inner, ceiling) {
                Ok(w) => w,
                Err(e) => {
                    out.push(refused("cloud-cap-refused", format!("{e:#}")));
                    continue;
                }
            };
            let cap_line = match &cap {
                Some(c) => match spent(name, c.ceiling().currency).and_then(|total| c.reconcile_total(total).map(|()| total).map_err(|e| e.to_string())) {
                    Ok(total) => crate::cli::spend_cap_line(name, total, Some(c.spent_this_month()), c.ceiling()),
                    Err(e) => {
                        out.push(refused("cloud-cap-unreconciled", format!("its spend ceiling could not be reconciled against the ledger ({e}); a cap that has not seen the ledger could hand the deployment its whole budget again")));
                        continue;
                    }
                },
                None => "cannot spend".to_string(),
            };
            let detail = format!("{detail}; {cap_line}");
            match slot {
                Some(h) => {
                    let was = h.is_serving();
                    h.publish(Arc::from(capped), cap, source);
                    drop(quiet);
                    out.push(if was { ProviderChange::Replaced { list: "clouds", name: name.clone(), detail } } else { ProviderChange::Added { list: "clouds", name: name.clone(), detail } });
                }
                None => match self.clouds.push(Arc::new(HotCloud::serving(capped, cap, source))) {
                    Ok(()) => out.push(ProviderChange::Added { list: "clouds", name: name.clone(), detail }),
                    Err(_) => out.push(refused("cloud-slots-full", format!("{} clouds are already held; restart to add more", crate::hot::PROVIDER_SLOTS))),
                },
            }
        }
        for h in self.clouds.iter().filter(|h| h.is_serving() && !h.source().is_empty()) {
            if !cfg.clouds.contains_key(h.name()) {
                let _q = h.quiesce();
                h.retire(String::new());
                out.push(ProviderChange::Retired { list: "clouds", name: h.name().to_string() });
            }
        }

        // ── the default cloud ──
        let mut d = self.default_cloud.write().unwrap_or_else(|e| e.into_inner());
        if *d != cfg.default_cloud {
            out.push(ProviderChange::DefaultCloud { from: d.clone(), to: cfg.default_cloud.clone() });
            *d = cfg.default_cloud.clone();
        }
        out
    }
}

/// The `[products.<id>]` block's guest keys, typed, when it names a guest.
fn guest_spec(p: &crate::config::ProductConfig) -> Option<monetize_product::GuestSpec> {
    p.guest.as_ref().map(|module| monetize_product::GuestSpec {
        module: module.clone(),
        digest: p.guest_digest.clone(),
        trust_roots: p.guest_trust_roots.clone(),
        grants: p.grants.clone(),
        http_hosts: p.http_hosts.clone(),
        grpc_hosts: p.guest_grpc_hosts.clone(),
        fuel: p.guest_fuel,
        call_timeout_ms: p.guest_call_timeout_ms,
        max_memory_bytes: p.guest_max_memory_bytes,
        http_auth: p.http_auth.clone(),
        hmac_secrets: p.hmac_secrets.clone(),
        secrets_dir: p.secrets_dir.clone(),
        plugin: p.plugin.clone(),
    })
}

/// The `[vendors.<name>]` block's guest keys, typed, when it names a guest.
fn vendor_guest_spec(cfg: &Config, v: &crate::config::VendorConfig) -> Option<monetize_product::GuestSpec> {
    v.guest.as_ref().map(|module| monetize_product::GuestSpec {
        module: module.clone(),
        digest: v.guest_digest.clone(),
        trust_roots: v.guest_trust_roots.clone(),
        grants: v.grants.clone(),
        http_hosts: v.http_hosts.clone(),
        grpc_hosts: Vec::new(),
        fuel: v.guest_fuel,
        call_timeout_ms: v.guest_call_timeout_ms,
        max_memory_bytes: v.guest_max_memory_bytes,
        http_auth: v.http_auth.clone(),
        hmac_secrets: v.hmac_secrets.clone(),
        secrets_dir: Some(cfg.secrets_dir()),
        plugin: None,
    })
}

/// **The address a PULLED product's plugin is built with** (`[products.<id>] instance`, no
/// `endpoint`): monetize never dials an estate ([`pulled`] wraps the plugin), but a linked
/// plugin builds its lazy channel from an address and refuses an empty one — so the ONE box
/// could not open with gunnar pulled (money-flow 2026-10-10). Never resolvable, never dialled.
pub use monetize_product::PULLED_NEVER_DIALLED;

/// The `[products.<id>]` block in the shape a factory is built from.
fn product_build<'a>(id: &'a str, p: &'a crate::config::ProductConfig, guest: Option<&'a monetize_product::GuestSpec>) -> ProductBuild<'a> {
    ProductBuild {
        id,
        endpoint: if p.instance.is_some() && p.endpoint.trim().is_empty() { PULLED_NEVER_DIALLED } else { &p.endpoint },
        identity_key: None,
        appliance_key: p.appliance_key.as_deref(),
        bearer_token: p.bearer_token.as_deref(),
        api_key_env: p.api_key_env.as_deref(),
        restoring_from_twin: p.restoring_from_twin.as_deref(),
        tls_ca: p.tls_ca.as_deref(),
        tls_client: match (p.tls_client_cert.as_deref(), p.tls_client_key.as_deref()) {
            (Some(c), Some(k)) => Some((c, k)),
            _ => None,
        },
        guest,
    }
}

/// The factory `[products.<id>]` names, or the refusal — worded as it always
/// was, because an operator's config has not changed meaning: a product this
/// binary does not link is not a product this binary can meter.
fn find_product_factory<'a>(id: &str, linked: &'a Linked) -> anyhow::Result<&'a ProductFactory> {
    linked.find_product(id).ok_or_else(|| {
        anyhow::anyhow!(
            "[products.{id}]: no plugin for that product is linked into this server (linked: {:?}). \
             The open build links no product; a deployment's binary registers the plugins it ships \
             (`Linked::product`), the way it registers its vendors and clouds",
            linked.product_names()
        )
    })
}

/// Build `[products.<id>]` through the factory registered under `id`. The
/// registry matches on no product name: gunnar, holger and every other plugin
/// reach this server the way a paid vendor does, by registration.
///
/// **G15**: `plugin = "<name>"` looks the factory up under `<name>` and serves
/// what it builds under the BLOCK's id ([`HotProduct::served_as`]). A plugin answering an
/// id that is neither the block's nor the named plugin's is refused: it would
/// otherwise serve as a second product of that id.
/// **A linked product's `[products.<id>] appliance_key`**, checked at start: a key that is no
/// ed25519 key stops the server rather than the first customer; none is `None`.
fn linked_ticket_key(id: &str, p: &crate::config::ProductConfig) -> anyhow::Result<Option<String>> {
    let Some(key) = p.appliance_key.as_deref().map(str::trim).filter(|k| !k.is_empty()) else {
        return Ok(None);
    };
    if crate::site_ticket::key_of(key).is_none() {
        anyhow::bail!("[products.{id}] appliance_key: not an ed25519 public key (64 hex characters), so no ticket its main mints could be checked");
    }
    Ok(Some(key.to_ascii_lowercase()))
}

/// The built product and, where it is a plugin of another id, the block's id it is served under.
fn build_product(id: &str, p: &crate::config::ProductConfig, linked: &Linked) -> anyhow::Result<(monetize_product::pulled::FactsSource, Option<&'static str>)> {
    if p.tls_client_cert.is_some() != p.tls_client_key.is_some() {
        anyhow::bail!("[products.{id}]: `tls_client_cert` and `tls_client_key` come together — one without the other presents no identity (f-grpc-tls)");
    }
    // A guest is linked under its block's id; its own `id` op is checked against `plugin`.
    let plugin = if p.guest.is_some() { id } else { p.plugin.as_deref().unwrap_or(id) };
    let factory = find_product_factory(plugin, linked)?;
    let guest = guest_spec(p);
    let built = (factory.build)(&product_build(id, p, guest.as_ref())).map_err(|e| anyhow::anyhow!(e))?;
    let answers = built().id;
    if answers == id {
        Ok((built, None))
    } else if p.plugin.is_some() && answers == plugin {
        Ok((built, Some(monetize_product_wasm::leak_id(id))))
    } else {
        anyhow::bail!(
            "[products.{id}]: the plugin answers product id `{answers}`, not `{id}` — two blocks would serve one product id. \
             Set `plugin = \"{answers}\"` to serve that plugin as `{id}` (G15)"
        )
    }
}

/// The block's extra keys as the JSON value a factory reads them through.
fn settings_json(table: &toml::Table) -> anyhow::Result<serde_json::Value> {
    serde_json::to_value(table).map_err(|e| anyhow::anyhow!("settings: {e}"))
}

/// Build the instance `name` (`<kind>/<instance>`) through the factory
/// linked for its KIND, and hand it out AS the instance: `vendor()` is `name`,
/// every handle is translated ([`monetize_pay::instance::Instance`]).
fn build_vendor(name: &str, cfg: &Config, v: &crate::config::VendorConfig, linked: &Linked) -> anyhow::Result<Box<dyn Payment>> {
    let block = monetize_pay::instance::block_of(name);
    let kind = monetize_pay::instance::kind_of(name);
    let Some(factory) = linked.find_vendor(kind) else {
        anyhow::bail!(
            "{block} enabled = true, but its kind `{kind}` is not linked into this server (linked: {:?}). \
             The open build links `invoice` and `sandbox` behind its cargo features; klarna, stripe and swish are the \
             vetra build's, from the private monetize-impl",
            linked.vendor_names()
        )
    };
    let settings = settings_json(&v.settings)?;
    let ctx = VendorBuild { name, data_dir: &cfg.data_dir, settings: Settings(&settings) };
    let inner = (factory.build)(&ctx).map_err(|e| anyhow::anyhow!("{e}"))?;
    Ok(Box::new(monetize_pay::instance::Instance::wrap(name, inner).map_err(|e| anyhow::anyhow!(e))?))
}

/// Build one cloud provider from its config block, **wrapped in its guard**.
///
/// The wrap is not optional and not a factory's choice. `ensure` is the only
/// call in monetize that can spend money at a provider, and the pool logic only
/// stops it buying the SAME thing twice — a loop over fifty tenants buys fifty
/// disks, each one correctly. So every provider that CAN spend leaves here
/// inside a `SpendCap`, and one whose factory says it cannot leaves inside a
/// `CannotSpend` that destroys and refuses any purchase it makes anyway. There
/// is no way to ask for a bare one.
fn build_cloud(
    name: &str,
    cfg: &Config,
    c: &crate::config::CloudConfig,
    linked: &Linked,
    products: &[Arc<Pulled>],
    reach: &monetize_cloud::server::GrowReach,
) -> anyhow::Result<GuardedCloud> {
    let (inner, ceiling) = build_cloud_bare(name, cfg, c, linked, products, reach)?;
    wrap_cloud(name, &cfg.data_dir, inner, ceiling)
}

/// The provider a `[clouds.<name>]` block builds, NOT yet wrapped, and the
/// ceiling it must be wrapped in. Every refusal [`build_cloud`] makes about the
/// block (no cap, a cap that limits nothing, a name nothing links) is made
/// here, before the factory runs.
fn build_cloud_bare(
    name: &str,
    cfg: &Config,
    c: &crate::config::CloudConfig,
    linked: &Linked,
    products: &[Arc<Pulled>],
    reach: &monetize_cloud::server::GrowReach,
) -> anyhow::Result<(Box<dyn Cloud>, Option<monetize_cloud::cap::Ceiling>)> {
    let factory = linked.find_cloud(name);
    // A name this binary cannot build is still asked for its ceiling first:
    // the cap is checked before the provider, deliberately, so the refusal an
    // operator sees is about the config's most dangerous omission.
    let spends = factory.map(|f| f.spends).unwrap_or(true);
    let ceiling = if spends {
        // REFUSE FIRST, before a credential is read or a socket is opened: an
        // enabled cloud with no ceiling is an automated system with an unbounded
        // credit card, and defaulting one would be choosing a number on the
        // operator's behalf that they would only discover on an invoice.
        let cap = c.cap.ok_or_else(|| {
            anyhow::anyhow!(
                "[clouds.{name}] is enabled but declares no `cap`. A spend ceiling is required, \
                 because this is the only place in monetize that spends money at a provider. Write one \
                 down, e.g. `cap = {{ total_minor = 1000, currency = \"Eur\" }}` for 10 EUR ever, or \
                 `monthly_minor` for a per-month window; set both and both hold. A ceiling of 0 is a \
                 valid and useful answer — it refuses every purchase, so the deployment may use only \
                 pool space that is already paid for."
            )
        })?;
        let ceiling = monetize_cloud::cap::Ceiling {
            monthly_minor: cap.monthly_minor,
            total_minor: cap.total_minor,
            currency: cap.currency.ok_or_else(|| anyhow::anyhow!("[clouds.{name}] cap needs a `currency` (\"Sek\" or \"Eur\")"))?,
        };
        // Refuse a ceiling that limits nothing HERE, with the config file's own
        // vocabulary, rather than letting the constructor say it in the library's.
        ceiling.check().map_err(|e| anyhow::anyhow!("[clouds.{name}] cap: {e}"))?;
        Some(ceiling)
    } else {
        None
    };
    let Some(factory) = factory else {
        anyhow::bail!(
            "[clouds.{name}] enabled = true, but that provider is not linked into this server (linked: {:?}). \
             The open build links `server` always and `sandbox` behind its cargo feature; upcloud and scaleway \
             are the vetra build's, from the private monetize-impl",
            linked.cloud_names()
        )
    };
    let mut settings = settings_json(&c.settings)?;
    // A cloud GUEST's HTTP door reads its credential from the deployment's
    // secrets directory — a top-level key, so the block cannot name it.
    if c.settings.get("guest").is_some() {
        if let Some(obj) = settings.as_object_mut() {
            obj.insert("secrets_dir".into(), serde_json::Value::String(cfg.secrets_dir().display().to_string()));
        }
    }
    let ctx = CloudBuild { name, data_dir: &cfg.data_dir, settings: Settings(&settings), ceiling, products };
    // The box's own iron counts the set its grow batcher can still enlarge
    // (g-dk-market): the `server` cloud is built with this registry's reach.
    let inner = if factory.name == monetize_cloud::server::PROVIDER {
        monetize_cloud::server::build_with(&ctx, reach.clone())
    } else {
        (factory.build)(&ctx)
    }
    .map_err(|e| anyhow::anyhow!("{e}"))?;
    Ok((inner, ceiling))
}

/// A built cloud, guarded, and the cap's shared ledger beside it (for the slot to hold).
type GuardedCloud = (Box<dyn Cloud>, Option<Arc<monetize_cloud::cap::Cap>>);

/// **The guard every cloud leaves the registry in**: its `SpendCap`, whose
/// running total is read from `<name>-spend.json` NOW — which is why a live
/// replacement calls this only once the old provider's purchases are over.
/// The cap's shared ledger rides beside the guarded cloud, for the slot to hold.
fn wrap_cloud(name: &str, data_dir: &Path, inner: Box<dyn Cloud>, ceiling: Option<monetize_cloud::cap::Ceiling>) -> anyhow::Result<GuardedCloud> {
    match ceiling {
        Some(ceiling) => {
            let spend_path = data_dir.join(format!("{name}-spend.json"));
            let capped = monetize_cloud::cap::SpendCap::new(inner, ceiling, spend_path).map_err(|e| anyhow::anyhow!("[clouds.{name}] spend cap: {e}"))?;
            let cap = capped.cap();
            Ok((Box::new(capped), Some(cap)))
        }
        // THE ONE EXEMPTION from the ceiling: a provider whose factory says it
        // cannot spend. Not trusted — held to it.
        None => Ok((Box::new(monetize_cloud::cap::CannotSpend::new(inner)), None)),
    }
}

/// The cloud of a server with none linked: refuses to provision, prices nothing.
pub struct NoCloud;

impl NoCloud {
    fn none() -> CloudError {
        CloudError::Refused("no cloud provider is linked into this server".into())
    }
}

/// Every op refused (a destroy names the resource it does not know): a server with no
/// provider is not in a position to claim it owns nothing, nor to buy anything.
impl Cloud for NoCloud {
    fn manifest(&self) -> CloudManifest {
        CloudManifest { provider: "none".into(), zones: Vec::new(), settings: Vec::new(), grow_refused: Some(NoCloud::none().to_string()) }
    }
    fn catalog(&self, _zone: &Zone) -> Result<Catalog, CloudError> {
        Err(NoCloud::none())
    }
    fn price(&self, _what: &Priced<'_>) -> Result<Price, CloudError> {
        Err(NoCloud::none())
    }
    fn lay(&self, _plan: &monetize_cloud::lay::EstatePlan, _journal: &monetize_cloud::grow::Journal) -> Result<monetize_cloud::lay::LaidEstate, CloudError> {
        Err(NoCloud::none())
    }
    fn change(&self, _change: &Change, _journal: &monetize_cloud::grow::Journal) -> Result<Changed, CloudError> {
        Err(NoCloud::none())
    }
    fn boot(&self, _boot: &Boot, _journal: &monetize_cloud::grow::Journal) -> Result<Booted, CloudError> {
        Err(NoCloud::none())
    }
    fn firewall(&self, _laid: &monetize_cloud::lay::LaidEstate) -> Result<(), CloudError> {
        Err(NoCloud::none())
    }
    fn destroy(&self, what: &Give<'_>) -> Result<Reclaimed, CloudError> {
        match what {
            Give::Resource(id) => Err(CloudError::UnknownResource((*id).clone())),
            Give::References(_) => Err(NoCloud::none()),
        }
    }
    fn inventory(&self, what: &Listing<'_>) -> Result<Listed, CloudError> {
        match what {
            Listing::Status(id) => Err(CloudError::UnknownResource((*id).clone())),
            Listing::Room { .. } => Ok(Listed::Room(RoomLeft::unwalled())),
            Listing::Admits(_) => Ok(Listed::Admitted),
            _ => Err(NoCloud::none()),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use monetize_cloud::{Ensured, Resource};
    use monetize::testing::{MockCloud, MockPay, MockProduct};

    /// ★ HENRIK-1: **an operator's own site is the declared `operator_site` estate only**; a
    /// customer's declared estate and an undeclared (sold) one are not.
    #[test]
    fn only_a_declared_operator_site_is_the_operators_own() {
        let site = crate::config::EstateConfig { product: "gunnar".into(), owner: String::new(), operator_site: true, ..Default::default() };
        let kund = crate::config::EstateConfig { product: "gunnar".into(), owner: "kund".into(), ..Default::default() };
        let reg = Registry::new().estate("test2-gunnar-rs", site).estate("kund", kund);
        assert!(reg.operator_site("test2-gunnar-rs"), "declared operator_site, even with no owner named");
        assert!(!reg.operator_site("kund"));
        assert!(!reg.operator_site("rickardtest"));
    }

    /// ★ G16 (ONE monetize): no estate runs a monetize, a sold one included, so no build
    /// links the `shop` cloud a sold estate's own monetize grew through, and a config that
    /// still names `[clouds.shop]` is refused at start by name.
    #[test]
    fn no_build_links_the_shop_cloud_of_a_sold_estates_own_monetize() {
        assert!(Linked::open().find_cloud("shop").is_none(), "{:?}", Linked::open().cloud_names());
        let cfg = Config::from_toml("data_dir = \"/nonexistent\"\n[clouds.shop]\nenabled = true\ncap = { total_minor = 0, currency = \"Eur\" }\ndoor = \"https://monetize.ignalina.dk\"\ncompany = \"acme\"\n").unwrap();
        let e = Registry::from_config(&cfg).err().expect("refused at start").to_string();
        assert!(e.contains("[clouds.shop]") && e.contains("not linked"), "{e}");
    }

    /// A capped cloud GUEST that does not load is skipped by name at start —
    /// never the end of the server (owner 2026-09-26: the UpCloud guest on the
    /// paid front must not take monetize down with a dead token).
    #[test]
    fn a_capped_cloud_guest_that_does_not_load_is_skipped_and_the_server_starts() {
        let cfg = Config::from_toml("data_dir = \"/nonexistent\"\n[clouds.acloud]\nenabled = true\ncap = { monthly_minor = 5000, currency = \"Eur\" }\nguest = \"/nonexistent/acloud.wasm\"\nguest_digest = \"00\"\n").unwrap();
        let reg = Registry::from_config(&cfg).expect("the server starts");
        assert!(reg.cloud_names().is_empty());
        let said: Vec<String> = reg.vendor_start_refusals().iter().map(|r| r.to_string()).collect();
        assert!(said.iter().any(|l| l.contains("cloud-guest-refused-at-start") && l.contains("acloud")), "{said:?}");
        // with no cap it is still refused whole, before anything is read
        let nocap = Config::from_toml("data_dir = \"/nonexistent\"\n[clouds.acloud]\nenabled = true\nguest = \"/nonexistent/acloud.wasm\"\n").unwrap();
        assert!(Registry::from_config(&nocap).is_err());
    }

    /// stinky monetize-server #23: a config with more vendors or clouds than
    /// the registry has slots is a CONFIG error the start returns by name —
    /// never a `panic!` that takes the server down.
    #[test]
    fn more_vendors_or_clouds_than_slots_is_a_config_error_not_a_panic() {
        fn mock_vendor(_: &VendorBuild<'_>) -> Result<Box<dyn Payment>, String> {
            Ok(Box::new(MockPay::new()))
        }
        fn mock_cloud(_: &CloudBuild<'_>) -> Result<Box<dyn Cloud>, String> {
            Ok(Box::new(MockCloud::new()))
        }
        let slots = crate::hot::PROVIDER_SLOTS;
        let mut linked = Linked::open();
        linked.vendor(VendorFactory { name: "mock", config_envs: &[], build: mock_vendor });
        let mut vendors = String::from("data_dir = \"/nonexistent\"\n");
        for i in 0..=slots {
            vendors.push_str(&format!("[vendors.mock.v{i}]\nenabled = true\n"));
        }
        let cfg = Config::from_toml(&vendors).unwrap();
        let said = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| Registry::from_config_with(&cfg, &linked).err().map(|e| format!("{e:#}"))))
            .expect("too many vendors is a config error, not a panic")
            .expect("too many vendors must refuse");
        assert!(said.contains(&slots.to_string()) && said.contains("vendors"), "{said}");

        let mut linked = Linked::open();
        let mut clouds = String::from("data_dir = \"/nonexistent\"\n");
        for i in 0..=slots {
            let name: &'static str = Box::leak(format!("c{i}").into_boxed_str());
            linked.cloud(CloudFactory { name, config_envs: &[], spends: false, build: mock_cloud });
            clouds.push_str(&format!("[clouds.{name}]\nenabled = true\n"));
        }
        let cfg = Config::from_toml(&clouds).unwrap();
        let said = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| Registry::from_config_with(&cfg, &linked).err().map(|e| format!("{e:#}"))))
            .expect("too many clouds is a config error, not a panic")
            .expect("too many clouds must refuse");
        assert!(said.contains(&slots.to_string()) && said.contains("clouds"), "{said}");
    }

    /// ★ release-3 (MEASURED on the dwarf mock 2026-09-29, install-orig-5): the
    /// paid front's monetize started bare, the feed wrote `[clouds.server]
    /// product = "gunnar"` before the start read its config, and the start
    /// DIED — `[products.gunnar]` arrives only when gunnar self-registers its
    /// plugin, after monetize serves. A cloud whose product is not linked YET
    /// is skipped by name at start; the watcher takes it in once the product is.
    #[test]
    fn a_cloud_whose_product_is_not_linked_yet_is_skipped_and_the_server_starts() {
        let cfg = Config::from_toml("data_dir = \"/nonexistent\"\n[clouds.server]\nenabled = true\nproduct = \"gunnar\"\npool_gib = 10\n").unwrap();
        let reg = Registry::from_config(&cfg).expect("the server starts");
        assert!(reg.cloud_names().is_empty());
        let said: Vec<String> = reg.vendor_start_refusals().iter().map(|r| r.to_string()).collect();
        assert!(said.iter().any(|l| l.contains("cloud-product-not-linked-at-start") && l.contains("server") && l.contains("gunnar")), "{said:?}");
    }

    /// **The tax page's jurisdiction comes from the estate's own `[tax]`**
    /// (owner 2026-09-28: DK, 25 %, exclusive) when no implementation is
    /// linked — not "no jurisdiction is linked" with every box `—`.
    #[test]
    fn an_open_build_takes_the_jurisdiction_its_tax_table_declares() {
        let dir = std::path::Path::new("/nonexistent");
        let open = Linked::open();
        let dk = open.build_tax(dir, &serde_json::json!({"jurisdiction": "DK", "rate_percent": 25, "prices": "exclusive"})).unwrap();
        assert_eq!(dk.jurisdiction(), "DK");
        // Declaring none is the ONE box's case (owner 2026-10-11: Stripe decides per
        // country, each payment is a row): the totals per buyer country, never `—`.
        let per = open.build_tax(dir, &serde_json::json!({})).unwrap();
        assert_eq!(per.jurisdiction(), monetize::tax::PER_COUNTRY, "declaring none is the per-country report");
        assert!(open.build_tax(dir, &serde_json::json!({"jurisdiction": "Denmark", "rate_percent": 25})).is_err(), "refused by name, never a silent default");
    }

    /// **c-dead-front: a front's monetize starts before its product registers.**
    /// A fresh or laid-again front is fed `[clouds.server] product = "gunnar"`
    /// by the installer, and gunnar's primary registers `[products.gunnar]`
    /// only later (self_register). The hot reload refused that block by name
    /// and kept serving (MEASURED release-2 mock: `cloud REFUSED
    /// [cloud-refused]`, then registered); a START on the same config ended
    /// the server (`the server stopped … no [products.gunnar] is linked`,
    /// MEASURED on the recharge mock, where the front waits for its feed
    /// before monetize starts) — and nothing ever started it again. The start
    /// skips such a cloud by name, like the reload; the watcher takes it in
    /// once the product is there.
    #[test]
    fn a_cloud_whose_product_is_not_linked_yet_is_skipped_at_start_and_the_server_starts() {
        let cfg = Config::from_toml("data_dir = \"/nonexistent\"\n[clouds.server]\nenabled = true\npool_gib = 10\nproduct = \"gunnar\"\n").unwrap();
        let reg = Registry::from_config(&cfg).expect("the server starts");
        assert!(!reg.cloud_names().contains(&"server"), "not built without its product");
        let said: Vec<String> = reg.vendor_start_refusals().iter().map(|r| r.to_string()).collect();
        assert!(said.iter().any(|l| l.contains("cloud-product-not-linked-at-start") && l.contains("gunnar")), "{said:?}");
    }

    #[test]
    fn lookups_are_by_name_and_the_default_cloud_wins() {
        let reg = Registry::new().product(MockProduct::gunnar()).vendor(MockPay::new()).cloud(MockCloud::new());
        assert!(reg.find_product("gunnar").is_some() && reg.find_product("holger").is_none());
        assert!(reg.find_vendor("mock").is_some() && reg.find_vendor("acme").is_none());
        assert_eq!(monetize_cloud::Cloud::manifest(reg.cloud_for_transactions()).provider, "mockcloud");
        let none = Registry::new();
        assert_eq!(monetize_cloud::Cloud::manifest(none.cloud_for_transactions()).provider, "none");
        assert_eq!(reg.default_cloud_name(), Some("mockcloud"), "the only cloud linked is the default");
        assert_eq!(none.default_cloud_name(), None, "NoCloud is not a cloud a console may mark");
        // Two linked: only a `default_cloud` that names one of them decides.
        let two = || Registry::new().cloud(MockCloud::new()).cloud(MockCloud::new());
        assert_eq!(two().default_cloud_name(), None, "two clouds and no default: a sale provisions nowhere");
        assert_eq!(two().with_default_cloud(Some("elsewhere".into())).default_cloud_name(), None, "a name nothing linked answers to is not the default");
        assert_eq!(two().with_default_cloud(Some("mockcloud".into())).default_cloud_name(), Some("mockcloud"));
        assert!(matches!(
            monetize_cloud::ask::ensure(&none.cloud_for_transactions(), "r", &Resource::Disk { server: monetize_cloud::ServerId("s".into()), gib: 1 }, None),
            Err(CloudError::Refused(_))
        ));
    }

    /// ★ **THE APPLIANCE KEY REACHES THE HOST, AND A BAD ONE STOPS THE
    /// SERVER RATHER THAN THE FIRST CUSTOMER.**
    ///
    /// `[products.gunnar] appliance_key` is the whole of the tenant path's
    /// configuration, and the two things that can go wrong with it are that it
    /// is silently dropped (every self-service renewal refused, nobody knows
    /// why) and that it is wrong (discovered at the first renewal, by a
    /// customer). Both are checked here, at build time.
    #[test]
    fn the_appliance_key_is_read_from_the_product_block_and_refused_by_name_when_it_is_not_a_key() {
        let dir = std::env::temp_dir().join(format!("monetize-appliance-key-{}-{:?}", std::process::id(), std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_nanos()));
        std::fs::create_dir_all(&dir).unwrap();
        // A real Ed25519 public key, in the 64 hex characters an operator
        // pastes out of the appliance.
        let key = ed25519_dalek::SigningKey::from_bytes(&[11u8; 32]).verifying_key();
        let hex: String = key.to_bytes().iter().map(|b| format!("{b:02x}")).collect();
        let cfg = |appliance_key: Option<&str>| crate::config::ProductConfig {
            // Never dialled: the plugin connects lazily and nothing here calls it.
            endpoint: "127.0.0.1:1".into(),
            appliance_key: appliance_key.map(str::to_owned),
            ..Default::default()
        };
        // A32-13: the key is the HOST's (the product checks no ticket): read from the block at
        // start, refused by name there when it is not a key.
        assert_eq!(linked_ticket_key("gunnar", &cfg(Some(&hex))).expect("a configured key is read"), Some(hex.clone()), "the key must reach the host, or every self-service renewal is refused and nobody can say why");
        assert_eq!(linked_ticket_key("gunnar", &cfg(None)).expect("a block with no key still builds"), None, "no key configured is no tenant renewal, and it must be the default");
        let bad = linked_ticket_key("gunnar", &cfg(Some("not an ed25519 key")));
        assert!(bad.is_err(), "a key that is not a key must stop the server, not the first customer");
        assert!(format!("{:#}", bad.err().unwrap()).contains("appliance_key"), "and the error must name the setting");
        let reg = Registry::new().product(crate::testing::products::TestProduct::gunnar()).ticket_key("gunnar", &hex);
        assert_eq!(reg.product_ticket_key("gunnar"), Some(hex.clone()));
        let other = ed25519_dalek::SigningKey::from_bytes(&[12u8; 32]).verifying_key().to_bytes().iter().map(|b| format!("{b:02x}")).collect::<String>();
        assert!(reg.deliver_product_key("gunnar", &other).unwrap_err().contains("pins"), "the operator's key wins over a delivered one");
        assert_eq!(reg.product_ticket_key("gunnar"), Some(hex.clone()));
        let open = Registry::new().product(crate::testing::products::TestProduct::gunnar());
        assert!(open.deliver_product_key("gunnar", "zz").is_err(), "a delivered key that is no key is refused");
        open.deliver_product_key("gunnar", &other).expect("the home estate's key is taken");
        assert_eq!(open.product_ticket_key("gunnar"), Some(other));

        let _ = std::fs::remove_dir_all(&dir);
    }

    /// **The open set never contains a private name, and the seam takes one
    /// on top without this file changing.** `Linked::open()` is exactly what the
    /// features say; registering a foreign factory makes its name buildable and
    /// its envs installable; the registry's own source has no string for it.
    #[test]
    fn the_open_set_is_what_the_features_say_and_a_registered_factory_is_found_by_name() {
        let open = Linked::open();
        for private in ["klarna", "stripe", "swish"] {
            assert!(open.find_vendor(private).is_none(), "{private} must not be in the open set: {:?}", open.vendor_names());
        }
        for private in ["upcloud", "scaleway"] {
            assert!(open.find_cloud(private).is_none(), "{private} must not be in the open set: {:?}", open.cloud_names());
        }
        assert!(open.find_cloud("server").map(|f| !f.spends).unwrap_or(false), "server is always linked, and cannot spend");
        assert_eq!(open.find_vendor("invoice").is_some(), cfg!(feature = "invoice"));
        assert_eq!(open.find_vendor("sandbox").is_some(), cfg!(feature = "sandbox"));
        assert!(open.product_names().is_empty(), "the open build links no product plugin: {:?}", open.product_names());
        assert_eq!(open.find_cloud("sandbox").map(|f| f.spends), cfg!(feature = "sandbox").then_some(true));
        let src = include_str!("registry.rs");
        for private in ["\"klarna\"", "\"stripe\"", "\"swish\"", "\"upcloud\"", "\"scaleway\""] {
            let mentions = src.matches(private).count();
            assert_eq!(mentions, 1, "{private} appears in registry.rs outside this assertion: the registry must not know the name");
        }

        // A foreign factory, registered on top: found, buildable, its envs carried.
        fn build(ctx: &VendorBuild<'_>) -> Result<Box<dyn Payment>, String> {
            assert_eq!((ctx.name, ctx.kind(), ctx.instance()), ("acme/play", "acme", "play"));
            assert_eq!(ctx.settings.get::<String>("environment").unwrap().as_deref(), Some("playground"));
            Ok(Box::new(MockPay::new()))
        }
        let mut linked = Linked::open();
        linked.vendor(VendorFactory { name: "acme", config_envs: &["MONETIZE_ACME_SECRET", "MONETIZE_ACME_{INSTANCE}_KEY"], build });
        // The open dummies' own envs ride along when their features are on (the money-free open
        // default, 2026-10-24); the foreign factory's plain name is listed, its per-instance one is not.
        let envs = linked.vendor_config_envs();
        assert!(envs.contains(&"MONETIZE_ACME_SECRET"), "{envs:?}");
        assert!(!envs.iter().any(|e| e.contains("{INSTANCE}")), "a per-instance name is not a name until it is expanded: {envs:?}");
        let cfg = Config::from_toml("[vendors.acme.play]\nenabled = true\nenvironment = \"playground\"").unwrap();
        assert_eq!(linked.vendor_instance_envs(&cfg), ["MONETIZE_ACME_PLAY_KEY"]);
        let reg = Registry::from_config_with(&cfg, &linked).unwrap();
        assert_eq!(reg.vendor_names(), vec!["acme/play"], "the server sees the instance, whatever the factory calls itself");
        // Refuse twin: the same config against the OPEN set is refused by name.
        let err = Registry::from_config(&cfg).err().map(|e| e.to_string()).expect("acme is not linked");
        assert!(err.contains("[vendors.acme.play]") && err.contains("not linked"), "{err}");
    }

    /// **An enabled cloud with no ceiling must not start.** It is the only place
    /// in monetize that spends money at a provider, and defaulting a number here
    /// would be choosing the operator's credit limit for them — discovered on an
    /// invoice. Absence is not unlimited, exactly as zero is not unlimited.
    ///
    /// A name this binary cannot build is refused on the MISSING CAP rather than
    /// on the missing provider, because the cap is checked first and deliberately so.
    #[test]
    fn refuse_twin_an_enabled_cloud_without_a_spend_ceiling_refuses_to_start() {
        let no_cap = Config::from_toml("[clouds.upcloud]\nenabled = true").unwrap();
        let err = Registry::from_config(&no_cap).err().map(|e| e.to_string()).expect("no ceiling must refuse");
        assert!(err.contains("no `cap`") || err.contains("declares no"), "the reason must name the cap: {err}");
        assert!(err.contains("monthly_minor"), "and show how to write one: {err}");
        assert!(err.contains("0 is a valid") || err.contains("monthly_minor = 0"), "and say that zero is a real answer: {err}");

        // A ZERO ceiling is accepted — it is the useful rehearsal setting, not a
        // misconfiguration. (The provider is not linked into the open build, so
        // it still fails — on the provider, which proves the cap check passed.)
        let zero = Config::from_toml("[clouds.upcloud]\nenabled = true\ncap = { monthly_minor = 0, currency = \"Eur\" }").unwrap();
        let err = Registry::from_config(&zero).err().map(|e| e.to_string()).expect("not linked into the open build");
        assert!(!err.contains("cap") && err.contains("not linked"), "a zero ceiling is valid; the failure must be about the provider: {err}");

        // A cloud that is NOT enabled needs no ceiling at all.
        let off = Config::from_toml("[clouds.upcloud]\nenabled = false").unwrap();
        assert!(Registry::from_config(&off).is_ok(), "a disabled cloud is not asked for a ceiling");

        // The SANDBOX cloud is refused on the same missing cap, before its own
        // constructor is reached: rule 4, it sits behind the ceiling like any provider.
        let sb_no_cap = Config::from_toml("[clouds.sandbox]\nenabled = true\nprices = { disk_minor_per_gib_month = 23 }").unwrap();
        let err = Registry::from_config(&sb_no_cap).err().map(|e| e.to_string()).expect("the sandbox cloud with no ceiling must refuse");
        assert!(err.contains("[clouds.sandbox]") && err.contains("no `cap`"), "{err}");
    }

    /// The rehearsal cloud, both ways round so neither arm is vacuous: a build
    /// that HAS it builds it capped and marked, and a build that does not must
    /// refuse the name rather than fall through to something real.
    #[test]
    fn the_sandbox_cloud_is_built_capped_or_refused_by_name() {
        let dir = std::env::temp_dir().join(format!("monetize-registry-sandbox-{}", std::process::id()));
        let _ = std::fs::remove_dir_all(&dir);
        let toml = format!(
            "data_dir = {:?}\n[clouds.sandbox]\nenabled = true\ncap = {{ total_minor = 1000, currency = \"Eur\" }}\npool_gib = 0\n\
             prices = {{ disk_minor_per_gib_month = 23, plan_minor_per_month = {{ \"STARTER-2xCPU-4GB\" = 1304 }} }}\n",
            dir.display()
        );
        let cfg = Config::from_toml(&toml).unwrap();
        #[cfg(feature = "sandbox")]
        {
            let reg = Registry::from_config(&cfg).unwrap();
            assert_eq!(reg.cloud_names(), vec!["sandbox"]);
            let cloud = reg.transaction_slot().expect("a cloud a sale spends at");
            assert_eq!(cloud.spend_ceiling(), Some(monetize_cloud::cap::Ceiling::total(1000, monetize_pay::Currency::Eur)), "inside the cap");
            // The applied output: a purchase through the registry's cloud lands as
            // marked fake iron in the data dir, at the declared price.
            let res = Resource::Disk { server: monetize_cloud::ServerId("gunnar-se-sto1".into()), gib: 10 };
            match monetize_cloud::ask::ensure(&cloud, "gunnar/alice/2026-09", &res, None).unwrap() {
                Ensured::Provisioned { id, cost } => {
                    assert_eq!(id.provider, "sandbox");
                    assert!(id.id.starts_with("sandbox:disk:"), "{}", id.id);
                    assert_eq!(cost.minor_per_month, 230, "23 × 10, declared");
                    assert_eq!(cost.currency, monetize_pay::Currency::Eur, "the table without a currency takes the cap's");
                }
                other => panic!("{other:?}"),
            }
            let state = std::fs::read_to_string(dir.join("sandbox-cloud.json")).expect("the registry gives a deployment a home for its iron");
            assert!(state.contains("\"monetize_provenance\": \"sandbox\""), "{state}");
            assert!(std::fs::read_to_string(dir.join("sandbox-spend.json")).unwrap().contains("230"), "the cap's total moved");
            // And the install carries its env into the unit.
            assert!(Linked::open().cloud_config_envs().contains(&"MONETIZE_SANDBOX_CLOUD_STATE"), "{:?}", Linked::open().cloud_config_envs());
            // Refuse twin: a zero in the declared table cannot start.
            let zero = Config::from_toml(&toml.replace("disk_minor_per_gib_month = 23", "disk_minor_per_gib_month = 0")).unwrap();
            let err = Registry::from_config(&zero).err().map(|e| e.to_string()).expect("a zero price table must refuse");
            assert!(err.contains("[clouds.sandbox]") && err.contains("disk at 0"), "{err}");
            // A key that does not parse is a refusal naming the key, never "unset".
            let bad = Config::from_toml(&toml.replace("pool_gib = 0", "pool_gib = \"lots\"")).unwrap();
            let err = Registry::from_config(&bad).err().map(|e| e.to_string()).expect("a mistyped knob must refuse");
            assert!(err.contains("[clouds.sandbox]") && err.contains("`pool_gib` does not parse"), "{err}");
        }
        #[cfg(not(feature = "sandbox"))]
        {
            let err = Registry::from_config(&cfg).err().map(|e| e.to_string()).expect("a build without the feature must refuse it");
            assert!(err.contains("[clouds.sandbox]") && err.contains("not linked"), "{err}");
            assert!(!Linked::open().cloud_config_envs().contains(&"MONETIZE_SANDBOX_CLOUD_STATE"));
        }
    }

    /// ★ **Several sites of one product, each its own account** (owner 2026-10-11: the ONE
    /// box sells gunnar for gunnar.rs AND test2.gunnar.rs, each through its own Stripe). With
    /// `[estates]` declared for a product an order routes by ITS estate — the tenant's
    /// (`@<estate>/<ns>`, else the product's own `instance`) or the calling estate's — and an
    /// order with no estate, or one not declared, or declared for another product, is refused
    /// by name: never the other site's account. A product with none declared keeps its route.
    #[test]
    fn several_sites_of_one_product_each_bill_through_their_own_account_and_no_other() {
        use monetize_pay::{Currency, Market, Money};
        let priced = Money { minor: 22_000, currency: Currency::Sek };
        struct Named(&'static str);
        impl Named {
            pub fn vendor(&self) -> &'static str {
                self.0
            }
            pub fn markets(&self) -> &[Market] {
                &[Market::Se]
            }
            pub fn reserve(&self, _: &monetize_pay::ChargeRequest) -> Result<monetize_pay::ChargeHandle, monetize_pay::PayError> {
                unreachable!()
            }
            pub fn draw(&self, _: &monetize_pay::ChargeHandle) -> Result<monetize_pay::ChargeStatus, monetize_pay::PayError> {
                unreachable!()
            }
            pub fn unlock(&self, _: &monetize_pay::ChargeHandle) -> Result<monetize_pay::ChargeStatus, monetize_pay::PayError> {
                unreachable!()
            }
            pub fn charge_status(&self, _: &monetize_pay::ChargeHandle) -> Result<monetize_pay::ChargeStatus, monetize_pay::PayError> {
                unreachable!()
            }
            pub fn parse_callback(&self, _: monetize_pay::RawCallback<'_>) -> Result<monetize_pay::CallbackEvent, monetize_pay::PayError> {
                unreachable!()
            }
            pub fn give_back(&self, _: &monetize_pay::ChargeHandle, _: Money) -> Result<monetize_pay::ChargeStatus, monetize_pay::PayError> {
                unreachable!()
            }
            pub fn settled(&self, _: &monetize_pay::ChargeHandle) -> Option<monetize_pay::Settlement> {
                None
            }
            pub fn redirect(&self, _: &monetize_pay::ChargeHandle) -> Option<String> {
                None
            }
            pub fn start_signup(&self, _: &monetize_pay::signup::SignupOrder) -> Result<Option<monetize_pay::signup::SignupCheckout>, monetize_pay::PayError> {
                Ok(None)
            }
        }
        impl monetize_pay::payment::Payment for Named {
            fn manifest(&self) -> monetize_pay::payment::PayManifest {
                monetize_pay::payment::PayManifest { vendor: self.vendor().to_string(), markets: self.markets().to_vec(), settings: Vec::new(), holds: true, test_payer: None }
            }
            fn page(&self, charge: &monetize_pay::payment::Charge<'_>, _back: &monetize_pay::payment::Return) -> Result<monetize_pay::payment::Page, monetize_pay::PayError> {
                Ok(match charge {
                    monetize_pay::payment::Charge::Order(req) => monetize_pay::payment::Page::Order { handle: self.reserve(req)?, url: None },
                    monetize_pay::payment::Charge::Signup(o) => monetize_pay::payment::Page::Signup(self.start_signup(o)?),
                    monetize_pay::payment::Charge::CardCheck(_o) => monetize_pay::payment::Page::CardCheck(None),
                    monetize_pay::payment::Charge::TestSignup(_o) => monetize_pay::payment::Page::TestSignup(None),
                })
            }
            fn status(&self, of: &monetize_pay::payment::Of<'_>) -> Result<monetize_pay::payment::Standing, monetize_pay::PayError> {
                Ok(match of {
                    monetize_pay::payment::Of::Charge(h) => monetize_pay::payment::Standing::Charge(self.charge_status(h)?),
                    monetize_pay::payment::Of::Page(h) => monetize_pay::payment::Standing::Page(self.redirect(h)),
                    monetize_pay::payment::Of::Signup { session: _ } => monetize_pay::payment::Standing::Signup(None),
                    monetize_pay::payment::Of::CardCheck { session: _ } => monetize_pay::payment::Standing::CardCheck(None),
                })
            }
            fn capture(&self, h: &monetize_pay::ChargeHandle) -> Result<monetize_pay::ChargeStatus, monetize_pay::PayError> {
                self.draw(h)
            }
            fn release(&self, h: &monetize_pay::ChargeHandle) -> Result<monetize_pay::ChargeStatus, monetize_pay::PayError> {
                self.unlock(h)
            }
            fn refund(&self, h: &monetize_pay::ChargeHandle, amount: monetize_pay::Money) -> Result<monetize_pay::ChargeStatus, monetize_pay::PayError> {
                self.give_back(h, amount)
            }
            fn callback(&self, raw: monetize_pay::RawCallback<'_>) -> Result<monetize_pay::CallbackEvent, monetize_pay::PayError> {
                self.parse_callback(raw)
            }
            fn settlement(&self, h: &monetize_pay::ChargeHandle) -> Option<monetize_pay::Settlement> {
                self.settled(h)
            }
            fn paid_since(&self, _since_unix_ms: i64) -> Result<Vec<monetize_pay::reconcile::PaidAtVendor>, monetize_pay::PayError> {
                Err(monetize_pay::reconcile::cannot_be_asked())
            }
            fn livemode(&self) -> Option<bool> {
                None
            }
        }
        let site = |vendor: &str, site: &str| crate::config::EstateConfig { product: "gunnar".into(), vendor: vendor.into(), owner: "ignalina".into(), site: site.into(), ..Default::default() };
        let r = Registry::new()
            .vendor(Named("stripe/gunnar"))
            .vendor(Named("stripe/gunnar-test2"))
            .vendor(Named("stripe/holger"))
            .route("gunnar", "stripe/gunnar")
            .route("holger", "stripe/holger")
            .home("gunnar", "gunnar-rs")
            .estate("gunnar-rs", site("stripe/gunnar", "gunnar.rs"))
            .estate("test2-gunnar-rs", site("stripe/gunnar-test2", "test2.gunnar.rs"));
        let by = |estate: Option<&str>| r.vendor_for_estate(&priced, Market::Se, "gunnar", estate, "").map(|v| v.name);
        assert_eq!(by(Some("test2-gunnar-rs")), Some("stripe/gunnar-test2"), "test2's own account");
        assert_eq!(by(Some("gunnar-rs")), Some("stripe/gunnar"));
        assert_eq!(by(None), None, "no estate named: refused, never the product's route");
        assert_eq!(by(Some("acme-gunnar-rs")), None, "an estate not declared: refused, never another site's");
        let e = r.route_for("gunnar", Some("acme-gunnar-rs")).unwrap_err();
        assert!(e.contains("acme-gunnar-rs") && e.contains("[estates"), "{e}");
        assert!(r.route_for("gunnar", None).unwrap_err().contains("which estate"), "the refusal says what is missing");
        assert_eq!(r.vendor_for_estate(&priced, Market::Se, "gunnar", Some("test2-gunnar-rs"), "stripe/gunnar").map(|v| v.name), None, "asking for the other site's account is not substituted");
        // ★ i54a run30: the console names the vendor KIND (`stripe`), the ONE box routes the
        // calling site's INSTANCE — the kind is that instance, another kind never is.
        let kind = "stripe/gunnar".split_once('/').unwrap().0; // the registry itself never names a vendor
        assert_eq!(r.vendor_for_estate(&priced, Market::Se, "gunnar", Some("test2-gunnar-rs"), kind).map(|v| v.name), Some("stripe/gunnar-test2"), "the console's `stripe` is the site's own account");
        assert_eq!(r.vendor_for_estate(&priced, Market::Se, "gunnar", Some("gunnar-rs"), kind).map(|v| v.name), Some("stripe/gunnar"));
        assert_eq!(r.vendor_for_estate(&priced, Market::Se, "gunnar", Some("test2-gunnar-rs"), "other").map(|v| v.name), None, "another kind is not substituted");
        assert!(r.route_for("holger", Some("test2-gunnar-rs")).is_err(), "an estate declared for gunnar never bills holger");
        assert_eq!(r.vendor_for_estate(&priced, Market::Se, "holger", None, "").map(|v| v.name), Some("stripe/holger"), "no estates for holger: its own route, as before");
        // A tenant's estate: `@<estate>/<ns>`, else the product's own.
        assert_eq!(r.estate_of_tenant("gunnar", "@test2-gunnar-rs/nina").as_deref(), Some("test2-gunnar-rs"));
        assert_eq!(r.estate_of_tenant("gunnar", "nina").as_deref(), Some("gunnar-rs"));
        assert_eq!(r.vendor_for_tenant(&priced, Market::Se, "gunnar", "@test2-gunnar-rs/nina", "").map(|v| v.name), Some("stripe/gunnar-test2"));
        assert_eq!(r.vendor_for_tenant(&priced, Market::Se, "gunnar", "nina", "").map(|v| v.name), Some("stripe/gunnar"));
        assert_eq!(r.vendor_for_tenant(&priced, Market::Se, "gunnar", "@acme/nina", "").map(|v| v.name), None);
    }

    /// `[estates.<estate>]` at load: a product, an ENABLED vendor instance, one account per site.
    #[test]
    fn a_declared_estate_names_a_product_and_its_own_enabled_account() {
        let base = "[products.gunnar]\nfree = true\nvendor = \"stripe/gunnar\"\n[vendors.stripe.gunnar]\nenabled = true\n[vendors.stripe.gunnar-test2]\nenabled = true\n";
        let ok = format!("{base}[estates.gunnar-rs]\nproduct = \"gunnar\"\nvendor = \"stripe/gunnar\"\nowner = \"ignalina\"\nsite = \"gunnar.rs\"\n[estates.test2-gunnar-rs]\nproduct = \"gunnar\"\nvendor = \"stripe/gunnar-test2\"\nowner = \"ignalina\"\nsite = \"test2.gunnar.rs\"\n");
        let cfg = Config::from_toml(&ok).expect("two sites, two accounts");
        assert_eq!(cfg.estates["test2-gunnar-rs"].vendor, "stripe/gunnar-test2");
        // ST-3: each site's own main's ticket key (64 hex), kept with its estate.
        let key = "0b".repeat(32);
        let keyed = Config::from_toml(&ok.replace("site = \"test2.gunnar.rs\"", &format!("site = \"test2.gunnar.rs\"\nappliance_key = \"{key}\""))).expect("a site's appliance key");
        assert_eq!(keyed.estates["test2-gunnar-rs"].appliance_key, key);
        for (bad, word) in [
            (ok.replace("vendor = \"stripe/gunnar-test2\"", "vendor = \"stripe/nobody\""), "stripe/nobody"),
            (ok.replace("vendor = \"stripe/gunnar-test2\"", "vendor = \"stripe/gunnar\""), "one account per site"),
            (ok.replace("product = \"gunnar\"\nvendor = \"stripe/gunnar-test2\"", "product = \"\"\nvendor = \"stripe/gunnar-test2\""), "names no product"),
            // ST-3: a site's appliance key that is no ed25519 public key is refused at load, by name.
            (ok.replace("site = \"test2.gunnar.rs\"", "site = \"test2.gunnar.rs\"\nappliance_key = \"zz\""), "appliance_key"),
        ] {
            let e = Config::from_toml(&bad).err().map(|e| e.to_string()).unwrap_or_default();
            assert!(e.contains("[estates.") && e.contains(word), "{word}: {e}");
        }
    }

    /// **The vendor for an order is decided by the PRICE first**: nobody at
    /// price 0, else the instance the PRODUCT is routed to — and `None` when
    /// it has no route, the route serves nothing or not the market, or the
    /// order names another, so the caller refuses by name rather than picking.
    #[test]
    fn the_vendor_for_an_order_is_nobody_at_price_zero_and_the_products_route_otherwise() {
        use monetize_pay::{Currency, Market, Money};
        let free = Money { minor: 0, currency: Currency::Sek };
        let priced = Money { minor: 22_000, currency: Currency::Sek };
        let none = Registry::new();
        assert_eq!(none.vendor_for(&free, Market::Se, "gunnar", "acme").map(|v| v.name), Some("none"), "price 0 ignores the name: nobody is called");
        assert!(none.vendor_for(&priced, Market::Se, "gunnar", "").is_none(), "220 SEK and nobody linked");
        assert!(none.vendor_for(&priced, Market::Se, "gunnar", "acme").is_none());
        let one = Registry::new().vendor(MockPay::new());
        assert!(one.vendor_for(&priced, Market::Se, "gunnar", "").is_none(), "no route: the only vendor is NOT chosen");
        let one = one.route("gunnar", "mock");
        assert_eq!(one.vendor_for(&priced, Market::Se, "gunnar", "").map(|v| v.name), Some("mock"), "gunnar's route");
        assert_eq!(one.vendor_for(&priced, Market::Fi, "gunnar", "mock").map(|v| v.name), Some("mock"));
        assert!(one.vendor_for(&priced, Market::Se, "gunnar", "acme").is_none(), "a name that is not the route is not substituted");
        assert!(one.vendor_for(&priced, Market::Se, "holger", "").is_none(), "another product's route is not borrowed");
        // A route to a vendor that does not serve the market takes nothing.
        struct Second;
        impl Second {
            pub fn vendor(&self) -> &'static str {
                "second"
            }
            pub fn markets(&self) -> &[Market] {
                &[Market::Se]
            }
            pub fn reserve(&self, _: &monetize_pay::ChargeRequest) -> Result<monetize_pay::ChargeHandle, monetize_pay::PayError> {
                unreachable!()
            }
            pub fn draw(&self, _: &monetize_pay::ChargeHandle) -> Result<monetize_pay::ChargeStatus, monetize_pay::PayError> {
                unreachable!()
            }
            pub fn unlock(&self, _: &monetize_pay::ChargeHandle) -> Result<monetize_pay::ChargeStatus, monetize_pay::PayError> {
                unreachable!()
            }
            pub fn charge_status(&self, _: &monetize_pay::ChargeHandle) -> Result<monetize_pay::ChargeStatus, monetize_pay::PayError> {
                unreachable!()
            }
            pub fn parse_callback(&self, _: monetize_pay::RawCallback<'_>) -> Result<monetize_pay::CallbackEvent, monetize_pay::PayError> {
                unreachable!()
            }
            pub fn give_back(&self, _: &monetize_pay::ChargeHandle, _: Money) -> Result<monetize_pay::ChargeStatus, monetize_pay::PayError> {
                unreachable!()
            }
            pub fn settled(&self, _: &monetize_pay::ChargeHandle) -> Option<monetize_pay::Settlement> {
                None
            }
            pub fn redirect(&self, _: &monetize_pay::ChargeHandle) -> Option<String> {
                None
            }
            pub fn start_signup(
                &self,
                _: &monetize_pay::signup::SignupOrder,
            ) -> Result<Option<monetize_pay::signup::SignupCheckout>, monetize_pay::PayError> {
                Ok(None)
            }
        }
        impl monetize_pay::payment::Payment for Second {
            fn manifest(&self) -> monetize_pay::payment::PayManifest {
                monetize_pay::payment::PayManifest { vendor: self.vendor().to_string(), markets: self.markets().to_vec(), settings: Vec::new(), holds: true, test_payer: None }
            }
            fn page(&self, charge: &monetize_pay::payment::Charge<'_>, _back: &monetize_pay::payment::Return) -> Result<monetize_pay::payment::Page, monetize_pay::PayError> {
                Ok(match charge {
                    monetize_pay::payment::Charge::Order(req) => monetize_pay::payment::Page::Order { handle: self.reserve(req)?, url: None },
                    monetize_pay::payment::Charge::Signup(o) => monetize_pay::payment::Page::Signup(self.start_signup(o)?),
                    monetize_pay::payment::Charge::CardCheck(_o) => monetize_pay::payment::Page::CardCheck(None),
                    monetize_pay::payment::Charge::TestSignup(_o) => monetize_pay::payment::Page::TestSignup(None),
                })
            }
            fn status(&self, of: &monetize_pay::payment::Of<'_>) -> Result<monetize_pay::payment::Standing, monetize_pay::PayError> {
                Ok(match of {
                    monetize_pay::payment::Of::Charge(h) => monetize_pay::payment::Standing::Charge(self.charge_status(h)?),
                    monetize_pay::payment::Of::Page(h) => monetize_pay::payment::Standing::Page(self.redirect(h)),
                    monetize_pay::payment::Of::Signup { session: _ } => monetize_pay::payment::Standing::Signup(None),
                    monetize_pay::payment::Of::CardCheck { session: _ } => monetize_pay::payment::Standing::CardCheck(None),
                })
            }
            fn capture(&self, h: &monetize_pay::ChargeHandle) -> Result<monetize_pay::ChargeStatus, monetize_pay::PayError> {
                self.draw(h)
            }
            fn release(&self, h: &monetize_pay::ChargeHandle) -> Result<monetize_pay::ChargeStatus, monetize_pay::PayError> {
                self.unlock(h)
            }
            fn refund(&self, h: &monetize_pay::ChargeHandle, amount: monetize_pay::Money) -> Result<monetize_pay::ChargeStatus, monetize_pay::PayError> {
                self.give_back(h, amount)
            }
            fn callback(&self, raw: monetize_pay::RawCallback<'_>) -> Result<monetize_pay::CallbackEvent, monetize_pay::PayError> {
                self.parse_callback(raw)
            }
            fn settlement(&self, h: &monetize_pay::ChargeHandle) -> Option<monetize_pay::Settlement> {
                self.settled(h)
            }
            fn paid_since(&self, _since_unix_ms: i64) -> Result<Vec<monetize_pay::reconcile::PaidAtVendor>, monetize_pay::PayError> {
                Err(monetize_pay::reconcile::cannot_be_asked())
            }
            fn livemode(&self) -> Option<bool> {
                None
            }
        }
        let two = Registry::new().vendor(MockPay::new()).vendor(Second).route("gunnar", "mock").route("holger", "second");
        assert_eq!(two.vendor_for(&priced, Market::Se, "gunnar", "").map(|v| v.name), Some("mock"));
        assert_eq!(two.vendor_for(&priced, Market::Se, "holger", "").map(|v| v.name), Some("second"));
        assert!(two.vendor_for(&priced, Market::Fi, "holger", "").is_none(), "second does not serve FI");
        assert!(two.vendor_for(&priced, Market::Se, "holger", "mock").is_none(), "holger's order never reaches gunnar's vendor");
    }

    #[test]
    fn refuse_twin_an_enabled_vendor_that_is_not_linked_fails_at_start() {
        let cfg = Config::from_toml("[vendors.acme.x]\nenabled = true").unwrap();
        let err = Registry::from_config(&cfg).err().map(|e| e.to_string()).expect("must fail");
        assert!(err.contains("acme") && err.contains("not linked"), "{err}");
        let cfg = Config::from_toml("[products.zork]\nendpoint = \"x\"").unwrap();
        assert!(Registry::from_config(&cfg).is_err());
        let cfg = Config::from_toml("[products.holger]\nendpoint = \"127.0.0.1:1\"").unwrap();
        // The open build links NO product: the same block builds only through a
        // registered factory, and the refusal names what IS linked.
        let err = Registry::from_config(&cfg).err().map(|e| e.to_string()).expect("holger is not linked into the open build");
        assert!(err.contains("[products.holger]") && err.contains("no plugin") && err.contains("linked: []"), "{err}");
        let mut with_holger = Linked::open();
        with_holger.product(crate::testing::products::HOLGER_FACTORY);
        assert_eq!(Registry::from_config_with(&cfg, &with_holger).unwrap().product_ids(), vec!["holger"]);

        // The sandbox vendor, both ways round, so neither arm is vacuous: a build
        // that HAS it can select it, and a build that does not must refuse the
        // name rather than fall through to something real.
        let sb = Config::from_toml("[vendors.sandbox.play]\nenabled = true").unwrap();
        #[cfg(feature = "sandbox")]
        {
            assert_eq!(Registry::from_config(&sb).unwrap().vendor_names(), vec!["sandbox/play"]);
            // A DEPLOYED sandbox must not be the in-memory one: its handles would
            // evaporate on restart and a captured attempt become unrefundable.
            let mut with_dir = sb.clone();
            with_dir.data_dir = std::path::PathBuf::from("/var/lib/monetize");
            let _ = Registry::from_config(&with_dir).unwrap();
            let sbx = monetize_pay::sandbox::SandboxConfig::from_env();
            assert!(sbx.state_path.is_none(), "the ENV default is in-memory; the registry is what gives a deployment a home");
        }
        #[cfg(not(feature = "sandbox"))]
        {
            let err = Registry::from_config(&sb).err().map(|e| e.to_string()).expect("a build without the feature must refuse it");
            assert!(err.contains("sandbox") && err.contains("not linked"), "{err}");
        }
    }

    /// A linked factory named `name` whose product answers `gunnar`, as the
    /// gunnar plugin does whatever block it is built for.
    fn answers_gunnar(name: &'static str) -> ProductFactory {
        ProductFactory { name, build: |_| Ok(Arc::new(|| MockProduct::gunnar().facts())), mint_identity: |_| Err("no identity".into()), guardian: None }
    }

    /// **G15: two estates of one product are two products.** gunnar.rs and
    /// test2.gunnar.rs both ran the gunnar plugin and both answered `gunnar`;
    /// `[products.gunnar-test2] plugin = "gunnar"` serves the same plugin as
    /// product `gunnar-test2`, and every lookup keyed on the id finds its own.
    #[test]
    fn a_block_naming_its_plugin_is_served_under_its_own_id() {
        let cfg = Config::from_toml("[products.gunnar]\n[products.gunnar-test2]\nplugin = \"gunnar\"\n").unwrap();
        let linked = { let mut l = Linked::open(); l.product(answers_gunnar("gunnar")); l };
        let reg = Registry::from_config_with(&cfg, &linked).expect("two blocks, one plugin");
        let mut ids: Vec<&str> = reg.products().map(|p| p.id()).collect();
        ids.sort();
        assert_eq!(ids, ["gunnar", "gunnar-test2"]);
        assert_eq!(reg.find_product("gunnar-test2").map(|p| p.id()), Some("gunnar-test2"));
        // A guest block carries the name to the guest host the same way.
        let g = Config::from_toml("[products.gunnar-test2]\nplugin = \"gunnar\"\nguest = \"/x/gunnar.wasm\"\n").unwrap();
        assert_eq!(guest_spec(&g.products["gunnar-test2"]).and_then(|s| s.plugin), Some("gunnar".into()));
    }

    /// **G15: one product id, two blocks, is refused at load** — by name, with
    /// the way out. A plugin that answers an id other than its block's would
    /// otherwise serve as a SECOND `gunnar`: facts, prices and the ledger for
    /// two estates under one id.
    #[test]
    fn two_blocks_serving_one_product_id_are_refused_at_load() {
        let cfg = Config::from_toml("[products.gunnar]\n[products.gunnar2]\n").unwrap();
        let linked = { let mut l = Linked::open(); l.product(answers_gunnar("gunnar")); l.product(answers_gunnar("gunnar2")); l };
        let e = format!("{:#}", Registry::from_config_with(&cfg, &linked).err().expect("one id, two blocks"));
        assert!(e.contains("[products.gunnar2]") && e.contains("`gunnar`") && e.contains("plugin"), "{e}");
        // Alone, the mismatch is refused too: the block's id is the product id.
        let alone = Config::from_toml("[products.gunnar2]\n").unwrap();
        assert!(Registry::from_config_with(&alone, &linked).is_err(), "a block whose plugin answers another id");
    }
}