nordisk/monetize
crates/monetize-server/src/estate_order.rs
//! **A private gunnar, ordered: the `lay estate <company>` job.**
//!
//! `private-gunnar-ops/PRIVATE-GUNNAR.md`: a company buys `<company>.gunnar.rs`
//! on gunnar.rs's front page, and the order is one more job type in this
//! server's own queue — the queue that already runs the paid disk growth
//! ([`crate::grow`]). The ONE monetize (`monetize.ignalina.dk`) holds the money,
//! lays the new estate at its cloud, has the kit install gunnar on it, and
//! captures only when the new estate calls home (its first Fleet report;
//! monetize never dials an estate's own channels) and its PUBLIC front answers
//! the buyer's way in, as a customer reaches it (the sale check, S4).
//!
//! ```text
//! ordered ──▶ held ──▶ laying ──▶ installing ──▶ paired ──▶ captured
//! │ │ │ │ │
//! │ └─────────┴────────────┴────────────┴──▶ failed ──▶ released
//! └── the page expired unpaid ───────────────────────────────────▶ released
//! ```
//!
//! | phase | the step | the world it reads |
//! |---|---|---|
//! | `ordered` | `pay.reserve` was asked; the buyer is on the vendor's page | `pay.status`: `Reserved` → `held` (the estate's secrets are minted, [`crate::custody`]) |
//! | `held` | waits for a slot (`max_open`) | the other runs' phases |
//! | `laying` | `cloud.lay_estate` + the DNS records | the provider, by label (a retry adopts) |
//! | `installing` | the kit, invoked ([`Installer`]) on the laid servers | its exit and its verdict file |
//! | `paired` | the estate called home, its main serves its data, and the sale check proves its PUBLIC front (S4); then `pay.capture` | its boxes' Fleet reports ([`crate::fleet::heard_since`], [`crate::fleet::serves_since`]); `https://<host>`: the login page, the first-login door, exactly one `<account>/welcome` ([`Installer::sale_check`]); `pay.capture` |
//! | `failed` | `release_for` + DNS removed + `orphans_for` empty, then `pay.release` | the provider's read-back |
//!
//! # Journaled and resumable, like the growth
//!
//! The run (`estate-orders/<company>/run.json`) is written, atomically, after
//! every transition and before the next step starts; the cloud's own journal
//! (`estate-orders/<company>/journal`) has every create as an intent before
//! the call and a confirmation after. Nothing is held in memory that a restart
//! needs: [`EstateOrders::from_config`] reads every run back, and [`tick`] —
//! at start and every few seconds — drives every open run from the phase its
//! file names. Each step is safe to repeat: the provider adopts what an
//! earlier attempt laid (same reference, same title), the kit adopts what is
//! laid (`--laid`), the vendor's capture and release are idempotent on the
//! handle.
//!
//! # The money is never ahead of the estate
//!
//! Nothing is laid before the vendor says the money is HELD. Nothing is
//! captured before the estate calls home and its public front proves it serves
//! what was sold (the sale check, S4). A failure gives back everything bought
//! under the reference and proves it (`orphans_for` empty) BEFORE the hold is
//! released — a release with iron still billing would be monetize paying for
//! an estate nobody bought.
//!
//! # One writer per run
//!
//! Every write of a run reads it, takes a step and writes the WHOLE run back:
//! the driver (its provider and payment calls in between), and the page's
//! `Start`, `Renew` and `ChangePlan`. Each holds the run's pen (`Pens`) from
//! its read to its write, so none writes over another's. Reads never take it.
use std::collections::{BTreeMap, BTreeSet};
use std::path::{Path, PathBuf};
use std::sync::{Arc, Condvar, Mutex};
use std::thread::ThreadId;
use std::time::{Duration, Instant};
use monetize_cloud::lay::{
company_name_refusal, reference_of, DiskPlan, EstatePlan, LaidEstate, MachinePlan,
};
use monetize_cloud::Zone;
use monetize_pay::{ChargeHandle, ChargeRequest, ChargeStatus, Money};
use crate::custody::Custody;
use crate::state::AppState;
/// **The account that buys and owns an estate is PROVEN** (one shop,
/// 2026-10-07): the `estate` ticket THIS shop's own Gunnar mints for a
/// signed-in gunnar.rs account, checked by the shop's gunnar product.
pub mod account;
/// **Plan changes** on a delivered estate: main and front by the buyer, the
/// twin following main.
pub mod change;
/// **The customer's own domain**: the two records, the check, the wait.
pub mod domain;
/// The size ladder, Mini to Gigantic: the sign-up page's one − / +.
pub mod ladder;
/// **What it costs**: the provider's plans and price, the margin, the rate,
/// the six-month term, the buy-time check.
pub mod price;
pub mod site;
/// **After the capture**: the six-month term, its renewal, and the rule for a
/// customer who stops paying.
pub mod term;
pub mod terminate;
/// **The twin sizes itself**: the damped lag/load rule.
pub mod twin;
// The operator sends a laid site's first sign-in link again (owner 2026-10-22).
pub mod first_link;
// MAIL-RELAY + NIGHT-PUBREPO (2026-10-28): the ONE box feeds an already-laid estate its keys again.
pub mod product_order;
pub mod refeed;
// OX-WGDS3 (2026-10-28): the operator's audited call on a laid site's console, through its
// public front, with the custody's break-glass bearer.
/// **A kit-installed operator site ADOPTED by the ONE box** (owner 2026-10-17): the run an update needs.
pub mod adopt;
/// A product guest's own lifecycle (`lifecycle.facts` / `lifecycle.lay_plan`, 2026-10-17).
pub mod guest_lifecycle;
/// **ONE lifecycle per product kind**: lay, update/upgrade, terminate, sale check.
pub mod lifecycle;
/// **One box laid again** for the estate guardian (5.4): the same lay + installer path.
pub mod relay;
/// The setup's own log in monetize: phases and the installer's lines, kept and published.
pub mod setup_log;
/// **A sold estate grows through its shop** (one shop, 2026-10-07): the
/// growth go-ahead signed once a data step is paid, and `SoldEstate`'s
/// calls, signed by the estate's client key.
pub mod sold;
/// The ONE box updates the estates and sites it laid (owner 2026-10-14, point 3).
pub mod update;
/// **Why `company` is a product's own name** (MONETIZE-FIX step 5, OM-12): the kind being sold
/// and every product this box links (its id and its family) are never sold as a company. The
/// names come from the registry, never from a list in the core.
pub fn product_name_refusal(state: &AppState, company: &str, kind: &str) -> Option<String> {
let ids = state.registry.product_ids();
let own = std::iter::once(kind)
.chain(ids.iter().copied())
.chain(ids.iter().map(|id| lifecycle::family_of(id)))
.any(|n| !n.is_empty() && n == company);
own.then(|| format!("{company:?} is a product's own name and is not sold as a company"))
}
/// **CODE RED** (owner 2026-10-18): what an automatic step left down, on every console page.
pub mod code_red;
/// Why an install makes no progress, by name; a CODE RED at 20 minutes (test3-holger-rs 2026-10-22).
pub mod install_why;
/// DEV-NARRATE on the ONE box: a narrated test site's boot logs, heard and kept (lane N2).
pub mod narrate;
/// The Overview's New-estate slot: a site made and handed to the one site order (OVW-CLEAN).
pub mod new_estate;
/// Closing a vendor's payment page, never blind (F-S-02/03, A17).
pub mod pages;
/// A refund or a dispute at the vendor on an estate's sale (F-S-01, A17).
pub mod reversed;
/// The estates that exist, classified for the Overview (OVW-CLEAN).
pub mod standing;
pub use domain::{CustomerRecord, DomainCheck, FileResolver, NoResolver};
pub use ladder::{
charge_words, default_ladder, ladder, monthly_words, resources_words, sells_kept, sells_now,
sells_refresh, total_words, Ladder, LadderStep, PricedStep, SELLS_FRESH_MS,
};
pub use price::{EstateSizes, Quote};
pub use term::{cancel, renew, Cancelled, Renewal, RenewalDone, TermPolicy};
/// The phase words, exactly as `EstateOrderStatusResponse.phase` carries them.
pub mod phase {
pub const ORDERED: &str = "ordered";
pub const HELD: &str = "held";
/// A customer's own domain: waiting for their CNAME and proof (no slot held).
pub const WAITING_DNS: &str = "waiting_dns";
pub const LAYING: &str = "laying";
pub const INSTALLING: &str = "installing";
pub const PAIRED: &str = "paired";
pub const CAPTURED: &str = "captured";
pub const FAILED: &str = "failed";
pub const RELEASED: &str = "released";
/// The paid period ended unpaid; the estate still runs (`term`).
pub const GRACE: &str = "grace";
/// Unpaid past the grace: every server stopped, every disk kept.
pub const SUSPENDED: &str = "suspended";
/// Unpaid past the notice: being given back.
pub const DELETING: &str = "deleting";
/// Given back after the notice; the name is free.
pub const DELETED: &str = "deleted";
/// **Paid, and it could not be set up** ([`super::PAID_RETRIES`] times):
/// everything laid was given back, the money is kept, and an operator
/// retries it or cancels it with a refund (A23). Not driven; not free.
pub const OWED: &str = "owed";
/// Cancelled by the operator and its money refunded; the name is free.
pub const REFUNDED: &str = "refunded";
/// **TERMINATE asked by its account** (owner 2026-10-12): being given back, step by step.
pub const TERMINATING: &str = "terminating";
/// Terminated by its account: machines, disks, records gone; the name is free.
pub const TERMINATED: &str = "terminated";
/// A phase the driver still has work in.
pub fn is_open(p: &str) -> bool {
matches!(
p,
ORDERED | HELD | WAITING_DNS | LAYING | INSTALLING | PAIRED | FAILED | TERMINATING
) || is_term(p)
}
/// The phases of a delivered estate, driven by `term`.
pub fn is_term(p: &str) -> bool {
matches!(p, CAPTURED | GRACE | SUSPENDED | DELETING)
}
/// Nothing stands under the name any more: it may be ordered again.
pub fn is_gone(p: &str) -> bool {
matches!(p, RELEASED | DELETED | REFUNDED | TERMINATED)
}
/// The phases that hold a slot: something is being bought or built.
pub fn holds_a_slot(p: &str) -> bool {
matches!(p, LAYING | INSTALLING | PAIRED)
}
}
// ── the configuration ───────────────────────────────────────────────────────
/// **`[estate_orders]`** in monetize.toml — present only on a monetize that
/// sells private estates (gunnar.rs's own). Absent: `EstateOrder.Start` is
/// refused by name and nothing here runs.
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(default)]
pub struct EstateOrderConfig {
/// **The product a sold estate IS** (G8; OM-4, the owner 2026-10-10): the
/// key its growth go-aheads and its facts are signed with, the fleet lane
/// its boxes call in on, and the product its accounts and room are booked
/// under — never a hard-coded "gunnar" (test2's sold estates would book
/// under PROD gunnar's). Unset: the one `[pricing.<product>]` on this box
/// is it; unset on a box that prices several (or none), the start is
/// refused by name ([`EstateOrders::from_config_with`]).
#[serde(default)]
pub product: String,
/// **The product KIND whose [`lifecycle::EstateLifecycle`] a sold estate follows**: the
/// `[products.<product>] plugin` when it names one ([`EstateOrders::from_config_with`]);
/// empty, the product id itself, else its family (`gunnar-test2` → `gunnar`).
#[serde(skip)]
pub lifecycle: String,
/// The cloud the estate is laid at (`[clouds.<name>]`).
pub cloud: String,
/// The vendor the hold is taken with (`[vendors.<name>]`).
pub vendor: String,
/// The serving estate's apex: an order for `acme` is `acme.<apex>`.
pub apex: String,
/// The currency the estate is SOLD in — the provider's own, `EUR` for
/// UpCloud (owner, 2026-10-04 19:45): no exchange rate in this code; the
/// buyer sees SEK/DKK through Stripe's Adaptive Pricing.
pub currency: String,
/// **The margin over the provider's cost**, basis points: 2000 = 20 %.
pub margin_bp: u32,
/// **The floor at the buy**: a quoted price that would leave less than
/// this over the provider's price NOW is not bought. 1000 = 10 %.
pub min_margin_bp: u32,
/// **The minimum term, prepaid**: the hold is this many months' price.
pub term_months: u32,
/// **A term of DAYS instead** (test2, the owner 2026-10-08: one day, so a real purchase
/// is cheap to repeat): when set, the hold is the monthly price pro rata — `monthly ×
/// days / 30`, rounded up — and the paid period this many days; `term_months` is then
/// not used. 0: the term is `term_months`.
#[serde(default)]
pub term_days: u32,
/// The provider's plans offered for front and main (empty: every plan
/// the provider lists).
pub plans: Vec<String>,
/// How long a customer's own domain may take to point at us (and prove it).
pub dns_wait_secs: u64,
/// How a customer's domain is read: `file:<path>` (the mock's world DNS)
/// or empty (no resolver: customer domains are refused at `Start`).
pub resolver: String,
/// At most this many estates are laid or installed at once.
pub max_open: usize,
/// At most this many private estates exist (delivered or in progress).
pub max_estates: usize,
/// An unpaid payment page is given up after this long.
pub order_ttl_secs: u64,
/// From the hold, the estate must answer within this long, or the job
/// fails and the hold is released (an install takes about 35 minutes).
pub capture_within_secs: u64,
/// **The install deadline** (`[estate_orders] install_deadline_mins`, lane F2 2026-10-20:
/// test3-holger-rs stood "installing" for a day, its twin retrying certify every 3 s): an
/// order that stays in `installing` this many minutes without every box PAIRED is FAILED,
/// its reason the installer's last pair status word for word. 0: no deadline.
pub install_deadline_mins: u64,
/// **How long a STOPPED operator-site install keeps its boxes** (`[estate_orders]
/// failed_site_keep_hours`, MAP-SEG 3, owner 2026-10-26: "an abandoned or failed lay must
/// remove its disks" — 18 storages no order owned, billed). Abandoned by the operator or
/// failed by the install deadline, the site waits this long for its operator ("Check again"
/// adopts boxes that paired since, F5; Terminate ends it now); then the box gives back every
/// machine, disk, release clone and DNS name it laid, by itself, and the site can be ordered
/// again. A protected site is never given back by itself. 0: given back at once.
pub failed_site_keep_hours: u64,
/// The vendor keeps a card authorisation about 7 days; the job never
/// runs past this many seconds after the hold, whatever
/// `capture_within_secs` says.
pub hold_window_secs: u64,
/// **REMOVED** (no-dwarf step 8, the owner 2026-10-08: "DWARFS MUST NOT BE INVOLVED!"):
/// workers that pulled the install through the `EstateInstall` door. A shop pairs what it
/// sells itself (`pairing`); a config that still names a worker is refused at start, by
/// name, rather than ignored.
pub install_workers: BTreeMap<String, String>,
/// **The shop pairs the estate itself** (`pairing = "<name>"`, no operator machine in a
/// sale, 2026-10-08): the installer a build LINKS under that name
/// ([`crate::registry::Linked::installer`]; gunnar's front links `ShopPairing`). A build
/// that links none of that name refuses to start, by name. (The kit as an `installer` argv, the
/// one process monetize spawned, went in v2.0 step 1: a shop pairs, or it does not sell.)
#[serde(default)]
pub pairing: String,
/// **The sold front's static feed** (`[estate_orders.sale_feed]`, rendered by the kit for
/// the SELLING shop): key → value with `{{COMPANY}}`, `{{HOST}}`, `{{APEX}}` filled per
/// order by the shop's own pairing. Never a shop credential (the pairing refuses one).
#[serde(default)]
pub sale_feed: BTreeMap<String, String>,
/// **The release media a sold estate is laid from** (`[estate_orders.media]`, no-dwarf
/// steps 3/4, 2026-10-08): kind (`front`, `gunnar`) → the provider's storage uuid of that
/// kind's SEEDLESS release medium, uploaded once per release, never per sale. Each
/// machine's medium is CLONED from its kind's at the lay; its seed reaches it as user_data.
/// Required with `pairing` (a machine with no medium boots nothing).
#[serde(default)]
pub media: BTreeMap<String, String>,
/// **The release media as OBJECTS** (`[estate_orders.media_objects.<kind>]`, owner
/// 2026-10-09: the release ISO media live in UpCloud Object Storage): kind → `{url, sha256,
/// bytes}`. A kind with no storage in [`Self::media`] is laid from its object: the provider
/// imports it (once per release and zone) and clones it per machine. With the custody's
/// `media.objsto` credential the shop PRESIGNS the url for each lay (an hour); without it
/// the url is used as it is (a public object).
#[serde(default)]
pub media_objects: BTreeMap<String, monetize_cloud::lay::MediaObject>,
/// **The release's TEMPLATES** (`[estate_orders.templates.<kind>]`, owner 2026-10-12,
/// option (a)): kind → `{uuid, for_release}` — a provider template made from that kind's
/// seedless release medium's installed disk (`cargo xtask estate release-template`). A
/// machine is laid from its kind's template ONLY while `for_release` is the release the
/// shop sells now ([`release_id`]); a stale one is passed over and the medium path stays.
#[serde(default)]
pub templates: BTreeMap<String, ReleaseTemplate>,
/// **The release's LIVE apps** (`[estate_orders.live_apps.<kind>]`, owner 2026-10-14): kind
/// (`gunnar`, `front`) → korp's live record of that kind's medium (its BASE, the app's pin,
/// length, signature); the archive is the media object `<kind>-live`. What an update
/// stages live on a box that runs the same BASE ([`update`]).
#[serde(default)]
pub live_apps: BTreeMap<String, update::LiveRecord>,
/// **What each release IS, for a person** (`[estate_orders.release_notes."<release id>"]`,
/// 2026-10-17): its label, when it was built and the commits it carries — written by the
/// kit beside the media it publishes; the Sites page names a release by it, never by a hash.
#[serde(default)]
pub release_notes: BTreeMap<String, update::ReleaseNote>,
/// **The operator site these terms lay ITSELF** (an operator-site order, owner 2026-10-13):
/// the estate label whose host IS the apex (`gunnar-rs` → `gunnar.rs`), set by
/// [`terms_of`] for such a run and never read from a file. Empty: every name is
/// `<company>.<apex>`.
#[serde(skip)]
pub site_itself: String,
/// ★ **A price these terms HOLD** (A33-5; the operator's own buy, owner 2026-10-12), minor
/// units a term (and a month); 0: priced from the provider at the margin, held to the floor.
/// Set by [`priced_for`], never read from a file: the one quote answers a held price as it
/// is, and the one lay's recheck reads the provider's price for the cap and the laid cost.
#[serde(skip)]
pub held_price_minor: u64,
/// **The ONE box's public IPv4, DEV-NARRATE's only source** (lane N2, 2026-10-20): fed as
/// `[estate_orders] narrate_from`. The estate of a site that says `[estates.<label>]
/// narrate = true` is laid (and its firewall written again) admitting udp/51822 from this
/// address alone, and the shop's narrate job ([`narrate`]) listens to its boxes. Empty: no
/// estate here is narrated, whatever a site says.
pub narrate_from: String,
/// **These terms narrate the estate they lay** — set by [`terms_of`] for the site order of
/// a site that says `narrate = true`, never read from a file: a sold estate, and any site
/// that does not say so, is laid with udp/51822 closed.
#[serde(skip)]
pub narrating: bool,
/// **The bench's `fleet-feed-late`** (lane GATE, 2026-10-20): seconds the shop's pairing
/// holds `fleet`, `fleet_instance` and `fleet_at` back from every box's first feed (fed in a
/// later one, before the install closes — nordisk-pairing `installer.late_feed`). 0: fed at
/// once. A MOCK knob: the kit renders it only for a `--where kvm` bench box.
pub bench_fleet_late_s: u64,
/// **The bench keeps an estate's secrets after its hand-off** (lane OX-WGDS3, 2026-10-28):
/// the bench's downstream leg keeps the custody's material after the site is ready. The hand-off's forgetting is skipped, said once in the run's history. A
/// MOCK knob: the kit renders it only for a `--where kvm` bench box.
pub bench_keep_secrets: bool,
/// **The installer's WireGuard link carries a pre-shared key** (lane WG-PSK-3, 2026-10-30;
/// nordisk-pairing 0.5.29): every new order's seeds and link file get one, minted with the
/// link's keys. OFF by default until every release this box sells reads it: a medium from
/// before 0.5.29 ignores the seed's PSK and could never be reached over a link that has one.
/// The switch: both products' media on nordisk-pairing >= 0.5.29 first, then
/// `installer_link_psk = true` here (WG-PSK-3 handback). Orders laid before keep their seeds.
pub installer_link_psk: bool,
/// **The install's budget, seconds**: the shop's pairing closes the install within it
/// (`monetize-shop-pairing`, at least 600); the fast test sets it low.
pub installer_timeout_secs: u64,
/// Where the job writes the estate's DNS records: `file:<path>` (the
/// mock's recording writer) or empty (no writer here: the names are
/// listed in the run and written by nobody).
pub dns: String,
pub zone: String,
/// Labels every resource carries beside the reference and its role.
pub labels: BTreeMap<String, String>,
/// `[estate_orders.shape]` as the file names it; absent, the product's lifecycle's
/// ([`lifecycle::Lifecycle::default_shape`]) at the start.
#[serde(rename = "shape", default, skip_serializing_if = "Option::is_none")]
pub shape_given: Option<Shape>,
/// The shape the estates are laid and priced at: [`Self::shape_given`], else the lifecycle's.
#[serde(skip)]
pub shape: Shape,
/// **The spend cap per estate**: at most this much a month at the
/// provider, in its currency (EUR minor). Fixed on each run at its order;
/// a quote, a lay, a renewal or a plan change over it is refused, and the
/// estate's own monetize may grow by what is left under it.
pub spend_cap_monthly_minor: u64,
/// The term's rules: reminders, the grace, the stop, the deletion.
pub term: TermPolicy,
/// **The DNS writer's settings** (`[estate_orders.dns_settings]`) — what
/// is not secret (an API base for a test's fake). The zone and the token
/// are the custodian's (`--custody-credential dns.<writer>`).
pub dns_settings: serde_json::Value,
/// **The twin's autoscale rule** (`[estate_orders.twin]`): the marks, the
/// hysteresis, the trend, the spacing, how often it is sampled.
pub twin: twin::TwinRule,
/// **The payment vendor's fee**, counted as COST by the floor at the buy
/// (the 10 % is what is left after it): a fixed part per charge (minor
/// units) and a percentage (basis points). The defaults are monetize's
/// own rule for room (`monetize::pricing::PriceRule`: Stripe 0.25 + 1.5 %).
pub fee_fixed_minor: u64,
pub fee_bp: u32,
/// **REMOVED (G12, 2026-10-10)**: the twin's lag and load were read by RUNNING the kit
/// (`cargo xtask … estate twin-health`). They are read natively now, from the estate's own
/// reports on the pull channel ([`twin::ReportedTwinHealth`]); a config that still names a command here
/// is refused at start ([`TWIN_HEALTH_COMMAND_REMOVED`]). Kept only to refuse it by name.
pub twin_health: Vec<String>,
/// **The size ladder** (`[[estate_orders.ladder]]`): the named steps the
/// sign-up page's one − / + moves through, smallest first. Absent: the
/// default ladder, Mini (the tested shape) to Gigantic ([`ladder`]).
pub ladder: Vec<LadderStep>,
/// **How long the buyer's first-login link lives**, seconds (private
/// ownership, 2026-10-07; default seven days): the kit records the invite
/// at gunnar with this lifetime (`operator_invite_ttl_secs`), and the
/// "ready" mail says it. Single use either way.
pub first_login_ttl_secs: u64,
/// **The door a sold estate calls the ONE monetize through** (ONE
/// monetize, owner 2026-10-10: no estate runs a monetize, a sold one
/// included): an https URL naming the one box, per product
/// (`https://<product>.monetize.ignalina.dk` in ops config). **Config
/// only** (G14): no default is made up in code (never the shop's web
/// address `monetize.<apex>`) — an `[estate_orders]` without it is
/// refused at start ([`EstateOrderConfig::shop_door`]). Handed to the kit
/// in `laid.json` with this monetize's public key.
pub shop_door: String,
/// **How long a sold estate is told before its machines stop** (one shop
/// 4, 2026-10-07; default 60 s): a paid plan change waits this long in
/// `queued`, so the estate's console can say it is down for maintenance rather than
/// "not responding".
pub resize_notice_secs: u64,
/// **Company names this shop never sells** (`reserved_names`; A44,
/// 2026-10-08): the kit names here the operator's own estates its
/// `estate.toml` declares (`test1`, `speed`, …) — an order for one is
/// answered "taken" before any money moves.
pub reserved_names: Vec<String>,
}
/// **The estate's three machines**, as templates over `{company}`. The
/// defaults are the kit's own declaration (`private-gunnar-ops/terraform`,
/// `estate.auto.tfvars.json` and an instance's `-<company>` suffix), so what
/// monetize lays is what the kit's `--laid` adopts without a change.
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct Shape {
pub front: MachineShape,
pub main: MachineShape,
pub twin: MachineShape,
/// **The product's own floor for its twin's memory**, MiB (owner 2026-10-23: Holger's twin
/// is a 1 GB box). Absent: 4 GiB, gunnar-server's floor ([`monetize_cloud::lay::TWIN_FLOOR_MEMORY_MIB`]).
#[serde(default, skip_serializing_if = "Option::is_none")]
pub twin_floor_mib: Option<u64>,
}
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct MachineShape {
pub hostname: String,
pub title: String,
pub plan: String,
#[serde(default = "disk_cdrom")]
pub boot_order: String,
/// The `role` label the kit's declaration stamps (`front` | `appliance` | `twin`).
pub role_label: String,
pub disks: Vec<DiskShape>,
/// Its network interfaces — laid and PRICED (each public IPv4 is billed).
/// Absent: a public and a utility IPv4, as the kit declares every box.
#[serde(default = "monetize_cloud::lay::default_interfaces")]
pub interfaces: Vec<monetize_cloud::lay::NetInterface>,
}
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct DiskShape {
pub title: String,
pub gib: u64,
pub tier: String,
/// The `volume` label (`system` | `data1` … | `twin`).
pub volume: String,
}
fn disk_cdrom() -> String {
"disk,cdrom".into()
}
/// **A shape's default is a Mini estate of three machines** — a 2-core 4 GB front, a 4-core
/// 8 GB main with four 10 GB data disks, a 1-core 4 GB twin — what a config built in code that
/// names no shape is laid and PRICED at, named by role alone (`front-{company}`, …): it names
/// no product (v2.0 P3). A config READ from a file that names no `[estate_orders.shape]` takes
/// its product's lifecycle's (its guest's, [`lifecycle::Lifecycle::default_shape`]) at the
/// start ([`EstateOrders::from_config_with`]); a product whose plan names its own machines (the
/// Lake) is laid by its plan.
impl Default for Shape {
fn default() -> Self {
let d = |title: &str, gib, tier: &str, volume: &str| DiskShape {
title: title.into(),
gib,
tier: tier.into(),
volume: volume.into(),
};
let boot = disk_cdrom;
Shape {
front: MachineShape {
hostname: "front-{company}".into(),
title: "the front — the web, the console, the relay-{company}".into(),
plan: "STARTER-2xCPU-4GB".into(),
boot_order: boot(),
role_label: "front".into(),
disks: vec![d("front-{company} system", 40, "standard", "system")],
interfaces: monetize_cloud::lay::default_interfaces(),
},
main: MachineShape {
hostname: "main-{company}".into(),
title: "the main — the primary, no shell-{company}".into(),
plan: "STARTER-4xCPU-8GB".into(),
boot_order: boot(),
role_label: "appliance".into(),
disks: vec![
d("main-{company} system", 50, "standard", "system"),
d("main-{company} data 1", 10, "maxiops", "data1"),
d("main-{company} data 2", 10, "maxiops", "data2"),
d("main-{company} data 3", 10, "maxiops", "data3"),
d("main-{company} data 4", 10, "maxiops", "data4"),
],
interfaces: monetize_cloud::lay::default_interfaces(),
},
twin: MachineShape {
hostname: "twin-{company}".into(),
title: "the twin — the log-holder, no shell-{company}".into(),
plan: "STARTER-1xCPU-4GB".into(),
boot_order: boot(),
role_label: "twin".into(),
disks: vec![
d("twin-{company} system", 20, "standard", "system"),
d("twin-{company} data", 20, "hdd", "twin"),
],
interfaces: monetize_cloud::lay::default_interfaces(),
},
twin_floor_mib: None,
}
}
}
impl Default for EstateOrderConfig {
fn default() -> Self {
EstateOrderConfig {
product: String::new(),
lifecycle: String::new(),
cloud: "upcloud".into(),
// ★ ST-5: never made up — a site's own account and apex ([`terms_on`]), else
// the ones configured; neither set is refused by name.
vendor: String::new(),
apex: String::new(),
currency: "EUR".into(),
margin_bp: 2_000,
min_margin_bp: 1_000,
term_months: 6,
term_days: 0,
plans: Vec::new(),
dns_wait_secs: 48 * 3_600,
resolver: String::new(),
max_open: 2,
max_estates: 10,
order_ttl_secs: 3_600,
capture_within_secs: 86_400,
install_deadline_mins: 60,
failed_site_keep_hours: 24,
hold_window_secs: 6 * 86_400,
install_workers: BTreeMap::new(),
pairing: String::new(),
sale_feed: BTreeMap::new(),
media: BTreeMap::new(),
media_objects: BTreeMap::new(),
templates: BTreeMap::new(),
live_apps: BTreeMap::new(),
release_notes: BTreeMap::new(),
site_itself: String::new(),
held_price_minor: 0,
narrate_from: String::new(),
narrating: false,
bench_fleet_late_s: 0,
bench_keep_secrets: false,
installer_link_psk: false,
installer_timeout_secs: 3 * 3_600,
dns: String::new(),
zone: "se-sto1".into(),
labels: BTreeMap::new(),
shape_given: None,
shape: Shape::default(),
spend_cap_monthly_minor: 200_000,
term: TermPolicy::default(),
dns_settings: serde_json::Value::Null,
fee_fixed_minor: monetize::pricing::PriceRule::default().fee_fixed_minor,
fee_bp: monetize::pricing::PriceRule::default().fee_bp,
twin: twin::TwinRule::default(),
twin_health: Vec::new(),
ladder: default_ladder(),
first_login_ttl_secs: 7 * 86_400,
shop_door: String::new(),
resize_notice_secs: 60,
reserved_names: Vec::new(),
}
}
}
impl EstateOrderConfig {
/// [`Self::product`], named or derived at the start
/// ([`EstateOrders::from_config_with`]). Empty — a config that never went
/// through the start — is refused by name, never answered "gunnar".
pub fn sold_product(&self) -> Result<&str, String> {
let p = self.product.trim();
if p.is_empty() {
return Err(
"[estate_orders] product is not set: name the product a sold estate is".into(),
);
}
Ok(p)
}
/// **The first-login link's lifetime**, seconds: the configured one held
/// to an hour at least and 30 days at most (gunnar keeps no invite longer).
pub fn first_login_ttl(&self) -> u64 {
self.first_login_ttl_secs.clamp(3_600, 30 * 86_400)
}
/// The estate's names: `<company>.<apex>` — the ONE naming both the
/// shop and the kit call ([`monetize_embed::sold::SoldNames`], A43).
pub fn host_of(&self, company: &str) -> String {
if self.is_site_itself(company) {
return self.apex.trim().trim_end_matches('.').to_ascii_lowercase();
}
monetize_embed::sold::SoldNames::of(company, &self.apex).host
}
/// **Where the estate these terms lay takes DEV-NARRATE knocks from**: the ONE box's
/// [`Self::narrate_from`] when these terms narrate it ([`Self::narrating`]), else empty
/// (udp/51822 stays closed).
pub fn narrate_source(&self) -> &str {
if self.narrating {
self.narrate_from.trim()
} else {
""
}
}
/// Whether `company` is the operator site these terms lay itself ([`Self::site_itself`]).
pub fn is_site_itself(&self, company: &str) -> bool {
!self.site_itself.is_empty() && self.site_itself == company
}
/// **Every name the estate serves**: its host and the names its product's lifecycle serves
/// beside it (Gunnar's and Holger's: `www.` and `git.`) — under the shop's apex for a sold
/// estate, the apex's own for an operator site laid itself. A product with no lifecycle here:
/// Gunnar's three, as before.
pub fn names_of(&self, company: &str) -> Vec<String> {
let host = self.host_of(company);
let prefixes = lifecycle::of(self)
.map(|l| l.served_prefixes())
.unwrap_or_else(|_| vec![String::new(), "www.".into(), "git.".into()]);
prefixes.iter().map(|p| format!("{p}{host}")).collect()
}
/// **The estate's DNS records**: [`records_for`] — a sold estate never takes the serving
/// estate's own names — or, for the operator site laid itself, exactly those names.
pub fn records_of(&self, company: &str, front_ipv4: &str) -> Result<Vec<DnsRecord>, String> {
if self.is_site_itself(company) {
return Ok(self
.names_of(company)
.into_iter()
.map(|name| DnsRecord {
name,
kind: "A".into(),
value: front_ipv4.into(),
proxied: false,
})
.collect());
}
// The sold names its product serves (A17: no `git.` for a product that serves none; the
// Lake's `s3.`, which Gunnar's set never had), each refused when it is the serving
// estate's own, as [`records_for`] refuses them.
// HOL-REPO-NAME: and every name the product serves under the apex (Holger's `repo.`):
// a buyer called `repo` never takes the site's repository name.
let own = lifecycle::of(self)
.map(|l| l.served_prefixes())
.unwrap_or_default();
let serving: BTreeSet<String> = ["", "www.", "git.", "monetize."]
.iter()
.map(|p| p.to_string())
.chain(own)
.map(|p| format!("{p}{}", self.apex))
.collect();
self.names_of(company)
.into_iter()
.map(|name| {
if serving.contains(&name) {
Err(format!(
"the record {name} is the serving estate's own name; nothing was written"
))
} else {
Ok(DnsRecord {
name,
kind: "A".into(),
value: front_ipv4.into(),
proxied: false,
})
}
})
.collect()
}
/// **The configured shop door** (one monetize, G14 2026-10-10): the
/// configured `https://<host>`, trimmed and without a trailing `/` —
/// refused by name when absent or not an https URL with a host. Nothing is
/// made up from the apex: the one monetize is not the shop's web address.
pub fn shop_door(&self) -> Result<String, String> {
let door = self.shop_door.trim().trim_end_matches('/');
if door.is_empty() {
return Err("[estate_orders] shop_door is not set: name the https door sold estates call the one monetize through".into());
}
let host = door.strip_prefix("https://").unwrap_or("");
if host.is_empty() || door.chars().any(char::is_whitespace) {
return Err(format!(
"[estate_orders] shop_door = {door:?} is not an https URL with a host"
));
}
Ok(door.to_string())
}
}
/// **The plan `lay_estate` is asked for**: the shape with `{company}` filled in, every resource
/// labelled with its role (and a disk with its volume), each role's system disk in the tier of
/// ITS medium kind's template (`life`'s [`lifecycle::Lifecycle::medium_kind`]: Holger's
/// front reads `holger-front`'s).
pub fn plan_for_as(
cfg: &EstateOrderConfig,
company: &str,
sizes: &EstateSizes,
twin_plan: &str,
life: &lifecycle::Lifecycle,
) -> EstatePlan {
plan_for_by(cfg, company, sizes, twin_plan, &|role| {
life.medium_kind(role)
})
}
fn plan_for_by(
cfg: &EstateOrderConfig,
company: &str,
sizes: &EstateSizes,
twin_plan: &str,
kind_of: &dyn Fn(&str) -> String,
) -> EstatePlan {
let fill = |t: &str| t.replace("{company}", company);
let or = |asked: &str, shape: &str| {
if asked.trim().is_empty() {
shape.to_string()
} else {
asked.trim().to_string()
}
};
let mut front = cfg.shape.front.clone();
front.plan = or(&sizes.front_plan, &front.plan);
let mut main = cfg.shape.main.clone();
main.plan = or(&sizes.main_plan, &main.plan);
if sizes.main_data_gib > 0 {
main.disks
.iter_mut()
.skip(1)
.for_each(|d| d.gib = sizes.main_data_gib);
}
// **The twin follows main** (owner, 2026-10-04): its plan is chosen from
// main's, and its data disk holds main's whole data set.
let mut twin = cfg.shape.twin.clone();
if !twin_plan.is_empty() {
twin.plan = twin_plan.to_string();
}
let main_data: u64 = main.disks.iter().skip(1).map(|d| d.gib).sum();
if let Some(d) = twin.disks.get_mut(1) {
d.gib = d.gib.max(main_data);
}
let machine = |role: &str, m: &MachineShape| {
let mut labels = cfg.labels.clone();
labels.insert("role".into(), m.role_label.clone());
MachinePlan {
role: role.into(),
hostname: fill(&m.hostname),
title: fill(&m.title),
plan: m.plan.clone(),
boot_order: m.boot_order.clone(),
labels: labels.clone(),
interfaces: m.interfaces.clone(),
// The release medium and the seed are the sale's (no-dwarf step 4): set where the
// lay is called, never kept with the plan.
medium: String::new(),
medium_object: None,
template: String::new(),
user_data: Default::default(),
disks: m
.disks
.iter()
.map(|d| {
let mut l = labels.clone();
l.insert("volume".into(), d.volume.clone());
// A system disk cloned from the release's template is laid in the TEMPLATE's
// tier (UpCloud: a custom template deploys only in its own tier, and only
// maxiops is templatized — live 409 STORAGE_TIER_ILLEGAL, 2026-10-14), and
// priced so: the quote, the buy-time recheck and the lay read this one plan.
let tier = match template_of(cfg, &kind_of(role)) {
Some(t) if d.volume == "system" => t.tier().to_string(),
_ => d.tier.clone(),
};
DiskPlan {
title: fill(&d.title),
gib: d.gib,
tier,
labels: l,
}
})
.collect(),
}
};
EstatePlan {
reference: reference_of(company),
company: company.into(),
zone: Zone(cfg.zone.clone()),
machines: vec![
machine("front", &front),
machine("main", &main),
machine("twin", &twin),
],
narrate_from: cfg.narrate_source().to_string(),
shape: None,
}
}
// ── the run ─────────────────────────────────────────────────────────────────
/// One transition, as the run keeps it.
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct Transition {
pub phase: String,
pub at_ms: u64,
pub note: String,
}
/// **One order, as its file holds it.** No secret is ever in here.
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct EstateRun {
pub company: String,
pub reference: String,
/// Where the "ready" / "failed" mail goes. Never logged.
pub email: String,
/// **The SITE it was bought on** (ST-5, ONE monetize 2026-10-11): the calling estate's
/// label (`test2-gunnar-rs`). Its money, its name (`<company>.<site>`) and its renewals
/// are that site's ([`terms_of`]). Empty: an order from before sites, the product's home.
#[serde(default)]
pub estate: String,
/// **Whose Gunnar it is**: the account name its first admin is to have,
/// as the sign-up page sent it (`EstateOrderStartRequest.account`); the
/// installer is told it as `{account}`, and the "ready" mail's first-login
/// link registers its first passkey. Never empty: it is also the tenant
/// of the order's `Instance` row on the gunnar ledger (G5), so a run
/// without it is refused at the door and does not parse from disk.
pub account: String,
pub phase: String,
pub ordered_at_ms: u64,
/// When the vendor first said the money is held; 0 before.
pub held_at_ms: u64,
/// What is held: the six months' price (`quote.total`).
pub amount: Money,
/// **The price as quoted** — sizes, the provider's cost, the rate, the
/// margin, the term — the estate's own billing record.
pub quote: Quote,
/// The customer's own domain (`gunnar.company1.com`); empty: `<company>.<apex>` only.
pub domain: String,
/// The ownership proof the customer publishes (public, not a secret).
pub proof: String,
/// When the job started waiting for the customer's records; 0 before.
pub dns_waiting_since_ms: u64,
/// When the customer's records were found; 0 before (or no own domain).
pub dns_verified_at_ms: u64,
/// When the money was captured, and until when the term is paid.
pub captured_at_ms: u64,
pub paid_until_ms: u64,
pub handle: Option<ChargeHandle>,
pub checkout_url: String,
pub laid: Option<LaidEstate>,
pub dns: Vec<DnsRecord>,
/// **The writer that wrote [`Self::dns`]**, by name (DNS-WRITER, 2026-10-26): its give-back
/// removes them with THAT writer, wherever the site's writes now. Empty: a run from before
/// (or a writer without a name): the site's writer of now.
#[serde(default, skip_serializing_if = "String::is_empty")]
pub dns_writer: String,
/// **The product this run is** (PROD-FIX 2, prod 2026-10-26): its site's product at the
/// order, kept, so a run whose site leaves the feed is still read as ITS product — never
/// the shop's default. Empty: a run from before (its site's, else the fleet's record).
#[serde(default, skip_serializing_if = "String::is_empty")]
pub product: String,
/// **Why the run is stopped by name, and when** (PROD-FIX 2): a final refusal of its terms
/// (its site left the feed, or sells another product). A stopped run is neither booked nor
/// driven; it is looked at again after [`STOPPED_LOOK_AGAIN_MS`], and resumes once its
/// terms read again. Empty: not stopped.
#[serde(default, skip_serializing_if = "String::is_empty")]
pub stopped_why: String,
#[serde(default)]
pub stopped_at_ms: u64,
/// The installer's verdict, as it wrote it.
pub receipt: Option<serde_json::Value>,
/// Why the job failed, or what it is waiting for now.
pub why: String,
pub history: Vec<Transition>,
/// The "ready" / "failed" mail went out (or was refused, said in `history`).
pub told: bool,
/// The provider ids the give-back has already asked to go (servers).
#[serde(default)]
pub given_back: Vec<String>,
/// **A terminate's give-back that failed, in a row** (F4): how many asks failed since the
/// last one that moved, and when the tick asks the provider again (unix ms; 0: at once).
#[serde(default)]
pub give_back_failures: u32,
#[serde(default)]
pub give_back_retry_at_ms: u64,
/// **The estate's WireGuard peer map** (public keys only), kept by the
/// custodian for the control and custody link it will take (§11).
#[serde(default)]
pub peers: Vec<WgPeer>,
/// What the estate costs a month at the provider, as read at the lay.
#[serde(default = "no_cap")]
pub laid_cost: Money,
/// **The estate's spend cap**, fixed at the order (EUR a month at the provider).
#[serde(default = "no_cap")]
pub spend_cap: Money,
/// The renewal the customer opened and has not paid yet.
#[serde(default)]
pub renewal: Option<Renewal>,
/// Every renewal paid, in order.
#[serde(default)]
pub renewals: Vec<RenewalDone>,
/// The reminders and warnings sent, once each: `<paid_until_ms>:<kind>:<day>`.
#[serde(default)]
pub reminded: Vec<String>,
/// **The deletion date**, fixed by the first mail of a lapse; 0 while paid.
#[serde(default)]
pub delete_on_ms: u64,
/// When the servers were stopped for non-payment; 0 while running.
#[serde(default)]
pub suspended_at_ms: u64,
/// The servers stopped for non-payment, started again when it is paid.
#[serde(default)]
pub stopped: Vec<String>,
/// **The plan change in flight**, if any (`change`).
#[serde(default)]
pub change: Option<change::PlanChange>,
/// Every plan change finished (done or failed), in order.
#[serde(default)]
pub changes: Vec<change::PlanChange>,
/// The twin's plan as it grew by itself (empty: an eighth of main's, the rule).
#[serde(default)]
pub twin_floor: String,
/// When the twin last changed plan (the autoscale's cooldown).
#[serde(default)]
pub twin_changed_at_ms: u64,
/// When the laid sizes were last read back from the provider
/// ([`refresh_laid`]); 0 never.
#[serde(default)]
pub sizes_read_at_ms: u64,
/// **The estate's client key, its public half** (64 hex; one shop,
/// 2026-10-07): what this shop verifies the SOLD estate's
/// calls under (`SoldEstate`). Read from custody at the install; empty for
/// an estate installed before it existed, until its next install.
#[serde(default)]
pub client_key: String,
/// **The growth go-ahead this shop signed** for the newest paid data step
/// ([`sold::Growth`]); the estate's boxes pull it in a `grow_go_ahead` order.
#[serde(default)]
pub growth: Option<sold::Growth>,
/// **Since when the order waits for the shop's cloud cap** (the owner,
/// 2026-10-08: "the customer must not know about a cap! it must be queued
/// and resized later"): 0 when it does not. While it waits the order stays
/// `held` — the buyer reads "being prepared" — and the operator is told
/// to raise the cap ([`ORDERS_WAIT`]). The job's own deadline does not run
/// meanwhile; the vendor's hold window still does.
#[serde(default)]
pub cap_waiting_since_ms: u64,
/// When the cap last admitted it after a wait: the job's deadline counts
/// from here. 0: it never waited.
#[serde(default)]
pub cap_cleared_at_ms: u64,
/// When a waiting order last asked the queue ([`QUEUE_LOOK_EVERY_MS`]).
#[serde(default)]
pub queue_looked_at_ms: u64,
/// ★ A32-22: when the operator was last told that this PAID order waits in the queue too
/// long ([`QUEUED_TELL_AFTER_MS`]); 0: never. The buyer then reads since when it waits and
/// the day it is set up or refunded by.
#[serde(default, skip_serializing_if = "is_zero_u64")]
pub queue_told_at_ms: u64,
/// ★ A32-22: when the operator was last told that this order is OWED ([`OWED_TELL_EVERY_MS`]).
#[serde(default, skip_serializing_if = "is_zero_u64")]
pub owed_told_at_ms: u64,
/// What it waits for, in the operator's words (empty when it does not).
#[serde(default)]
pub queue_why: String,
/// How often this PAID order failed and was given back ([`PAID_RETRIES`]).
#[serde(default)]
pub failures: u32,
/// **When its buyer cancelled** (no renewal; [`term::cancel`]); 0: not.
#[serde(default)]
pub cancelled_at_ms: u64,
/// When the "ready"/"failed" mail was last tried (A47: retried hourly until sent).
#[serde(default)]
pub told_tried_at_ms: u64,
/// **When the shop forgot the estate's secrets** (the owner via the
/// coordinator, 2026-10-08: after a PASSed hand-off the shop keeps only its
/// own facts key and what reads health and billing — never a key that could
/// open the buyer's data or backups). 0: not yet.
#[serde(default)]
pub secrets_forgotten_at_ms: u64,
/// **When the release clones were given back** (no-dwarf step 4, 2026-10-08): once the
/// estate is handed over, each machine's cdrom clone of the release medium is ejected and
/// deleted (`Change::ForgetMedia`, journaled clones only). 0: not yet, or none.
#[serde(default)]
pub media_forgotten_at_ms: u64,
/// **The release each box runs** (F4 sold upgrade, 2026-10-09): role → the release's
/// identity ([`release_id`]), set at the lay and after each upgrade. A role absent ran
/// whatever was laid before it was kept.
#[serde(default)]
pub release_of: BTreeMap<String, String>,
/// **When the estate took its installer material** (F4, `SoldEstate.TakeMaterial`): the
/// hand-off forgets the custody only after it ([`sold::material_waits`]). 0: not (yet).
#[serde(default)]
pub material_taken_at_ms: u64,
/// When the buyer was last mailed a new sign-in link (S10); 0: never.
#[serde(default)]
pub sign_in_again_at_ms: u64,
/// When the sale check last asked the public front (S4, [`SALE_CHECK_EVERY_MS`]); 0: never.
#[serde(default)]
pub sale_checked_at_ms: u64,
/// When the ONE box last looked whether a silent PAIRED estate's boxes lack the fleet seam
/// ([`SEAM_HEAL_EVERY_MS`], HRS-DIAG); 0: never.
#[serde(default)]
pub seam_looked_at_ms: u64,
/// **Ordered by the operator's TEST BUTTON** (2026-10-11), or a `site test-order`
/// ([`is_test_paid`]), paid with the vendor's test payment method: real machines, play
/// money. What a console filters on.
#[serde(default)]
pub test: bool,
/// **The operator's own buy** (owner 2026-10-12): ordered by the admin of one of the
/// operator's OWN sites ([`crate::config::EstateConfig::operator_site`]) at
/// [`OPERATOR_TEST_MINOR`], the margin floor not asked of it. Its ledger row says
/// "operator test" and is never revenue.
#[serde(default)]
pub operator_test: bool,
/// **An OPERATOR-SITE ORDER** (owner 2026-10-13): the ONE box lays the declared operator
/// site [`EstateRun::estate`] (its `[estates.<label>]`) itself, under the site's own apex
/// names; `company` is the label as the cloud names an estate ([`site::company_of`]).
#[serde(default)]
pub site_order: bool,
/// The digest of the bearer the laid site's console was fed ([`crate::fleet::bearer_digest`]),
/// when it was minted and until when it opens; restated at every tick ([`site::reassert`]).
#[serde(default)]
pub site_bearer_sha256: String,
#[serde(default)]
pub site_bearer_at_ms: u64,
#[serde(default)]
pub site_bearer_until_ms: u64,
/// The ticket key the laid site's primary offered ([`InstallVerdict::ticket_key`]).
#[serde(default)]
pub site_ticket_key: String,
/// The seed's PUBLIC key the order named ([`SiteFeed::seed_key`]); empty: none.
#[serde(default)]
pub site_seed_key: String,
/// **The update in flight** (owner 2026-10-14: the ONE box updates what it laid), if any.
#[serde(default)]
pub update: Option<update::EstateUpdate>,
/// Every update finished (done or failed), the newest [`update::KEEP_FINISHED`].
#[serde(default)]
pub updates: Vec<update::EstateUpdate>,
/// **Live updates OFF for this estate** (a military customer): every box is updated from its
/// keep medium only. Off unless the operator says so (`EstateOrder.SetLiveUpdate`).
#[serde(default)]
pub live_update_off: bool,
/// **When the ONE box ADOPTED this kit-installed site** ([`adopt`], owner 2026-10-17); 0:
/// laid here. An adopted run is updated by this box and NEVER laid, re-laid, fenced, given
/// back or billed by it: its machines carry no label of this shop.
#[serde(default)]
pub adopted_at_ms: u64,
/// **When the estate's front last asked for an update** (`SoldEstate.UpdateTake`, every 2 min
/// on a front that carries the update taker; kept at most hourly); 0: never — its front runs a
/// release with no taker, so an update of it goes FRONT FIRST ([`update::start`]).
#[serde(default)]
pub update_taken_at_ms: u64,
/// ★ **The account this order makes when it is paid** (pay once, owner 2026-10-22): a
/// visitor bought with no account yet; [`account::NewAccount`] is made at the payment, once
/// (its session), and `account` is its name. `None`: a proven account's order (its ticket).
#[serde(default, skip_serializing_if = "Option::is_none")]
pub new_account: Option<account::NewAccount>,
/// ★ **Money the vendor said went back on this estate's sales** (F-S-01, A17): its order's,
/// its renewals' and its plan changes' refunds and disputes, once each ([`reversed`]).
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub reversals: Vec<reversed::EstateReversal>,
/// **When a reversal took the paid time back** (all of a sale refunded, or a dispute lost);
/// 0: none, or a renewal paid since. Such an estate goes the nonpay way — grace, then stopped
/// with every disk kept — and is never DELETED by it: the operator decides.
#[serde(default)]
pub reversed_at_ms: u64,
/// ★ **Payment pages the shop let go of that the vendor did not close** (F-S-02/03, A17):
/// kept, said, and asked to close again on every tick ([`pages::look`]) — never dropped.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub unreleased: Vec<pages::OpenPage>,
}
/// **What the operator's own buy costs** (owner 2026-10-12): EUR 0.50, the vendor's smallest
/// charge, held, laid and captured the normal way.
pub const OPERATOR_TEST_MINOR: u64 = 50;
/// Pure: **is this the operator's own buy?** — `account` (the plain name its site knows) is
/// the admin of `estate`, one of the operator's OWN sites. Never true without a site.
pub fn is_operator_buy(state: &AppState, estate: &str, account: &str) -> bool {
let estate = estate.trim();
!estate.is_empty()
&& state
.registry
.operator_admin_of(estate)
.is_some_and(|admin| admin == account.trim())
}
/// Pure: ★ A33-5 — **the terms an order is priced on**: `cfg` itself, or, for the operator's
/// own buy ([`is_operator_buy`], kept as [`EstateRun::operator_test`]), the same terms HOLDING
/// [`OPERATOR_TEST_MINOR`] a term ([`EstateOrderConfig::held_price_minor`]) with no floor and no
/// fee share asked. Data the one quote ([`price::quote_as_laid`]) and the one lay's recheck
/// ([`price::recheck`]) read; neither branches on who pays.
pub fn priced_for(cfg: &EstateOrderConfig, operator: bool) -> EstateOrderConfig {
if !operator {
return cfg.clone();
}
EstateOrderConfig {
held_price_minor: OPERATOR_TEST_MINOR,
min_margin_bp: 0,
fee_fixed_minor: 0,
fee_bp: 0,
..cfg.clone()
}
}
/// **The operator's words for an order the cloud's cap holds back**: in the
/// run's `why`, its history, the ALERT line and the functional status — never
/// in anything the buyer reads.
pub const ORDERS_WAIT: &str = "orders wait: raise the cap";
/// The operator's words for an order `max_estates` holds back (A09).
pub const ORDERS_WAIT_ESTATES: &str = "orders wait: raise max_estates";
fn no_cap() -> Money {
Money {
minor: 0,
currency: monetize_pay::Currency::Eur,
}
}
impl EstateRun {
/// **The name its boxes pull as, and the guardian knows it by**: the company — or, for an
/// operator site laid itself, the site's own label (`gunnar-rs`), the estate it already is.
pub fn fleet_name(&self) -> &str {
if self.site_order {
&self.estate
} else {
&self.company
}
}
/// **The canonical name of `host`** (WWW-CANON, owner 2026-10-30: the sites are
/// `www.<site>`): `www.<host>` when this estate's lay named it (its DNS records), else
/// `host`. The bare name stays the passkeys' relying party; every URL a buyer is given is
/// on this one.
pub fn canonical_host(&self, host: &str) -> String {
let www = format!("www.{host}");
if !host.starts_with("www.") && self.dns.iter().any(|d| d.name.eq_ignore_ascii_case(&www)) {
www
} else {
host.to_string()
}
}
/// **What the buyer is told**, the product called as `w` says.
pub fn customer_view_as(
&self,
apex: &str,
w: &lifecycle::ProductWords,
) -> (String, String, String) {
let t = &w.title;
let host = if self.site_order {
apex.to_string()
} else {
format!("{}.{apex}", self.company)
};
let (state, words) = match self.phase.as_str() {
phase::ORDERED => ("waiting_for_payment", "Waiting for your payment on the payment page.".to_string()),
// ★ A32-22: a paid order that waits long reads since when, and the day it is set up or refunded by.
phase::HELD if self.captured_at_ms > 0 && self.queue_told_at_ms > 0 => (
"queued",
format!(
"Your {t} is paid and waits for room to be set up (since {}); we are on it, and you get a mail when it runs. If it is not set up by {}, it is set up by the operator or refunded in full.",
iso_date(self.held_since_ms()),
iso_date(self.held_since_ms().saturating_add(QUEUED_DEADLINE_MS))
),
),
phase::HELD if self.captured_at_ms > 0 => ("queued", format!("Your {t} is paid and being prepared.")),
phase::HELD => ("queued", format!("Your payment is held, not charged. Your {t} is being prepared.")),
phase::WAITING_DNS => (
"waiting_for_dns",
format!("{} Add the two records below at the DNS provider of {}; we look every minute.", if self.captured_at_ms > 0 { format!("Your {t} is paid.") } else { "Your payment is held, not charged.".to_string() }, self.domain),
),
phase::LAYING => ("laying", "Your machines and disks are being set up.".to_string()),
phase::INSTALLING | phase::PAIRED => ("installing", format!("{t} is being installed on your machines.")),
phase::CAPTURED if self.site_order && self.adopted_at_ms > 0 => ("ready", format!("The site runs at https://{host}: installed by its kit, adopted by this box on {}, which updates it.", iso_date(self.adopted_at_ms))),
phase::CAPTURED if self.site_order => ("ready", format!("The site is ready at https://{host}.{}", if self.account.is_empty() { "" } else if self.told { " Its first sign-in link was mailed." } else { " Its first sign-in link is being mailed." })),
phase::CAPTURED => (
"ready",
format!(
"Your {t} is ready at https://{}; it is paid until {}.{}",
if self.domain.is_empty() { &host } else { &self.domain },
iso_date(self.paid_until_ms),
if self.account.is_empty() { "" } else if self.told { " Your first sign-in link was mailed to you." } else { " Your first sign-in link is being mailed to you." }
),
),
phase::GRACE => ("overdue", format!("Your {t}'s paid period ended on {}. It still runs; renew to keep it running.", iso_date(self.paid_until_ms))),
phase::SUSPENDED => (
"suspended",
if self.delete_on_ms > 0 {
format!("Your {t} is stopped; every disk and all your data are kept until {}. Renew and it starts again.", iso_date(self.delete_on_ms))
} else {
format!("Your {t} is stopped; every disk and all your data are kept. Renew and it starts again.")
},
),
phase::TERMINATING => ("terminating", format!("Your {t} is being terminated, as you asked: its machines, disks and records are being given back.")),
phase::TERMINATED => ("terminated", format!("Your {t} was terminated, as you asked: its machines, disks and records are gone, and nothing more is charged.")),
phase::DELETING | phase::DELETED => ("deleted", format!("Your {t} was deleted after its paid period ended on {} and was not renewed.", iso_date(self.paid_until_ms))),
phase::FAILED if self.captured_at_ms > 0 => ("queued", format!("Your {t} is paid and being prepared; it takes longer than usual.")),
// ★ A32-22: the buyer reads the day we were told and the day it is set up or refunded by.
phase::OWED => (
"queued",
format!(
"Your {t} is paid. Setting it up takes longer than usual: we were told on {} and set it up or refund you in full by {}; you get a mail either way.",
iso_date(self.owed_since_ms()),
iso_date(self.owed_since_ms().saturating_add(OWED_DEADLINE_MS))
),
),
phase::REFUNDED => ("cancelled", format!("Your order was cancelled and {} was refunded.", self.amount.in_words())),
phase::FAILED => ("failed", format!("Your {t} could not be set up. Nothing will be charged: the hold is being released.")),
_ => ("failed", format!("Your {t} could not be set up, and nothing was charged: the hold is released.")),
};
let shown = if self.domain.is_empty() {
self.canonical_host(&host)
} else {
self.domain.clone()
};
let url = if matches!(self.phase.as_str(), phase::CAPTURED | phase::GRACE) {
format!("https://{shown}")
} else {
String::new()
};
(state.to_string(), words, url)
}
/// [`Self::operator_view`], the product called as `w` says.
pub fn operator_view_as(
&self,
apex: &str,
w: &lifecycle::ProductWords,
) -> (String, String, String) {
match self.phase.as_str() {
// F2: an operator site whose install stopped waits for its operator, its reason named.
phase::FAILED if self.site_order && self.stuck_install() => (INSTALL_FAILED.into(), format!("{} could not be installed: {}.", w.title, self.failure_note().unwrap_or("not recorded")), String::new()),
phase::OWED => ("owed".into(), format!("The setup failed after payment; the operator can retry or refund. Last failure: {}", self.last_failure().unwrap_or("not recorded")), String::new()),
phase::FAILED if self.captured_at_ms > 0 => ("owed".into(), "The setup failed after payment; what was laid is being given back, then it is set up again once by itself or the operator can retry or refund.".into(), String::new()),
// F12: a paired site names the capture step that holds it, in that step's words.
phase::PAIRED => {
let (st, words, url) = self.customer_view_as(apex, w);
match capture_hold(&self.phase, &self.why) {
Some(held) => (st, format!("{words} Why: {held}."), url),
None => (st, words, url),
}
}
_ => self.customer_view_as(apex, w),
}
}
/// **The run's own reason and since when** (test3-holger-rs 2026-10-22: "queued (held)" with
/// its `queue_admits` reason in `why`, shown nowhere): `(why, "YYYY-MM-DD HH:MMZ")`, since =
/// when the run entered its phase. `None` when it says none.
pub fn why_said(&self) -> Option<(String, String)> {
let why = self.why.trim();
if why.is_empty() {
return None;
}
let since = self
.history
.iter()
.rev()
.find(|t| t.phase == self.phase)
.map(|t| format!("{} {}", iso_date(t.at_ms), install_why::hm_utc(t.at_ms)))
.unwrap_or_default();
Some((why.to_string(), since))
}
/// **The newest `failed` step's own words** in the history (never the run's later `why`).
pub fn failure_note(&self) -> Option<&str> {
self.history
.iter()
.rev()
.find(|t| t.phase == phase::FAILED && !t.note.trim().is_empty())
.map(|t| t.note.trim())
}
/// **An install that was stopped** (F2): FAILED by the install deadline or abandoned by the
/// operator — not a lay or an installer that failed by itself.
pub fn stuck_install(&self) -> bool {
self.phase == phase::FAILED
&& self
.failure_note()
.is_some_and(|n| n == ABANDONED || n.starts_with(INSTALL_DEADLINE_HEAD))
}
/// ★ A32-22: since when the order waits in HELD (its newest move into it; its hold or capture
/// when the history says none).
pub fn held_since_ms(&self) -> u64 {
self.history
.iter()
.rev()
.find(|t| t.phase == phase::HELD)
.map(|t| t.at_ms)
.unwrap_or(self.captured_at_ms.max(self.held_at_ms))
}
/// ★ A32-22: since when the order is OWED (its newest move into it).
pub fn owed_since_ms(&self) -> u64 {
self.history
.iter()
.rev()
.find(|t| t.phase == phase::OWED)
.map(|t| t.at_ms)
.unwrap_or(self.captured_at_ms)
}
/// **The last failure, in its own words** (live 2026-10-14: an owed order said "Last
/// failure: not recorded" while its history held it): the run's `why` while it says one,
/// else the newest `failed` step of its history.
pub fn last_failure(&self) -> Option<&str> {
let why = self.why.trim();
if !why.is_empty() {
return Some(why);
}
self.history
.iter()
.rev()
.find(|t| t.phase == phase::FAILED && !t.note.trim().is_empty())
.map(|t| t.note.trim())
}
/// The records the customer must set, the proof named `proof` (the product's).
pub fn customer_records_as(&self, apex: &str, proof: &str) -> Vec<CustomerRecord> {
if self.domain.is_empty() {
Vec::new()
} else {
domain::required_as(proof, &self.domain, &self.company, apex, &self.proof)
}
}
}
// ── the DNS ─────────────────────────────────────────────────────────────────
pub use monetize_cloud::dns::DnsRecord;
/// **The estate's names**, the kit's `subzone` shape: `<company>.<apex>`,
/// `www.`, `git.` — every one an A record to the front ([`monetize_embed::sold::SoldNames`]).
/// Refused by name: any record that would be one of the SERVING estate's own
/// names.
pub fn records_for(company: &str, apex: &str, front_ipv4: &str) -> Result<Vec<DnsRecord>, String> {
let serving: BTreeSet<String> = ["", "www.", "git.", "monetize."]
.iter()
.map(|p| format!("{p}{apex}"))
.collect();
monetize_embed::sold::SoldNames::of(company, apex)
.names()
.into_iter()
.map(|name| {
if serving.contains(&name) {
Err(format!(
"the record {name} is the serving estate's own name; nothing was written"
))
} else {
Ok(DnsRecord {
name,
kind: "A".into(),
value: front_ipv4.into(),
proxied: false,
})
}
})
.collect()
}
/// **Where an estate's names are written** — `monetize_cloud::dns::DnsWriter`:
/// the account's writer is linked (Cloudflare, `monetize-cloud-impl`, through
/// [`crate::registry::Linked::dns`]); the mock has no DNS, so [`FileDns`]
/// records what would have been written.
pub use monetize_cloud::dns::DnsWriter as EstateDns;
/// No writer here: the names are listed in the run and written by nobody.
pub struct NoDns;
impl EstateDns for NoDns {
/// It writes into no provider's zone (RA1-5): the zone guard passes it.
fn zoneless(&self) -> bool {
true
}
fn write(&self, records: &[DnsRecord]) -> Result<String, String> {
Ok(format!(
"no DNS writer is configured here: {} record(s) listed in the run, none written",
records.len()
))
}
fn remove(&self, _names: &[String]) -> Result<String, String> {
Ok("no DNS writer is configured here: nothing to remove".into())
}
fn listed(&self) -> Result<Vec<DnsRecord>, String> {
Ok(Vec::new())
}
fn writes(&self) -> bool {
false
}
}
/// **The mock's DNS**: a JSON file of records. What the account's writer
/// would hold, so a test can see the names come and go.
pub struct FileDns(pub PathBuf);
impl FileDns {
fn save(&self, rows: &[DnsRecord]) -> Result<(), String> {
let text = serde_json::to_vec_pretty(rows).map_err(|e| e.to_string())?;
write_atomic(&self.0, &text)
}
}
impl EstateDns for FileDns {
/// It writes into no provider's zone (RA1-5): the zone guard passes it.
fn zoneless(&self) -> bool {
true
}
fn write(&self, records: &[DnsRecord]) -> Result<String, String> {
let mut rows = self.listed()?;
rows.retain(|r| !records.iter().any(|n| n.name == r.name));
rows.extend(records.iter().cloned());
self.save(&rows)?;
Ok(format!(
"{} record(s) written to {}",
records.len(),
self.0.display()
))
}
fn remove(&self, names: &[String]) -> Result<String, String> {
let mut rows = self.listed()?;
let before = rows.len();
rows.retain(|r| !names.contains(&r.name));
self.save(&rows)?;
Ok(format!(
"{} record(s) removed from {}",
before - rows.len(),
self.0.display()
))
}
fn lists(&self) -> bool {
true
}
fn listed(&self) -> Result<Vec<DnsRecord>, String> {
match std::fs::read(&self.0) {
Ok(b) if b.is_empty() => Ok(Vec::new()),
Ok(b) => serde_json::from_slice(&b).map_err(|e| format!("{}: {e}", self.0.display())),
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(Vec::new()),
Err(e) => Err(format!("{}: {e}", self.0.display())),
}
}
}
/// The refusal of a config that still names a twin-health command (removed, G12).
pub const TWIN_HEALTH_COMMAND_REMOVED: &str = "[estate_orders] twin_health is REMOVED (G12, 2026-10-10): the twin's lag and load are no longer read by running a command (the kit's `cargo xtask … estate twin-health`) — the estate REPORTS them on the pull channel (its boxes' Fleet.Report `twin_review`), read natively here; monetize never dials an estate. Remove the line";
/// The refusal of a config that still names an install worker (removed, no-dwarf step 8).
pub const WORKERS_REMOVED: &str = "[estate_orders.install_workers] is REMOVED (2026-10-08, \"DWARFS MUST NOT BE INVOLVED!\"): no worker pulls a sale's install any more — the shop pairs what it sells itself (`pairing = \"shop-pairing\"`). Remove the table";
/// The words a job waiting for its DNS records to answer is parked with.
pub const DNS_WAIT: &str = "the estate's DNS records do not answer yet";
/// The DNS writer every open build has: `[estate_orders] dns = "manual"`.
pub const MANUAL_DNS: &str = "manual";
/// **The FREE DNS writer** (`dns = "manual"`, the owner 2026-10-08): the shop TELLS the operator
/// the records to set by hand — in the run, in an ALERT line — and waits until the resolver
/// (`[estate_orders] resolver`, best the zone's authoritative nameserver: `dns:<ip>:53`)
/// answers each with its value; only then does the installer start. A give-back tells which
/// names to remove. DNS-only records: a proxied name answers another address and never settles.
pub struct ManualDns {
pub resolver: Arc<dyn domain::DomainCheck>,
}
impl ManualDns {
/// Pure: the operator's words for records to set or remove by hand.
pub fn said(set: bool, records: &[String]) -> String {
format!(
"{} by hand at the zone's DNS: {}",
if set {
"set these records"
} else {
"remove these records"
},
records.join(" · ")
)
}
}
impl EstateDns for ManualDns {
/// It writes into no provider's zone (RA1-5): the zone guard passes it.
fn zoneless(&self) -> bool {
true
}
fn write(&self, records: &[DnsRecord]) -> Result<String, String> {
let rows: Vec<String> = records
.iter()
.map(|r| format!("{} {} {}", r.kind, r.name, r.value))
.collect();
let line = Self::said(true, &rows);
eprintln!("monetize-server: ALERT estate-orders.dns-by-hand: {line}");
skidbladnir::functional_status(
"monetize-server/estate-orders",
"dns-by-hand",
false,
&line,
);
Ok(format!("{line}; the installer waits until they answer"))
}
fn remove(&self, names: &[String]) -> Result<String, String> {
let line = Self::said(false, names);
eprintln!("monetize-server: ALERT estate-orders.dns-by-hand: {line}");
Ok(line)
}
fn listed(&self) -> Result<Vec<DnsRecord>, String> {
Ok(Vec::new())
}
/// Every A record answers with its value at the resolver (other kinds are the operator's).
fn settled(&self, records: &[DnsRecord]) -> Result<bool, String> {
for r in records.iter().filter(|r| r.kind == "A") {
if !self.resolver.a(&r.name)?.contains(&r.value) {
return Ok(false);
}
}
Ok(true)
}
}
/// **The installer `[estate_orders] pairing` names**, built from what this build links —
/// refused by name when it links none of that name (an open build links none).
pub fn linked_installer(
c: &EstateOrderConfig,
linked: &crate::registry::Linked,
data_dir: &std::path::Path,
) -> Result<Arc<dyn Installer>, String> {
let name = c.pairing.trim();
let Some(f) = linked.find_installer(name) else {
let have = linked.installer_names();
return Err(format!(
"[estate_orders] pairing = {name:?}: this build links no such installer ({}); the shop's own pairing is `monetize-shop-pairing` (`ShopPairing`, linked by the solo monetize box)",
if have.is_empty() { "it links none".to_string() } else { format!("it links {}", have.join(", ")) }
));
};
(f.build)(&crate::registry::InstallerBuild {
orders: c,
data_dir,
})
.map_err(|e| format!("[estate_orders] pairing = {name:?}: {e}"))
}
/// **The writer `[estate_orders] dns` names**: `manual` (the FREE one, every build: the
/// operator sets the records by hand and the shop waits until `resolver` answers them),
/// `file:<path>` (the mock's recording file), empty (no writer), or a LINKED writer by name
/// (`cloudflare`, the commercial build) — refused by name when this build links none. A
/// linked writer gets its zone and token from the custodian (`dns.<name>`), unsealed for each
/// call; a credential that was never sealed is said at the first write, by name.
pub fn dns_writer(
c: &EstateOrderConfig,
linked: &crate::registry::Linked,
custody: &Custody,
resolver: Arc<dyn domain::DomainCheck>,
) -> Result<Arc<dyn EstateDns>, String> {
if let Some(p) = c.dns.strip_prefix("file:") {
return Ok(Arc::new(FileDns(PathBuf::from(p))));
}
if c.dns.is_empty() {
return Ok(Arc::new(NoDns));
}
if c.dns.trim() == MANUAL_DNS {
if !resolver.reads() {
return Err(format!(
"[estate_orders] dns = \"{MANUAL_DNS}\": the shop waits until the records set by hand answer, and [estate_orders] resolver names none (best: the zone's authoritative nameserver, `dns:<ip>:53`)"
));
}
return Ok(Arc::new(ManualDns { resolver }));
}
let name = c.dns.trim();
let Some(f) = linked.find_dns(name) else {
let have = linked.dns_names();
return Err(format!(
"[estate_orders] dns = {name:?}: this build links no such DNS writer ({}); `file:<path>` is the mock's, empty is none",
if have.is_empty() { "it links none".to_string() } else { format!("it links {}", have.join(", ")) }
));
};
let custody = custody.clone();
let key = format!("dns.{name}");
let credential: monetize_cloud::dns::CredentialSource = Arc::new(move || {
let bytes = custody.unseal_credential(&key)?;
monetize_cloud::dns::DnsCredential::from_json(&bytes)
});
let settings = c.dns_settings.clone();
let w = (f.build)(monetize_cloud::dns::DnsBuild {
name,
settings: monetize_cloud::dns::Settings(&settings),
credential,
})
.map_err(|e| format!("[estate_orders] dns = {name:?}: {e}"))?;
Ok(Arc::from(w))
}
// ── the installer ───────────────────────────────────────────────────────────
/// **`laid.json`**: what the job hands the kit — the estate as the provider
/// laid it, and the names its front must serve (`--laid`).
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct LaidOrder {
pub company: String,
pub apex: String,
/// The customer's own domain, or empty.
#[serde(default)]
pub domain: String,
/// Every name the front serves and makes a certificate for.
pub names: Vec<String>,
pub estate: LaidEstate,
/// **The estate's spend cap** (EUR minor a month at the provider).
#[serde(default)]
pub spend_cap_monthly_minor: u64,
/// **The ONE monetize's door** (G14, G16): the configured
/// [`EstateOrderConfig::shop_door`], never made up. The estate runs no
/// monetize (ONE monetize, owner 2026-10-10): each of its gunnar boxes PULLS
/// its orders here (`--fleet <shop_door> --fleet-instance <company>`), and
/// the estate's signed `SoldEstate` calls go here. It holds no cloud token
/// and no growth cap of its own.
#[serde(default)]
pub shop_door: String,
/// **The ONE monetize's public key** (64 hex): what the boxes verify every
/// pulled order and entitlement under (`--monetize-pubkey`).
#[serde(default)]
pub monetize_pubkey: String,
/// **How long the buyer's first-login link lives**, seconds
/// ([`EstateOrderConfig::first_login_ttl_secs`]); the kit feeds it with
/// the invite's hash.
#[serde(default)]
pub first_login_ttl_secs: u64,
/// **The estate's pay page** (lane M4): where its next period is paid — the site row's
/// renewal page (`[estate_orders.term] renew_url` on the run's site). The pairing feeds it to
/// a product whose front serves no pay page of its own (Holger's `refusal_link`). Empty: none.
#[serde(default)]
pub pay_page: String,
}
/// What the `installing` step hands the installer.
#[derive(Clone, Debug)]
pub struct InstallRequest {
/// **What the estate's product says it is** (its lifecycle's facts, lane L18; v2.0 P3): the
/// kind, and the contract and sale proof the pairing speaks for it. Default: no product.
pub facts: crate::facts::Facts,
pub company: String,
pub apex: String,
/// The order's owner ([`EstateRun::account`]); empty when none was named.
pub account: String,
/// `laid.json`: [`LaidEstate`], exactly as the provider reported it.
pub laid: PathBuf,
/// The custody hand-off directory ([`crate::custody::handoff`]).
pub handoff: PathBuf,
/// The job's own directory; the verdict is `<run>/install-verdict.json`.
pub run: PathBuf,
/// **The one role to lay** (`main` | `twin`) when the guardian lays a box again
/// ([`relay::relay_one`]); empty: the whole estate.
pub role: String,
/// **Where the installer says what it is doing** — kept with the order and
/// published ([`setup_log`]); silent when nobody listens.
pub narrate: setup_log::Narrate,
/// **The relay a sold estate's main mails through** (S10 resend, P1 2026-10-11): feed
/// entries `secret.smtp_url` / `secret.smtp_from` of the estate's PRODUCT ([`relay_feed`]);
/// empty: none fed.
pub relay: BTreeMap<String, String>,
/// **An operator site laid itself** ([`site`]): what its front is fed as the enrolled site
/// it is. `None`: a sold estate.
pub site: Option<SiteFeed>,
/// **The estate's off-site copy** (lane O1, [`offsite_feed`]): the store the shop was fed
/// for it, the estate's own prefix and, once minted, its recipient — in the feed keys the
/// product's main reads them under. The installer feeds them to the boxes the product ships
/// a copy from (Holger's main and twin), never the front. Empty: no copy.
pub offsite: BTreeMap<String, String>,
}
/// What an operator site's front is fed over a sold front's feed ([`InstallRequest::site`]).
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct SiteFeed {
/// The site's own bearer at the ONE box (its console sells as this site). Never logged.
pub bearer: String,
/// The product its console sells (`[estates.<site>] product`).
pub product: String,
/// The site's own label (`gunnar-rs`): the name its boxes pull as.
pub label: String,
/// **The seed's public key** (one OpenSSH line; empty: none): fed as the machine principal
/// the kit seeds the site's declared repositories as (gunnar's `installer-seed`). Its
/// private half never leaves the operator's kit.
pub seed_key: String,
/// **The site's public face is on** (`[estates.<label>] public_site`, TASKS-12): fed as
/// `console_public_site = on`.
pub public_site: bool,
/// **The site's CLI hole is open to the internet** (`[estates.<label>] cli_hole_public`,
/// RA1-14): fed as `front_cli_hole = public`.
pub cli_hole_public: bool,
/// **The repositories of the site's estate that are public** (`[estates.<label>]
/// public_repositories`, NIGHT-PUBREPO): fed to its main and twin as `public_repositories`.
pub public_repositories: Vec<String>,
}
/// The host's fed secrets naming the relay a SOLD estate's main mails through, **one per
/// product** (owner 2026-10-28: Gunnar sites send as estate@gunnar.rs, Holger sites as
/// estate@holger.rs): `<secrets_dir>/secret.sold_smtp_url.<product>`, `…sold_smtp_from.<product>`
/// ([`sold_relay_names`]), kept apart from the box's own relay (`smtp_url`) so the operator
/// decides what a buyer's box may hold.
pub const SOLD_RELAY_URL: &str = "sold_smtp_url";
pub const SOLD_RELAY_FROM: &str = "sold_smtp_from";
/// MAIL-RELAY: one more root the sold relay's certificate is trusted under (PEM, optional; a
/// relay on a private CA, the bench's), fed as [`FEED_SMTP_CA`]; per product as the relay.
pub const SOLD_RELAY_CA: &str = "sold_smtp_ca";
/// The feed keys a main reads them under (gunnar-server `feed::FEED_SMTP_*`).
pub const FEED_SMTP_URL: &str = "secret.smtp_url";
pub const FEED_SMTP_FROM: &str = "secret.smtp_from";
/// The feed key of the relay's extra root (the ONE box's own `secret.smtp_ca`).
pub const FEED_SMTP_CA: &str = "secret.smtp_ca";
/// Pure: **the names of `product`'s sold relay** in the host's secrets — `(url, from, ca)`,
/// each suffixed with the product's family as the product mail is (`smtp_url.holger`; `holger-lean` → `.holger`).
pub fn sold_relay_names(product: &str) -> (String, String, String) {
let p = lifecycle::family_of(product).to_ascii_lowercase();
(
format!("{SOLD_RELAY_URL}.{p}"),
format!("{SOLD_RELAY_FROM}.{p}"),
format!("{SOLD_RELAY_CA}.{p}"),
)
}
/// The relay of `product`'s sold estates as the shop holds it now; a half one is said and none
/// fed.
pub fn sold_relay(state: &AppState, product: &str) -> BTreeMap<String, String> {
relay_feed(state.estates.relay_fed.as_deref(), product).unwrap_or_else(|why| {
eprintln!("monetize-server: REFUSED [sold-relay] {why}: the sold {product} estate is fed no relay (its resend is refused by name)");
BTreeMap::new()
})
}
/// **The relay of `product`'s sold estates, as the shop was fed it**: `secret.smtp_url` /
/// `secret.smtp_from` (and `secret.smtp_ca` when the relay's root is named), all or none, from
/// that product's names alone ([`sold_relay_names`]) — never another product's.
pub fn relay_feed(
secrets: Option<&Path>,
product: &str,
) -> Result<BTreeMap<String, String>, String> {
let Some(dir) = secrets else {
return Ok(BTreeMap::new());
};
let read = |n: &str| {
monetize_product_wasm::secrets::read(dir, n)
.ok()
.and_then(|s| String::from_utf8(s.bytes().to_vec()).ok())
.map(|v| v.trim().to_owned())
.filter(|v| !v.is_empty())
};
let (url_n, from_n, ca_n) = sold_relay_names(product);
let p = lifecycle::family_of(product);
match (read(&url_n), read(&from_n)) {
// RA5-42: the old unsuffixed names (before OX-RELAY2) are said by name, never dropped.
(None, None) if read(SOLD_RELAY_URL).is_some() || read(SOLD_RELAY_FROM).is_some() => Err(format!(
"this box holds the old unsuffixed {SOLD_RELAY_URL}/{SOLD_RELAY_FROM} and no {url_n}/{from_n}: the sold relay is per product now, feed it under {url_n} and {from_n}"
)),
(None, None) => Ok(BTreeMap::new()),
(Some(url), Some(from)) => {
let mut f = BTreeMap::from([(FEED_SMTP_URL.to_owned(), url), (FEED_SMTP_FROM.to_owned(), from)]);
if let Some(ca) = read(&ca_n) {
f.insert(FEED_SMTP_CA.to_owned(), ca);
}
Ok(f)
}
(Some(_), None) => Err(format!("the {p} sold estates' relay is named ({url_n}) and its sender is not ({from_n})")),
(None, Some(_)) => Err(format!("the {p} sold estates' sender is named ({from_n}) and its relay is not ({url_n})")),
}
}
/// **The boot's word on the old unsuffixed sold-relay names** (RA5-42): `secret.sold_smtp_url`
/// / `…sold_smtp_from` from before OX-RELAY2 are read by nobody now. `Some(ALERT …)` when this
/// box still holds one, so the operator feeds the per-product names before a buyer's mail is
/// refused.
pub fn unsuffixed_relay_alert(secrets: &Path) -> Option<String> {
let held: Vec<&str> = [SOLD_RELAY_URL, SOLD_RELAY_FROM, SOLD_RELAY_CA]
.into_iter()
.filter(|n| monetize_product_wasm::secrets::read(secrets, n).is_ok())
.collect();
(!held.is_empty()).then(|| {
format!(
"ALERT sold-relay.unsuffixed this box holds {} (the names before the relay went per product): nothing reads them; feed {}.<product> and {}.<product> for each product it sells",
held.iter().map(|n| format!("secret.{n}")).collect::<Vec<_>>().join(", "),
SOLD_RELAY_URL,
SOLD_RELAY_FROM
)
})
}
/// The host's fed secrets naming the store an operator site's estate ships its OFF-SITE copy
/// to (lane O1, `<secrets_dir>/secret.sold_offsite_*`): the operator's store, kept apart from
/// the box's own off-box copy's feed, as the sold relay is from the box's relay. All four or
/// none.
pub const SOLD_OFFSITE_ENDPOINT: &str = "sold_offsite_r2_endpoint";
pub const SOLD_OFFSITE_BUCKET: &str = "sold_offsite_bucket";
pub const SOLD_OFFSITE_ACCESS_KEY: &str = "sold_offsite_r2_access_key";
pub const SOLD_OFFSITE_SECRET_KEY: &str = "sold_offsite_r2_secret_key";
/// **The feed keys an estate's main reads its off-site copy from** (holger server-lib
/// `offsite::FEED_*`, lane M5) — the same words the ONE box's own copy is fed in.
pub const FEED_OFFSITE_RECIPIENT: &str = "offsite_recipient";
pub const FEED_OFFSITE_ENDPOINT: &str = "offsite_r2_endpoint";
pub const FEED_OFFSITE_BUCKET: &str = "offsite_bucket";
pub const FEED_OFFSITE_PREFIX: &str = "offsite_prefix";
pub const FEED_OFFSITE_ACCESS_KEY: &str = "secret.offsite_r2_access_key";
pub const FEED_OFFSITE_SECRET_KEY: &str = "secret.offsite_r2_secret_key";
/// **The estate's off-site prefix**: `<product>/<company>/` — one estate's copies under its own
/// name, every product's under its own.
pub fn offsite_prefix(product: &str, company: &str) -> String {
let p = product.trim();
format!(
"{}/{}/",
if p.is_empty() { "estate" } else { p },
company.trim()
)
}
/// **The estate's off-site feed, as the shop was fed its store** (lane O1): `Ok(empty)` when the
/// host was fed no store; the four store keys, the estate's prefix and (when one was minted)
/// its recipient otherwise; half a store is refused by the NAMES it lacks, never a value.
pub fn offsite_feed(
secrets: Option<&Path>,
product: &str,
company: &str,
recipient: Option<&str>,
) -> Result<BTreeMap<String, String>, String> {
let Some(dir) = secrets else {
return Ok(BTreeMap::new());
};
let read = |n: &str| {
monetize_product_wasm::secrets::read(dir, n)
.ok()
.and_then(|s| String::from_utf8(s.bytes().to_vec()).ok())
.map(|v| v.trim().to_owned())
.filter(|v| !v.is_empty())
};
let names = [
(SOLD_OFFSITE_ENDPOINT, FEED_OFFSITE_ENDPOINT),
(SOLD_OFFSITE_BUCKET, FEED_OFFSITE_BUCKET),
(SOLD_OFFSITE_ACCESS_KEY, FEED_OFFSITE_ACCESS_KEY),
(SOLD_OFFSITE_SECRET_KEY, FEED_OFFSITE_SECRET_KEY),
];
let got: Vec<(&str, &str, Option<String>)> = names
.iter()
.map(|(host, feed)| (*host, *feed, read(host)))
.collect();
if got.iter().all(|(_, _, v)| v.is_none()) {
return Ok(BTreeMap::new());
}
let missing: Vec<&str> = got
.iter()
.filter(|(_, _, v)| v.is_none())
.map(|(h, _, _)| *h)
.collect();
if !missing.is_empty() {
return Err(format!(
"the off-site store is fed without {}",
missing.join(", ")
));
}
let mut f: BTreeMap<String, String> = got
.into_iter()
.filter_map(|(_, feed, v)| v.map(|v| (feed.to_string(), v)))
.collect();
f.insert(
FEED_OFFSITE_PREFIX.to_string(),
offsite_prefix(product, company),
);
if let Some(r) = recipient.map(str::trim).filter(|r| !r.is_empty()) {
f.insert(FEED_OFFSITE_RECIPIENT.to_string(), r.to_string());
}
Ok(f)
}
/// **What an estate's install is fed of its off-site copy** (lane O1): the site's OWN estate,
/// on a site that says `offsite` ([`crate::registry::Linked::offsite`]), the store the shop holds
/// now and the recipient its custody keeps; nothing for any other estate. A half store is
/// said and none fed.
pub fn site_offsite(state: &AppState, run: &EstateRun, product: &str) -> BTreeMap<String, String> {
if !run.site_order || !state.registry.offsite(&run.estate) {
return BTreeMap::new();
}
let recipient = state
.estates
.custody
.as_ref()
.and_then(|c| c.offsite_recipient(&run.company).ok().flatten());
offsite_feed(
state.estates.relay_fed.as_deref(),
product,
&run.company,
recipient.as_deref(),
)
.unwrap_or_else(|why| {
eprintln!(
"monetize-server: REFUSED [site-offsite] {why}: {} is fed no off-site copy",
run.reference
);
BTreeMap::new()
})
}
/// **The installer's verdict file** (`install-verdict.json`), written by the
/// kit beside `laid.json` whatever the outcome.
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct InstallVerdict {
/// `PASS` | `FAIL`.
pub verdict: String,
#[serde(default)]
pub detail: String,
/// The kit's own receipt (commits, medium digest, the log gate's verdict).
#[serde(default)]
pub receipt: serde_json::Value,
/// **The estate's WireGuard peer map**, from the installer's door: every
/// box's tunnel PUBLIC key (made on the box), overlay and utility address.
#[serde(default)]
pub peers: Vec<WgPeer>,
/// **Each box's fleet key** (base64 Ed25519 public key, by role) as the box made it, when
/// the kit read it back; a role it names none for pins its own key on its first call to
/// the pull channel inside the pin window (G-D5).
#[serde(default)]
pub appliance_keys: BTreeMap<String, String>,
/// **The ticket key the estate's primary offered** (64 hex, its `ticket_pubkey`), when the
/// installer read it: an operator site's accounts' tickets are checked under it ([`site`]).
#[serde(default)]
pub ticket_key: String,
}
/// One box of an estate's WireGuard mesh, as monetize keeps it: public halves only.
#[derive(Clone, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(default)]
pub struct WgPeer {
pub name: String,
pub kind: String,
pub overlay: String,
pub utility: String,
pub endpoint: String,
pub public_key: String,
}
pub const VERDICT_FILE: &str = "install-verdict.json";
/// The custody files of the order's seeds: `seed.<kind>.toml` (`front`, and the sold
/// product's box kind: `gunnar`, `holger`).
pub const SEED_FILE_PREFIX: &str = "seed.";
/// The medium kinds a paired lay of `c`'s product needs: its lifecycle's front and app media.
/// A product whose lifecycle is not known yet (its guest not followed) names none here; its
/// order is refused by name at the start, before any money moves, when one is missing.
fn media_kinds_of(c: &EstateOrderConfig) -> Vec<String> {
match lifecycle::of(c) {
Ok(l) => {
let u = l.update_plan();
let mut v = vec![u.front_medium, u.app_medium];
v.retain(|k| !k.is_empty());
v.dedup();
v
}
Err(_) => Vec::new(),
}
}
/// Pure: **why `[estate_orders.media]` cannot lay a paired estate**, or `None` — a kind with
/// no release medium, or one that names no storage.
pub fn media_missing(c: &EstateOrderConfig) -> Option<String> {
let kinds = media_kinds_of(c);
let missing: Vec<&str> = kinds
.iter()
.map(String::as_str)
.filter(|k| {
c.media.get(*k).is_none_or(|u| u.trim().is_empty())
&& c.media_objects
.get(*k)
.is_none_or(|o| o.url.trim().is_empty() || o.sha256.len() != 64)
})
.collect();
(!missing.is_empty()).then(|| {
format!(
"[estate_orders] pairs the estate itself (`pairing = {:?}`) and neither [estate_orders.media] nor [estate_orders.media_objects] names a release medium for {}: a sold estate's machines are cloned from the release's seedless media (`cargo xtask estate release-image` uploads them once per release)",
c.pairing.trim(),
missing.join(" and ")
)
})
}
/// **Does every site name its media where the shop names none?** (2026-10-11: test2 sells
/// from DEV media, gunnar.rs from the release.) The shop's own complete media answer for
/// every site; else each declared site of the sold product must name its own
/// (`[estates.<e>.orders] media`/`media_objects`). Stub until GREEN.
pub fn media_missing_on_sites(
c: &EstateOrderConfig,
estates: &BTreeMap<String, crate::config::EstateConfig>,
) -> Option<String> {
let own = media_missing(c)?;
let product = c.product.trim();
let sites: Vec<(&String, &crate::config::EstateConfig)> = estates
.iter()
.filter(|(_, e)| e.product == product)
.collect();
// ★ T2-BOX (owner 2026-11-03: an EMPTY box): no site of the product yet lays nothing — every
// order is bought on a site. The site the operator adds later names its own media, and the
// box's loader judges it then (a site with none is refused below, by name).
if sites.is_empty() {
return None;
}
let lacking: Vec<String> = sites
.iter()
.filter_map(|(id, e)| {
let site = EstateOrderConfig {
media: e.orders.media.clone(),
media_objects: e.orders.media_objects.clone(),
pairing: c.pairing.clone(),
product: c.product.clone(),
lifecycle: c.lifecycle.clone(),
..EstateOrderConfig::default()
};
media_missing(&site).map(|_| id.to_string())
})
.collect();
(!lacking.is_empty()).then(|| format!("{own} — and the site(s) {} name no media of their own ([estates.<e>.orders] media / media_objects)", lacking.join(", ")))
}
/// Pure: **the sites that write their own zone, and with which writer** — every declared site
/// naming `[estates.<e>.orders] dns`, of ANY product the box lays (lane L13, 2026-10-18: a
/// Holger site on the box that sells Gunnar writes holger.rs with its own writer, never the
/// shop's gunnar.rs one).
pub fn site_dns_writers(
estates: &BTreeMap<String, crate::config::EstateConfig>,
) -> BTreeMap<String, String> {
estates
.iter()
.filter_map(|(id, e)| {
e.orders
.dns
.as_deref()
.map(str::trim)
.filter(|w| !w.is_empty())
.map(|w| (id.clone(), w.to_owned()))
})
.collect()
}
/// **The writer `c.dns` names, or a refusal that says why by name** (2026-10-11): a writer this
/// build does not link no longer stops the shop (the ONE box, linking none, fed `dns =
/// "cloudflare"`): it SELLS nothing for that zone and says so. Stub until GREEN.
pub fn writer_or_refused(
c: &EstateOrderConfig,
linked: &crate::registry::Linked,
custody: &Custody,
resolver: Arc<dyn domain::DomainCheck>,
guests: &BTreeMap<String, serde_json::Value>,
data_dir: &Path,
) -> Arc<dyn EstateDns> {
// A writer declared as a signed guest (`[dns_writers.<name>]`, the owner 2026-10-11): the
// provider is a plugin the server takes in, never linked.
let built = match guests.get(c.dns.trim()) {
// Held live (2026-10-17): a module written over this one is taken in by the watcher.
Some(block) => monetize_cloud_wasm::dns::HotDns::build(data_dir, c.dns.trim(), block)
.map(|g| g as Arc<dyn EstateDns>),
None => dns_writer(c, linked, custody, resolver),
};
// Every writer is held behind its zone (A32-3): a name outside it is refused by name here,
// before the writer (a guest, a linked provider) is asked; the guest's own refusal is the
// second line.
built
.map(|w| Arc::new(monetize_cloud::dns::Zoned(w)) as Arc<dyn EstateDns>)
.unwrap_or_else(|why| {
eprintln!("{}", writer_refused_line(&why));
Arc::new(RefusedDns(why))
})
}
/// **The operator's line for a refused writer**: the writer and the refusal's clause ids,
/// [`log_safe`] — the box's log redactor (nordisk-pairing `redact`) withholds a WHOLE line
/// that says "secret" or "token", and on 2026-10-12 it withheld exactly this refusal
/// (`[secret-dir] …`): the operator read only "this shop writes no DNS records".
pub fn writer_refused_line(why: &str) -> String {
format!("monetize-server: estate orders: ALERT estate-orders.dns-writer {} — nothing is sold where it writes", log_safe(why))
}
/// The words the box's log redactor withholds a whole line for (nordisk-pairing
/// `redact::class`, the `token` class), lower case.
pub const REDACTOR_WORDS: &[&str] = &["secret", "token", "bearer", "password"];
/// **A sentence the box's log redactor keeps** — each of [`REDACTOR_WORDS`], in any case, is
/// spelt with its second letter starred (`[secret-dir]` → `[s*cret-dir]`, `Bearer` →
/// `B*arer`). For OUR OWN sentences (a refusal's words and clause ids) only: a value is
/// never put in one, so nothing is unredacted by it; the redactor still withholds every
/// line that carries a credential's shape.
pub fn log_safe(s: &str) -> String {
let lower = s.to_ascii_lowercase();
let mut out: Vec<u8> = s.as_bytes().to_vec();
for w in REDACTOR_WORDS {
let mut from = 0;
while let Some(i) = lower[from..].find(w) {
out[from + i + 1] = b'*';
from += i + w.len();
}
}
String::from_utf8(out).unwrap_or_else(|_| s.to_string())
}
/// **The `[dns_writers.<name>]` blocks as the server loads them**: every block that names a
/// guest is given the deployment's secrets directory (`Config::secrets_dir`, where the feed
/// writes `secret.<name>`), exactly as a `[clouds.<name>]` guest is (`registry`, the cloud
/// factory) — a top-level key, so the block cannot name it. Without it the loader refused a
/// writer whose `http_auth` names its token ([secret-dir], the ONE box 2026-10-12), and a
/// writer that loaded would have read `<data_dir>/secrets`, where nothing is fed.
pub fn dns_guest_blocks(
guests: &BTreeMap<String, serde_json::Value>,
secrets_dir: &Path,
) -> BTreeMap<String, serde_json::Value> {
guests
.iter()
.map(|(name, block)| {
let mut b = block.clone();
if b.get("guest").is_some() {
if let Some(obj) = b.as_object_mut() {
obj.insert(
"secrets_dir".into(),
serde_json::Value::String(secrets_dir.display().to_string()),
);
}
}
(name.clone(), b)
})
.collect()
}
/// **A DNS writer that could not be built**: it writes nothing and says why, by name.
pub struct RefusedDns(pub String);
impl EstateDns for RefusedDns {
/// It writes into no provider's zone (RA1-5): the zone guard passes it.
fn zoneless(&self) -> bool {
true
}
fn write(&self, _records: &[DnsRecord]) -> Result<String, String> {
Err(self.0.clone())
}
fn remove(&self, _names: &[String]) -> Result<String, String> {
Err(self.0.clone())
}
fn listed(&self) -> Result<Vec<DnsRecord>, String> {
Ok(Vec::new())
}
fn writes(&self) -> bool {
false
}
}
/// Pure: **each machine's release medium**: the storage of its role's kind (`life`'s
/// [`lifecycle::Lifecycle::medium_kind`], `[estate_orders.media]`), cloned at the lay; a
/// kind with no storage laid from its OBJECT (`[estate_orders.media_objects]`, its url already
/// presigned for this lay when the shop holds the bucket's read key — [`presigned_objects`]); a
/// kind with neither is left to the provider's own template.
pub fn give_release_media_as(
plan: &mut monetize_cloud::lay::EstatePlan,
media: &BTreeMap<String, String>,
objects: &BTreeMap<String, monetize_cloud::lay::MediaObject>,
life: &lifecycle::Lifecycle,
) {
give_release_media_by(plan, media, objects, &|role| life.medium_kind(role))
}
fn give_release_media_by(
plan: &mut monetize_cloud::lay::EstatePlan,
media: &BTreeMap<String, String>,
objects: &BTreeMap<String, monetize_cloud::lay::MediaObject>,
kind_of: &dyn Fn(&str) -> String,
) {
for m in plan.machines.iter_mut() {
let kind = kind_of(&m.role);
let kind = kind.as_str();
if let Some(uuid) = media.get(kind).map(|u| u.trim()).filter(|u| !u.is_empty()) {
m.medium = uuid.to_string();
} else if let Some(o) = objects.get(kind).filter(|o| !o.url.trim().is_empty()) {
m.medium_object = Some(o.clone());
}
}
}
/// **A release template** (`[estate_orders.templates.<kind>]`): the provider's template id,
/// and the release ([`release_id`]) whose medium it was installed from.
#[derive(Clone, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ReleaseTemplate {
pub uuid: String,
pub for_release: String,
/// The template's storage tier, which a disk cloned from it is laid in. Unset:
/// [`TEMPLATE_TIER`] (UpCloud templatizes only `maxiops`).
#[serde(default, skip_serializing_if = "String::is_empty")]
pub tier: String,
}
/// The tier of a template that names none: UpCloud makes templates only of `maxiops`
/// storage ("Only maxiops storages can be templatized").
pub const TEMPLATE_TIER: &str = "maxiops";
impl ReleaseTemplate {
/// The tier a disk cloned from this template is laid in.
pub fn tier(&self) -> &str {
if self.tier.trim().is_empty() {
TEMPLATE_TIER
} else {
self.tier.trim()
}
}
}
/// Pure: **the template `kind`'s machines are laid from**, when there is one made for the
/// release sold now — the one rule [`give_templates_as`] and [`plan_for_as`]'s disk tiers share.
pub fn template_of<'a>(c: &'a EstateOrderConfig, kind: &str) -> Option<&'a ReleaseTemplate> {
let t = c
.templates
.get(kind)
.filter(|t| !t.uuid.trim().is_empty())?;
let sold = release_id(c, kind);
(!sold.is_empty() && t.for_release == sold).then_some(t)
}
/// Pure: **each machine laid from its kind's template** — only a template made for the release
/// sold now (`release_id(c, kind)`); the medium stays named (a template that is gone at the
/// provider is the lay's refusal, never a silent install). Returns, per role, what was given
/// or why not (said in the order's log). Each role's medium kind as `life` says.
pub fn give_templates_as(
plan: &mut monetize_cloud::lay::EstatePlan,
c: &EstateOrderConfig,
life: &lifecycle::Lifecycle,
) -> Vec<String> {
let mut said = Vec::new();
for m in plan.machines.iter_mut() {
let kind = life.medium_kind(&m.role);
let kind = kind.as_str();
let Some(t) = c.templates.get(kind).filter(|t| !t.uuid.trim().is_empty()) else {
continue;
};
let sold = release_id(c, kind);
if sold.is_empty() || t.for_release != sold {
said.push(format!("{}: the {kind} template {} was made for another release ({}) than the one sold now ({}): laid from the medium", m.role, t.uuid, t.for_release, if sold.is_empty() { "none" } else { &sold }));
continue;
}
m.template = t.uuid.trim().to_string();
said.push(format!(
"{}: laid from the {kind} template {} (release {})",
m.role,
m.template,
&sold[..sold.len().min(12)]
));
}
said
}
/// **The release's identity** for a medium kind: its storage id (`[estate_orders.media]`) or
/// its object's sha256 (`[estate_orders.media_objects]`); empty when none is named. A box laid
/// or upgraded from the release's media runs `release_id(cfg, "gunnar")`.
pub fn release_id(c: &EstateOrderConfig, kind: &str) -> String {
match c
.media
.get(kind)
.map(|u| u.trim())
.filter(|u| !u.is_empty())
{
Some(id) => id.to_string(),
None => c
.media_objects
.get(kind)
.map(|o| o.sha256.clone())
.unwrap_or_default(),
}
}
/// **The bucket's READ key** the shop presigns release media with: `media.objsto` in the
/// custody, `{"access_key_id": …, "secret_access_key": …, "region": …}`. Never printed.
#[derive(serde::Deserialize)]
pub struct ObjstoKey {
pub access_key_id: String,
pub secret_access_key: String,
#[serde(default = "default_objsto_region")]
pub region: String,
}
fn default_objsto_region() -> String {
"auto".into()
}
/// The custody credential the bucket's READ pair is sealed as.
pub const MEDIA_OBJSTO: &str = "media.objsto";
/// The fed secret (`<secrets_dir>/secret.media_objsto`) it arrives as from the kit.
pub const MEDIA_OBJSTO_FED: &str = "secret.media_objsto";
/// How long a presigned release-medium URL lives: the provider's import starts within it.
pub const MEDIA_PRESIGN_SECS: u64 = 3_600;
/// **The release objects for this lay**: each url presigned for [`MEDIA_PRESIGN_SECS`] when
/// the custody holds `media.objsto`, else as configured (a public object).
#[allow(unused_variables)]
pub fn presigned_objects(
c: &EstateOrderConfig,
custody: Option<&Custody>,
fed: Option<&Path>,
now_secs: u64,
) -> Result<BTreeMap<String, monetize_cloud::lay::MediaObject>, String> {
let key: Option<ObjstoKey> = match custody.map(|c| c.unseal_credential(MEDIA_OBJSTO)) {
Some(Ok(bytes)) => Some(serde_json::from_slice(&bytes).map_err(|e| format!("the custody's {MEDIA_OBJSTO} is not {{access_key_id, secret_access_key, region}}: {e}"))?),
// **Fed, not yet sealed** (F-O2): the kit feeds a selling front the bucket's READ pair as
// `secret.media_objsto`; the first lay seals it into the custody, where it stays.
_ => match (custody, fed.and_then(|f| std::fs::read(f).ok()).filter(|b| !b.is_empty())) {
(Some(cu), Some(bytes)) => {
let k: ObjstoKey = serde_json::from_slice(&bytes).map_err(|e| format!("the fed {MEDIA_OBJSTO} is not {{access_key_id, secret_access_key, region}}: {e}"))?;
cu.seal_credential(MEDIA_OBJSTO, &bytes).map_err(|e| format!("sealing {MEDIA_OBJSTO}: {e}"))?;
Some(k)
}
_ => None,
},
};
c.media_objects
.iter()
.map(|(kind, o)| {
let url = match &key {
#[cfg(any(test, feature = "custody-ring"))]
Some(k) => presign_get(&o.url, k, now_secs, MEDIA_PRESIGN_SECS)?,
#[cfg(not(any(test, feature = "custody-ring")))]
Some(_) => {
return Err("this build cannot presign (custody-ring is off)".to_string())
}
None => o.url.clone(),
};
Ok((
kind.clone(),
monetize_cloud::lay::MediaObject { url, ..o.clone() },
))
})
.collect()
}
#[cfg(any(test, feature = "custody-ring"))]
/// Pure: **an S3 GET presigned with AWS SigV4** (query auth, `UNSIGNED-PAYLOAD`) for a
/// path-style object `url` (`https://<endpoint>/<bucket>/<key>`), valid `expires` seconds from
/// `now_secs`.
pub fn presign_get(
url: &str,
k: &ObjstoKey,
now_secs: u64,
expires: u64,
) -> Result<String, String> {
use ring::{digest, hmac};
let (scheme, rest) = url
.split_once("://")
.ok_or_else(|| format!("{}: not a URL", url.split('?').next().unwrap_or("")))?;
let (host, path) = rest
.split_once('/')
.map(|(h, p)| (h, format!("/{p}")))
.unwrap_or((rest, "/".into()));
let path = path.split('?').next().unwrap_or("/").to_string();
let amz_date = amz_date_of(now_secs);
let date = &amz_date[..8];
let scope = format!("{date}/{}/s3/aws4_request", k.region);
let enc = |s: &str| {
s.bytes()
.map(|b| {
if b.is_ascii_alphanumeric() || b"-_.~".contains(&b) {
(b as char).to_string()
} else {
format!("%{b:02X}")
}
})
.collect::<String>()
};
let canonical_path = path.split('/').map(enc).collect::<Vec<_>>().join("/");
let credential = format!("{}/{scope}", k.access_key_id);
let query = format!(
"X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential={}&X-Amz-Date={amz_date}&X-Amz-Expires={expires}&X-Amz-SignedHeaders=host",
enc(&credential)
);
let canonical =
format!("GET\n{canonical_path}\n{query}\nhost:{host}\n\nhost\nUNSIGNED-PAYLOAD");
let hex = |b: &[u8]| b.iter().map(|x| format!("{x:02x}")).collect::<String>();
let to_sign = format!(
"AWS4-HMAC-SHA256\n{amz_date}\n{scope}\n{}",
hex(digest::digest(&digest::SHA256, canonical.as_bytes()).as_ref())
);
let mac = |key: &[u8], m: &str| {
hmac::sign(&hmac::Key::new(hmac::HMAC_SHA256, key), m.as_bytes())
.as_ref()
.to_vec()
};
let kd = mac(
&mac(
&mac(
&mac(format!("AWS4{}", k.secret_access_key).as_bytes(), date),
&k.region,
),
"s3",
),
"aws4_request",
);
let sig = hex(&mac(&kd, &to_sign));
Ok(format!(
"{scheme}://{host}{canonical_path}?{query}&X-Amz-Signature={sig}"
))
}
/// `YYYYMMDDTHHMMSSZ` of a Unix time, UTC (pure).
pub fn amz_date_of(secs: u64) -> String {
let days = (secs / 86_400) as i64;
let rem = secs % 86_400;
// Civil from days (Howard Hinnant).
let z = days + 719_468;
let era = z.div_euclid(146_097);
let doe = z - era * 146_097;
let yoe = (doe - doe / 1_460 + doe / 36_524 - doe / 146_096) / 365;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let d = doy - (153 * mp + 2) / 5 + 1;
let m = if mp < 10 { mp + 3 } else { mp - 9 };
let y = yoe + era * 400 + i64::from(m <= 2);
format!(
"{y:04}{m:02}{d:02}T{:02}{:02}{:02}Z",
rem / 3_600,
rem % 3_600 / 60,
rem % 60
)
}
/// Pure: **each machine's user_data from the sealed seeds** — `seed.<kind>.toml` by its
/// role's kind as `life` says; a machine whose kind has no seed gets none.
pub fn give_seeds_as(
plan: &mut monetize_cloud::lay::EstatePlan,
material: &[(String, String)],
life: &lifecycle::Lifecycle,
) {
give_seeds_by(plan, material, &|role| life.medium_kind(role))
}
fn give_seeds_by(
plan: &mut monetize_cloud::lay::EstatePlan,
material: &[(String, String)],
kind_of: &dyn Fn(&str) -> String,
) {
for m in plan.machines.iter_mut() {
let want = format!("{SEED_FILE_PREFIX}{}.toml", kind_of(&m.role));
if let Some((_, text)) = material.iter().find(|(n, _)| *n == want) {
m.user_data = monetize_cloud::lay::UserData(text.clone());
}
}
}
/// **The `installing` step's seam**: monetize invokes the kit; it does not link
/// it (PRIVATE-GUNNAR.md §2).
/// **A site's first-admin door, as its public front answers** (owner 2026-10-22): asked before
/// the operator sends a site's first sign-in link again ([`first_link`]).
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum FirstDoor {
/// Unspent, and the custody's invite opens it: the link can be sent again.
Open,
/// The first admin has signed in: the door is spent.
Spent,
/// Unspent, but the custody's invite no longer opens it (its lifetime is over): why.
Closed(String),
}
pub trait Installer: Send + Sync {
/// Install gunnar on the laid servers. `Ok` is the verdict the installer
/// wrote — a `FAIL` included; `Err` is an installer that could not run or
/// wrote nothing.
fn install(&self, req: &InstallRequest) -> Result<InstallVerdict, String>;
/// **The order's pairing material, minted BEFORE the lay** (the shop pairs the estate
/// itself, 2026-10-08): the files the custody seals beside the order's seeds — the
/// installer CA and its keys, the link keys, and each machine KIND's seed
/// (`seed.front.toml`, `seed.gunnar.toml`, [`SEED_FILE_PREFIX`]) that the lay hands that
/// machine as its `user_data`. Empty (the default): the installer brings its own media.
/// **The sale check, over the PUBLIC front** (S4, stinky 2026-10-10): before the order is
/// captured — its term started and the buyer mailed the first-login link — the installer
/// proves the sold instance serves what the buyer is about to be sent to, at
/// `https://<host>` (the name the customer reaches; never the estate's WireGuard or
/// utility net): (1) the login page answers, (2) the first-login door answers the invite
/// the ready mail will carry, (3) exactly ONE `<account>/welcome` repository exists. `Ok`
/// is the words of what was proven. `Err` keeps the order at `paired`, nothing captured,
/// until its deadline gives it back like any estate that never answered. The default:
/// this installer proves nothing, so nothing it installs is captured.
fn sale_check(&self, at: &SaleCheckAt<'_>) -> Result<String, String> {
let _ = at;
Err(
"this shop's installer proves no sale check (S4): the public front was not looked at"
.into(),
)
}
/// [`Installer::mint`] for an estate of the product `facts` describe (lane L18: a site of
/// another product on a box that sells one is minted its own product's material).
/// Default: [`Installer::mint`].
fn mint_for(
&self,
facts: &crate::facts::Facts,
company: &str,
host: &str,
) -> Result<Vec<(String, String)>, String> {
let _ = facts;
self.mint(company, host)
}
fn mint(&self, company: &str, host: &str) -> Result<Vec<(String, String)>, String> {
let _ = (company, host);
Ok(Vec::new())
}
/// **Is there an install path at all?** (the Sells rule, 2026-10-08): `false` only for the
/// shop that names none — it SELLS nothing (`EstateOrder.Sells` false, `Start` refused
/// before any money moves).
fn installs(&self) -> bool {
true
}
/// **The site's first-admin door at its public front** (owner 2026-10-22), as the sale
/// check finds it: spent, open to the custody's invite, or closed to it. The default: this
/// installer cannot ask.
fn first_door(&self, at: &SaleCheckAt<'_>) -> Result<FirstDoor, String> {
let _ = at;
Err("this shop's installer cannot ask a site's first-admin door".into())
}
/// **Every laid box's pair status, asked again over the installer link** (F5, 2026-10-20:
/// test3-holger-rs was FAILED by its install deadline, then a reboot of its front paired
/// every box, and the ONE box still read it as failed). Only asked, never changed: no box
/// is fed, closed or re-laid. `req` is the order's install request (its laid file, the
/// hand-off with the installer's material, its run directory). The default: this installer
/// cannot ask, so the operator's retry lays the estate fresh.
fn pair_status(&self, _req: &InstallRequest) -> Result<PairCheck, String> {
Err("this installer cannot ask the boxes their pair status again".into())
}
/// ★ **The fleet seam fed again to a PAIRED estate that has not called home** (HRS-DIAG
/// 2026-11-03: holger.rs paired while this box's sale feed named no `fleet_at`, so its main
/// and twin, laid with no public IPv4 and no DNS, could never report). Over each box's
/// installer link, with the custody's material (`req.handoff`): every box re-fed what it was
/// fed with the seam this box's sale feed names NOW added, nothing else changed, nothing
/// laid. `Ok(None)`: nothing to feed (its boxes already hold that seam); `Ok(Some(words))`:
/// fed again. `instance` is the estate the ONE box knows the boxes by (the run's fleet name:
/// an operator site's label, else its company), never another. The default: this installer
/// feeds nothing again.
fn refeed_seam(
&self,
_req: &InstallRequest,
_instance: &str,
) -> Result<Option<String>, String> {
Ok(None)
}
/// **`GET /live/running` on `role`'s log door, over the installer link, with the custody's
/// installer material** (F9, 2026-10-21: test3-holger-rs's front never took its material, so
/// its primary keeps none and the front cannot reach its twin's live door; the ONE box takes
/// that box's update step from the custody's copy, [`update::custody_step`]). `req` is the
/// order's install request as for [`Installer::pair_status`]. The default: this installer
/// reaches no live door (`[live-unsupported]`: the box is then upgraded from its keep medium).
fn live_running(
&self,
_req: &InstallRequest,
role: &str,
) -> Result<update::LiveRunning, String> {
Err(format!(
"[live-unsupported] this installer reaches no live door: the {role}'s is not asked"
))
}
/// **`POST /live/stage` to `role`'s log door** over the installer link, with the custody's
/// installer material (F9). The default: refused.
fn live_stage(
&self,
_req: &InstallRequest,
role: &str,
_stage: &update::LiveStage,
) -> Result<String, String> {
Err(format!(
"[live-unsupported] this installer stages nothing: the {role} is not staged"
))
}
}
/// **What a re-check heard** ([`Installer::pair_status`]): each laid box by its role, its pair
/// status line as the box said it (` pair twin: … · PAIRED`), or why it did not answer; and
/// the ticket key the primary offers (empty: none).
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct PairCheck {
pub boxes: Vec<(String, Result<String, String>)>,
pub ticket_key: String,
}
/// No installer configured: refused by name.
pub struct NoInstaller;
impl Installer for NoInstaller {
fn install(&self, _req: &InstallRequest) -> Result<InstallVerdict, String> {
Err("[estate_orders] installer is empty: nothing here can install an estate".into())
}
fn installs(&self) -> bool {
false
}
}
/// **The DNS writer's credential, as the kit FEEDS it** (S2): `<secrets_dir>/secret.dns_<name>`,
/// `{"zone_id": …, "api_token": …}`; sealed into the custody as `dns.<name>` (replacing an older
/// one) and the fed file removed. `Ok(false)`: nothing fed (a `file:`/`manual`/empty writer has
/// no credential). A fed file that is not a credential is refused by name, never its value.
pub fn seal_fed_dns(custody: &Custody, secrets_dir: &Path, name: &str) -> Result<bool, String> {
if name.is_empty() || name == MANUAL_DNS || name.starts_with("file:") {
return Ok(false);
}
let fed = secrets_dir.join(format!("secret.dns_{name}"));
let Ok(bytes) = std::fs::read(&fed) else {
return Ok(false);
};
if bytes.iter().all(u8::is_ascii_whitespace) {
return Ok(false);
}
monetize_cloud::dns::DnsCredential::from_json(&bytes)
.map_err(|e| format!("{}: {e}", fed.display()))?;
custody
.seal_credential(&format!("dns.{name}"), &bytes)
.map_err(|e| format!("sealing dns.{name}: {e}"))?;
let _ = std::fs::remove_file(&fed);
Ok(true)
}
/// **Where [`Installer::sale_check`] looks** (S4): the sold instance's PUBLIC name
/// (`https://<host>`, what the buyer reaches), the account whose `<account>/welcome` must be
/// its one welcome repository, the laid front (its public address, for a world with no DNS
/// for `host`: a mock), the run's directory, and the two secrets the proof needs while the
/// shop still holds them: the console's break-glass bearer (it lists the repositories while
/// no admin passkey exists) and the first-login invite the ready mail will carry. Never
/// logged, never written: `{:?}` withholds them.
pub struct SaleCheckAt<'a> {
/// What the sold instance's product says it is ([`InstallRequest::facts`]): the sale proof
/// the check runs is its own.
pub facts: &'a crate::facts::Facts,
pub company: &'a str,
pub host: &'a str,
pub account: &'a str,
pub front: Option<&'a monetize_cloud::lay::LaidMachine>,
pub run_dir: &'a Path,
pub console_token: &'a str,
pub first_login: &'a str,
}
impl std::fmt::Debug for SaleCheckAt<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "SaleCheckAt {{ company: {:?}, host: {:?}, account: {:?}, run_dir: {:?}, console_token: <withheld>, first_login: <withheld> }}", self.company, self.host, self.account, self.run_dir)
}
}
/// How often a sale check that did not pass is asked again (S4): each one asks the sold
/// front for a passkey ceremony, which the front rate-limits per client.
pub const SALE_CHECK_EVERY_MS: u64 = 30 * 1000;
/// The words a missed sale check is parked with (and asked again under).
const SALE_CHECK_MISSED: &str = "the sale check over";
/// How often a buyer may ask for a new sign-in link.
pub const SIGN_IN_AGAIN_EVERY_MS: u64 = 10 * 60 * 1000;
/// How long a queued resend waits for the estate's main to pull it.
pub const SIGN_IN_AGAIN_ORDER_TTL_MS: u64 = 24 * 60 * 60 * 1000;
/// **`EstateOrder.SendSignInAgain`** (S10, owner 2026-10-11 05:53Z): a delivered estate's buyer
/// — the caller proved the order's account — asks for a NEW first-login link. It travels as a
/// SIGNED ORDER ([`kind::SIGN_IN_AGAIN`]) queued for the estate's main: whose account, the
/// order's address, the console host, how long — never a secret. The main pulls it, verifies
/// it under its monetize key, mints the fresh invite itself (only while the account holds no
/// passkey) and mails the link itself: the shop never holds a resend's login secret. (The
/// FIRST link is the shop's, minted at install: OM-21 decision A.) At most once per
/// [`SIGN_IN_AGAIN_EVERY_MS`], a newer ask superseding an unacked older one; never for an
/// order not delivered, nor for an estate the fleet does not hold as sold (no main pulls).
///
/// [`kind::SIGN_IN_AGAIN`]: monetize_embed::fleet::kind::SIGN_IN_AGAIN
pub fn send_sign_in_again(state: &AppState, company: &str) -> Result<String, StartRefusal> {
let a = ask_sign_in_again(state, company, None)?;
Ok(format!("a new sign-in link is on its way: {} mints it and mails it to the order's address; it works once, for {}", a.host, a.ttl))
}
/// **A queued `sign_in_again`** ([`ask_sign_in_again`]): the signed order's seq, the host and
/// role it is for, and the link's lifetime in words.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SignInAsked {
pub seq: u64,
pub host: String,
pub role: String,
pub ttl: String,
}
/// [`send_sign_in_again`]'s order, queued: what was asked, never what the main will answer
/// (HOL-MAIL: `site first-link` follows the answer itself). `to` (HOL-FIRST-ADMIN, `site
/// first-link --to`): the address the main mails instead of the order's; it is the run's
/// recorded address from the moment the order is queued (a refused ask changes nothing).
pub fn ask_sign_in_again(
state: &AppState,
company: &str,
to: Option<&str>,
) -> Result<SignInAsked, StartRefusal> {
use monetize_embed::fleet::{kind, SignInAgain};
let eo = &state.estates;
let cfg = eo.cfg.as_ref().ok_or_else(|| {
StartRefusal::NotSold(
"this monetize sells no private estates ([estate_orders] is absent)".into(),
)
})?;
let run = eo
.get(company)
.filter(|r| !phase::is_gone(&r.phase))
.ok_or_else(|| StartRefusal::NotFound(format!("no order for {company:?}")))?;
// FIRST-LINK-1: the run's own name, and the name its boxes pull under — an operator
// site's is its LABEL (`gunnar-rs`), a sold estate's its company.
let company = run.company.as_str();
let fleet = run.fleet_name();
let host = terms_of(state, cfg, &run)
.map(|c| c.host_of(company))
.unwrap_or_else(|_| cfg.host_of(company));
if !phase::is_term(&run.phase) {
return Err(StartRefusal::NotNow(format!(
"{host} is not delivered yet: its ready mail will carry the first sign-in link"
)));
}
if run.account.is_empty() {
return Err(StartRefusal::NotNow(format!(
"{host}'s order names no account to sign in as"
)));
}
let now = state.now();
if run.sign_in_again_at_ms > 0
&& now
< run
.sign_in_again_at_ms
.saturating_add(SIGN_IN_AGAIN_EVERY_MS)
{
let left = (run.sign_in_again_at_ms + SIGN_IN_AGAIN_EVERY_MS - now).div_ceil(60_000);
return Err(StartRefusal::NotNow(format!(
"a new sign-in link was asked a moment ago; ask again in {left} minute(s)"
)));
}
// The machine that mints and mails the link: the product's primary (Gunnar's main).
let role = terms_of(state, cfg, &run)
.and_then(|c| lifecycle::of(&c))
.or_else(|_| lifecycle::of(cfg))
.map(|l| l.primary_role())
.map_err(StartRefusal::NotSold)?;
let not_pulled = |why: String| {
StartRefusal::NotNow(format!(
"{host}'s {role} cannot pull a new sign-in link: {why}"
))
};
let product = crate::fleet::run_product(state, &run).map_err(not_pulled)?;
let product = product.as_str();
let g = state
.fleet
.guardian()
.ok_or_else(|| not_pulled("the fleet store is not open".into()))?;
if g.store.product_of(fleet).as_deref() != Some(product) {
return Err(not_pulled(format!(
"the fleet does not hold {fleet} as a sold estate"
)));
}
let ttl_secs = cfg.first_login_ttl();
let to = to
.map(str::trim)
.filter(|t| !t.is_empty())
.map(str::to_owned);
let body = SignInAgain {
account: run.account.clone(),
to: to.clone().unwrap_or_else(|| run.email.clone()),
host: host.clone(),
ttl_secs,
};
let body = serde_json::to_string(&body).map_err(|e| not_pulled(e.to_string()))?;
let _pen = eo.pen_for_a_call(cfg, company)?;
let order = monetize_guardian::Guardian::order(
product,
fleet,
&role,
kind::SIGN_IN_AGAIN,
body,
now,
SIGN_IN_AGAIN_ORDER_TTL_MS,
);
let queued = g
.queue_order(order, |o| o.kind == kind::SIGN_IN_AGAIN && o.role == role)
.map_err(|e| not_pulled(e.to_string()))?;
let ttl = ttl_words(ttl_secs);
if let Some(mut r) = eo.get(company) {
r.sign_in_again_at_ms = now.max(1);
let p = r.phase.clone();
if let Some(to) = to.filter(|t| *t != r.email) {
r.email = to;
r.history.push(Transition {
phase: p.clone(),
at_ms: now,
note: format!(
"the first admin's address was set by the operator to {}",
first_link::masked(&r.email)
),
});
}
r.history.push(Transition {
phase: p,
at_ms: now,
note: format!("a new sign-in link for {} was asked: signed order {} ({}) queued for {fleet}'s {role} to pull — the estate mints it and mails it to the order's address", run.account, queued.seq, kind::SIGN_IN_AGAIN),
});
kept(state, &r, "a new sign-in link was asked");
}
Ok(SignInAsked {
seq: queued.seq,
host,
role,
ttl,
})
}
/// The words a shop with no DNS writer refuses an order with (S2, trace-private 2026-10-09).
pub const NO_DNS_WRITER: &str = "this shop writes no DNS records for what it sells ([estate_orders] dns is empty): a sold host would never resolve";
/// The words a shop with no install path refuses an order with (the Sells rule), the product
/// called as `w` says.
pub fn no_install_path(w: &lifecycle::ProductWords) -> String {
format!("this shop has no way to install a private {} yet ([estate_orders] names no pairing, installer or install worker)", w.title)
}
// ── the run's pen ───────────────────────────────────────────────────────────
/// How long a page's call waits for a run's pen before it is refused as
/// busy ([`StartRefusal::Busy`]). The driver holds it for one step: a
/// vendor's answer, a mail — minutes only while servers are resized or
/// stopped, and then the buyer is told to ask again rather than kept waiting.
pub const PEN_WAIT: Duration = Duration::from_secs(60);
/// **One writer per run at a time** (2026-10-06). The driver read the run,
/// took its step (provider and payment calls) and wrote the WHOLE run back;
/// a `ChangePlan` written in between was gone from the run though the buyer
/// had been answered (MEASURED: the wire test, red 1 run in 3), and the other
/// way a change's write put `grace` back to `captured`. A renewal written
/// over is a page the buyer can pay that nothing captures. And two saves at
/// once share one temp file. So every write holds the run's pen from its read
/// to its write: the driver per step, `Start`, `Renew` and `ChangePlan` for
/// their whole read-modify-write. [`EstateOrders::save`] is only ever called
/// holding it — the saves INSIDE a step (a give-back's progress, the "ready"
/// mail, a resize's progress) are under the driver's pen and take nothing.
///
/// Reads (`Status`, `List`, [`EstateOrders::get`]) never take it: a pen held
/// through a 35-minute install keeps no page waiting.
#[derive(Default)]
struct Pens {
book: Mutex<PenBook>,
freed: Condvar,
}
#[derive(Default)]
struct PenBook {
/// The thread that holds each run's pen.
held: BTreeMap<String, ThreadId>,
/// How many writers wait for each.
waiting: BTreeMap<String, usize>,
}
/// **A run's pen, held**: dropped (or unwound past), the next writer takes it.
pub(crate) struct Pen<'a> {
pens: &'a Pens,
company: String,
/// Taken again on the thread that holds it: nothing to give back.
nested: bool,
}
impl Drop for Pen<'_> {
fn drop(&mut self) {
if !self.nested {
self.pens
.book
.lock()
.unwrap_or_else(|e| e.into_inner())
.held
.remove(&self.company);
self.pens.freed.notify_all();
}
}
}
/// **A run's pen, lent** ([`Pens::lend`]): dropped, the pen is this thread's again.
pub(crate) struct LentPen<'a> {
pens: &'a Pens,
company: String,
lent: bool,
}
impl Drop for LentPen<'_> {
fn drop(&mut self) {
if self.lent {
// Taken back for the guard that lent it: that guard gives it up, not this one.
std::mem::forget(self.pens.take(&self.company, None));
}
}
}
impl Pens {
/// Take `company`'s pen, waiting at most `wait` (`None`: as long as it takes).
fn take(&self, company: &str, wait: Option<Duration>) -> Option<Pen<'_>> {
let me = std::thread::current().id();
let mut book = self.book.lock().unwrap_or_else(|e| e.into_inner());
if book.held.get(company) == Some(&me) {
// A write nested in another on the same thread would be written
// over by the outer one: a bug, said loudly in a debug build —
// and never a driver deadlocked on itself in production.
if cfg!(debug_assertions) {
panic!("the pen of {company} is taken again on the thread that holds it");
}
return Some(Pen {
pens: self,
company: company.into(),
nested: true,
});
}
let give_up = wait.map(|w| Instant::now() + w);
*book.waiting.entry(company.into()).or_default() += 1;
while book.held.contains_key(company) {
book = match give_up {
None => self.freed.wait(book).unwrap_or_else(|e| e.into_inner()),
Some(at) => {
let left = at.saturating_duration_since(Instant::now());
if left.is_zero() {
break;
}
self.freed
.wait_timeout(book, left)
.unwrap_or_else(|e| e.into_inner())
.0
}
};
}
if let Some(n) = book.waiting.get_mut(company) {
*n -= 1;
if *n == 0 {
book.waiting.remove(company);
}
}
if book.held.contains_key(company) {
return None;
}
book.held.insert(company.into(), me);
Some(Pen {
pens: self,
company: company.into(),
nested: false,
})
}
/// **Lend `company`'s pen while this thread waits on something slow** (F2: the installer
/// runs for up to its budget, and an operator's Abandon or the install deadline must reach
/// the run meanwhile). Held by this thread, it is given up now and taken back, waited for
/// as long as it takes, when the loan is dropped; not held here, the loan does nothing.
fn lend(&self, company: &str) -> LentPen<'_> {
let me = std::thread::current().id();
let mut book = self.book.lock().unwrap_or_else(|e| e.into_inner());
let lent = book.held.get(company) == Some(&me);
if lent {
book.held.remove(company);
self.freed.notify_all();
}
LentPen {
pens: self,
company: company.into(),
lent,
}
}
/// This thread holds `company`'s pen.
fn held_here(&self, company: &str) -> bool {
self.book
.lock()
.unwrap_or_else(|e| e.into_inner())
.held
.get(company)
== Some(&std::thread::current().id())
}
}
// ── the queue ───────────────────────────────────────────────────────────────
/// Each site's own zone writer, by site, with the writer word it was built from.
pub type SiteDns = BTreeMap<String, (String, Arc<dyn EstateDns>)>;
/// **Every private-estate order this server holds**, and what drives them.
pub struct EstateOrders {
pub cfg: Option<EstateOrderConfig>,
/// `<data_dir>/estate-orders`; `None`: nothing is written (a server that sells none).
dir: Option<PathBuf>,
runs: Mutex<BTreeMap<String, EstateRun>>,
/// One writer per run ([`Pens`]).
pens: Pens,
driving: Mutex<BTreeSet<String>>,
/// **The front keep boots' jera lane** ([`update::look`]): one queued job per estate whose
/// update waits for the shop to keep-boot its front; started on the first, on the tick's runtime.
pub front_keep_lane: std::sync::OnceLock<Arc<jera::jobs::chain::ChainQueue>>,
/// The keep upgrades running in this shop, by company: one at a time per estate
/// ([`sold::keep_boot_box`]). Per shop, never process-wide: two shops in one process
/// (the tests) never refuse each other's.
pub upgrading: Mutex<BTreeSet<String>>,
/// **The release-clone sweep's lane** ([`maybe_sweep`]): one jera ChainQueue, started on
/// the first sweep on the tick's runtime (owner rule: background work is a jera job).
pub sweep_lane: std::sync::OnceLock<Arc<jera::jobs::chain::ChainQueue>>,
/// **The DEV-NARRATE receiver's lane** ([`narrate::look`], lane N2): one queued listening
/// window at a time, started on the first narrated estate on the tick's runtime.
pub narrate_lane: std::sync::OnceLock<Arc<jera::jobs::chain::ChainQueue>>,
/// **The New-estate slot's late orders** ([`new_estate::order_when_taken`], OVW-5): one jera
/// ChainQueue, started on the first site the box took late, on the call's runtime.
pub new_estate_lane: std::sync::OnceLock<Arc<jera::jobs::chain::ChainQueue>>,
/// **Each estate's drive lane** ([`tick`], B6): one jera chain per estate, so an estate's
/// drives run one at a time and two estates never wait for each other.
pub drive_lanes: Mutex<BTreeMap<String, Arc<jera::jobs::chain::ChainQueue>>>,
/// When each delivered estate's term was last looked at (in memory).
term_seen: Mutex<BTreeMap<String, u64>>,
/// Each run whose site no longer resolves → the reason last said ([`run_terms_said`]).
terms_said: Mutex<BTreeMap<String, String>>,
/// Each run's last `Instance` row as booked ([`book`]), in memory.
booked: Mutex<BTreeMap<String, monetize::Step>>,
pub installer: Arc<dyn Installer>,
pub dns: Arc<dyn EstateDns>,
/// Each site's own zone writer (`[estates.<e>.orders] dns`), over [`EstateOrders::dns`],
/// with the writer word it was built from. Behind a lock: a site the config watcher takes
/// in LIVE gets its writer then ([`EstateOrders::follow_site_dns`]) — built only at start,
/// a site fed after it fell back to the shop's writer (none) and its Sites row read "this
/// box's DNS token does not write its zone" (test3.gunnar.rs, 2026-10-14).
pub site_dns: std::sync::RwLock<SiteDns>,
/// How a customer's own domain is read (`waiting_dns`).
pub resolver: Arc<dyn DomainCheck>,
pub custody: Option<Custody>,
/// Where the twin's lag and load come from (its autoscale).
pub twin_health: Arc<dyn twin::TwinHealth>,
/// **`Sells`' last answer and when (unix ms)** — kept [`ladder::SELLS_FRESH_MS`]
/// ([`sells_now`]), so a console asking every minute never waits on the
/// provider's price list twice in a row, and an answer that came after a
/// console stopped waiting is there for its next ask.
pub sells_seen: Mutex<Option<(bool, u64)>>,
/// A refresh of `sells_seen` is running in the background (A32).
pub sells_refreshing: std::sync::atomic::AtomicBool,
/// **The new-estate ladder as last priced, and when** (A32): the sign-up
/// page reads it at once while a lay holds the cloud guest for minutes.
/// The new-estate ladder last priced, and when, per CALLING site (`""`: no site) — each
/// site's page reads its own terms (private buy flow, 2026-10-12).
pub ladder_seen: Mutex<BTreeMap<String, (ladder::Ladder, u64)>>,
/// The sites whose kept ladder is being priced again now.
pub ladder_refreshing: Mutex<BTreeSet<String>>,
/// **Tests only**: keep a sold estate's secrets after its hand-off. The
/// twin's health reading goes through the estate's door with its pairing
/// material until the estate issues a read-only health token (round
/// 5.3.1); production FORGETS them (2026-10-08) and nothing sets this.
pub keep_secrets_for_health_reads: std::sync::atomic::AtomicBool,
/// **One admission at a time** (the UpCloud mockup run 4, 2026-10-08: two paid
/// orders driven at once both read the other as not yet laying and both were laid
/// past `max_estates` 1): held from the queue's look until the admitted order reads
/// LAYING in [`Self::runs`].
pub admitting: Mutex<()>,
/// When the release-clone sweep last ran ([`maybe_sweep`]); 0: not yet.
pub swept_at: Mutex<u64>,
/// Where the kit's fed READ pair of the release bucket lands ([`MEDIA_OBJSTO_FED`]).
pub media_key_fed: Option<PathBuf>,
/// The host's secrets directory the sold estates' relay is read from ([`relay_feed`]).
pub relay_fed: Option<PathBuf>,
}
impl Default for EstateOrders {
fn default() -> Self {
EstateOrders {
cfg: None,
dir: None,
runs: Mutex::default(),
pens: Pens::default(),
driving: Mutex::default(),
front_keep_lane: Default::default(),
upgrading: Mutex::default(),
sweep_lane: Default::default(),
narrate_lane: Default::default(),
new_estate_lane: Default::default(),
drive_lanes: Mutex::default(),
term_seen: Mutex::default(),
terms_said: Mutex::default(),
booked: Mutex::default(),
sells_seen: Mutex::default(),
sells_refreshing: Default::default(),
ladder_seen: Mutex::default(),
ladder_refreshing: Default::default(),
keep_secrets_for_health_reads: Default::default(),
admitting: Mutex::default(),
swept_at: Mutex::default(),
media_key_fed: None,
relay_fed: None,
site_dns: Default::default(),
installer: Arc::new(NoInstaller),
dns: Arc::new(NoDns),
resolver: Arc::new(NoResolver),
custody: None,
twin_health: Arc::new(twin::NoTwinHealth),
}
}
}
/// Write `bytes` to `path` atomically: a temp file, fsync, rename.
fn write_atomic(path: &Path, bytes: &[u8]) -> Result<(), String> {
use std::io::Write as _;
if let Some(d) = path.parent() {
std::fs::create_dir_all(d).map_err(|e| format!("{}: {e}", d.display()))?;
}
let tmp = path.with_extension("tmp");
let mut f = std::fs::File::create(&tmp).map_err(|e| format!("{}: {e}", tmp.display()))?;
f.write_all(bytes)
.and_then(|_| f.sync_all())
.map_err(|e| format!("{}: {e}", tmp.display()))?;
std::fs::rename(&tmp, path).map_err(|e| format!("{}: {e}", path.display()))
}
impl EstateOrders {
/// The server's: `[estate_orders]` read, every run read back from its file
/// (a torn one set aside as `run.json.torn` and named on stderr) — with
/// the open set of DNS writers ([`Self::from_config_with`] for a build's own).
pub fn from_config(cfg: &crate::config::Config) -> anyhow::Result<Self> {
Self::from_config_with(cfg, &crate::registry::Linked::open())
}
/// [`Self::from_config`], with the DNS writers `linked` can build:
/// `dns = "file:<path>"` is the mock's recording file, `""` is no writer,
/// any other word is a LINKED writer by name (`cloudflare`), refused by
/// name when this build has none — its zone and token unsealed from the
/// custodian (`dns.<name>`) for each call.
pub fn from_config_with(
cfg: &crate::config::Config,
linked: &crate::registry::Linked,
) -> anyhow::Result<Self> {
let Some(mut c) = cfg.estate_orders.clone() else {
return Ok(Self::default());
};
price::sale_currency(&c).map_err(|e| anyhow::anyhow!(e))?;
c.term.check().map_err(|e| anyhow::anyhow!(e))?;
if c.spend_cap_monthly_minor == 0 {
anyhow::bail!("[estate_orders] spend_cap_monthly_minor is 0: every private estate has a spend cap");
}
let pairing = c.pairing.trim().to_string();
if !c.install_workers.is_empty() {
anyhow::bail!("{WORKERS_REMOVED}");
}
if !c.twin_health.is_empty() {
anyhow::bail!("{TWIN_HEALTH_COMMAND_REMOVED}");
}
c.shop_door().map_err(|e| anyhow::anyhow!(e))?;
// OM-4 (one monetize, the owner 2026-10-10): the product a sold
// estate is, named or derived — never a hard-coded "gunnar".
let named = c.product.trim().to_string();
if named.is_empty() {
match (cfg.pricing.len(), cfg.pricing.keys().next()) {
(1, Some(only)) => c.product = only.clone(),
(0, _) => anyhow::bail!("[estate_orders] product is not set and no [pricing.<product>] names one: name the product a sold estate is"),
_ => anyhow::bail!(
"[estate_orders] product is not set and this box prices several products ({}): name the one a sold estate is",
cfg.pricing.keys().cloned().collect::<Vec<_>>().join(", ")
),
}
} else {
c.product = named;
}
// The product's KIND (its `[products.<id>] plugin`) picks its lifecycle (`lifecycle::of`).
c.lifecycle = cfg
.products
.get(&c.product)
.and_then(|p| p.plugin.clone())
.unwrap_or_default();
// The shape: the file's, else the lifecycle's default (a kind with none keeps the type's).
c.shape = match (
&c.shape_given,
lifecycle::of(&c).ok().and_then(|l| l.default_shape()),
) {
(Some(given), _) => given.clone(),
(None, Some(own)) => own,
(None, None) => c.shape.clone(),
};
if !pairing.is_empty() {
media_missing_on_sites(&c, &cfg.estates)
.map_or(Ok(()), |why| Err(anyhow::anyhow!(why)))?;
}
let installer: Arc<dyn Installer> = if !pairing.is_empty() {
linked_installer(&c, linked, &cfg.data_dir).map_err(|e| anyhow::anyhow!(e))?
} else {
Arc::new(NoInstaller)
};
let custody =
Custody::open(cfg.data_dir.join("custody")).map_err(|e| anyhow::anyhow!(e))?;
let resolver = domain::resolver_of(&c.resolver).map_err(|e| anyhow::anyhow!(e))?;
// S2: the DNS writer's credential FED by the kit (`secret.dns_<name>`) is sealed into the
// custody, where the writer unseals it for each call.
let mut writers: Vec<String> = vec![c.dns.trim().to_owned()];
writers.extend(site_dns_writers(&cfg.estates).into_values());
writers.sort();
writers.dedup();
for w in writers.iter().filter(|w| !w.is_empty()) {
match seal_fed_dns(&custody, &cfg.secrets_dir(), w) {
Ok(true) => eprintln!("monetize-server: estate orders: the fed DNS credential for {w:?} is sealed in the custody"),
Ok(false) => {}
Err(e) => anyhow::bail!(e),
}
}
// A writer this build does not link refuses by NAME where it would write; the shop
// and every other zone keep serving (2026-10-11).
// The guest writers read their token from the FED secrets directory, as the clouds do.
let dns_guests = dns_guest_blocks(&cfg.dns_writers, &cfg.secrets_dir());
let dns: Arc<dyn EstateDns> = writer_or_refused(
&c,
linked,
&custody,
resolver.clone(),
&dns_guests,
&cfg.data_dir,
);
let mut site_dns = BTreeMap::new();
for (id, w) in site_dns_writers(&cfg.estates) {
let one = EstateOrderConfig {
dns: w.clone(),
..c.clone()
};
let writer = writer_or_refused(
&one,
linked,
&custody,
resolver.clone(),
&dns_guests,
&cfg.data_dir,
);
site_dns.insert(id, (w, writer));
}
if !dns.writes() && !site_dns.values().any(|(_, d)| d.writes()) {
eprintln!("monetize-server: estate orders: ALERT estate-orders.dns-writer {NO_DNS_WRITER} — this shop sells nothing until a zone it sells under names a linked writer");
}
let mut orders = Self::with(
c,
cfg.data_dir.join("estate-orders"),
installer,
dns,
custody,
)
.resolving(resolver)
.watching(Arc::new(twin::ReportedTwinHealth));
orders.site_dns = std::sync::RwLock::new(site_dns);
orders.media_key_fed = Some(cfg.secrets_dir().join(MEDIA_OBJSTO_FED));
orders.relay_fed = Some(cfg.secrets_dir());
if let Some(alert) = unsuffixed_relay_alert(&cfg.secrets_dir()) {
eprintln!("monetize-server: estate orders: {alert}");
}
Ok(orders)
}
/// The writer of `estate`'s zone: its own (`[estates.<e>.orders] dns`), else the shop's.
/// Stub until GREEN.
pub fn dns_on(&self, estate: &str) -> Arc<dyn EstateDns> {
self.site_dns
.read()
.unwrap_or_else(|e| e.into_inner())
.get(estate.trim())
.map(|(_, d)| d.clone())
.unwrap_or_else(|| self.dns.clone())
}
/// The NAME of the writer of `estate`'s zone: its own, else the shop's (`[estate_orders] dns`).
pub fn dns_name_on(&self, estate: &str) -> String {
let own = self
.site_dns
.read()
.unwrap_or_else(|e| e.into_inner())
.get(estate.trim())
.map(|(w, _)| w.clone());
own.unwrap_or_else(|| {
self.cfg
.as_ref()
.map(|c| c.dns.trim().to_owned())
.unwrap_or_default()
})
}
/// **The writer named `name`**, when this shop holds one: a site's, or the shop's own.
pub fn dns_named(&self, name: &str) -> Option<Arc<dyn EstateDns>> {
let name = name.trim();
if name.is_empty() {
return None;
}
let site = self
.site_dns
.read()
.unwrap_or_else(|e| e.into_inner())
.values()
.find(|(w, _)| w == name)
.map(|(_, d)| d.clone());
site.or_else(|| {
self.cfg
.as_ref()
.filter(|c| c.dns.trim() == name)
.map(|_| self.dns.clone())
})
}
/// **The writer of a run's names** (DNS-WRITER, 2026-10-26): the one that WROTE them
/// ([`EstateRun::dns_writer`]) while this shop holds it, else the site's of now. The line
/// says when the two differ (the site's writer changed since the names were written), and
/// is an ALERT when the one that wrote them is not held any more.
pub fn dns_of_run(&self, run: &EstateRun) -> (Arc<dyn EstateDns>, Option<String>) {
let now = self.dns_name_on(&run.estate);
let wrote = run.dns_writer.trim();
if wrote.is_empty() || wrote == now {
return (self.dns_on(&run.estate), None);
}
match self.dns_named(wrote) {
Some(d) => (d, Some(format!("its DNS names were written by {wrote:?}, so they are removed there (the site writes with {now:?} now)"))),
None => (self.dns_on(&run.estate), Some(format!("ALERT: its DNS names were written by {wrote:?}, which this shop no longer holds; they are looked for with {now:?}"))),
}
}
/// Whether any zone this shop sells under is written (the shop's own, or a site's).
pub fn writes_anywhere(&self) -> bool {
self.dns.writes()
|| self
.site_dns
.read()
.unwrap_or_else(|e| e.into_inner())
.values()
.any(|(_, d)| d.writes())
}
/// Give `estate` a writer of its own (built from `[estates.<e>.orders] dns`). Stub.
pub fn site_dns(self, estate: &str, writer: Arc<dyn EstateDns>) -> Self {
self.site_dns
.write()
.unwrap_or_else(|e| e.into_inner())
.insert(estate.to_owned(), (String::new(), writer));
self
}
/// **Each site's zone writer follows the config LIVE** (the config watcher, every change):
/// a site of the product sold here whose `[estates.<e>.orders] dns` is new or names another
/// writer gets it built now ([`writer_or_refused`], as at start); a site that names none any
/// more falls back to the shop's. Before 2026-10-14 the writers were built at start only, so
/// a site the watcher took in (test3.gunnar.rs) read the shop's writer — none — and its
/// Sites row said "this box's DNS token does not write its zone" while test2, in the same
/// Cloudflare zone, wrote. Answers a line per change.
pub fn follow_site_dns(
&self,
cfg: &crate::config::Config,
linked: &crate::registry::Linked,
) -> Vec<String> {
let (Some(c), Some(custody)) = (self.cfg.as_ref(), self.custody.as_ref()) else {
return Vec::new();
};
let wanted: BTreeMap<String, String> = site_dns_writers(&cfg.estates);
let held: BTreeMap<String, String> = self
.site_dns
.read()
.unwrap_or_else(|e| e.into_inner())
.iter()
.map(|(k, (w, _))| (k.clone(), w.clone()))
.collect();
let mut lines = Vec::new();
let guests = dns_guest_blocks(&cfg.dns_writers, &cfg.secrets_dir());
for (id, w) in &wanted {
if held.get(id) == Some(w) {
continue;
}
if let Err(e) = seal_fed_dns(custody, &cfg.secrets_dir(), w) {
lines.push(format!(
"estate orders: the site {id}'s DNS writer {w:?}: {}",
log_safe(&e)
));
}
let one = EstateOrderConfig {
dns: w.clone(),
..c.clone()
};
let writer = writer_or_refused(
&one,
linked,
custody,
self.resolver.clone(),
&guests,
&cfg.data_dir,
);
lines.push(format!(
"estate orders: the site {id} writes its zone with {w:?}{}",
if writer.writes() {
""
} else {
" — REFUSED, see the ALERT"
}
));
self.site_dns
.write()
.unwrap_or_else(|e| e.into_inner())
.insert(id.clone(), (w.clone(), writer));
}
let mut map = self.site_dns.write().unwrap_or_else(|e| e.into_inner());
let gone: Vec<String> = map
.iter()
.filter(|(k, (w, _))| !w.is_empty() && !wanted.contains_key(*k))
.map(|(k, _)| k.clone())
.collect();
for id in gone {
map.remove(&id);
lines.push(format!("estate orders: the site {id} names no DNS writer any more: the shop's writes its zone"));
}
lines
}
/// Built from parts (the tests', and `from_config`'s).
pub fn with(
cfg: EstateOrderConfig,
dir: PathBuf,
installer: Arc<dyn Installer>,
dns: Arc<dyn EstateDns>,
custody: Custody,
) -> Self {
let mut runs = BTreeMap::new();
if let Ok(rd) = std::fs::read_dir(&dir) {
for e in rd.flatten() {
let file = e.path().join("run.json");
let Ok(bytes) = std::fs::read(&file) else {
continue;
};
match serde_json::from_slice::<EstateRun>(&bytes) {
Ok(run) => {
runs.insert(run.company.clone(), run);
}
Err(err) => {
let _ = std::fs::rename(&file, file.with_extension("json.torn"));
eprintln!("monetize-server: estate order {}: the run file would not parse ({err}); set aside as run.json.torn", file.display());
}
}
}
}
EstateOrders {
cfg: Some(cfg),
dir: Some(dir),
runs: Mutex::new(runs),
pens: Pens::default(),
driving: Mutex::default(),
front_keep_lane: Default::default(),
upgrading: Mutex::default(),
sweep_lane: Default::default(),
narrate_lane: Default::default(),
new_estate_lane: Default::default(),
drive_lanes: Mutex::default(),
term_seen: Mutex::default(),
terms_said: Mutex::default(),
booked: Mutex::default(),
sells_seen: Mutex::default(),
sells_refreshing: Default::default(),
ladder_seen: Mutex::default(),
ladder_refreshing: Default::default(),
keep_secrets_for_health_reads: Default::default(),
admitting: Mutex::default(),
swept_at: Mutex::default(),
media_key_fed: None,
relay_fed: None,
site_dns: Default::default(),
installer,
dns,
resolver: Arc::new(NoResolver),
custody: Some(custody),
twin_health: Arc::new(twin::NoTwinHealth),
}
}
/// The same, reading customers' domains through `resolver`.
pub fn resolving(mut self, resolver: Arc<dyn DomainCheck>) -> Self {
self.resolver = resolver;
self
}
/// The same, reading the twins' lag and load through `health`.
pub fn watching(mut self, health: Arc<dyn twin::TwinHealth>) -> Self {
self.twin_health = health;
self
}
pub fn get(&self, company: &str) -> Option<EstateRun> {
let runs = self.runs.lock().unwrap_or_else(|e| e.into_inner());
// An operator site laid here is also found by its own label (its fleet name).
runs.get(company).cloned().or_else(|| {
runs.values()
.find(|r| r.site_order && r.estate == company)
.cloned()
})
}
pub fn all(&self) -> Vec<EstateRun> {
self.runs
.lock()
.unwrap_or_else(|e| e.into_inner())
.values()
.cloned()
.collect()
}
/// The job's own directory: `<dir>/<company>`.
pub fn run_dir(&self, company: &str) -> Option<PathBuf> {
self.dir.as_ref().map(|d| d.join(company))
}
/// The cloud's journal of this estate's lay.
/// `<data_dir>/estate-orders`, when this server writes.
pub fn dir(&self) -> Option<&std::path::Path> {
self.dir.as_deref()
}
pub fn journal(&self, company: &str) -> Result<monetize_cloud::grow::Journal, String> {
let dir = self.run_dir(company).ok_or("no estate-orders directory")?;
std::fs::create_dir_all(&dir).map_err(|e| format!("{}: {e}", dir.display()))?;
monetize_cloud::grow::Journal::open(dir.join("journal")).map_err(|e| e.to_string())
}
/// Write the run, then keep it: the file first, so memory never says
/// what the disk does not. Only ever called holding the run's pen —
/// through [`save`], which also books the run's ledger row.
fn write(&self, run: &EstateRun) -> Result<(), String> {
debug_assert!(
self.pens.held_here(&run.company),
"the run of {} is written without its pen",
run.company
);
if let Some(dir) = self.run_dir(&run.company) {
let bytes = serde_json::to_vec_pretty(run).map_err(|e| e.to_string())?;
write_atomic(&dir.join("run.json"), &bytes)?;
}
self.runs
.lock()
.unwrap_or_else(|e| e.into_inner())
.insert(run.company.clone(), run.clone());
Ok(())
}
/// **The run's pen, waited for as long as it takes** — the driver's: a
/// page's call holds it only for its own few calls.
pub(crate) fn pen(&self, company: &str) -> Pen<'_> {
// A wait without a limit returns only holding the pen; asking again
// is the answer if it ever did not.
loop {
if let Some(pen) = self.pens.take(company, None) {
return pen;
}
}
}
/// [`Pens::lend`] on this server's pens.
fn lend_pen(&self, company: &str) -> LentPen<'_> {
self.pens.lend(company)
}
/// **The run's pen for the tick**, only when nobody holds it now.
fn try_pen(&self, company: &str) -> Option<Pen<'_>> {
self.pens.take(company, Some(Duration::ZERO))
}
/// **The run's pen for a page's call**: waited for at most [`PEN_WAIT`],
/// then refused as busy — before anything was held or changed.
fn pen_for_a_call(
&self,
cfg: &EstateOrderConfig,
company: &str,
) -> Result<Pen<'_>, StartRefusal> {
self.pens.take(company, Some(PEN_WAIT)).ok_or_else(|| {
StartRefusal::Busy(format!("{} is being worked on right now; ask again in a minute — nothing was held or changed", cfg.host_of(company)))
})
}
/// **How many writers wait for this run's pen** — the tests' window on
/// the one-writer rule.
#[cfg(test)]
pub(crate) fn pen_waiters(&self, company: &str) -> usize {
self.pens
.book
.lock()
.unwrap_or_else(|e| e.into_inner())
.waiting
.get(company)
.copied()
.unwrap_or(0)
}
}
// ── starting an order ───────────────────────────────────────────────────────
/// Why `Start` said no — each maps to its own gRPC code.
#[derive(Debug, PartialEq, Eq)]
pub enum StartRefusal {
/// This server sells no private estates.
NotSold(String),
/// The name breaks the rule.
BadName(String),
/// The name is taken (an order in progress, or a delivered estate).
Taken(String),
/// The vendor said no, or is not linked.
Vendor(String),
/// The provider's answer cannot be priced (or not at our margin, or over the spend cap).
Price(String),
/// Not now: a renewal before its window opens, or of an estate in no state to renew.
NotNow(String),
/// The run's pen stayed taken past [`PEN_WAIT`]: ask again (nothing was held).
Busy(String),
/// No such order (a read that names one nobody placed).
NotFound(String),
}
impl std::fmt::Display for StartRefusal {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
StartRefusal::NotSold(w)
| StartRefusal::BadName(w)
| StartRefusal::Taken(w)
| StartRefusal::Vendor(w)
| StartRefusal::Price(w)
| StartRefusal::NotNow(w)
| StartRefusal::Busy(w)
| StartRefusal::NotFound(w) => f.write_str(w),
}
}
}
/// **`EstateOrder.Quote`**: price an estate of these sizes now, from the
/// provider's own answer. Moves nothing.
pub fn quote(state: &AppState, company: &str, sizes: &EstateSizes) -> Result<Quote, StartRefusal> {
let cfg = state.estates.cfg.as_ref().ok_or_else(|| {
StartRefusal::NotSold(
"this monetize sells no private estates ([estate_orders] is absent)".into(),
)
})?;
quote_on(state, cfg, company, sizes)
}
/// [`quote`] on the terms `cfg` — a site's own ([`terms_on`]): its margin and its term price
/// and hold the order, the same terms the lay's recheck holds it to (turn run6 A3).
pub fn quote_on(
state: &AppState,
cfg: &EstateOrderConfig,
company: &str,
sizes: &EstateSizes,
) -> Result<Quote, StartRefusal> {
let cloud = state.registry.find_cloud(&cfg.cloud).ok_or_else(|| {
StartRefusal::NotSold(format!("the cloud {:?} is not linked here", cfg.cloud))
})?;
let name = if company.is_empty() { "quote" } else { company };
// The product's own order, checked against its form (a product with no lifecycle quotes
// as before and is refused at the start).
let sizes = &match lifecycle::of(cfg) {
Ok(l) => price::order_of(&l.facts(), sizes).map_err(StartRefusal::BadName)?,
Err(_) => sizes.clone(),
};
let q = price::quote(cfg, cloud, name, sizes).map_err(StartRefusal::Price)?;
// **The spend cap per estate is never the buyer's to meet** (the owner,
// 2026-10-08: "the customer must not know about a cap"; wiring trace
// A03): every size is offered and sold, and an order over the cap WAITS
// in the queue until the operator raises it (`queue_admits`).
Ok(q)
}
/// **What the estate's OWN monetize grew, read back** (private ownership
/// (d), 2026-10-07): main's data set (and the twin's disk after it) grows
/// under the order's headroom by the estate's own monetize, on the seller's
/// cloud account — the disks keep their ids and get bigger. The provider's
/// inventory is read and every laid disk takes its size there when larger
/// (a disk never shrinks); the run's quoted `main_data_gib` follows main's
/// data members, so the renewal, the ladder's floor and every later quote are
/// priced from what is LAID, not from what was ordered. Answers the line to
/// note when something grew.
pub fn refresh_laid(
state: &AppState,
cfg: &EstateOrderConfig,
run: &mut EstateRun,
) -> Result<Option<String>, String> {
if run.laid.is_none() {
return Ok(None);
}
// The primary (Gunnar's main) is the machine whose data members the quote's data size is.
let primary = lifecycle::of(cfg)?.primary_role();
let cloud = state
.registry
.find_cloud(&cfg.cloud)
.ok_or_else(|| format!("the cloud {:?} is not linked here", cfg.cloud))?;
let inv = monetize_cloud::ask::account(&cloud)
.map_err(|e| format!("the provider's inventory could not be read: {e}"))?;
// A provider may answer an id with its provider's prefix (`upcloud/…`).
let size_of = |id: &str| {
inv.owned
.iter()
.filter(|o| o.kind == monetize_cloud::OwnedKind::Disk)
.find(|o| o.id.id == id || o.id.id.ends_with(&format!("/{id}")))
.map(|o| o.gib)
};
let mut grew = false;
let (members, data, lines) = {
let Some(laid) = run.laid.as_mut() else {
return Ok(None);
};
for m in laid.machines.iter_mut() {
for d in m.disks.iter_mut() {
// A disk never shrinks: only a larger size is taken.
if let Some(g) = size_of(&d.id).filter(|g| *g > d.gib) {
d.gib = g;
grew = true;
}
}
}
let main = laid.machine(&primary);
let members = main.map(|m| m.disks.len().saturating_sub(1)).unwrap_or(0);
let data = main
.and_then(|m| m.disks.iter().skip(1).map(|d| d.gib).min())
.unwrap_or(0);
let lines: Vec<(String, Vec<u64>)> = laid
.machines
.iter()
.map(|m| (m.role.clone(), m.disks.iter().map(|d| d.gib).collect()))
.collect();
(members, data, lines)
};
if !grew && data <= run.quote.sizes.main_data_gib {
return Ok(None);
}
run.quote.sizes.main_data_gib = run.quote.sizes.main_data_gib.max(data);
for line in run.quote.machines.iter_mut() {
if let Some((_, disks)) = lines.iter().find(|(role, _)| *role == line.role) {
line.disks_gib = disks.clone();
}
}
Ok(Some(format!("{primary}'s data is laid at {members} × {data} GB now (the estate's own monetize grew it); its renewal and every later price are from what is laid")))
}
/// **The price of a delivered estate NOW**, as it stands: its sizes, the
/// twin it has (as it was laid, or as it grew), the provider's price today +
/// the margin. No cap is
/// applied here: the estate's own cap is the caller's (`term::within_cap`).
pub fn quote_for_run(state: &AppState, run: &EstateRun) -> Result<Quote, StartRefusal> {
let base = state.estates.cfg.as_ref().ok_or_else(|| {
StartRefusal::NotSold(
"this monetize sells no private estates ([estate_orders] is absent)".into(),
)
})?;
// the run's site's own terms, as its Start and its lay (turn run6 A3)
let site_terms = terms_of(state, base, run).map_err(StartRefusal::NotSold)?;
let cfg = &site_terms;
let cloud = state.registry.find_cloud(&cfg.cloud).ok_or_else(|| {
StartRefusal::NotSold(format!("the cloud {:?} is not linked here", cfg.cloud))
})?;
// **What is LAID is what is priced** (2026-10-13): the twin this estate has, never the
// one the rule would give a new order of its sizes — an estate laid with a 2 GB twin
// before the 4 GB floor is not charged for a twin it does not have, and is not resized.
let laid_twin = run
.laid
.as_ref()
.and_then(|l| l.machine("twin"))
.map(|m| m.plan.as_str())
.unwrap_or("");
price::quote_as_laid(
&priced_for(cfg, run.operator_test),
cloud,
&run.company,
&run.quote.sizes,
&run.twin_floor,
laid_twin,
)
.map_err(StartRefusal::Price)
}
/// **The names a new estate keeps for itself** (private ownership,
/// 2026-10-07): the install's bootstrap admin (it holds the seller's key),
/// the shop's own machines and the anonymous reader — no buyer's account.
/// A product's own machine principals are its facts' `reserved_accounts`.
pub const RESERVED_ACCOUNTS: &[&str] = &[
"admin",
"anonymous",
"monetize",
"front-certify",
"installer-seed",
];
/// The prefixes of the names it keeps: a laid-again front's restore
/// identities and the kit's own machine credentials.
pub const RESERVED_ACCOUNT_PREFIXES: &[&str] = &["monetize-restore-", "kit-"];
/// Pure: why `account` cannot be a buyer's, or `None`: the shop's own names, and the ones the
/// product keeps for its own machines (`own`, its facts' `reserved_accounts`); the product
/// called as `w` says.
pub fn reserved_account_as(
account: &str,
own: &[String],
w: &lifecycle::ProductWords,
) -> Option<String> {
(RESERVED_ACCOUNTS.contains(&account) || own.iter().any(|o| o == account) || RESERVED_ACCOUNT_PREFIXES.iter().any(|p| account.starts_with(p)))
.then(|| format!("the account name {account:?} is one the new {} keeps for itself (its install's admin or one of its own machines): choose another", w.title))
}
/// **Write the run and book its row** (G5, 5.4): the run file first
/// ([`EstateOrders::write`]), then its `Instance` row on the owner's gunnar
/// account ([`book`]). Every writer of a run goes through here, holding the
/// run's pen.
pub(crate) fn save(state: &AppState, run: &EstateRun) -> Result<(), String> {
let before = state
.estates
.get(&run.company)
.filter(|r| r.ordered_at_ms == run.ordered_at_ms)
.map(|r| r.history.len())
.unwrap_or(0);
state.estates.write(run)?;
// A run written down again clears what [`kept`] raised for it.
code_red::clear(state, &unsaved_key(&run.company));
// Every phase the order moved to is a live setup line (the test button).
setup_log::publish_transitions(&state.bus, &instance_product(state), run, before);
book(state, run);
Ok(())
}
fn unsaved_key(company: &str) -> String {
format!("save:{company}:run")
}
/// **[`save`], said when it fails** (A28 M16: three sites dropped it, `let _ = save`): CODE
/// RED `save:<estate>:run` names what was not written down `after`, and the next write of the
/// run clears it. `false`: not written — memory is ahead of the disk until it is.
pub(crate) fn kept(state: &AppState, run: &EstateRun, after: &str) -> bool {
let Err(e) = save(state, run) else {
return true;
};
let site = state
.estates
.cfg
.as_ref()
.map(|c| c.host_of(&run.company))
.unwrap_or_else(|| run.company.clone());
code_red::raise(
state,
&unsaved_key(&run.company),
&site,
"the ONE box",
&format!("the estate's run was NOT written after {after}: memory and disk disagree, and a restart forgets it"),
&e,
"fix what the error names (the data directory: full, read-only or gone); the next write of the run clears this",
);
false
}
/// **The product this shop's estates are rows of**: its `[estate_orders]` product's lifecycle
/// kind ([`lifecycle::instance_product`]) — `gunnar` on a box that sells Gunnar.
pub fn instance_product(state: &AppState) -> String {
lifecycle::instance_product(state.estates.cfg.as_ref())
}
/// Pure: **the run as its ledger row** — on `(gunnar, run.account)`, the
/// account the order was proven for; `None` for a run that names none.
pub fn instance_step(cfg: Option<&EstateOrderConfig>, run: &EstateRun) -> Option<monetize::Step> {
let tenant = run.account.trim();
if tenant.is_empty() {
return None;
}
let host = if !run.domain.is_empty() {
run.domain.clone()
} else {
cfg.map(|c| c.host_of(&run.company)).unwrap_or_default()
};
let product = lifecycle::instance_product(cfg);
Some(monetize::Step::Instance {
reference: monetize::ledger::instance_reference(
&product,
tenant,
run.ordered_at_ms,
&run.company,
),
product,
tenant: tenant.into(),
company: run.company.clone(),
host,
phase: run.phase.clone(),
monthly: run.quote.monthly,
paid_until_unix_ms: run.paid_until_ms,
vendor: run
.handle
.as_ref()
.map(|h| h.vendor.clone())
.unwrap_or_default(),
operator_test: run.operator_test,
note: String::new(),
})
}
/// **Book the run's row on its owner's gunnar account** (G5): appended only
/// when it says something the account's last row for this estate does not
/// (the ledger keeps the same line once). A refusal is an ALERT, not the
/// run's failure: the next [`tick`] books it again.
pub(crate) fn book(state: &AppState, run: &EstateRun) {
// A run stopped by name is not asked for its terms on every tick (PROD-FIX 2).
if !run.stopped_why.is_empty() {
return;
}
// OVW-2 (prod 2026-10-29: `REFUSED [estate-undeclared] test3-holger-rs`, a run terminated
// on 10-23): a GONE run has nothing more to bill. Its terms are read without a word, and
// once its site left the feed it is not booked at all (with no terms its row would name the
// shop's default product).
let terms = if phase::is_gone(&run.phase) {
match state
.estates
.cfg
.as_ref()
.map(|base| terms_of(state, base, run))
{
Some(Ok(c)) => Some(c),
Some(Err(_)) => return,
None => None,
}
} else {
// ★ ST-4/ST-5: on the account of the run's OWN site (`@<site>/<account>` away from
// home) and named on that site.
run_terms(state, run)
};
let booked = EstateRun {
account: ledger_account(state, run),
..run.clone()
};
let Some(step) = instance_step(terms.as_ref(), &booked) else {
return;
};
if state
.estates
.booked
.lock()
.unwrap_or_else(|e| e.into_inner())
.get(&run.company)
== Some(&step)
{
return;
}
match state.ledger.append(state.now(), step.clone()) {
Ok(_) => {
state.estates.booked.lock().unwrap_or_else(|e| e.into_inner()).insert(run.company.clone(), step);
}
Err(e) => eprintln!("monetize-server: ALERT estate-orders.account-row estate order {}: its row on the account {:?} is not written ({e}); the next tick tries again", run.company, run.account),
}
}
/// **`EstateOrder.Start`**: reserve the name, price the estate from the
/// provider's answer (+ the margin, six months prepaid), ask the vendor for
/// the hold, write the run (`ordered`) and its row on the owner's gunnar
/// account. Nothing is laid here — nothing is laid before the vendor says the
/// money is held.
///
/// The order names its OWNER: the account name its first admin is to have
/// (monetize's username rule, and never a name the estate keeps for itself —
/// [`reserved_account_as`]). Kept in the run, handed to the installer, and the
/// tenant of its ledger row (G5). None named is refused here, before any
/// hold: a private Gunnar is its buyer's.
pub fn start_for(
state: &AppState,
company: &str,
email: &str,
own_domain: &str,
account: &str,
sizes: &EstateSizes,
) -> Result<EstateRun, StartRefusal> {
start_with(
state,
company,
email,
own_domain,
account,
sizes,
&OrderedBy::Buyer,
)
}
/// [`start_for`] from the site `estate` (the caller's own bearer, [`crate::auth::CallerEstate`]):
/// held through that site's own account and named on that site ([`terms_on`], ST-5).
pub fn start_on(
state: &AppState,
The file goes on; the console shows all of it.