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

nordisk/gunnar

crates/gunnar-repair/src/order.rs

//! **The repair order an estate PULLS** (owner 2026-10-10 08:25): its kinds and bodies.
//!
//! monetize never calls into an estate. Its guardian "fires" only by queuing a signed
//! order; the server or the twin fetches it on its next heartbeat, and the SHARED pump
//! (monetize-estate-side `pump::check`, A28 M11) checks it: signed by the monetize key
//! (`nordisk-order-v1`, strict Ed25519), for this box, newer than the last one taken, in
//! its window. What is gunnar's is only what the order says ([`decode`]) and the SAME verb
//! the local button and the boot check run ([`crate::exec::execute`]).
//!
//! Until 2026-10-26 this module also carried its own copy of the canonical form, the
//! envelope check and the signature check; the shared check was made as strict as this
//! copy first (P4c), and `gunnar-front/tests/one_order_check.rs` held the two together
//! until the copy could go.

use serde::{Deserialize, Serialize};

use crate::plan::{refuse, Refusal, RefusalCode};

/// The order kinds and their bodies. Unknown body fields are ignored.
pub mod kind {
    pub const RECHARGE_FROM_TWIN: &str = "recharge_from_twin";
    pub const FENCE_PRIMARY: &str = "fence_primary";
    pub const ADOPT_TWIN: &str = "adopt_twin";
    pub const REVIEW_TWIN: &str = "review_twin";
    pub const RESTATE_ENTITLEMENTS: &str = "restate_entitlements";
    pub const ENTITLEMENT: &str = "entitlement";
    pub const ADD_PRINCIPAL: &str = "add_principal";
    pub const GROW_GO_AHEAD: &str = "grow_go_ahead";
    pub const SIGN_IN_AGAIN: &str = "sign_in_again";
    pub const ADD_USERS: &str = "add_users";
    pub const GROW_ROOM: &str = "grow_room";
    pub const PURGE_TENANT: &str = "purge_tenant";
    /// monetize-product `pull::order::REFEED` (MAIL-RELAY + NIGHT-PUBREPO, 2026-10-28): estate
    /// keys fed again to a laid box by the ONE box (`{"keys": {key: value}}`). Gunnar takes the
    /// mail relay's keys and the public site's switch ([`REFEED_KEYS`]) and refuses any other by
    /// name.
    pub const REFEED: &str = "refeed";
    /// The estate keys Gunnar takes again from a `refeed` (monetize-embed `fleet::refeed`): the
    /// mains' mail relay (monetize-mail `FEED_SMTP_*`) and the site's public-site switch
    /// ([`super::PUBLIC_SITE_KEY`], GRS-PUBLIC 2026-10-28).
    pub const REFEED_KEYS: [&str; 4] = [
        "secret.smtp_url",
        "secret.smtp_from",
        "secret.smtp_ca",
        super::PUBLIC_SITE_KEY,
    ];
}

/// **The site's public-site switch, as the ONE box feeds it** (`console_public_site = on | off`,
/// the lay's own word: gunnar-front `console::FEED_PUBLIC_SITE`, monetize-shop-pairing
/// `contract::FEED_PUBLIC_SITE`). At lay it reaches the front; in a `refeed` it reaches the main,
/// which stores it as the console's switch ([`PUBLIC_SITE_SETTING`]).
///
/// GRS-PUBLIC (live bug 2026-10-28): gunnar.rs was laid by its kit before its declaration said
/// `public_site = true`, and a keep boot never feeds a front again, so its console was never
/// told and https://gunnar.rs/ asked every reader to log in. The refeed is how a declaration
/// made after the lay reaches a laid site.
pub const PUBLIC_SITE_KEY: &str = "console_public_site";

/// **The generic gunnar setting the console keeps its public-site switch under** (gunnar-ui
/// `console_setting::PUBLIC_SITE`; the stored position wins over the front's feed). gunnar-server
/// gives the name no meaning: the main stores the refeed's position under it, as an
/// administrator's `Admin.SetSetting` would (gunnar-front `tests/public_site_refeed.rs` holds the
/// two names together against the real console).
pub const PUBLIC_SITE_SETTING: &str = "console.public-site";

/// **What a `refeed` feeds this box again**: the mains' mail relay, the site's public-site
/// switch, or both. At least one; [`decode`] refuses a refeed that feeds nothing.
#[derive(Clone, PartialEq, Eq, Default, Debug, Serialize, Deserialize)]
#[serde(default)]
pub struct Refeed {
    /// The relay the mains mail through (MAIL-RELAY).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub relay: Option<MailRelay>,
    /// The site's public-site switch (GRS-PUBLIC): `Some(true)` on, `Some(false)` off.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub public_site: Option<bool>,
}

