crates/monetize-front/src/intake.rs
//! **The feed taken into the ONE monetize** (monetize-admin, the pull model:
//! every estate calls monetize, monetize dials none — [`apply_pull`]). Once
//! gunnar-front `src/monetize.rs` (gunnar-live 4c3d24d); its estate-dialing
//! lane is retired (OM-8). Plugins are RECEIVED at runtime.
//!
//! * the server is `monetize_server::cli::serve` on a thread of its own (it
//! blocks on its own runtime), gRPC on loopback `127.0.0.1:50061` behind a
//! bearer minted here;
//! * **a plugin arrives in the feed** — the signed `.wasm` guests are built and
//! signed by the Build Thing; the installer delivers the estate's own:
//!
//! | feed key | value |
//! |---|---|
//! | `plugin.<id>.wasm` | the module, base64 (standard alphabet) |
//! | `plugin.<id>.p7s` | its detached CMS signature, base64 (optional) |
//! | `plugin.<id>.toml` | the body of its `[products.<id>]` block (TOML: `grants`, `guest_digest`, `guest_trust_roots`, `http_hosts`, …) |
//! | `plugin.<id>.prices` | its price list (`[<market>] <unit> = <minor units>`) |
//! | `plugin_root.<name>` | a trust root (DER, base64) a block's signature may chain to |
//! | `vendor.<kind>.<instance>.*`, `cloud.<name>.*`, `signup_guest.<product>.*` | payment vendors (one per account: `vendor.stripe.gunnar.*` is `[vendors.stripe.gunnar]`, the instance `stripe/gunnar` a product names with `vendor =`), cloud guests, the signup sink — the same shape |
//! | `secret.<name>` / `secret_b64.<name>` | what a guest's host doors read (0600) |
//! | `monetize.toml` | the operator's fragment (policy, markets, `[products.<id>] grow…`, …) under what the front decides |
//!
//! The front writes the module to `<data>/monetize/guests/<id>.wasm` (and
//! `.wasm.p7s`) atomically — staged and renamed, so monetize's watcher never
//! reads half a file — and the block into `<data>/monetize/monetize.toml`,
//! where monetize's guest watcher takes it in within 2 s, through every gate
//! a start passes (digest and/or CMS signature BEFORE compile, ABI,
//! manifest, grants): `guest ADDED`, and `REPLACED` when a new module or
//! block arrives. Never a restart.
//! * **what is filled in**: `guest` (the path written), every fed
//! `plugin_root.*` when the block names no roots. Nothing names an
//! `endpoint`: monetize dials no estate. A block with no roots is refused by
//! monetize by name (`[guest-unsigned]`): every guest it runs is signed.
//!
//! **What it OFFERS the installer**: `monetize_pubkey`, monetize's verifying
//! key (hex) — what every metered box checks its facts with.
use std::collections::BTreeMap;
use std::net::SocketAddr;
use std::path::{Path, PathBuf};
use base64::Engine as _;
/// Under the data root: monetize's own directory.
pub const MONETIZE_REL: &str = "monetize";
pub const CONFIG_REL: &str = "monetize/monetize.toml";
pub const DATA_REL: &str = "monetize/data";
/// Where received plugin modules are written.
pub const GUESTS_REL: &str = "monetize/guests";
/// Where fed trust roots are written.
pub const ROOTS_REL: &str = "monetize/trust";
/// The bearer between the console and monetize (both in this process).
pub const TOKEN_REL: &str = "monetize/token";
/// monetize's gRPC, on loopback only: nothing outside this box dials it.
pub const GRPC: SocketAddr = SocketAddr::new(std::net::IpAddr::V4(std::net::Ipv4Addr::LOCALHOST), 50061);
/// The feed key prefix of a plugin: `plugin.<id>.wasm|p7s|toml`.
pub const FEED_PLUGIN_PREFIX: &str = "plugin.";
/// The feed key prefix of a trust root: `plugin_root.<name>`.
pub const FEED_ROOT_PREFIX: &str = "plugin_root.";
/// The offer of monetize's identity (a product that signs its calls: gunnar).
pub const OFFER_MONETIZE_IDENTITY: &str = "monetize_identity";
/// The offer of monetize's verifying key (hex) — what a metered box checks its facts with.
pub const OFFER_MONETIZE_PUBKEY: &str = "monetize_pubkey";
/// One plugin as the feed carries it.
#[derive(Debug, Clone)]
pub struct Plugin {
pub id: String,
/// The module; EMPTY for a product the running binary links (fed with no
/// `plugin.<id>.wasm`, served by the native plugin, its block names no guest).
pub wasm: Vec<u8>,
pub p7s: Option<Vec<u8>>,
pub block: monetize_server::config::ProductConfig,
/// Its price list, `<market>.<unit> = <minor units per unit-month>`, from
/// `plugin.<id>.prices` (TOML `[SE] disk_gib_month = 2200`) — written into
/// monetize's `[prices.<id>.<market>]`. Absent: REFUSED ([plugin-no-prices])
/// unless the block says `free = true` (owner 2026-09-25: the paid front
/// never serves a product free by accident).
///
/// TODO(owner): a CHANGED list for a product already served takes effect
/// at monetize's next restart, not live (monetize's watcher applies guest
/// blocks only). An owner decision, left as it is.
pub prices: BTreeMap<String, BTreeMap<String, u64>>,
/// The prices' TOML as fed.
pub prices_toml: Option<String>,
}
fn plain_id(id: &str) -> bool {
!id.is_empty() && id.chars().all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_')
}
/// **The plugins and trust roots in a feed**, decoded — pure. A plugin that
/// cannot be taken is refused BY NAME and left out (whatever served before
/// keeps serving); the rest are taken.
pub fn plugins(feed: &BTreeMap<String, String>) -> (Vec<Plugin>, BTreeMap<String, Vec<u8>>, Vec<String>) {
plugins_linked(feed, &[])
}
/// [`plugins`] for a binary that LINKS the product plugins `linked` (lane L26): such a
/// product may come with no `plugin.<id>.wasm` — its block and prices only — and is
/// served by the native plugin. A product not in `linked` still needs its module.
pub fn plugins_linked(feed: &BTreeMap<String, String>, linked: &[&str]) -> (Vec<Plugin>, BTreeMap<String, Vec<u8>>, Vec<String>) {
let b64 = base64::engine::general_purpose::STANDARD;
let mut refused = Vec::new();
let mut roots = BTreeMap::new();
for (k, v) in feed {
if let Some(name) = k.strip_prefix(FEED_ROOT_PREFIX) {
match (plain_id(name), b64.decode(v.trim())) {
(true, Ok(der)) => {
roots.insert(name.to_string(), der);
}
(false, _) => refused.push(format!("REFUSED [plugin-root-name] `{k}`: the name is not a plain word")),
(_, Err(e)) => refused.push(format!("REFUSED [plugin-root-base64] `{k}` is not base64: {e}")),
}
}
}
let ids: std::collections::BTreeSet<String> = feed
.keys()
.filter_map(|k| k.strip_prefix(FEED_PLUGIN_PREFIX))
.filter_map(|rest| rest.rsplit_once('.').map(|(id, _)| id.to_string()))
.collect();
let mut out = Vec::new();
for id in ids {
let get = |what: &str| feed.get(&format!("{FEED_PLUGIN_PREFIX}{id}.{what}")).map(|v| v.trim().to_string()).filter(|v| !v.is_empty());
if !plain_id(&id) {
refused.push(format!("REFUSED [plugin-id] `plugin.{id}.*`: the product id is not a plain word"));
continue;
}
let wasm = match get("wasm").map(|w| b64.decode(&w)) {
None if linked.contains(&id.as_str()) => Vec::new(),
None => {
refused.push(format!("REFUSED [plugin-no-module] `plugin.{id}.*` carries no `plugin.{id}.wasm`"));
continue;
}
Some(Ok(w)) if w.starts_with(b"\0asm") => w,
Some(Ok(_)) => {
refused.push(format!("REFUSED [plugin-not-wasm] `plugin.{id}.wasm` is not a WebAssembly module"));
continue;
}
Some(Err(e)) => {
refused.push(format!("REFUSED [plugin-base64] `plugin.{id}.wasm` is not base64: {e}"));
continue;
}
};
let p7s = match get("p7s").map(|s| b64.decode(s)) {
None => None,
Some(Ok(s)) => Some(s),
Some(Err(e)) => {
refused.push(format!("REFUSED [plugin-base64] `plugin.{id}.p7s` is not base64: {e}"));
continue;
}
};
let block = match get("toml").map(|t| toml::from_str::<monetize_server::config::ProductConfig>(&t)) {
None => {
refused.push(format!("REFUSED [plugin-no-block] `plugin.{id}.*` carries no `plugin.{id}.toml` — a module is not taken without its [products.{id}] block"));
continue;
}
Some(Err(e)) => {
refused.push(format!("REFUSED [plugin-block] `plugin.{id}.toml` is not a [products.{id}] block: {e}"));
continue;
}
Some(Ok(b)) => b,
};
let prices_toml = get("prices");
let prices = match prices_toml.as_deref().map(toml::from_str::<BTreeMap<String, BTreeMap<String, u64>>>) {
None => BTreeMap::new(),
Some(Ok(p)) => p,
Some(Err(e)) => {
refused.push(format!("REFUSED [plugin-prices] `plugin.{id}.prices` is not `[<market>] <unit> = <minor units>`: {e}"));
continue;
}
};
if prices.is_empty() && !block.free {
refused.push(format!("REFUSED [plugin-no-prices] `plugin.{id}.*` carries no `plugin.{id}.prices` and its block does not say `free = true` — the paid front serves nothing unpriced"));
continue;
}
// ★ A28 H26 (T12 12.5): every product is PULLED from its estate. A block with an
// endpoint and no `instance` would be a product this box calls directly (the guest's push
// ops, which the guests no longer answer), so it is refused by name.
// ★ T2-BOX (owner 2026-11-03: an EMPTY box whose roots are clicked in the Sites page): a
// block with NEITHER is a product with no home site yet. It is HELD, not served — nothing
// of it is taken until the operator's first site of it names the estate it is pulled from
// (monetize-admin `app_config`: the feed in force then carries its `instance`).
if block.instance.as_deref().map(str::trim).unwrap_or("").is_empty() {
if block.endpoint.trim().is_empty() && block.guest_grpc_hosts.is_empty() && block.restoring_from_twin.is_none() {
continue;
}
refused.push(format!("REFUSED [plugin-no-instance] `plugin.{id}.toml` names no `instance`: [products.{id}] is pulled from the estate whose main serves it, and the ONE box dials no estate"));
continue;
}
out.push(Plugin { id, wasm, p7s, block, prices, prices_toml });
}
(out, roots, refused)
}
/// The feed key prefix of a secret, UTF-8: `secret.<name>`.
pub const FEED_SECRET_PREFIX: &str = "secret.";
/// The feed key prefix of a secret, base64: `secret_b64.<name>`.
pub const FEED_SECRET_B64_PREFIX: &str = "secret_b64.";
/// The feed key prefix of a payment vendor guest:
/// `vendor.<kind>.<instance>.wasm|p7s|toml` — one per vendor ACCOUNT
/// (`vendor.stripe.gunnar.*` is the `[vendors.stripe.gunnar]` block, the
/// instance `stripe/gunnar`).
pub const FEED_VENDOR_PREFIX: &str = "vendor.";
/// Where the secrets a guest's host doors read are written
/// (`<dir>/secret.<name>`, 0600, the directory 0700) — monetize's `secrets_dir`.
pub const SECRETS_REL: &str = "monetize/secrets";
/// Where received vendor modules are written.
pub const VENDORS_REL: &str = "monetize/vendors";
/// **The secrets in a feed**, by name — pure. `secret.<name>` is taken as its
/// UTF-8 bytes, `secret_b64.<name>` decoded. A refusal names the KEY only,
/// never a byte of the value.
pub fn secrets(feed: &BTreeMap<String, String>) -> (BTreeMap<String, Vec<u8>>, Vec<String>) {
let b64 = base64::engine::general_purpose::STANDARD;
let mut out = BTreeMap::new();
let mut refused = Vec::new();
for (k, v) in feed {
let (name, bytes) = if let Some(n) = k.strip_prefix(FEED_SECRET_B64_PREFIX) {
match b64.decode(v.trim()) {
Ok(b) => (n, b),
Err(_) => {
refused.push(format!("REFUSED [secret-base64] `{k}` is not base64 (the value is not shown)"));
continue;
}
}
} else if let Some(n) = k.strip_prefix(FEED_SECRET_PREFIX) {
(n, v.trim().as_bytes().to_vec())
} else {
continue;
};
// The writer's own rule (`secrets::valid_name`): a per-product secret is dotted,
// `sold_smtp_url.<product>` (OX-RELAY2), and monetize-server reads it by that name.
if monetize_product_wasm::secrets::valid_name(name).is_err() {
refused.push(format!("REFUSED [secret-name] `{k}`: the name is not a secret name (A-Z a-z 0-9 _ . -, not starting with a dot)"));
continue;
}
if bytes.is_empty() {
refused.push(format!("REFUSED [secret-empty] `{k}` is empty"));
continue;
}
out.insert(name.to_string(), bytes);
}
(out, refused)
}
/// One payment vendor guest as the feed carries it.
#[derive(Debug, Clone)]
pub struct Vendor {
/// `<kind>.<instance>` (`stripe.gunnar`), as in the feed key, the door's
/// `POST /vendor/<name>` and the file names; [`Vendor::id`] is the
/// server's `<kind>/<instance>`.
pub name: String,
pub wasm: Vec<u8>,
pub p7s: Option<Vec<u8>>,
pub block: monetize_server::config::VendorConfig,
}
impl Vendor {
/// The instance id monetize knows the vendor by: `stripe.gunnar` → `stripe/gunnar`.
pub fn id(&self) -> String {
vendor_id(&self.name).unwrap_or_else(|| self.name.clone())
}
}
/// `stripe.gunnar` → `stripe/gunnar`, or `None` when the name is not a
/// checked `<kind>.<instance>`.
pub fn vendor_id(name: &str) -> Option<String> {
let (kind, instance) = name.split_once('.')?;
let id = format!("{kind}/{instance}");
if monetize_pay::instance::split(&id).is_ok() { Some(id) } else { None }
}
/// **The vendor guests in a feed** (`vendor.<kind>.<instance>.wasm|p7s|toml`), decoded —
/// pure; the same rules as a product plugin, minus prices (a vendor sells
/// nothing). A block that carries a key (anything but the NAMES of secrets)
/// is monetize's to refuse at load, by name.
pub fn vendors(feed: &BTreeMap<String, String>) -> (Vec<Vendor>, Vec<String>) {
let b64 = base64::engine::general_purpose::STANDARD;
let mut refused = Vec::new();
let names: std::collections::BTreeSet<String> = feed
.keys()
.filter_map(|k| k.strip_prefix(FEED_VENDOR_PREFIX))
.filter_map(|rest| rest.rsplit_once('.').map(|(n, _)| n.to_string()))
.collect();
let mut out = Vec::new();
for name in names {
let get = |what: &str| feed.get(&format!("{FEED_VENDOR_PREFIX}{name}.{what}")).map(|v| v.trim().to_string()).filter(|v| !v.is_empty());
if vendor_id(&name).is_none() {
refused.push(format!(
"REFUSED [vendor-name] `vendor.{name}.*`: a vendor is `vendor.<kind>.<instance>.*` (lower-case letters, digits and `-`), one per account — e.g. `vendor.stripe.acme.*`"
));
continue;
}
let wasm = match get("wasm").map(|w| b64.decode(w)) {
Some(Ok(w)) if w.starts_with(b"\0asm") => w,
Some(Ok(_)) => {
refused.push(format!("REFUSED [vendor-not-wasm] `vendor.{name}.wasm` is not a WebAssembly module"));
continue;
}
Some(Err(e)) => {
refused.push(format!("REFUSED [vendor-base64] `vendor.{name}.wasm` is not base64: {e}"));
continue;
}
None => {
refused.push(format!("REFUSED [vendor-no-module] `vendor.{name}.*` carries no `vendor.{name}.wasm`"));
continue;
}
};
let p7s = match get("p7s").map(|s| b64.decode(s)) {
None => None,
Some(Ok(s)) => Some(s),
Some(Err(e)) => {
refused.push(format!("REFUSED [vendor-base64] `vendor.{name}.p7s` is not base64: {e}"));
continue;
}
};
let block = match get("toml").map(|t| toml::from_str::<monetize_server::config::VendorConfig>(&t)) {
None => {
refused.push(format!("REFUSED [vendor-no-block] `vendor.{name}.*` carries no `vendor.{name}.toml` — a module is not taken without its [vendors.{name}] block"));
continue;
}
Some(Err(e)) => {
refused.push(format!("REFUSED [vendor-block] `vendor.{name}.toml` is not a [vendors.{name}] block: {e}"));
continue;
}
Some(Ok(b)) => b,
};
out.push(Vendor { name, wasm, p7s, block });
}
(out, refused)
}
/// The feed key of the operator's monetize settings: a `monetize.toml`
/// fragment (policy, markets, default_cloud, `[clouds.*]` without a module,
/// `[signups.<product>]`, `[tax]`, a product's `bundles`/`grow`, …). What only this front
/// knows is never taken from it ([`FRONT_OWNS`]).
pub const FEED_MONETIZE_TOML: &str = "monetize.toml";
/// The feed key prefix of a cloud guest: `cloud.<name>.wasm|p7s|toml`.
pub const FEED_CLOUD_PREFIX: &str = "cloud.";
/// Where received cloud modules are written.
pub const CLOUDS_REL: &str = "monetize/clouds";
/// The top-level keys this front decides and a fed fragment may not.
pub const FRONT_OWNS: &[&str] = &["grpc_addr", "data_dir", "token_file", "secrets_dir", "signing_key", "require_prices", "control_principals", "backup_dir"];
/// The feed key prefix of an **admin principal of monetize's control API**
/// (owner 2026-09-28: the ONE operator console outside the estates signs
/// every call, SSHSIG): `monetize_principal.<name>`, the line of a product's
/// fed principal (`machine admin <openssh public key>`) under a prefix of its
/// own, so no product's primary ever enrols it. Written into monetize's
/// `[control_principals]`, which only this front decides ([`FRONT_OWNS`]);
/// revoking the console for this estate is removing the key.
pub const FEED_MONETIZE_PRINCIPAL_PREFIX: &str = "monetize_principal.";
/// **The fed control principals** (`monetize_principal.<name>`), name →
/// OpenSSH public key line — pure. A line that is not `<user|machine|deploy>
/// admin <key>` is refused by name and left out; the rest are taken.
pub fn control_principals(feed: &BTreeMap<String, String>) -> (BTreeMap<String, String>, Vec<String>) {
let mut out = BTreeMap::new();
let mut refused = Vec::new();
for (k, v) in feed {
let Some(name) = k.strip_prefix(FEED_MONETIZE_PRINCIPAL_PREFIX) else { continue };
let mut parts = v.trim().splitn(3, ' ');
let (kind, role, key) = (parts.next().unwrap_or(""), parts.next().unwrap_or(""), parts.next().unwrap_or("").trim());
let why = if !plain_id(name) {
"the name is not a plain word"
} else if !matches!(kind, "user" | "machine" | "deploy") {
"the kind is not user, machine or deploy"
} else if role != "admin" {
"the role is not admin (monetize's control API has no other)"
} else if monetize_proto::control_auth::sshsig::fingerprint_of(key).is_none() {
"the key is not an OpenSSH public key line"
} else {
out.insert(name.to_string(), key.to_string());
continue;
};
refused.push(format!("REFUSED [monetize-principal] `{k}`: {why} — the line is `<user|machine|deploy> admin <openssh public key>`"));
}
(out, refused)
}
/// **The operator's monetize settings** from the feed, parsed — pure. A key
/// this front owns is dropped and named; a fragment that is not TOML is
/// refused whole.
pub fn operator_toml(feed: &BTreeMap<String, String>) -> (Option<toml::Table>, Vec<String>) {
let Some(text) = feed.get(FEED_MONETIZE_TOML).filter(|t| !t.trim().is_empty()) else { return (None, Vec::new()) };
match toml::from_str::<toml::Table>(text) {
Ok(mut t) => {
let mut lines = Vec::new();
for k in FRONT_OWNS {
if t.remove(*k).is_some() {
lines.push(format!("REFUSED [monetize-toml-front-owns] `{k}` in the fed {FEED_MONETIZE_TOML}: this front decides it; the rest of the fragment is taken"));
}
}
(Some(t), lines)
}
Err(e) => (None, vec![format!("REFUSED [monetize-toml] the fed {FEED_MONETIZE_TOML} is not TOML: {e}")]),
}
}
/// One cloud guest as the feed carries it.
#[derive(Debug, Clone)]
pub struct Cloud {
pub name: String,
pub wasm: Vec<u8>,
pub p7s: Option<Vec<u8>>,
pub block: toml::Table,
}
/// **The cloud guests in a feed** (`cloud.<name>.wasm|p7s|toml`) — pure; the
/// vendor rules. The block is monetize's to judge (a `cap` is required).
pub fn clouds(feed: &BTreeMap<String, String>) -> (Vec<Cloud>, Vec<String>) {
modules_at(feed, FEED_CLOUD_PREFIX)
}
/// The feed key prefix of the SIGNUP guest: `signup_guest.<product>.wasm|p7s|toml`
/// — the sink that creates a paying customer's account for `<product>`
/// (`[signups.<product>] guest = …`).
pub const FEED_SIGNUP_PREFIX: &str = "signup_guest.";
/// Where the received signup module is written.
pub const SIGNUP_REL: &str = "monetize/signup";
/// The feed key prefix of a DNS guest: `dns_guest.<name>.wasm|p7s|toml` (`[dns_writers.<name>]`).
pub const FEED_DNS_PREFIX: &str = "dns_guest.";
/// Where a received DNS module is written.
pub const DNS_REL: &str = "monetize/dns";
/// **Signed modules in a feed under `prefix`** (`<prefix><name>.wasm|p7s|toml`) —
/// pure; the vendor rules, the block kept as TOML for its section to judge.
pub fn modules_at(feed: &BTreeMap<String, String>, prefix: &str) -> (Vec<Cloud>, Vec<String>) {
let what = prefix.trim_end_matches('.');
let b64 = base64::engine::general_purpose::STANDARD;
let mut refused = Vec::new();
let names: std::collections::BTreeSet<String> = feed.keys().filter_map(|k| k.strip_prefix(prefix)).filter_map(|r| r.rsplit_once('.').map(|(n, _)| n.to_string())).collect();
let mut out = Vec::new();
for name in names {
let get = |part: &str| feed.get(&format!("{prefix}{name}.{part}")).map(|v| v.trim().to_string()).filter(|v| !v.is_empty());
if !plain_id(&name) {
refused.push(format!("REFUSED [{what}-name] `{prefix}{name}.*`: the name is not a plain word"));
continue;
}
let wasm = match get("wasm").map(|w| b64.decode(w)) {
Some(Ok(w)) if w.starts_with(b"\0asm") => w,
Some(_) => {
refused.push(format!("REFUSED [{what}-not-wasm] `{prefix}{name}.wasm` is not a base64 WebAssembly module"));
continue;
}
None => {
refused.push(format!("REFUSED [{what}-no-module] `{prefix}{name}.*` carries no `{prefix}{name}.wasm`"));
continue;
}
};
let p7s = match get("p7s").map(|s| b64.decode(s)) {
None => None,
Some(Ok(s)) => Some(s),
Some(Err(e)) => {
refused.push(format!("REFUSED [{what}-base64] `{prefix}{name}.p7s` is not base64: {e}"));
continue;
}
};
let block = match get("toml").map(|t| toml::from_str::<toml::Table>(&t)) {
Some(Ok(b)) => b,
Some(Err(e)) => {
refused.push(format!("REFUSED [{what}-block] `{prefix}{name}.toml` is not TOML: {e}"));
continue;
}
None => {
refused.push(format!("REFUSED [{what}-no-block] `{prefix}{name}.*` carries no `{prefix}{name}.toml`"));
continue;
}
};
out.push(Cloud { name, wasm, p7s, block });
}
(out, refused)
}
/// Pure: `top` over `base`, tables merged deep, `top` winning every leaf.
pub fn merge_under(mut base: toml::Table, top: toml::Table) -> toml::Table {
for (k, v) in top {
match (base.remove(&k), v) {
(Some(toml::Value::Table(b)), toml::Value::Table(t)) => {
base.insert(k, toml::Value::Table(merge_under(b, t)));
}
(_, v) => {
base.insert(k, v);
}
}
}
base
}
/// **The file monetize reads**: the operator's fragment underneath, then
/// what this front computed (its own keys, the products, vendors and prices
/// it took in, and the cloud guests at the paths it wrote) on top. A merge
/// monetize could not read is refused by name and the front's own config
/// is written alone.
pub fn render(root: &Path, cfg: &monetize_server::config::Config, cls: &[Cloud], signups: toml::Table, roots: &BTreeMap<String, Vec<u8>>, operator: Option<toml::Table>, refused: &mut Vec<String>) -> anyhow::Result<String> {
let own = toml::Table::try_from(cfg)?;
let mut top = toml::Table::new();
for k in FRONT_OWNS.iter().chain(["products", "vendors", "prices"].iter()) {
if let Some(v) = own.get(*k) {
top.insert(k.to_string(), v.clone());
}
}
let mut cl = toml::Table::new();
for c in cls {
let mut b = c.block.clone();
b.insert("guest".into(), toml::Value::String(root.join(CLOUDS_REL).join(format!("{}.wasm", c.name)).display().to_string()));
if !b.contains_key("guest_trust_roots") {
b.insert("guest_trust_roots".into(), toml::Value::Array(roots.keys().map(|n| toml::Value::String(root.join(ROOTS_REL).join(format!("{n}.der")).display().to_string())).collect()));
}
cl.insert(c.name.clone(), toml::Value::Table(b));
}
if !cl.is_empty() {
top.insert("clouds".into(), toml::Value::Table(cl));
}
if !signups.is_empty() {
top.insert("signups".into(), toml::Value::Table(signups));
}
// A product or vendor this front does not serve is not taken from the
// fragment: without its module it would be a LINKED plugin this open
// binary does not carry, and monetize refuses to start on one.
let mut base = operator.clone().unwrap_or_default();
let served = |list: &str| -> Vec<String> { top.get(list).and_then(|v| v.as_table()).map(|t| t.keys().cloned().collect()).unwrap_or_default() };
if let Some(toml::Value::Table(t)) = base.get_mut("products") {
let served = served("products");
t.retain(|k, _| served.iter().any(|s| s == k));
}
// Vendors are `[vendors.<kind>.<instance>]`: kept per INSTANCE.
if let Some(toml::Value::Table(kinds)) = base.get_mut("vendors") {
let served = top.get("vendors").and_then(|v| v.as_table()).cloned().unwrap_or_default();
kinds.retain(|kind, instances| match (served.get(kind).and_then(|v| v.as_table()), instances.as_table_mut()) {
(Some(s), Some(i)) => {
i.retain(|instance, _| s.contains_key(instance));
!i.is_empty()
}
_ => false,
});
}
let merged = merge_under(base, top.clone());
let text = toml::to_string(&merged)?;
if operator.is_some() {
if let Err(e) = toml::from_str::<monetize_server::config::Config>(&text) {
refused.push(format!("REFUSED [monetize-toml] the fed {FEED_MONETIZE_TOML} does not make a config monetize reads: {e} — the front's own is written"));
return Ok(toml::to_string(&merge_under(toml::Table::new(), top))?);
}
}
Ok(text)
}
/// **monetize's config, from the feed** — pure but for the paths it names.
/// Each received plugin becomes its `[products.<id>]` block, `guest` the path
/// written and the fed roots when it names none (see the module docs).
pub fn config(root: &Path, plugins: &[Plugin], roots: &BTreeMap<String, Vec<u8>>) -> monetize_server::config::Config {
config_with(root, plugins, &[], roots)
}
/// [`config`] with the fed payment VENDOR guests too: each becomes its
/// `[vendors.<name>]` block, `guest` the path written, the fed roots when the
/// block names none; `secrets_dir` is where [`apply_pull`] wrote the secrets
/// (`secret.<name>`) the blocks name. Every product is served as fed: the ONE
/// monetize dials no estate (every estate calls it), so no block waits for one.
pub fn config_with(root: &Path, plugins: &[Plugin], vendors: &[Vendor], roots: &BTreeMap<String, Vec<u8>>) -> monetize_server::config::Config {
// `require_prices`: monetize itself refuses an unpriced product too
// ([product-no-prices]), not only this decoder.
// `backup_dir`: where `Ledger.Export` writes, which `ledger_backup` seals off the box.
let mut cfg = monetize_server::config::Config { grpc_addr: GRPC, data_dir: root.join(DATA_REL), token_file: Some(root.join(TOKEN_REL)), require_prices: true, secrets_dir: Some(root.join(SECRETS_REL)), backup_dir: Some(root.join(crate::ledger_backup::BACKUP_REL)), ..Default::default() };
for v in vendors {
let mut b = v.block.clone();
b.guest = Some(root.join(VENDORS_REL).join(format!("{}.wasm", v.name)));
if b.guest_trust_roots.is_empty() {
b.guest_trust_roots = roots.keys().map(|n| root.join(ROOTS_REL).join(format!("{n}.der"))).collect();
}
cfg.vendors.insert(v.id(), b);
}
for p in plugins {
let mut b = p.block.clone();
// A linked product (no module fed) is the binary's own plugin: no guest.
b.guest = (!p.wasm.is_empty()).then(|| root.join(GUESTS_REL).join(format!("{}.wasm", p.id)));
if b.guest.is_some() && b.guest_trust_roots.is_empty() {
b.guest_trust_roots = roots.keys().map(|n| root.join(ROOTS_REL).join(format!("{n}.der"))).collect();
}
if !p.prices.is_empty() {
cfg.prices.insert(p.id.clone(), p.prices.clone());
}
cfg.products.insert(p.id.clone(), b);
}
cfg
}
/// Write `bytes` to `rel` atomically (staged + renamed), only when it differs.
fn put(root: &Path, rel: &Path, bytes: &[u8]) -> anyhow::Result<()> {
put_mode(root, rel, bytes, None)
}
fn put_mode(root: &Path, rel: &Path, bytes: &[u8], mode: Option<u32>) -> anyhow::Result<()> {
use std::io::Write as _;
use std::os::unix::fs::{OpenOptionsExt as _, PermissionsExt as _};
let p = root.join(rel);
if std::fs::read(&p).map(|b| b == bytes).unwrap_or(false) {
if let Some(m) = mode {
std::fs::set_permissions(&p, std::fs::Permissions::from_mode(m))?;
}
return Ok(());
}
if let Some(parent) = p.parent() {
std::fs::create_dir_all(parent)?;
}
let mut tmp = p.clone().into_os_string();
tmp.push(".staging");
// A leftover staging file would keep its old mode: start from none.
let _ = std::fs::remove_file(&tmp);
let mut o = std::fs::OpenOptions::new();
o.write(true).create_new(true);
if let Some(m) = mode {
o.mode(m);
}
// DURABLE before it counts: the bytes, the rename, the directory entry.
// MEASURED loki 2026-09-26 (the pro kit's mock proof): the product's module,
// registered seconds before vm1 was power-cycled, was GONE after the
// boot — `REFUSED [plugin-no-module]`, monetize never started — because
// the staging file and the rename were still only in the page cache.
let mut f = o.open(&tmp)?;
f.write_all(bytes)?;
f.sync_all()?;
drop(f);
std::fs::rename(&tmp, &p)?;
if let Some(dir) = p.parent() {
// Best effort, as gunnar-server's `durable` does: a filesystem that will
// not open a directory for fsync still has the file itself synced.
if let Ok(d) = std::fs::File::open(dir) {
let _ = d.sync_all();
}
}
Ok(())
}
/// **Take in what the ONE monetize's feed carries** (G6, the pull model):
/// write the roots and modules FIRST, then the config that names them — the
/// watcher sees the files before the block. Every fed product is served with
/// no endpoint (estates call monetize), the signup guest is taken as fed.
/// Answers the refusal lines.
pub fn apply_pull(root: &Path, feed: &BTreeMap<String, String>) -> anyhow::Result<Vec<String>> {
apply_pull_linked(root, feed, &[])
}
/// [`apply_pull`] for a binary that links the product plugins `linked`
/// ([`plugins_linked`]): such a product may be fed with no module.
pub fn apply_pull_linked(root: &Path, feed: &BTreeMap<String, String>, linked: &[&str]) -> anyhow::Result<Vec<String>> {
// One writer at a time.
static ONE: std::sync::Mutex<()> = std::sync::Mutex::new(());
let _one = ONE.lock().unwrap_or_else(|e| e.into_inner());
let (ps, roots, mut refused) = plugins_linked(feed, linked);
for (n, der) in &roots {
put(root, &Path::new(ROOTS_REL).join(format!("{n}.der")), der)?;
}
// The secrets BEFORE any block that names them (LANE-A-API "stripe plugin
// for the front", lane E 1): monetize's one writer, 0600 in a 0700
// directory; a name is logged, a value never.
let (secs, sec_refused) = secrets(feed);
refused.extend(sec_refused);
let dir = root.join(SECRETS_REL);
for (n, bytes) in &secs {
if std::fs::read(dir.join(format!("secret.{n}"))).map(|b| &b == bytes).unwrap_or(false) {
continue;
}
monetize_product_wasm::secrets::write(&dir, n, bytes).map_err(|e| anyhow::anyhow!("secret.{n}: {e}"))?;
}
// The vendor guests (lane E 2): the module and its signature, then the block.
let (vs, v_refused) = vendors(feed);
refused.extend(v_refused);
for v in &vs {
match &v.p7s {
Some(sig) => put(root, &Path::new(VENDORS_REL).join(format!("{}.wasm.p7s", v.name)), sig)?,
None => {
let _ = std::fs::remove_file(root.join(VENDORS_REL).join(format!("{}.wasm.p7s", v.name)));
}
}
put(root, &Path::new(VENDORS_REL).join(format!("{}.wasm", v.name)), &v.wasm)?;
}
for p in &ps {
let module = Path::new(GUESTS_REL).join(format!("{}.wasm", p.id));
if p.wasm.is_empty() {
// Linked: no module, and no old one left beside a block that names none.
let _ = std::fs::remove_file(root.join(&module));
let _ = std::fs::remove_file(root.join(GUESTS_REL).join(format!("{}.wasm.p7s", p.id)));
continue;
}
match &p.p7s {
Some(sig) => put(root, &Path::new(GUESTS_REL).join(format!("{}.wasm.p7s", p.id)), sig)?,
// No signature with this module: an old one never stands beside it.
None => {
let _ = std::fs::remove_file(root.join(GUESTS_REL).join(format!("{}.wasm.p7s", p.id)));
}
}
put(root, &module, &p.wasm)?;
}
let mut cfg = config_with(root, &ps, &vs, &roots);
let (principals, p_refused) = control_principals(feed);
refused.extend(p_refused);
cfg.control_principals = principals;
// The cloud guests (like the vendors: module and signature, then the block).
let (cls, c_refused) = clouds(feed);
refused.extend(c_refused);
for c in &cls {
match &c.p7s {
Some(sig) => put(root, &Path::new(CLOUDS_REL).join(format!("{}.wasm.p7s", c.name)), sig)?,
None => {
let _ = std::fs::remove_file(root.join(CLOUDS_REL).join(format!("{}.wasm.p7s", c.name)));
}
}
put(root, &Path::new(CLOUDS_REL).join(format!("{}.wasm", c.name)), &c.wasm)?;
}
// The SIGNUP guests (one per product, each its own [signups.<product>]):
// module and signature, then the block, its guest the path written. The
// block's name is its product; monetize hands the guest `product` from it.
let (sg, s_refused) = modules_at(feed, FEED_SIGNUP_PREFIX);
refused.extend(s_refused);
let mut signups = toml::Table::new();
for g in &sg {
match &g.p7s {
Some(sig) => put(root, &Path::new(SIGNUP_REL).join(format!("{}.wasm.p7s", g.name)), sig)?,
None => {
let _ = std::fs::remove_file(root.join(SIGNUP_REL).join(format!("{}.wasm.p7s", g.name)));
}
}
put(root, &Path::new(SIGNUP_REL).join(format!("{}.wasm", g.name)), &g.wasm)?;
let mut b = g.block.clone();
b.remove("product");
b.insert("guest".into(), toml::Value::String(root.join(SIGNUP_REL).join(format!("{}.wasm", g.name)).display().to_string()));
if !b.contains_key("guest_trust_roots") {
b.insert("guest_trust_roots".into(), toml::Value::Array(roots.keys().map(|n| toml::Value::String(root.join(ROOTS_REL).join(format!("{n}.der")).display().to_string())).collect()));
}
signups.insert(g.name.clone(), toml::Value::Table(b));
}
// The DNS guests (`[dns_writers.<name>]`, the owner 2026-10-11): module and signature,
// then the block, its guest the path written, verified under the plugin root.
let (dg, d_refused) = modules_at(feed, FEED_DNS_PREFIX);
refused.extend(d_refused);
let mut dns_writers = toml::Table::new();
for g in &dg {
match &g.p7s {
Some(sig) => put(root, &Path::new(DNS_REL).join(format!("{}.wasm.p7s", g.name)), sig)?,
None => {
let _ = std::fs::remove_file(root.join(DNS_REL).join(format!("{}.wasm.p7s", g.name)));
}
}
put(root, &Path::new(DNS_REL).join(format!("{}.wasm", g.name)), &g.wasm)?;
let mut b = g.block.clone();
b.insert("guest".into(), toml::Value::String(root.join(DNS_REL).join(format!("{}.wasm", g.name)).display().to_string()));
if !b.contains_key("guest_trust_roots") {
b.insert("guest_trust_roots".into(), toml::Value::Array(roots.keys().map(|n| toml::Value::String(root.join(ROOTS_REL).join(format!("{n}.der")).display().to_string())).collect()));
}
dns_writers.insert(g.name.clone(), toml::Value::Table(b));
}
let (operator, o_refused) = operator_toml(feed);
refused.extend(o_refused);
let mut operator = operator;
if !dns_writers.is_empty() {
let mut o = operator.unwrap_or_default();
o.insert("dns_writers".into(), toml::Value::Table(dns_writers));
operator = Some(o);
}
let text = render(root, &cfg, &cls, signups, &roots, operator, &mut refused)?;
if std::fs::read_to_string(root.join(CONFIG_REL)).map(|t| t != text).unwrap_or(true) {
crate::write_secret(root, CONFIG_REL, &text)?;
}
Ok(refused)
}
/// **Prepare monetize's files**: the bearer between the front's console and
/// monetize, and monetize's signing key. Idempotent; minted once, kept across
/// installations.
pub fn prepare(root: &Path) -> anyhow::Result<()> {
std::fs::create_dir_all(root.join(DATA_REL))?;
std::fs::create_dir_all(root.join(GUESTS_REL))?;
if std::fs::read_to_string(root.join(TOKEN_REL)).map(|t| t.trim().len() < 32).unwrap_or(true) {
let mut b = [0u8; 32];
rand_core::RngCore::fill_bytes(&mut rand_core::OsRng, &mut b);
crate::write_secret(root, TOKEN_REL, &b.iter().map(|x| format!("{x:02x}")).collect::<String>())?;
}
// ★ UW-3: the OPERATOR bearer (monetize-server's `<data_dir>/operator-token`): the money
// verbs (refund, settle) take it and nothing else does; this box's own console holds it.
if operator_token(root).map(|t| t.len() < 32 || Some(&t) == console_token(root).as_ref()).unwrap_or(true) {
let mut b = [0u8; 32];
rand_core::RngCore::fill_bytes(&mut rand_core::OsRng, &mut b);
crate::write_secret(root, OPERATOR_TOKEN_REL, &b.iter().map(|x| format!("{x:02x}")).collect::<String>())?;
}
signer(root)?;
Ok(())
}
/// monetize's signing key (minted once, 0600) — its public half is `monetize_pubkey`.
pub fn signer(root: &Path) -> anyhow::Result<monetize::Signer> {
let path = monetize_server::config::Config { data_dir: root.join(DATA_REL), ..Default::default() }.signing_key_path();
if path.exists() {
return Ok(monetize::Signer::from_path(&path)?);
}
let s = monetize::Signer::generate();
// Minted ONCE and written atomically (staged, synced, renamed, 0600): a VM
// stopped hard in the first seconds of its first start (MEASURED on holger's
// mock 2026-09-28) left a torn `signing.key`, and every later start refused
// "not a 32-byte key" — monetize never served again on that front.
use std::os::unix::fs::PermissionsExt as _;
let dir = path.parent().ok_or_else(|| anyhow::anyhow!("{}: a signing key's path names no directory", path.display()))?;
std::fs::create_dir_all(dir)?;
let mut staging = path.clone().into_os_string();
staging.push(".staging");
let staging = PathBuf::from(staging);
let _ = std::fs::remove_file(&staging);
s.write_to(&staging)?;
std::fs::set_permissions(&staging, std::fs::Permissions::from_mode(0o600))?;
std::fs::File::open(&staging)?.sync_all()?;
std::fs::rename(&staging, &path)?;
if let Ok(d) = std::fs::File::open(dir) {
let _ = d.sync_all();
}
Ok(s)
}
/// **What monetize on the front offers**: its verifying key (hex) — every
/// metered box of the estate checks its facts with it. (The identity a signing
/// product enrols is the product's to offer.)
///
/// LEGACY (ONE-monetize model, owner 2026-10-10 13:35Z): no estate runs a
/// monetize, so no estate front offers a key of its own. Estates verify the ONE
/// monetize's per-product key (G8), handed over at enrolment (`attach`).
pub fn offers(root: &Path) -> BTreeMap<String, String> {
let mut o = BTreeMap::new();
match signer(root) {
Ok(s) => {
o.insert(OFFER_MONETIZE_PUBKEY.to_string(), s.public_key().iter().map(|b| format!("{b:02x}")).collect());
}
Err(e) => tracing::error!(error = %format!("{e:#}"), "monetize: its signing key cannot be minted — not offered"),
}
o
}
/// The bearer the front's own console presents to monetize on loopback.
pub fn console_token(root: &Path) -> Option<String> {
std::fs::read_to_string(root.join(TOKEN_REL)).ok().map(|t| t.trim().to_string()).filter(|t| !t.is_empty())
}
/// ★ UW-3: where the OPERATOR bearer is kept — monetize-server's
/// `<data_dir>/`[`monetize_server::config::OPERATOR_TOKEN_FILE`].
pub const OPERATOR_TOKEN_REL: &str = "monetize/data/operator-token";
/// **The operator's bearer to monetize**: what the box's own console refunds and settles
/// with (`monetize_ui_server::money`). `None` before [`prepare`].
pub fn operator_token(root: &Path) -> Option<String> {
std::fs::read_to_string(root.join(OPERATOR_TOKEN_REL)).ok().map(|t| t.trim().to_string()).filter(|t| !t.is_empty())
}
/// **Serve monetize on a thread of its own** from the config already written,
/// gRPC on `grpc` (the medium: [`GRPC`]; tests: `127.0.0.1:0`).
///
/// **No bearer, no monetize** (stinky #9): a missing, empty or whitespace
/// `monetize/token` is refused here, by the file's name — an empty bearer is
/// open mode, every call on loopback let in. [`prepare`] mints it.
pub fn serve_thread(root: &Path, grpc: SocketAddr) -> Result<(), String> {
let token = console_token(root).ok_or_else(|| {
format!("ALERT front.bearer-missing monetize: NO bearer in {} (missing or empty) — refusing to serve: an empty bearer lets every gRPC call on loopback in unauthenticated", root.join(TOKEN_REL).display())
})?;
let path = root.join(CONFIG_REL);
let mut first = monetize_server::config::Config::load(&path).map_err(|e| format!("its config does not load: {e:#}"))?;
first.grpc_addr = grpc;
// A32-14, stays a task: the embedded monetize server's own thread and runtime, for the front's life; its stop is said.
std::thread::Builder::new()
.name("monetize".into())
.spawn(move || {
if let Err(e) = monetize_server::cli::serve(first, &path, token, &monetize_server::registry::Linked::open()) {
tracing::error!(error = %format!("{e:#}"), "monetize: the server stopped");
}
})
.map(|_| ())
.map_err(|e| format!("the server thread could not start: {e}"))
}
#[cfg(test)]
mod tests {
use super::*;
/// **No bearer, no monetize** (stinky #9): a missing or empty
/// `monetize/token` is refused with an ALERT that names the file. It is
/// never served OPEN, which would answer anyone on loopback.
#[test]
fn a_missing_or_empty_token_is_refused_by_name_and_never_served_open() {
for content in [None, Some(""), Some(" \n")] {
let dir = tempfile::tempdir().unwrap();
let root = dir.path();
prepare(root).unwrap();
apply_pull(root, &BTreeMap::new()).unwrap();
match content {
None => std::fs::remove_file(root.join(TOKEN_REL)).unwrap(),
Some(c) => std::fs::write(root.join(TOKEN_REL), c).unwrap(),
}
let e = serve_thread(root, "127.0.0.1:0".parse().unwrap()).expect_err("no bearer: monetize is not served");
assert!(e.contains("ALERT") && e.contains(&root.join(TOKEN_REL).display().to_string()), "{content:?}: {e}");
}
}
/// The signing key is minted once and written atomically: 32 bytes, 0600,
/// no staging file left, the same key on every later start; a torn staging
/// file from a hard stop is ignored and replaced.
#[test]
fn the_signing_key_is_written_whole_and_read_back_the_same() {
use std::os::unix::fs::PermissionsExt as _;
let dir = tempfile::tempdir().unwrap();
let path = monetize_server::config::Config { data_dir: dir.path().join(DATA_REL), ..Default::default() }.signing_key_path();
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
let mut staging = path.clone().into_os_string();
staging.push(".staging");
std::fs::write(&staging, b"torn").unwrap();
let first = signer(dir.path()).unwrap().public_key();
assert_eq!(std::fs::read(&path).unwrap().len(), 32);
assert_eq!(std::fs::metadata(&path).unwrap().permissions().mode() & 0o777, 0o600);
assert!(!std::path::Path::new(&staging).exists(), "no staging file is left");
assert_eq!(signer(dir.path()).unwrap().public_key(), first, "minted once");
}
fn b64(b: &[u8]) -> String {
base64::engine::general_purpose::STANDARD.encode(b)
}
/// A real guest module for the admission test: a product's guest built for
/// wasm32 (`MONETIZE_FRONT_TEST_GUEST`), when one is on this box.
fn a_real_guest() -> Option<Vec<u8>> {
let p = crate::test_guest();
std::fs::read(p).ok()
}
#[test]
fn monetize_offers_its_verifying_key_minted_once() {
let dir = tempfile::tempdir().unwrap();
prepare(dir.path()).unwrap();
let o = offers(dir.path());
assert_eq!(o[OFFER_MONETIZE_PUBKEY].len(), 64, "hex ed25519");
assert!(o[OFFER_MONETIZE_PUBKEY].chars().all(|c| c.is_ascii_hexdigit()));
assert_eq!(offers(dir.path()), o, "minted once");
assert!(!o.contains_key(OFFER_MONETIZE_IDENTITY), "an identity is the signing product's to offer");
assert!(console_token(dir.path()).is_some_and(|t| t.len() >= 32), "the console's bearer to monetize");
}
/// ★ UW-3: **the operator's bearer is minted beside the service one** — 0600, under
/// monetize's data dir (where monetize-server reads `operator-token`), never the same
/// secret, minted once — so the box's own console can refund and settle with no shell.
#[test]
fn prepare_mints_an_operator_bearer_apart_from_the_service_one() {
use std::os::unix::fs::PermissionsExt as _;
let dir = tempfile::tempdir().unwrap();
prepare(dir.path()).unwrap();
let op = operator_token(dir.path()).expect("an operator bearer");
assert!(op.len() >= 32, "{op}");
assert_ne!(Some(op.clone()), console_token(dir.path()), "two names for one secret separate nothing");
let path = monetize_server::config::Config { data_dir: dir.path().join(DATA_REL), ..Default::default() }.operator_token_file_path();
assert_eq!(std::fs::read_to_string(&path).unwrap().trim(), op, "where monetize-server reads it");
assert_eq!(std::fs::metadata(&path).unwrap().permissions().mode() & 0o777, 0o600);
prepare(dir.path()).unwrap();
assert_eq!(operator_token(dir.path()), Some(op), "minted once");
assert_eq!(OPERATOR_TOKEN_REL, format!("{DATA_REL}/{}", monetize_server::config::OPERATOR_TOKEN_FILE));
}
/// **Bare**: with nothing in the feed, monetize serves no product at all.
#[test]
fn a_fed_secret_is_written_owner_only_and_a_refusal_never_shows_a_value() {
let feed = BTreeMap::from([
("secret.stripe_key".to_string(), "rk_test_not_a_real_key".to_string()),
("secret_b64.bin".to_string(), b64(b"\x01\x02")),
("secret_b64.broken".to_string(), "sk_test_%%%not-base64".to_string()),
("secret.bad name".to_string(), "sk_test_hidden".to_string()),
]);
let (s, refused) = secrets(&feed);
assert_eq!(s.get("stripe_key").map(Vec::as_slice), Some(b"rk_test_not_a_real_key".as_slice()));
assert_eq!(s.get("bin").map(Vec::as_slice), Some([1u8, 2].as_slice()));
assert_eq!(refused.len(), 2, "{refused:?}");
assert!(refused.iter().all(|r| !r.contains("sk_test")), "a refusal names the key, never the value: {refused:?}");
let dir = tempfile::tempdir().unwrap();
let lines = apply_pull(dir.path(), &feed).unwrap();
assert!(lines.iter().all(|r| !r.contains("sk_test") && !r.contains("rk_test")), "{lines:?}");
use std::os::unix::fs::PermissionsExt as _;
let f = dir.path().join(SECRETS_REL).join("secret.stripe_key");
assert_eq!(std::fs::read(&f).unwrap(), b"rk_test_not_a_real_key");
assert_eq!(std::fs::metadata(&f).unwrap().permissions().mode() & 0o777, 0o600);
let cfg = monetize_server::config::Config::load(&dir.path().join(CONFIG_REL)).unwrap();
assert_eq!(cfg.secrets_dir.as_deref(), Some(dir.path().join(SECRETS_REL).as_path()), "monetize reads the secrets where the front wrote them");
}
/// **A per-product secret is taken under its dotted name** (REL-ONE-D 2026-10-29): the sold
/// relay of each product is fed as `secret.sold_smtp_url.<product>` / `…sold_smtp_from.<product>`
/// (OX-RELAY2), the names monetize-server reads (`estate_order::sold_relay_names`). The intake
/// takes every name the secrets writer takes (`secrets::valid_name`), so the ONE box's whole
/// feed is not refused for them; a name the writer refuses (a leading dot, a slash) still is.
#[test]
fn a_per_product_secret_is_taken_under_its_dotted_name() {
let feed = BTreeMap::from([
("secret.sold_smtp_url.gunnar".to_string(), "smtp://relay%40example.invalid:tok@smtp.example:587".to_string()),
("secret.sold_smtp_from.holger".to_string(), "Holger <estate@example.invalid>".to_string()),
("secret..hidden".to_string(), "sk_test_hidden".to_string()),
("secret.a/b".to_string(), "sk_test_hidden".to_string()),
]);
let (s, refused) = secrets(&feed);
assert_eq!(s.get("sold_smtp_url.gunnar").map(Vec::as_slice), Some(b"smtp://relay%40example.invalid:tok@smtp.example:587".as_slice()));
assert_eq!(s.get("sold_smtp_from.holger").map(Vec::as_slice), Some(b"Holger <estate@example.invalid>".as_slice()));
assert_eq!(refused.len(), 2, "a leading dot and a slash are refused: {refused:?}");
assert!(refused.iter().all(|r| r.contains("[secret-name]") && !r.contains("sk_test")), "{refused:?}");
let dir = tempfile::tempdir().unwrap();
apply_pull(dir.path(), &feed).unwrap();
use std::os::unix::fs::PermissionsExt as _;
let f = dir.path().join(SECRETS_REL).join("secret.sold_smtp_url.gunnar");
assert_eq!(std::fs::read(&f).unwrap(), b"smtp://relay%40example.invalid:tok@smtp.example:587");
assert_eq!(std::fs::metadata(&f).unwrap().permissions().mode() & 0o777, 0o600);
}
#[test]
fn the_operators_monetize_toml_lies_under_what_the_front_decides_and_a_cloud_guest_lands_at_its_path() {
let vendor_block = "enabled = true\ngrants = [\"http\"]\nhttp_hosts = [\"api.stripe.com\"]\n";
let fragment = "grpc_addr = \"0.0.0.0:1\"\ndefault_cloud = \"upcloud\"\ndefault_market = \"SE\"\n[policy]\nperiod = \"month\"\ngrace_days = 14\nretention_days = 90\n[clouds.server]\nenabled = true\npool_gib = 37\nproduct = \"alpha\"\n[products.ghost]\nendpoint = \"x:1\"\n[vendors.stripe.gunnar]\nstatement_descriptor = \"ESTATE.TEST\"\nenabled = false\n[vendors.stripe.holger]\nenabled = true\n[vendors.klarna.gunnar]\nenabled = true\n";
let cloud = "enabled = true\ncap = { monthly_minor = 5000, total_minor = 5000, currency = \"Eur\" }\ngrants = [\"http\", \"state\"]\nhttp_hosts = [\"api.upcloud.com\"]\nzone = \"se-sto1\"\n[[http_auth]]\nhost = \"api.upcloud.com\"\nprefix = \"Bearer \"\nsecret = \"upcloud_token\"\n";
let feed = BTreeMap::from([
("vendor.stripe.gunnar.wasm".to_string(), b64(b"\0asm\x01\0\0\0")),
("vendor.stripe.gunnar.toml".to_string(), vendor_block.to_string()),
("cloud.upcloud.wasm".to_string(), b64(b"\0asm\x01\0\0\0")),
("cloud.upcloud.p7s".to_string(), b64(b"sig")),
("cloud.upcloud.toml".to_string(), cloud.to_string()),
("plugin_root.korp".to_string(), b64(b"der")),
(FEED_MONETIZE_TOML.to_string(), fragment.to_string()),
]);
let dir = tempfile::tempdir().unwrap();
let lines = apply_pull(dir.path(), &feed).unwrap();
assert!(lines.iter().any(|l| l.contains("[monetize-toml-front-owns]") && l.contains("grpc_addr")), "{lines:?}");
let cfg = monetize_server::config::Config::load(&dir.path().join(CONFIG_REL)).unwrap();
assert_eq!(cfg.grpc_addr, GRPC, "the front decides where monetize listens");
assert_eq!(cfg.default_cloud.as_deref(), Some("upcloud"));
assert!(cfg.clouds.contains_key("server"), "the operator's own-iron cloud is taken");
let up = &cfg.clouds["upcloud"];
assert!(up.enabled && up.cap.and_then(|c| c.monthly_minor) == Some(5000), "the cap is the fed block's");
assert_eq!(up.settings.get("guest").and_then(|g| g.as_str()), Some(dir.path().join(CLOUDS_REL).join("upcloud.wasm").display().to_string().as_str()));
assert_eq!(std::fs::read(dir.path().join(CLOUDS_REL).join("upcloud.wasm.p7s")).unwrap(), b"sig");
assert!(!cfg.products.contains_key("ghost"), "a product this front does not serve is not taken");
assert!(!cfg.vendors.contains_key("klarna/gunnar"), "nor a vendor");
assert!(!cfg.vendors.contains_key("stripe/holger"), "nor another account of a served kind");
let s = &cfg.vendors["stripe/gunnar"];
assert!(s.enabled, "the fed block wins");
assert_eq!(s.settings.get("statement_descriptor").and_then(|v| v.as_str()), Some("ESTATE.TEST"), "the served vendor keeps what only the fragment says");
// a fragment that is not TOML is refused whole and the front's own config stands
let bad = BTreeMap::from([(FEED_MONETIZE_TOML.to_string(), "= nope".to_string())]);
assert!(apply_pull(dir.path(), &bad).unwrap().iter().any(|l| l.contains("[monetize-toml]")));
assert!(monetize_server::config::Config::load(&dir.path().join(CONFIG_REL)).is_ok());
}
#[test]
fn a_fed_monetize_principal_becomes_a_control_principal_and_nothing_else_names_one() {
use monetize_proto::control_auth::sshsig::ssh_key;
let ui = ssh_key::PrivateKey::from(ssh_key::private::Ed25519Keypair::from_seed(&[5; 32])).public_key().to_openssh().unwrap();
let dir = tempfile::tempdir().unwrap();
let feed = BTreeMap::from([
("monetize_principal.monetize-ui".to_string(), format!("machine admin {ui}")),
("monetize_principal.guest".to_string(), format!("machine none {ui}")),
("monetize_principal.bad".to_string(), "machine admin ssh-ed25519 notakey".to_string()),
(FEED_MONETIZE_TOML.to_string(), "[control_principals]\nintruder = \"ssh-ed25519 AAAA x\"\n".to_string()),
]);
let lines = apply_pull(dir.path(), &feed).unwrap();
assert!(lines.iter().any(|l| l.contains("[monetize-principal]") && l.contains("monetize_principal.guest") && l.contains("not admin")), "{lines:?}");
assert!(lines.iter().any(|l| l.contains("[monetize-principal]") && l.contains("monetize_principal.bad")), "{lines:?}");
assert!(lines.iter().any(|l| l.contains("[monetize-toml-front-owns]") && l.contains("control_principals")), "{lines:?}");
let cfg = monetize_server::config::Config::load(&dir.path().join(CONFIG_REL)).unwrap();
assert_eq!(cfg.control_principals, BTreeMap::from([("monetize-ui".to_string(), ui)]));
// Revoked: the key leaves the feed, the line leaves monetize's config.
assert!(apply_pull(dir.path(), &BTreeMap::new()).unwrap().is_empty());
assert!(monetize_server::config::Config::load(&dir.path().join(CONFIG_REL)).unwrap().control_principals.is_empty());
}
/// The signup guest lands as fed (pull: no primary to point it at, nothing
/// minted for it), the operator's plans under it.
#[test]
fn the_signup_guest_lands_as_fed_with_the_operators_plans() {
let fragment = "[signups.alpha]\nwelcome_url = \"https://alpha.rs/welcome\"\nmax_sold_percent_of_capacity = 600\n[signups.alpha.plans.\"alpha/10gb-1m\"]\namount_minor = 10000\ncurrency = \"Sek\"\ncap_bytes = 10737418240\nperiod_days = 30\n";
let feed = BTreeMap::from([
("signup_guest.alpha.wasm".to_string(), b64(b"\0asm\x01\0\0\0")),
("signup_guest.alpha.p7s".to_string(), b64(b"sig")),
("signup_guest.alpha.toml".to_string(), "grants = [\"grpc\", \"state\"]\n".to_string()),
("plugin_root.korp".to_string(), b64(b"der")),
(FEED_MONETIZE_TOML.to_string(), fragment.to_string()),
]);
let dir = tempfile::tempdir().unwrap();
apply_pull(dir.path(), &feed).unwrap();
let cfg = monetize_server::config::Config::load(&dir.path().join(CONFIG_REL)).unwrap();
let s = &cfg.signups["alpha"].settings;
assert_eq!(s.get("guest").and_then(|v| v.as_str()), Some(dir.path().join(SIGNUP_REL).join("alpha.wasm").display().to_string().as_str()));
assert!(s.get("endpoint").is_none() && s.get("identity_key").is_none() && s.get("bearer_token").is_none(), "monetize dials no estate: {s:?}");
assert!(s.get("product").is_none(), "the block's name is its product: {s:?}");
assert_eq!(cfg.signup_settings("alpha").unwrap().get("product").and_then(|v| v.as_str()), Some("alpha"));
assert_eq!(cfg.signups["alpha"].plans["alpha/10gb-1m"].cap_bytes, 10737418240, "the operator's plans stand under it");
assert_eq!(std::fs::read(dir.path().join(SIGNUP_REL).join("alpha.wasm.p7s")).unwrap(), b"sig");
}
/// ★ **A fed DNS guest lands as its `[dns_writers.<name>]`** (the owner 2026-10-11: the
/// provider is a signed plugin, never linked) — the module and its signature written, the
/// block with the path the front wrote and the plugin root it is verified under.
#[test]
fn a_fed_dns_guest_becomes_its_dns_writers_block() {
let block = "grants = [\"http\"]\nhttp_hosts = [\"api.cloudflare.com\"]\nzone_id = \"z1\"\n[[http_auth]]\nhost = \"api.cloudflare.com\"\nprefix = \"Bearer \"\nsecret = \"dns_cloudflare_token\"\n";
let feed = BTreeMap::from([
("dns_guest.cloudflare.wasm".to_string(), b64(b"\0asm\x01\0\0\0")),
("dns_guest.cloudflare.p7s".to_string(), b64(b"sig")),
("dns_guest.cloudflare.toml".to_string(), block.to_string()),
("plugin_root.korp".to_string(), b64(b"der")),
]);
let dir = tempfile::tempdir().unwrap();
apply_pull(dir.path(), &feed).unwrap();
let cfg = monetize_server::config::Config::load(&dir.path().join(CONFIG_REL)).unwrap();
let b = cfg.dns_writers.get("cloudflare").expect("[dns_writers.cloudflare]");
assert_eq!(b.get("guest").and_then(|v| v.as_str()), Some(dir.path().join(DNS_REL).join("cloudflare.wasm").display().to_string().as_str()));
assert_eq!(b.get("zone_id").and_then(|v| v.as_str()), Some("z1"));
assert!(b.get("guest_trust_roots").and_then(|v| v.as_array()).is_some_and(|a| !a.is_empty()), "verified under the plugin root: {b:?}");
assert_eq!(std::fs::read(dir.path().join(DNS_REL).join("cloudflare.wasm.p7s")).unwrap(), b"sig");
}
#[test]
fn a_fed_vendor_guest_becomes_its_vendors_block_with_the_path_the_front_wrote() {
let block = "enabled = true\ncountry = \"IE\"\ngrants = [\"http\", \"hmac\", \"state\"]\nhttp_hosts = [\"api.stripe.com\"]\nhmac_secrets = [\"stripe_webhook\"]\nsuccess_url = \"https://alpha.rs/paid/{reference}\"\ncancel_url = \"https://alpha.rs/cancel/{reference}\"\nlivemode = false\nwebhook_secret = \"stripe_webhook\"\n\n[[http_auth]]\nhost = \"api.stripe.com\"\nprefix = \"Bearer \"\nsecret = \"stripe_key\"\n";
let feed = BTreeMap::from([
("vendor.stripe.gunnar.wasm".to_string(), b64(b"\0asm\x01\0\0\0")),
("vendor.stripe.gunnar.p7s".to_string(), b64(b"sig")),
("vendor.stripe.gunnar.toml".to_string(), block.to_string()),
("vendor.klarna.gunnar.p7s".to_string(), b64(b"sig")),
("vendor.stripe.wasm".to_string(), b64(b"\0asm\x01\0\0\0")),
("plugin_root.korp".to_string(), b64(b"der")),
]);
let (vs, refused) = vendors(&feed);
assert_eq!(vs.len(), 1);
assert!(refused.iter().any(|r| r.contains("[vendor-no-module]") && r.contains("klarna.gunnar")), "{refused:?}");
assert!(refused.iter().any(|r| r.contains("[vendor-name]") && r.contains("`vendor.stripe.*`")), "a vendor names its account: {refused:?}");
let dir = tempfile::tempdir().unwrap();
apply_pull(dir.path(), &feed).unwrap();
assert_eq!(std::fs::read(dir.path().join(VENDORS_REL).join("stripe.gunnar.wasm")).unwrap(), b"\0asm\x01\0\0\0");
assert_eq!(std::fs::read(dir.path().join(VENDORS_REL).join("stripe.gunnar.wasm.p7s")).unwrap(), b"sig");
let cfg = monetize_server::config::Config::load(&dir.path().join(CONFIG_REL)).unwrap();
let v = cfg.vendors.get("stripe/gunnar").expect("[vendors.stripe.gunnar]");
assert_eq!(v.guest.as_deref(), Some(dir.path().join(VENDORS_REL).join("stripe.gunnar.wasm").as_path()));
assert_eq!(v.guest_trust_roots, vec![dir.path().join(ROOTS_REL).join("korp.der")]);
assert_eq!(v.http_auth.len(), 1);
assert_eq!(v.hmac_secrets, vec!["stripe_webhook".to_string()]);
let text = std::fs::read_to_string(dir.path().join(CONFIG_REL)).unwrap();
assert!(["rk_live", "rk_test", "sk_live", "sk_test", "whsec_"].iter().all(|k| !text.contains(k)), "the block names secrets, it never holds one");
}
/// Lane L26: a product the binary links is fed with no module and its block names no
/// guest; an old module of it is removed; an unlinked one is still refused by name.
#[test]
fn a_linked_product_needs_no_module() {
let feed = BTreeMap::from([("plugin.holger.toml".to_string(), "instance = \"holger-rs\"\nfree = true\n".to_string()), ("plugin.kassa.toml".to_string(), "free = true\n".to_string())]);
let (ps, _, refused) = plugins_linked(&feed, &["holger"]);
assert_eq!(ps.iter().map(|p| p.id.as_str()).collect::<Vec<_>>(), vec!["holger"]);
assert!(refused.len() == 1 && refused[0].contains("[plugin-no-module]") && refused[0].contains("plugin.kassa.wasm"), "{refused:?}");
assert!(plugins(&feed).2.iter().any(|r| r.contains("plugin.holger.wasm")), "the open set links no holger");
let dir = tempfile::tempdir().unwrap();
std::fs::create_dir_all(dir.path().join(GUESTS_REL)).unwrap();
std::fs::write(dir.path().join(GUESTS_REL).join("holger.wasm"), b"\0asm old").unwrap();
apply_pull_linked(dir.path(), &feed, &["holger"]).unwrap();
assert!(!dir.path().join(GUESTS_REL).join("holger.wasm").exists(), "no old module beside a block that names none");
let cfg = monetize_server::config::Config::load(&dir.path().join(CONFIG_REL)).unwrap();
assert!(cfg.products["holger"].guest.is_none() && cfg.products["holger"].guest_trust_roots.is_empty(), "the native plugin serves it");
assert!(!cfg.products.contains_key("kassa"));
}
#[test]
fn with_no_plugin_fed_monetize_is_bare() {
let dir = tempfile::tempdir().unwrap();
assert!(config(dir.path(), &[], &BTreeMap::new()).products.is_empty());
assert!(apply_pull(dir.path(), &BTreeMap::new()).unwrap().is_empty());
assert!(monetize_server::config::Config::load(&dir.path().join(CONFIG_REL)).unwrap().products.is_empty());
}
#[test]
fn a_fed_plugin_is_refused_by_name_when_it_is_not_whole() {
let wasm = b64(b"\0asm\x01\0\0\0");
let f = |pairs: &[(&str, &str)]| pairs.iter().map(|(k, v)| (k.to_string(), v.to_string())).collect::<BTreeMap<_, _>>();
let refused = |feed: BTreeMap<String, String>| plugins(&feed).2.join("\n");
assert!(refused(f(&[("plugin.alpha.toml", "grants = []")])).contains("[plugin-no-module]"));
// Prices with no module are refused: no product brings its own module any more.
assert!(refused(f(&[("plugin.alpha.prices", "[SE]\ndisk_gib_month = 1000\n")])).contains("[plugin-no-module]"));
assert!(refused(f(&[("plugin.beta.prices", "[SE]\nrepo_month = 100\n")])).contains("[plugin-no-module]"));
assert!(refused(f(&[("plugin.alpha.wasm", &wasm)])).contains("[plugin-no-block]"));
assert!(refused(f(&[("plugin.alpha.wasm", &b64(b"MZ")), ("plugin.alpha.toml", "")])).contains("[plugin-not-wasm]"));
assert!(refused(f(&[("plugin.alpha.wasm", "!!"), ("plugin.alpha.toml", "grants = []")])).contains("[plugin-base64]"));
assert!(refused(f(&[("plugin.alpha.wasm", &wasm), ("plugin.alpha.toml", "grants = 7")])).contains("[plugin-block]"));
assert!(refused(f(&[("plugin.../x.wasm", &wasm), ("plugin.../x.toml", "")])).contains("[plugin-id]"));
// RA2-16: the retired `/kop` keys are refused by name, nothing of the block is taken.
let retired = refused(f(&[("plugin.alpha.wasm", &wasm), ("plugin.alpha.toml", "instance = \"a\"\nfree = true\nreturn_hosts = [\"a.example\"]")]));
assert!(retired.contains("[plugin-block]") && retired.contains("return_hosts"), "{retired}");
assert!(refused(f(&[("plugin.alpha.wasm", &wasm), ("plugin.alpha.toml", "grants = []")])).contains("[plugin-no-prices]"), "no prices, and not `free = true`");
let (ok, _, none) = plugins(&f(&[("plugin.alpha.wasm", &wasm), ("plugin.alpha.toml", "instance = \"alpha-rs\"\nfree = true\ngrants = []")]));
assert!(none.is_empty() && ok.len() == 1 && ok[0].block.grants.is_empty());
}
/// ★ **A product block names the estate it is PULLED from** (A28 H26, T12 12.5): a block
/// with no `instance` would be a product monetize calls directly — the guest's push ops,
/// which the guests no longer answer — so the feed refuses it by name, and nothing of it
/// is taken. A block with its `instance` is taken as before.
#[test]
fn a_product_block_without_its_instance_is_refused_by_name() {
let wasm = b64(b"\0asm\x01\0\0\0");
let f = |toml: &str| BTreeMap::from([("plugin.alpha.wasm".to_string(), wasm.clone()), ("plugin.alpha.toml".to_string(), toml.to_string())]);
for block in ["free = true\nendpoint = \"10.9.9.9:50051\"\n", "free = true\ninstance = \" \"\nendpoint = \"10.9.9.9:50051\"\n"] {
let (ok, _, refused) = plugins(&f(block));
assert!(ok.is_empty(), "{block:?}: taken");
assert!(refused.len() == 1 && refused[0].contains("[plugin-no-instance]") && refused[0].contains("[products.alpha]"), "{block:?}: {refused:?}");
}
// T2-BOX: a block that dials nothing and names no home yet is HELD: not served, not refused.
for block in ["free = true\ngrants = []\n", "free = true\ninstance = \" \"\n"] {
let (ok, _, refused) = plugins(&f(block));
assert!(ok.is_empty() && refused.is_empty(), "{block:?}: held, neither taken nor refused: {refused:?}");
}
let (ok, _, refused) = plugins(&f("free = true\ninstance = \"alpha-rs\"\n"));
assert!(refused.is_empty() && ok.len() == 1 && ok[0].block.instance.as_deref() == Some("alpha-rs"), "{refused:?}");
}
/// A fed plugin: the module, its signature and the roots are written before
/// the block that names them; the block is served as fed — no endpoint, no
/// twin, no appliance key filled in (monetize dials no estate); a block's
/// own fields stand.
#[test]
fn a_fed_plugin_is_written_and_served_as_fed() {
let dir = tempfile::tempdir().unwrap();
let feed = BTreeMap::from([
("plugin.alpha.wasm".to_string(), b64(b"\0asm\x01\0\0\0")),
("plugin.alpha.p7s".to_string(), b64(b"sig")),
("plugin.alpha.toml".to_string(), "instance = \"alpha-rs\"\nvendor = \"stripe/alpha\"\ngrants = []\n".to_string()),
("plugin.alpha.prices".to_string(), "[SE]\ndisk_gib_month = 2200\n".to_string()),
("vendor.stripe.alpha.wasm".to_string(), b64(b"\0asm\x01\0\0\0")),
("vendor.stripe.alpha.toml".to_string(), "enabled = true\n".to_string()),
("plugin.beta.wasm".to_string(), b64(b"\0asm\x01\0\0\0")),
("plugin.beta.toml".to_string(), "instance = \"beta-rs\"\nfree = true\nguest_digest = \"ab\"\ngrants = []\n".to_string()),
("plugin_root.owner".to_string(), b64(b"DER")),
// An estate's offer is nothing to the ONE monetize.
("offer.vm3.ticket_pubkey".to_string(), "cd".repeat(32)),
]);
assert!(apply_pull(dir.path(), &feed).unwrap().is_empty());
let cfg = monetize_server::config::Config::load(&dir.path().join(CONFIG_REL)).unwrap();
let g = &cfg.products["alpha"];
assert_eq!(g.endpoint, "", "monetize dials no estate");
assert!(g.guest_grpc_hosts.is_empty() && g.restoring_from_twin.is_none() && g.appliance_key.is_none());
assert!(g.bearer_token.is_none());
assert_eq!(g.guest_trust_roots, vec![dir.path().join(ROOTS_REL).join("owner.der")]);
assert_eq!(g.guest.as_deref(), Some(dir.path().join(GUESTS_REL).join("alpha.wasm").as_path()));
assert_eq!(std::fs::read(dir.path().join(GUESTS_REL).join("alpha.wasm.p7s")).unwrap(), b"sig");
assert_eq!(std::fs::read(dir.path().join(ROOTS_REL).join("owner.der")).unwrap(), b"DER");
assert_eq!(cfg.products["beta"].instance.as_deref(), Some("beta-rs"), "a block's own field is kept as fed");
assert!(!dir.path().join(MONETIZE_REL).join("identity-alpha").exists(), "nothing is minted for a product");
}
/// ★ **G6, the solo monetize (pull model, owner 2026-10-10)**: ONE monetize
/// serves every product from its own box and never dials an estate, so a
/// product's block has no endpoint and never waits for a primary. Two
/// products, each billed through its own Stripe instance and pulled from its own estate,
/// are both served.
#[test]
fn the_solo_monetize_serves_every_fed_product_with_no_endpoint_to_wait_for() {
let dir = tempfile::tempdir().unwrap();
let wasm = b64(b"\0asm\x01\0\0\0");
let feed = BTreeMap::from([
("plugin.gunnar.wasm".to_string(), wasm.clone()),
("plugin.gunnar.toml".to_string(), "instance = \"gunnar-rs\"\nvendor = \"stripe/gunnar\"\nguest_digest = \"ab\"\ngrants = []\n".to_string()),
("plugin.gunnar.prices".to_string(), "[SE]\ndisk_gib_month = 2200\n".to_string()),
("plugin.holger.wasm".to_string(), wasm.clone()),
("plugin.holger.toml".to_string(), "instance = \"holger-rs\"\nvendor = \"stripe/holger\"\nguest_digest = \"cd\"\ngrants = []\n".to_string()),
("plugin.holger.prices".to_string(), "[SE]\nrepo_month = 4900\n".to_string()),
("vendor.stripe.gunnar.wasm".to_string(), wasm.clone()),
("vendor.stripe.gunnar.toml".to_string(), "enabled = true\nguest_digest = \"ef\"\n".to_string()),
("vendor.stripe.holger.wasm".to_string(), wasm.clone()),
("vendor.stripe.holger.toml".to_string(), "enabled = true\nguest_digest = \"01\"\n".to_string()),
]);
assert!(apply_pull(dir.path(), &feed).unwrap().is_empty());
let cfg = monetize_server::config::Config::load(&dir.path().join(CONFIG_REL)).unwrap();
assert_eq!(cfg.grpc_addr, GRPC, "loopback only: the box's edge carries what is public");
for (id, vendor) in [("gunnar", "stripe/gunnar"), ("holger", "stripe/holger")] {
let p = cfg.products.get(id).unwrap_or_else(|| panic!("{id} is served"));
assert_eq!(p.endpoint, "", "{id}: monetize never dials an estate");
assert_eq!(p.vendor.as_deref(), Some(vendor));
assert_eq!(p.guest.as_deref(), Some(dir.path().join(GUESTS_REL).join(format!("{id}.wasm")).as_path()));
assert!(p.bearer_token.is_none() && p.restoring_from_twin.is_none());
}
assert!(cfg.vendors.contains_key("stripe/gunnar") && cfg.vendors.contains_key("stripe/holger"));
// (G4's signup guests are not fed here: every product is PULLED now (A28 H26), and a
// pulled product's accounts are made by its estate's main, never by a signup guest.)
}
/// **A plugin's prices** (`plugin.<id>.prices`) become monetize's
/// `[prices.<id>.<market>]`; a product fed none is REFUSED by name
/// ([plugin-no-prices], owner 2026-09-25) unless its block says
/// `free = true`, and monetize is told to refuse it too
/// (`require_prices`); a malformed list is refused by name and the plugin
/// not taken.
#[test]
fn a_plugins_prices_become_monetizes_price_list_and_a_malformed_list_is_refused() {
let dir = tempfile::tempdir().unwrap();
let wasm = b64(b"\0asm\x01\0\0\0");
let feed = BTreeMap::from([
("plugin.beta.wasm".to_string(), wasm.clone()),
("plugin.beta.toml".to_string(), "instance = \"beta-rs\"\nvendor = \"stripe/beta\"\nguest_digest = \"ab\"\ngrants = []\n".to_string()),
("plugin.beta.prices".to_string(), "[SE]\nrepo_month = 100\n[FI]\nrepo_month = 10\n".to_string()),
("vendor.stripe.beta.wasm".to_string(), wasm.clone()),
("vendor.stripe.beta.toml".to_string(), "enabled = true\n".to_string()),
("plugin.kassa.wasm".to_string(), wasm.clone()),
("plugin.kassa.toml".to_string(), "instance = \"kassa-rs\"\nguest_digest = \"ab\"\ngrants = [\"http\"]\n".to_string()),
]);
let refused = apply_pull(dir.path(), &feed).unwrap();
assert!(refused.len() == 1 && refused[0].starts_with("REFUSED [plugin-no-prices] `plugin.kassa.*`"), "{refused:?}");
let cfg = monetize_server::config::Config::load(&dir.path().join(CONFIG_REL)).unwrap();
assert!(cfg.require_prices, "monetize refuses an unpriced product on the paid front too");
assert!(!cfg.products.contains_key("kassa"), "an unpriced product is not served free by accident");
assert_eq!(cfg.prices["beta"]["SE"]["repo_month"], 100);
assert_eq!(cfg.prices["beta"]["FI"]["repo_month"], 10);
// `free = true`, said on purpose: served, with no list.
let mut free = feed.clone();
free.insert("plugin.kassa.toml".into(), "instance = \"kassa-rs\"\nfree = true\nguest_digest = \"ab\"\ngrants = [\"http\"]\n".into());
assert!(apply_pull(dir.path(), &free).unwrap().is_empty());
let cfg = monetize_server::config::Config::load(&dir.path().join(CONFIG_REL)).unwrap();
assert!(cfg.products["kassa"].free && !cfg.prices.contains_key("kassa"));
let mut bad = feed.clone();
bad.insert("plugin.beta.prices".into(), "SE = 7".into());
let (ok, _, refused) = plugins(&bad);
assert!(refused.iter().any(|r| r.contains("[plugin-prices]")) && !ok.iter().any(|p| p.id == "beta"));
}
/// **A real signed guest arriving in the feed is admitted by
/// monetize's own gates** (the registry built as `serve` builds it), and a
/// module that is not the one its block pins is refused by name and
/// skipped at start.
#[test]
fn a_real_guest_fed_with_its_digest_is_admitted_by_monetize_and_a_forged_one_is_not() {
let Some(wasm) = a_real_guest() else { return };
let id = std::env::var("MONETIZE_FRONT_TEST_GUEST_ID").unwrap_or_else(|_| "holger".to_string());
let dir = tempfile::tempdir().unwrap();
prepare(dir.path()).unwrap();
let digest = blake3::hash(&wasm).to_hex().to_string();
// Signed under the fed plugin root, as every guest monetize runs is (owner 2026-10-17).
use guest_host::verify::sign::{self, dev};
let (ca_key, ca_der) = dev::mint_ca("plugin-root").unwrap();
let p7s = sign::sign_module(&wasm, &dev::new_ed25519_signer(&ca_key, &ca_der, "plugin-signing").unwrap()).unwrap();
let mut feed = BTreeMap::from([
(format!("plugin.{id}.wasm"), b64(&wasm)),
(format!("plugin.{id}.p7s"), b64(&p7s)),
("plugin_root.korp".to_string(), b64(&ca_der)),
(format!("plugin.{id}.toml"), format!("instance = \"{id}-rs\"\nfree = true\nguest_digest = \"{digest}\"\ngrants = []\n")),
]);
apply_pull(dir.path(), &feed).unwrap();
let cfg = monetize_server::config::Config::load(&dir.path().join(CONFIG_REL)).unwrap();
let reg = monetize_server::registry::Registry::from_config_with(&cfg, &monetize_server::registry::Linked::open()).unwrap();
assert_eq!(reg.product_ids(), vec![id.clone()]);
// Another module under the same pinned digest: refused before it compiles.
let mut forged = wasm.clone();
forged.push(0);
feed.insert(format!("plugin.{id}.wasm"), b64(&forged));
apply_pull(dir.path(), &feed).unwrap();
let cfg = monetize_server::config::Config::load(&dir.path().join(CONFIG_REL)).unwrap();
// Refused by name and SKIPPED — never the end of monetize's start (it
// was: a refused plugin kept in the config stopped the whole server).
let reg = monetize_server::registry::Registry::from_config_with(&cfg, &monetize_server::registry::Linked::open()).expect("a refused guest does not stop the start");
assert!(reg.product_ids().is_empty(), "the forged module is not served");
assert!(reg.start_refusals().iter().any(|r| r.to_string().starts_with(&format!("guest REFUSED [guest-refused-at-start] [products.{id}]"))));
}
}