/// **The relay a laid main mails through, fed again** (a `refeed`'s relay keys, monetize-embed
/// `fleet::Refeed`, MAIL-RELAY 2026-10-28; named here so this crate links no monetize). **`smtp_url` is a
/// secret** (the relay's token): its `Debug` withholds it.
#[derive(Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(default)]
pub struct MailRelay {
    /// `smtp://` (STARTTLS) or `smtps://` (implicit TLS), `user:token@host[:port]`.
    pub smtp_url: String,
    /// The address this box mails AS.
    pub smtp_from: String,
    /// One more root the relay's certificate is trusted under (PEM); empty: none.
    #[serde(skip_serializing_if = "String::is_empty")]
    pub smtp_ca: String,
}

impl std::fmt::Debug for MailRelay {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("MailRelay")
            .field("smtp_url", &"<withheld>")
            .field("smtp_from", &self.smtp_from)
            .field("smtp_ca", &!self.smtp_ca.is_empty())
            .finish()
    }
}

/// A decoded order body.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum Order {
    /// Recharge `main` from `twin` (whichever of the two pulled it runs the pump).
    /// `fence`: the old primary to fence FIRST (`fence_primary`), when the twin may still
    /// hear it; the recharge then runs with `primary_fenced`. `None` = no fence: a twin
    /// that still hears a live primary is refused `not_fenced`.
    RechargeFromTwin {
        main: String,
        twin: String,
        #[serde(default)]
        accept_lost: bool,
        #[serde(default)]
        epoch: u64,
        #[serde(default)]
        case: Option<String>,
        #[serde(default)]
        fence: Option<String>,
        /// The guardian's proof that the old primary is fenced AT THE CLOUD (its server
        /// given back and read back gone: old server id, when, case). Some = the recharge
        /// runs with `primary_fenced`; empty = none.
        #[serde(default)]
        fenced: Option<String>,
    },
    /// Fence the old primary at `primary`: no client writes, no shipping.
    FencePrimary {
        primary: String,
        #[serde(default)]
        epoch: u64,
        #[serde(default)]
        case: Option<String>,
    },
    /// Certify the twin at `twin` for this primary.
    AdoptTwin {
        twin: String,
        #[serde(default)]
        principal: String,
        #[serde(default)]
        case: Option<String>,
    },
    /// Review the twin; the review rides the next Report.
    ReviewTwin {
        #[serde(default)]
        case: Option<String>,
    },
    /// Restate monetize's entitlement facts on a main that came back without them
    /// (after a recharge): `snapshot` is a `monetize_embed::Snapshot` as JSON, kept raw
    /// here so this crate links no monetize. The box verifies it under its monetize
    /// key, applies each fact newer-wins and deletes nothing.
    RestateEntitlements {
        snapshot: serde_json::Value,
        #[serde(default)]
        case: Option<String>,
    },
    /// One tenant's signed entitlement fact (`monetize_embed::EntitlementFact` as JSON, the
    /// whole body): what `Entitlement.Set` took when monetize still dialled in. Verified
    /// under the box's monetize key; newer wins.
    Entitlement { fact: serde_json::Value },
    /// A paid signup's account (monetize-product `pull::AddPrincipal`): `username` is the
    /// principal and its namespace, `principal_kind` the product's word (`person` | `org`),
    /// `signup` the reference that makes a repeat of the same order idempotent.
    AddPrincipal {
        username: String,
        principal_kind: String,
        signup: String,
    },
    /// A sold estate's paid data step (monetize-embed `fleet::GrowGoAhead`, G16-d):
    /// `go_ahead` = monetize's signed `EstateGrowth` JSON, `data_gib` = GB a data disk
    /// THIS box's role is paid to. The twin grows first; the main only once its twin
    /// holds main's whole set.
    GrowGoAhead { go_ahead: String, data_gib: u64 },
    /// A sold estate's RESEND sign-in link (monetize-embed `fleet::SignInAgain`, S10, owner
    /// 2026-10-11 05:53Z), for the main: `account` = whose first passkey, `to` = the order's
    /// address, `host` = the console host the link points at, `ttl_secs` = how long the
    /// invite lives. No secret: THIS box mints the invite and mails the link itself
    /// (monetize-estate-side `sign_in`).
    SignInAgain {
        account: String,
        to: String,
        host: String,
        ttl_secs: u64,
    },
    /// ★ UW-5: **"Add users", granted** (monetize-product `pull::AddUsers`): `tenant` the
    /// account, `ask` monetize's `UsersAsk` as JSON (no address), `order` the reference a
    /// retry answers the first grant under. The ack is the product's `UsersAnswer` as JSON.
    AddUsers {
        tenant: String,
        ask: serde_json::Value,
        order: String,
    },
    /// ★ UW-4: **room sold past the set** (monetize-product `pull::GrowRoom`): queued, the
    /// set to be resized; the box keeps the newest want and says it to its operator.
    GrowRoom { need_bytes: u64, reason: String },
    /// ★ UW-11: delete the account `tenant` for non-payment (monetize-product
    /// `pull::PurgeTenant`): no `job` starts the purge (or adopts the running one), a `job`
    /// asks that job's state. The ack is `pull::PurgeAnswer` as JSON.
    PurgeTenant {
        tenant: String,
        reason: String,
        job: String,
    },
    /// **A `refeed`: estate keys fed again** by the ONE box: the relay this box mails through
    /// (MAIL-RELAY: kept as the relay fed at lay is, used from the next mail) and the site's
    /// public-site switch (GRS-PUBLIC: stored as the console's switch).
    Refeed(Refeed),
}

#[derive(Deserialize)]
struct AddUsersBody {
    #[serde(default)]
    tenant: String,
    #[serde(default)]
    ask: serde_json::Value,
    #[serde(default)]
    order: String,
}

#[derive(Deserialize)]
struct PurgeTenantBody {
    #[serde(default)]
    tenant: String,
    #[serde(default)]
    reason: String,
    #[serde(default)]
    job: String,
}

#[derive(Deserialize)]
struct GrowRoomBody {
    #[serde(default)]
    need_bytes: u64,
    #[serde(default)]
    reason: String,
}

#[derive(Deserialize)]
struct RechargeBody {
    main: String,
    twin: String,
    #[serde(default)]
    accept_lost: bool,
    #[serde(default)]
    epoch: u64,
    #[serde(default)]
    case: Option<String>,
    #[serde(default)]
    fence: Option<String>,
    #[serde(default)]
    fenced: Option<String>,
}

#[derive(Deserialize)]
struct FenceBody {
    primary: String,
    #[serde(default)]
    epoch: u64,
    #[serde(default)]
    case: Option<String>,
}

#[derive(Deserialize)]
struct AdoptBody {
    twin: String,
    #[serde(default)]
    principal: String,
    #[serde(default)]
    case: Option<String>,
}

#[derive(Deserialize)]
struct RestateBody {
    snapshot: serde_json::Value,
    #[serde(default)]
    case: Option<String>,
}

#[derive(Deserialize)]
struct AddPrincipalBody {
    #[serde(default)]
    username: String,
    #[serde(default)]
    kind: String,
    #[serde(default)]
    signup: String,
}

#[derive(Deserialize, Default)]
struct ReviewBody {
    #[serde(default)]
    case: Option<String>,
}

/// The ack the box sends back in its next Report.
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct OrderAck {
    pub seq: u64,
    pub ok: bool,
    pub detail: String,
    /// The verb's [`crate::Receipt`] / [`crate::TwinReview`] as JSON; empty when refused.
    pub receipt_json: String,
}

/// **What a checked order says**: its body, read for its `kind`. Unknown body fields are
/// ignored; a body that does not decode, or lacks what its kind needs, is refused by name;
/// so is a kind this box does not know. Pure: the order was checked (signature, addressee,
/// sequence, window) by the shared pump before it gets here.
pub fn decode(seq: u64, kind: &str, body: &[u8]) -> Result<Order, Refusal> {
    let bad = |m: String| refuse(RefusalCode::BadOrder, m);
    let undecodable = |e: serde_json::Error| {
        bad(format!(
            "order {seq} ({kind}) has a body that does not decode: {e}"
        ))
    };
    let body: &[u8] = if body.is_empty() { b"{}" } else { body };
    Ok(match kind {
        kind::RECHARGE_FROM_TWIN => {
            let b: RechargeBody = serde_json::from_slice(body).map_err(undecodable)?;
            Order::RechargeFromTwin {
                main: b.main,
                twin: b.twin,
                accept_lost: b.accept_lost,
                epoch: b.epoch,
                case: b.case,
                fence: b.fence.filter(|f| !f.trim().is_empty()),
                fenced: b.fenced.filter(|f| !f.trim().is_empty()),
            }
        }
        kind::FENCE_PRIMARY => {
            let b: FenceBody = serde_json::from_slice(body).map_err(undecodable)?;
            Order::FencePrimary {
                primary: b.primary,
                epoch: b.epoch,
                case: b.case,
            }
        }
        kind::ADOPT_TWIN => {
            let b: AdoptBody = serde_json::from_slice(body).map_err(undecodable)?;
            Order::AdoptTwin {
                twin: b.twin,
                principal: b.principal,
                case: b.case,
            }
        }
        kind::REVIEW_TWIN => {
            let b: ReviewBody = serde_json::from_slice(body).map_err(undecodable)?;
            Order::ReviewTwin { case: b.case }
        }
        kind::RESTATE_ENTITLEMENTS => {
            let b: RestateBody = serde_json::from_slice(body).map_err(undecodable)?;
            Order::RestateEntitlements {
                snapshot: b.snapshot,
                case: b.case,
            }
        }
        kind::ENTITLEMENT => {
            let fact: serde_json::Value = serde_json::from_slice(body).map_err(undecodable)?;
            if !fact.is_object() {
                return Err(bad(format!(
                    "order {} is an entitlement whose body is not a signed fact",
                    seq
                )));
            }
            Order::Entitlement { fact }
        }
        kind::ADD_PRINCIPAL => {
            let b: AddPrincipalBody = serde_json::from_slice(body).map_err(undecodable)?;
            if b.username.trim().is_empty() || b.signup.trim().is_empty() {
                return Err(bad(format!(
                    "order {} is an add_principal without a username or a signup",
                    seq
                )));
            }
            Order::AddPrincipal {
                username: b.username,
                principal_kind: b.kind,
                signup: b.signup,
            }
        }
        kind::GROW_GO_AHEAD => {
            let b: GrowGoAheadBody = serde_json::from_slice(body).map_err(undecodable)?;
            if b.go_ahead.trim().is_empty() || b.data_gib == 0 {
                return Err(bad(format!(
                    "order {} is a grow_go_ahead without monetize's signed step or a size",
                    seq
                )));
            }
            Order::GrowGoAhead {
                go_ahead: b.go_ahead,
                data_gib: b.data_gib,
            }
        }
        kind::SIGN_IN_AGAIN => {
            let b: SignInAgainBody = serde_json::from_slice(body).map_err(undecodable)?;
            // What it names; whether a link can carry it is the main's to check before it
            // mints anything (monetize-estate-side `sign_in::check`, in the executor).
            if b.account.trim().is_empty() || b.to.trim().is_empty() || b.host.trim().is_empty() {
                return Err(bad(format!(
                    "order {seq} is a sign_in_again without an account, an address or a host"
                )));
            }
            Order::SignInAgain {
                account: b.account,
                to: b.to,
                host: b.host,
                ttl_secs: b.ttl_secs,
            }
        }
        kind::ADD_USERS => {
            let b: AddUsersBody = serde_json::from_slice(body).map_err(undecodable)?;
            if b.tenant.trim().is_empty() || b.order.trim().is_empty() || !b.ask.is_object() {
                return Err(bad(format!(
                    "order {} is an add_users without an account, an ask or an order reference",
                    seq
                )));
            }
            Order::AddUsers {
                tenant: b.tenant,
                ask: b.ask,
                order: b.order,
            }
        }
        kind::GROW_ROOM => {
            let b: GrowRoomBody = serde_json::from_slice(body).map_err(undecodable)?;
            if b.need_bytes == 0 {
                return Err(bad(format!(
                    "order {} is a grow_room that wants nothing",
                    seq
                )));
            }
            Order::GrowRoom {
                need_bytes: b.need_bytes,
                reason: b.reason,
            }
        }
        kind::PURGE_TENANT => {
            let b: PurgeTenantBody = serde_json::from_slice(body).map_err(undecodable)?;
            if b.tenant.trim().is_empty() || b.reason.trim().is_empty() {
                return Err(bad(format!(
                    "order {} is a purge_tenant without an account or a reason",
                    seq
                )));
            }
            Order::PurgeTenant {
                tenant: b.tenant,
                reason: b.reason,
                job: b.job,
            }
        }
        kind::REFEED => {
            // The body carries the relay's token: its decode error is not echoed.
            #[derive(Deserialize, Default)]
            #[serde(default)]
            struct RefeedBody {
                keys: std::collections::BTreeMap<String, String>,
            }
            let b: RefeedBody = serde_json::from_slice(body).map_err(|_| {
                bad(format!(
                    "order {seq} is a refeed whose body does not decode as estate keys"
                ))
            })?;
            if let Some(k) = b
                .keys
                .keys()
                .find(|k| !kind::REFEED_KEYS.contains(&k.as_str()))
            {
                return Err(bad(format!("order {seq} is a refeed of the estate key {k:?}, which Gunnar does not take again (it takes {}): nothing was kept", kind::REFEED_KEYS.join(", "))));
            }
            let get = |k: &str| {
                b.keys
                    .get(k)
                    .map(|v| v.trim().to_string())
                    .unwrap_or_default()
            };
            let relay = if ["secret.smtp_url", "secret.smtp_from", "secret.smtp_ca"]
                .iter()
                .any(|k| b.keys.contains_key(*k))
            {
                let r = MailRelay {
                    smtp_url: get("secret.smtp_url"),
                    smtp_from: get("secret.smtp_from"),
                    smtp_ca: get("secret.smtp_ca"),
                };
                let url = r.smtp_url.as_str();
                if url.is_empty() || r.smtp_from.is_empty() {
                    return Err(bad(format!(
                        "order {seq} is a refeed that names no mail relay or no sender"
                    )));
                }
                if !(url.starts_with("smtp://") || url.starts_with("smtps://")) {
                    return Err(bad(format!(
                        "order {seq} is a refeed whose relay is not smtp:// or smtps://"
                    )));
                }
                Some(r)
            } else {
                None
            };
            let public_site = match b.keys.get(PUBLIC_SITE_KEY).map(|v| v.trim().to_ascii_lowercase()) {
                None => None,
                Some(v) if v == "on" => Some(true),
                Some(v) if v == "off" => Some(false),
                Some(v) => return Err(bad(format!("order {seq} is a refeed of {PUBLIC_SITE_KEY} = {v:?}, which is neither `on` nor `off`: nothing was kept"))),
            };
            if relay.is_none() && public_site.is_none() {
                return Err(bad(format!("order {seq} is a refeed that feeds nothing Gunnar takes (no mail relay, no {PUBLIC_SITE_KEY}): nothing was kept")));
            }
            Order::Refeed(Refeed { relay, public_site })
        }
        other => {
            return Err(bad(format!(
                "order {} has kind {other:?}, which this box does not know",
                seq
            )))
        }
    })
}

#[derive(Deserialize)]
struct GrowGoAheadBody {
    #[serde(default)]
    go_ahead: String,
    #[serde(default)]
    data_gib: u64,
}

#[derive(Deserialize)]
struct SignInAgainBody {
    #[serde(default)]
    account: String,
    #[serde(default)]
    to: String,
    #[serde(default)]
    host: String,
    #[serde(default)]
    ttl_secs: u64,
}

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

    /// A pulled order as the shared pump hands it on: its sequence, kind and body.
    #[derive(Clone)]
    struct Raw {
        seq: u64,
        role: String,
        kind: String,
        body: Vec<u8>,
    }

    fn golden() -> Raw {
        Raw { seq: 3, role: "twin".into(), kind: "recharge_from_twin".into(), body: br#"{"accept_lost":false,"epoch":5,"main":"10.6.0.10:50051","twin":"10.6.0.12:50051"}"#.to_vec() }
    }

    fn check(o: &Raw) -> Result<Order, Refusal> {
        let _ = &o.role;
        decode(o.seq, &o.kind, &o.body)
    }

    #[test]
    fn a_recharge_order_decodes_its_body() {
        let order = check(&golden()).unwrap();
        assert!(
            matches!(order, Order::RechargeFromTwin { ref main, epoch: 5, .. } if main == "10.6.0.10:50051")
        );
        let mut o = golden();
        o.body = b"{not json".to_vec();
        let e = check(&o).unwrap_err();
        assert!(
            e.code == RefusalCode::BadOrder
                && e.message
                    .contains("order 3 (recharge_from_twin) has a body that does not decode"),
            "{e}"
        );
    }

    #[test]
    fn unknown_fields_are_ignored_and_unknown_kinds_refused() {
        let mut o = golden();
        o.body = br#"{"main":"m","twin":"t","case":"c-1","future":42}"#.to_vec();
        assert!(
            matches!(check(&o).unwrap(), Order::RechargeFromTwin { case: Some(c), .. } if c == "c-1")
        );
        o.body = br#"{"main":"m","twin":"t","fence":"10.6.0.9:50051"}"#.to_vec();
        assert!(
            matches!(check(&o).unwrap(), Order::RechargeFromTwin { fence: Some(f), .. } if f == "10.6.0.9:50051"),
            "the old primary to fence rides the body"
        );
        o.body = br#"{"main":"m","twin":"t","fence":""}"#.to_vec();
        assert!(
            matches!(
                check(&o).unwrap(),
                Order::RechargeFromTwin { fence: None, .. }
            ),
            "an empty fence is none"
        );
        o.body =
            br#"{"main":"m","twin":"t","fenced":"server/0a1b read back gone 21:30Z case c-9"}"#
                .to_vec();
        assert!(
            matches!(check(&o).unwrap(), Order::RechargeFromTwin { fenced: Some(f), fence: None, .. } if f.starts_with("server/0a1b")),
            "the cloud fence's proof rides the body"
        );
        o.body = br#"{"main":"m","twin":"t","fenced":"  "}"#.to_vec();
        assert!(
            matches!(
                check(&o).unwrap(),
                Order::RechargeFromTwin { fenced: None, .. }
            ),
            "an empty proof is none"
        );
        o.kind = "format_disk".into();
        assert!(check(&o).is_err());
    }

    /// monetize's guardian queues `restate_entitlements → main` after a recharge
    /// (monetize feat/r5-4-guardian 2a87383): the snapshot rides the body as JSON.
    #[test]
    fn a_restate_order_carries_its_snapshot_and_one_without_is_refused() {
        let mut o = golden();
        o.role = "main".into();
        o.kind = kind::RESTATE_ENTITLEMENTS.into();
        o.body = br#"{"snapshot":{"issued_unix_ms":9,"facts":[],"signature":[1,2]},"case":"c-7"}"#
            .to_vec();
        match check(&o).expect("a known kind") {
            Order::RestateEntitlements { snapshot, case } => {
                assert_eq!(snapshot["issued_unix_ms"], 9);
                assert_eq!(case.as_deref(), Some("c-7"));
            }
            other => panic!("{other:?}"),
        }
        o.body = br#"{"case":"c-7"}"#.to_vec();
        let e = check(&o).expect_err("no snapshot, nothing to restate");
        assert_eq!(e.code, RefusalCode::BadOrder);
        assert!(e.message.contains("snapshot"), "{e}");
    }

    /// ONE monetize (G1-pull #1): an entitlement leaves monetize as order `entitlement`,
    /// the body the signed fact itself (monetize-product `pull::order::ENTITLEMENT`).
    #[test]
    fn an_entitlement_order_carries_the_fact_as_its_body() {
        let mut o = golden();
        o.role = "main".into();
        o.kind = kind::ENTITLEMENT.into();
        o.body = br#"{"tenant":"acme","plan":"10gb","state":"Paid","signature":[1]}"#.to_vec();
        match check(&o).expect("a known kind") {
            Order::Entitlement { fact } => assert_eq!(fact["tenant"], "acme"),
            other => panic!("{other:?}"),
        }
        o.body = b"[1,2]".to_vec();
        assert_eq!(
            check(&o).expect_err("not a fact").code,
            RefusalCode::BadOrder
        );
    }

    /// ONE monetize (G1-pull): a paid signup's account leaves monetize as order
    /// `add_principal` (monetize-product `pull::order::ADD_PRINCIPAL`), body
    /// `{username, kind, signup}`. A body without a name or a signup is no order.
    #[test]
    fn an_add_principal_order_carries_the_signups_name_and_reference() {
        let mut o = golden();
        o.role = "main".into();
        o.kind = kind::ADD_PRINCIPAL.into();
        o.body = br#"{"username":"bob","kind":"person","signup":"stripe:cs_1"}"#.to_vec();
        assert_eq!(
            check(&o).expect("a known kind"),
            Order::AddPrincipal {
                username: "bob".into(),
                principal_kind: "person".into(),
                signup: "stripe:cs_1".into()
            }
        );
        for body in [
            &br#"{"kind":"person","signup":"stripe:cs_1"}"#[..],
            br#"{"username":"bob","kind":"person"}"#,
            br#"{"username":" ","kind":"person","signup":"s"}"#,
        ] {
            o.body = body.to_vec();
            let e = check(&o).expect_err("no name or no signup");
            assert_eq!(e.code, RefusalCode::BadOrder, "{e}");
        }
    }

    /// ★ UW-5 / UW-4: `add_users` carries `{tenant, ask, order}` (monetize-product
    /// `pull::AddUsers`) and `grow_room` `{need_bytes, reason}` (`pull::GrowRoom`). An
    /// `add_users` without a tenant or an order, or a `grow_room` of nothing, is no order.
    #[test]
    fn add_users_and_grow_room_orders_carry_monetizes_bodies() {
        let mut o = golden();
        o.role = "main".into();
        o.kind = kind::ADD_USERS.into();
        o.body = br#"{"tenant":"acme","ask":{"count":1,"room_bytes":0,"name":"eve"},"order":"gunnar/acme/users-1"}"#.to_vec();
        assert_eq!(
            check(&o).expect("a known kind"),
            Order::AddUsers {
                tenant: "acme".into(),
                ask: serde_json::json!({"count": 1, "room_bytes": 0, "name": "eve"}),
                order: "gunnar/acme/users-1".into()
            }
        );
        o.body = br#"{"tenant":"acme","ask":{"count":1}}"#.to_vec();
        assert_eq!(check(&o).expect_err("no order").code, RefusalCode::BadOrder);
        o.kind = kind::GROW_ROOM.into();
        o.body = br#"{"need_bytes":32212254720,"reason":"acme needs 30 GiB"}"#.to_vec();
        assert_eq!(
            check(&o).expect("a known kind"),
            Order::GrowRoom {
                need_bytes: 30 << 30,
                reason: "acme needs 30 GiB".into()
            }
        );
        o.body = br#"{"need_bytes":0}"#.to_vec();
        assert_eq!(
            check(&o).expect_err("nothing wanted").code,
            RefusalCode::BadOrder
        );
        assert_eq!(
            (kind::ADD_USERS, kind::GROW_ROOM),
            ("add_users", "grow_room"),
            "monetize-product pull::order, word for word"
        );
    }

    /// ★ UW-11: `purge_tenant` carries `{tenant, reason, job}` (monetize-product
    /// `pull::PurgeTenant`); no account or no reason is no order.
    #[test]
    fn a_purge_tenant_order_carries_monetizes_body() {
        let mut o = golden();
        o.role = "main".into();
        o.kind = kind::PURGE_TENANT.into();
        o.body = br#"{"tenant":"bob","reason":"nonpay: bob lapsed","job":"j-1"}"#.to_vec();
        assert_eq!(
            check(&o).expect("a known kind"),
            Order::PurgeTenant {
                tenant: "bob".into(),
                reason: "nonpay: bob lapsed".into(),
                job: "j-1".into()
            }
        );
        o.body = br#"{"tenant":"bob","reason":"nonpay"}"#.to_vec();
        assert_eq!(
            check(&o).expect("a start"),
            Order::PurgeTenant {
                tenant: "bob".into(),
                reason: "nonpay".into(),
                job: String::new()
            }
        );
        for body in [
            &br#"{"reason":"nonpay"}"#[..],
            br#"{"tenant":"bob","reason":" "}"#,
        ] {
            o.body = body.to_vec();
            assert_eq!(
                check(&o).expect_err("no account or no reason").code,
                RefusalCode::BadOrder
            );
        }
        assert_eq!(
            kind::PURGE_TENANT,
            "purge_tenant",
            "monetize-product pull::order, word for word"
        );
    }

    /// G16-d: a sold estate's paid data step leaves monetize as order `grow_go_ahead`,
    /// body `{go_ahead, data_gib}` (monetize-embed `fleet::GrowGoAhead`). No go-ahead or
    /// no size is no order.
    #[test]
    fn a_grow_go_ahead_order_carries_monetizes_signed_step_and_this_roles_size() {
        let mut o = golden();
        o.kind = kind::GROW_GO_AHEAD.into();
        o.body = br#"{"go_ahead":"{\"estate\":\"acme\"}","data_gib":120}"#.to_vec();
        assert_eq!(
            check(&o).expect("a known kind"),
            Order::GrowGoAhead {
                go_ahead: r#"{"estate":"acme"}"#.into(),
                data_gib: 120
            }
        );
        for body in [
            &br#"{"data_gib":120}"#[..],
            br#"{"go_ahead":" ","data_gib":120}"#,
            br#"{"go_ahead":"{}"}"#,
            br#"{"go_ahead":"{}","data_gib":0}"#,
        ] {
            o.body = body.to_vec();
            let e = check(&o).expect_err("no go-ahead or no size");
            assert_eq!(e.code, RefusalCode::BadOrder, "{e}");
        }
    }

    /// ★ S10 (owner 2026-10-11 05:53Z): a sold estate's RESEND sign-in link leaves monetize as
    /// order `sign_in_again` for the estate's main (monetize-embed `fleet::SignInAgain`), body
    /// `{account, to, host, ttl_secs}` — whose passkey, which address, which console host, how
    /// long — and NO secret: the estate mints the invite and mails the link itself. No account,
    /// no address or no host is no order; an address or a name that could carry a second mail
    /// header is refused by the main before it mints (gunnar-server `tests/sign_in_again.rs`).
    #[test]
    fn a_sign_in_again_order_names_the_account_the_address_and_the_host_and_no_secret() {
        let mut o = golden();
        o.role = "main".into();
        o.kind = kind::SIGN_IN_AGAIN.into();
        o.body = br#"{"account":"alice","to":"buyer@mock.invalid","host":"acme.gunnar.rs","ttl_secs":604800}"#.to_vec();
        let got = check(&o).expect("a known kind");
        assert_eq!(
            serde_json::to_value(&got).unwrap(),
            serde_json::json!({"kind": "sign_in_again", "account": "alice", "to": "buyer@mock.invalid", "host": "acme.gunnar.rs", "ttl_secs": 604800})
        );
        for body in [
            &br#"{"to":"buyer@mock.invalid","host":"acme.gunnar.rs"}"#[..],
            br#"{"account":"alice","host":"acme.gunnar.rs"}"#,
            br#"{"account":"alice","to":"buyer@mock.invalid"}"#,
        ] {
            o.body = body.to_vec();
            let e = check(&o).expect_err("not a resend this box can mail");
            assert!(e.to_string().contains("sign_in_again"), "{e}");
        }
    }

    /// ★ **MAIL-RELAY: a `refeed` names the relay and the sender**; one that names neither, a key
    /// Gunnar does not take again (`public_repositories`),
    /// or a relay that is not `smtp://`/`smtps://`, is refused by name, never echoing the token;
    /// its Debug withholds the token.
    #[test]
    fn a_refeed_order_names_the_relay_and_a_key_gunnar_does_not_take_is_refused_by_name() {
        let mut o = golden();
        o.role = "main".into();
        o.kind = kind::REFEED.into();
        o.body = br#"{"keys":{"secret.smtp_url":"smtps://gunnar%40gunnar.rs:t0kenVALUE@relay.mock.invalid:465","secret.smtp_from":"Gunnar <gunnar@mock.invalid>"}}"#.to_vec();
        match check(&o).expect("a known kind") {
            Order::Refeed(Refeed {
                relay: Some(r),
                public_site: None,
            }) => {
                assert_eq!(
                    (r.smtp_url.as_str(), r.smtp_from.as_str()),
                    (
                        "smtps://gunnar%40gunnar.rs:t0kenVALUE@relay.mock.invalid:465",
                        "Gunnar <gunnar@mock.invalid>"
                    )
                );
                assert!(!format!("{r:?}").contains("t0kenVALUE"));
            }
            other => panic!("{other:?}"),
        }
        for (body, word) in [
            (&br#"{"keys":{"secret.smtp_from":"g@mock.invalid"}}"#[..], "no mail relay"),
            (br#"{"keys":{"secret.smtp_url":"smtps://u:t0kenVALUE@r.mock.invalid"}}"#, "no sender"),
            (br#"{"keys":{"secret.smtp_url":"http://u:t0kenVALUE@r.mock.invalid","secret.smtp_from":"g@mock.invalid"}}"#, "not smtp://"),
            (br#"{"keys":{"public_repositories":"crates"}}"#, "public_repositories"),
            (br#"{"keys":7}"#, "does not decode"),
        ] {
            o.body = body.to_vec();
            let e = check(&o).expect_err("not a refeed Gunnar takes").to_string();
            assert!(e.contains("refeed") && e.contains(word) && !e.contains("t0kenVALUE"), "{e}");
        }
    }

    /// ★ **GRS-PUBLIC (live bug 2026-10-28): a `refeed` feeds the site's public-site switch
    /// again** (`console_public_site = on | off`, the lay's own word). gunnar.rs was laid by its
    /// kit before its declaration said `public_site = true`, and a keep boot never feeds a front
    /// again, so its console's switch stayed OFF and https://gunnar.rs/ asked every reader to log
    /// in. The switch arrives alone or beside the relay; a value that is neither `on` nor `off`,
    /// and a refeed that feeds nothing, are refused by name.
    #[test]
    fn a_refeed_order_feeds_the_public_site_switch_alone_or_beside_the_relay() {
        assert!(
            kind::REFEED_KEYS.contains(&"console_public_site"),
            "{:?}",
            kind::REFEED_KEYS
        );
        let mut o = golden();
        o.role = "main".into();
        o.kind = kind::REFEED.into();
        for (value, on) in [("on", true), (" OFF ", false)] {
            o.body = format!(r#"{{"keys":{{"console_public_site":"{value}"}}}}"#).into_bytes();
            let got =
                serde_json::to_value(check(&o).expect("the switch alone is a refeed Gunnar takes"))
                    .unwrap();
            assert_eq!(
                (got["kind"].as_str(), got["public_site"].as_bool()),
                (Some("refeed"), Some(on)),
                "{got}"
            );
            assert!(got["relay"].is_null(), "no relay was fed: {got}");
        }
        o.body = br#"{"keys":{"secret.smtp_url":"smtps://u:t0kenVALUE@relay.mock.invalid:465","secret.smtp_from":"g@mock.invalid","console_public_site":"on"}}"#.to_vec();
        let both = check(&o).expect("the relay and the switch together");
        assert!(!format!("{both:?}").contains("t0kenVALUE"), "{both:?}");
        let got = serde_json::to_value(&both).unwrap();
        assert_eq!(
            (
                got["relay"]["smtp_from"].as_str(),
                got["public_site"].as_bool()
            ),
            (Some("g@mock.invalid"), Some(true)),
            "{got}"
        );
        for (body, word) in [
            (
                &br#"{"keys":{"console_public_site":"yes"}}"#[..],
                "console_public_site",
            ),
            (br#"{"keys":{}}"#, "nothing"),
        ] {
            o.body = body.to_vec();
            let e = check(&o)
                .expect_err("not a refeed Gunnar takes")
                .to_string();
            assert!(e.contains("refeed") && e.contains(word), "{e}");
        }
    }
}