server/lib/src/room.rs
//! **Where more room for a repository is bought** (`holger.room.v1`, 2026-10-17).
//!
//! The console is a thin layer. It shows what a repository holds
//! (`UsageService.ReadUsage`, caps included) and the box's disk
//! (`AdminService.Capacity`), and for buying it links the person to THIS SITE's own buy pages
//! (A12, owner 2026-10-23: the buyer stays on the site; the shared buy flow, monetize-site-buy,
//! on holger's front relays every step to the ONE box). It never prices, orders or refuses an
//! order itself.
//!
//! **The relay's doors** ([`buy_me`], [`buy_ticket`], [`signup_account`]): the front holds the
//! site's bearer at the ONE box and asks this box who a request's person is, for that person's
//! own actor ticket (signed by this box's fleet key, as before), and whether a paid sign-up's
//! account holds its passkey yet.
//!
//! The shop is `HOLGER_SHOP_URL` when set. Without it, a metered build points at
//! the ONE shop (`ONE_SHOP`) and an open build sells nothing (an empty link).
//!
//! **The actor ticket** (metered): the ONE box sells only to a buyer the product vouched for.
//! The site's buy relay asks this box for the person's ticket ([`buy_ticket`]):
//! monetize-embed's `ActorTicket` (ten minutes, one use), signed by this box's own fleet key
//! (`fleet/box.ed25519`, the key the ONE box pinned for this main). Holger vouches for the
//! PERSON, not an amount: the plan and its price are the shop's. (The ONE box's own `/kop` page
//! that once carried it as `ticket=` is deleted: OD6, 2026-10-26.)
//!
//! **A private Holger** (`PrivateEstate`, 2026-10-21): any person signed in here may order an
//! estate of their own (Gunnar's rule, lane SU1: the order is the signed-in account's) — front, main and twin, laid by the ONE box — on
//! this site's own sign-up page (`/signup?plan=private`, `PRIVATE_CHOICE`; A12, owner 2026-10-23:
//! the buyer never sees monetize), which relays the order to the ONE box; the console follows no
//! other link (A28 M10, holger-ui `private_estate_link`). The relay names this site and the person, and carries
//! their `estate` ticket: the same `ActorTicket`, purpose `estate`, tenant the person's
//! principal, signed by the same fleet key. The ONE box checks it under the key it pinned for
//! this site, so the order is that person's and nobody else's.
use std::sync::Arc;
use tonic::{Request, Response, Status};
use crate::grpc::holger_room_proto as pb;
use crate::grpc::HolgerGrpc;
/// The ONE shop: the box that sells every product of the suite. Only the
/// metered build knows it; an open build links no shop unless told one.
#[cfg(feature = "metered")]
pub const ONE_SHOP: &str = "https://monetize.ignalina.dk";
/// The environment variable that names the shop (empty: sells nothing).
pub const SHOP_ENV: &str = "HOLGER_SHOP_URL";
/// What the console shows when this box sells nothing.
pub const SELLS_NOTHING: &str =
"more room for this repository is set by the operator of this server";
/// What the console shows beside the shop link (the paid build's words; an open build sells
/// nothing and says none of them, A28 M13).
#[cfg(feature = "metered")]
pub const BUY_THERE: &str = "more room for this repository is bought on its plan page";
/// **The shop this build links to**: `HOLGER_SHOP_URL`, else [`default_shop`]. An open build
/// links none and does not read the variable (A28 M13): it sells nothing whatever it is told.
pub fn shop() -> String {
#[cfg(feature = "metered")]
return std::env::var(SHOP_ENV).unwrap_or_else(|_| default_shop().to_owned());
#[cfg(not(feature = "metered"))]
String::new()
}
/// The shop this build links to, before the environment is read.
pub fn default_shop() -> &'static str {
#[cfg(feature = "metered")]
return ONE_SHOP;
#[cfg(not(feature = "metered"))]
""
}
/// What the console shows when the person may not buy (not an admin of the repository).
#[cfg(feature = "metered")]
pub const AN_ADMIN_BUYS: &str = "more room for this repository is bought by an admin of it";
/// What the console shows beside the private-Holger link.
#[cfg(feature = "metered")]
pub const ORDER_THERE: &str = "your own private Holger is ordered on this site";
/// **The site's own buy pages** (holger-ui's `/signup`, A12): the account (a free account, a
/// plan, the account's grow), the private choice, and an instance's grow.
#[cfg(feature = "metered")]
pub const ACCOUNT_PAGE: &str = "/signup?plan=account";
#[cfg(feature = "metered")]
pub const PRIVATE_CHOICE: &str = "/signup?plan=private";
#[cfg(feature = "metered")]
pub const GROW_PAGE: &str = "/signup?plan=grow";
/// What the console shows beside the grow link.
#[cfg(feature = "metered")]
pub const BUY_HERE: &str = "more room for this account is bought on its plan page";
/// What the console shows when this box sells no private Holger.
pub const ORDERS_NOTHING: &str = "a private Holger is ordered from the operator of this server";
/// Pure: **the private-Holger order link**: this site's own private choice, where a shop sells
/// here (`shop` non-empty, a site named); the words say whether the person must sign in first.
pub fn private_reply_for(shop: &str, site: &str, signed_in: bool) -> pb::PrivateEstateReply {
if shop.trim().is_empty() || site.trim().is_empty() {
return pb::PrivateEstateReply {
shop_url: String::new(),
words: ORDERS_NOTHING.to_owned(),
};
}
#[cfg(not(feature = "metered"))]
{
// An open build orders nothing, whatever it is handed (A28 M13).
let _ = signed_in;
pb::PrivateEstateReply {
shop_url: String::new(),
words: ORDERS_NOTHING.to_owned(),
}
}
#[cfg(feature = "metered")]
pb::PrivateEstateReply {
shop_url: PRIVATE_CHOICE.to_owned(),
words: if signed_in {
ORDER_THERE
} else {
SIGN_IN_TO_ORDER
}
.to_owned(),
}
}
/// What the sign-up page shows a visitor beside the private-Holger choice.
#[cfg(feature = "metered")]
pub const SIGN_IN_TO_ORDER: &str =
"a private Holger is ordered by a person signed in here: sign in, or buy an account first";
/// The plan word `/-/signup` answers for an account that holds no paid plan (lane G12): its
/// grow choice is "Buy a plan".
pub const PLAN_FREE: &str = "free";
/// The plan word for an account that holds one: its grow choice is "Grow your account".
#[cfg(feature = "metered")]
pub const PLAN_HELD: &str = "plan";
/// **The sign-up page's choices** (`GET /-/signup`, lanes SU1 and G12, Gunnar's flow): the ONE
/// shop's account sign-up for this site and its private-estate order — for a visitor the
/// pay-once page with no ticket, for a signed-in person with their ACCOUNT and its `estate`
/// ticket — and, signed in, the account's plan word with its grow link (the Room link) and, on
/// a sold private estate, the change of that estate's size. Empty links: this box sells
/// nothing. Every price, plan and payment is the shop's.
#[derive(Clone, Debug, Default, PartialEq, Eq, serde::Serialize)]
pub struct SignupLinks {
pub account_url: String,
pub private_offered: bool,
pub private_url: String,
pub signed_in: bool,
/// The person's account: the repository a redeemed sign-up bound them to (`x14`), on a
/// sold estate the buyer's account name, else their subject. Empty for a visitor.
pub account: String,
/// [`PLAN_FREE`] or [`PLAN_HELD`]; empty for a visitor.
pub plan: String,
/// "Buy a plan" / "Grow your account": the shop's page for more room for the account.
pub grow_account_url: String,
/// The company of the sold private estate this box is (`acme`); empty elsewhere.
pub instance: String,
/// "Grow <company>'s Holger": the shop's change of that estate's size.
pub grow_instance_url: String,
pub words: String,
}
/// **Who is signed in, as the sign-up page needs them**: their account, whether it holds a
/// paid plan, whether the account is theirs to grow (a sign-up bound it to them), and the
/// company of the sold estate this box is.
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Signer<'a> {
pub account: &'a str,
pub holds_plan: bool,
pub can_grow: bool,
pub instance: Option<&'a str>,
}
/// Pure: the sign-up page's links at the shop `shop` (empty: none) for the site `site`, for
/// the person signed in when there is one.
#[cfg(feature = "metered")]
pub fn signup_links_for(shop: &str, site: &str, person: Option<&Signer<'_>>) -> SignupLinks {
let mut links = SignupLinks {
signed_in: person.is_some(),
account: person.map(|p| p.account.to_owned()).unwrap_or_default(),
plan: person
.map(|p| if p.holds_plan { PLAN_HELD } else { PLAN_FREE }.to_owned())
.unwrap_or_default(),
..SignupLinks::default()
};
let base = shop.trim().trim_end_matches('/');
let site = site.trim();
if base.is_empty() || site.is_empty() {
links.words = ORDERS_NOTHING.to_owned();
return links;
}
// The site's own pages (A12): the relay on the front settles the sign-up and the orders.
links.account_url = ACCOUNT_PAGE.to_owned();
links.private_offered = true;
links.private_url = PRIVATE_CHOICE.to_owned();
links.words = ORDER_THERE.to_owned();
if let Some(p) = person {
if p.account.is_empty() {
links.words = SIGN_IN_TO_ORDER.to_owned();
}
// ★ BUY-ONE-2 (Gunnar's `may_buy`): the private tab is a buyer's; a signed-in person
// with no account of their own (the operator) is offered none.
if !p.can_grow {
links.private_offered = false;
links.private_url = String::new();
}
if p.can_grow {
links.grow_account_url = ACCOUNT_PAGE.to_owned();
}
if let Some(company) = p.instance.filter(|c| !c.is_empty()) {
links.instance = company.to_owned();
links.grow_instance_url = format!("{GROW_PAGE}&instance={}", encode(company));
}
}
links
}
/// The environment variable naming the site a sold private Holger was bought at
/// (`https://holger.rs`; Gunnar's `GUNNAR_UI_PROVIDER`).
pub const PROVIDER_ENV: &str = "HOLGER_PROVIDER";
/// Pure: **a provider as the operator names it** — an `https://` site with no path (Gunnar's
/// `provider_named`), else none.
pub fn provider_named(raw: Option<&str>) -> Option<String> {
let site = raw?.trim().trim_end_matches('/');
let host = site.strip_prefix("https://")?;
(!host.is_empty()
&& host
.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '.' || c == '-' || c == ':'))
.then(|| site.to_owned())
}
/// Pure: **the provider of the sold estate `company` reached at `host`** — the estate's own name
/// less its company (`acme.holger.rs` → `https://holger.rs`); an own domain names none.
pub fn provider_from(company: &str, host: &str) -> Option<String> {
let rest = host.trim().trim_end_matches('.').to_ascii_lowercase();
let apex = rest.strip_prefix(&format!("{company}."))?;
(apex.contains('.')
&& apex
.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '.' || c == '-'))
.then(|| format!("https://{apex}"))
}
/// Pure: **the sign-up page's links on a sold private Holger** (Gunnar's `auth::routes::shop`
/// for a provided console): nothing is bought here — no account, no grow of the account; "New
/// instance" and "Grow <company>'s Holger" are the provider's pages, and the line says where it
/// was bought. No provider known: nothing is offered.
#[cfg(feature = "metered")]
pub fn sold_links_for(
provider: Option<&str>,
company: &str,
person: Option<&Signer<'_>>,
) -> SignupLinks {
let mut links = SignupLinks {
signed_in: person.is_some(),
account: person.map(|p| p.account.to_owned()).unwrap_or_default(),
plan: person
.map(|p| if p.holds_plan { PLAN_HELD } else { PLAN_FREE }.to_owned())
.unwrap_or_default(),
..SignupLinks::default()
};
let Some(p) = provider
.map(|p| p.trim_end_matches('/'))
.filter(|p| !p.is_empty())
else {
links.words = ORDERS_NOTHING.to_owned();
return links;
};
links.private_offered = true;
links.private_url = format!("{p}{PRIVATE_CHOICE}");
links.words = ORDER_THERE.to_owned();
if person.is_some() {
links.grow_instance_url = format!("{p}{GROW_PAGE}&instance={}", encode(company));
let site = p.strip_prefix("https://").unwrap_or(p);
links.words =
format!("This Holger was bought at {site}. More room for it is bought there.");
}
links
}
/// Pure: **an account name** as the shop's sign-up takes one (`^[a-z0-9][a-z0-9._-]{1,31}$`).
pub fn account_name_ok(name: &str) -> bool {
let b = name.as_bytes();
(2..=32).contains(&b.len())
&& (b[0].is_ascii_lowercase() || b[0].is_ascii_digit())
&& b.iter().all(|c| {
c.is_ascii_lowercase() || c.is_ascii_digit() || matches!(c, b'.' | b'_' | b'-')
})
}
/// Pure: **the person's account** (A10 row 12): the repository a redeemed sign-up bound
/// `subject` to (`sold`, the record the twin carries); on a sold estate, its buyer (the estate's
/// owners' group) by the account name the shop's first-login invite gave them (`display`);
/// anybody else by `subject`, as before.
pub fn account_for(
sold: &std::collections::BTreeMap<String, crate::sold::SoldRepository>,
subject: &str,
display: &str,
groups: &[String],
) -> String {
if let Some((name, _)) = sold
.iter()
.find(|(_, s)| s.owner == subject && !s.signup.is_empty())
{
return name.clone();
}
if groups.iter().any(|g| g == crate::auth::ESTATE_OWNERS) && account_name_ok(display) {
return display.to_owned();
}
subject.to_owned()
}
/// Pure: **the company of a sold private estate**, from the instance this box reports for: the
/// ONE box names a sold estate's boxes by its company (`fleet_instance = acme`), a site by its
/// host or label (`holger.rs`, `test3-holger-rs`). `None`: this box is a site.
pub fn sold_company(instance: &str) -> Option<&str> {
let b = instance.as_bytes();
((1..=16).contains(&b.len())
&& b[0].is_ascii_lowercase()
&& b.iter()
.all(|c| c.is_ascii_lowercase() || c.is_ascii_digit()))
.then_some(instance)
}
/// **Does `account` hold a paid plan here**: a stored fact for it that is not `Free`.
#[cfg(feature = "metered")]
pub fn account_holds_plan(data_root: &std::path::Path, account: &str) -> bool {
let store = crate::metered::EntitlementStore::awaiting(
crate::metered::store_dir(data_root),
Arc::new(Default::default()),
);
matches!(store.get(account), Ok(Some(f)) if !matches!(f.state, monetize_product::State::Free))
}
/// **The account of the person signed in**, read from this box's sold record.
pub fn person_account(
data_root: Option<&std::path::Path>,
who: &crate::auth::AuthIdentity,
) -> String {
let sold = data_root.map(crate::sold::read).unwrap_or_default();
account_for(
&sold,
&who.subject,
who.display.as_deref().unwrap_or_default(),
&who.groups,
)
}
/// **This site, as the shop knows it**, from what a door holds: the estate this box reports
/// for (its enrolment under `data_root`), else the passkey relying party's host.
#[cfg(feature = "metered")]
pub fn site_from(data_root: Option<&std::path::Path>, auth: &crate::auth::AuthConfig) -> String {
let enrolled = data_root
.and_then(|root| holger_fleet::config::FleetConfig::load(root).ok().flatten())
.map(|c| c.instance.trim().to_owned())
.filter(|i| !i.is_empty());
enrolled
.or_else(|| {
origin_of(auth).map(|o| {
o.trim_end_matches('/')
.rsplit("://")
.next()
.unwrap_or_default()
.to_owned()
})
})
.unwrap_or_default()
}
/// The passkey relying party's origin, `https://host/`.
#[cfg(feature = "metered")]
fn origin_of(auth: &crate::auth::AuthConfig) -> Option<String> {
auth.methods.iter().find_map(|m| match m {
// WWW-CANON: the site's own origin (`https://www.<rp_id>`), not the kept bare one.
crate::auth::AuthMethodConfig::Passkey {
rp_id, rp_origin, ..
} => Some(format!("{}/", crate::auth::site_origin(rp_id, rp_origin))),
_ => None,
})
}
/// **The sign-up page's links for the person asking** (`GET /-/signup`): the shop
/// (`HOLGER_SHOP_URL`, else this build's), this site, and — for somebody signed in (`who`) on a
/// metered box with a data root — their account, its plan, its grow link and its `estate`
/// ticket.
pub fn signup_links(
data_root: Option<&std::path::Path>,
auth: &crate::auth::AuthConfig,
who: Option<&crate::auth::AuthIdentity>,
) -> SignupLinks {
let shop = shop();
#[cfg(feature = "metered")]
{
let site = site_from(data_root, auth);
let person = match (who.filter(|i| !i.subject.trim().is_empty()), data_root) {
(Some(id), Some(root)) => {
let account = person_account(Some(root), id);
let bound = crate::sold::read(root).contains_key(&account);
let holds_plan = bound && account_holds_plan(root, &account);
Some((account, holds_plan, bound))
}
_ => None,
};
let signer = person.as_ref().map(|(account, holds_plan, bound)| Signer {
account,
holds_plan: *holds_plan,
can_grow: *bound,
instance: sold_company(&site),
});
// A16: a SOLD private Holger's shop is its provider's (Gunnar's provided console).
if let Some(company) = sold_company(&site) {
let host = origin_of(auth)
.map(|o| {
o.trim_end_matches('/')
.rsplit("://")
.next()
.unwrap_or_default()
.to_owned()
})
.unwrap_or_default();
let provider = provider_named(std::env::var(PROVIDER_ENV).ok().as_deref())
.or_else(|| provider_from(company, &host));
return sold_links_for(provider.as_deref(), company, signer.as_ref());
}
signup_links_for(&shop, &site, signer.as_ref())
}
#[cfg(not(feature = "metered"))]
{
// An open build sells nothing from here.
let _ = (data_root, auth, who, shop);
SignupLinks {
words: ORDERS_NOTHING.to_owned(),
..SignupLinks::default()
}
}
}
/// **Who asks, as the relay on the front needs them** (`GET /-/buy/me`): a visitor, or a person
/// with their account, the tenant its room is metered as (the account), whether the account
/// is theirs (a sign-up bound it), and the caps it holds now.
#[derive(Clone, Debug, Default, PartialEq, Eq, serde::Serialize)]
pub struct BuyMe {
pub who: &'static str,
pub account: String,
pub tenant: String,
pub bound: bool,
pub caps: std::collections::BTreeMap<String, u64>,
}
/// **Who asks** (`GET /-/buy/me`).
pub fn buy_me(
data_root: Option<&std::path::Path>,
who: Option<&crate::auth::AuthIdentity>,
) -> BuyMe {
let Some(id) = who.filter(|i| !i.subject.trim().is_empty()) else {
return BuyMe {
who: "visitor",
..BuyMe::default()
};
};
let account = person_account(data_root, id);
let bound = data_root.is_some_and(|root| crate::sold::read(root).contains_key(&account));
#[cfg(feature = "metered")]
let caps = match data_root.filter(|_| bound) {
Some(root) => {
let store = crate::metered::EntitlementStore::awaiting(
crate::metered::store_dir(root),
Arc::new(Default::default()),
);
store
.get(&account)
.ok()
.flatten()
.map(|f| f.caps)
.unwrap_or_default()
}
None => Default::default(),
};
#[cfg(not(feature = "metered"))]
let caps = Default::default();
BuyMe {
who: "person",
tenant: account.clone(),
account,
bound,
caps,
}
}
/// What a buyer reads when the signed-in person has no account of their own here to buy for
/// (gunnar-ui-server `auth::private::NO_ACCOUNT_TO_BUY_FOR`, with Holger's name).
#[cfg(feature = "metered")]
pub const NO_ACCOUNT_TO_BUY_FOR: &str = "This sign-in has no account of its own on this Holger to buy a private Holger for. Sign in as the account that buys, or create one first.";
/// monetize-embed's `PURPOSE_CANCEL` (named here: a lone clone's crates.io monetize-embed
/// predates the constant).
#[cfg(feature = "metered")]
pub const PURPOSE_CANCEL: &str = "cancel";
/// **The person's own actor ticket for the relay** (`POST /-/buy/ticket {purpose, caps}`, hex of
/// its JSON bytes): `estate` for anyone signed in (their account orders a private Holger);
/// `order` (covering exactly `caps`), `read`, `renew` and `cancel` ("Cancel order", covering no
/// amount) only for the account that is theirs.
/// Refused as `(status, words)`.
#[cfg(feature = "metered")]
pub fn buy_ticket(
data_root: Option<&std::path::Path>,
who: Option<&crate::auth::AuthIdentity>,
purpose: &str,
caps: &std::collections::BTreeMap<String, u64>,
now_unix_ms: i64,
) -> Result<String, (u16, String)> {
use monetize_embed::ticket::{PURPOSE_ESTATE, PURPOSE_ORDER, PURPOSE_READ, PURPOSE_RENEW};
let root = data_root.ok_or((503, "this box holds no fleet key to vouch with".to_owned()))?;
let me = buy_me(Some(root), who);
if me.who != "person" {
return Err((401, "sign in to buy".to_owned()));
}
let caps = match purpose {
PURPOSE_ESTATE => std::collections::BTreeMap::new(),
PURPOSE_ORDER | PURPOSE_READ | PURPOSE_RENEW | PURPOSE_CANCEL if !me.bound => {
return Err((404, NO_ACCOUNT_TO_BUY_FOR.to_owned()))
}
PURPOSE_ORDER => caps.clone(),
// A cancel buys nothing, so it covers no amount (Gunnar's `cancel_ticket`).
PURPOSE_READ | PURPOSE_RENEW | PURPOSE_CANCEL => std::collections::BTreeMap::new(),
_ => {
return Err((
400,
format!("{purpose:?} is not a ticket this site vouches for"),
))
}
};
signed_ticket_covering(root, &me.account, purpose, caps, now_unix_ms).map_err(|why| (503, why))
}
/// **Whether the paid sign-up's account `user` holds its passkey here** (`GET /-/buy/account`,
/// the console's question): `none` (not made here yet), `no_passkey` (made, its sign-up not
/// redeemed), `has_passkey`.
pub fn signup_account(data_root: Option<&std::path::Path>, user: &str) -> &'static str {
match data_root.and_then(|root| crate::sold::read(root).remove(user.trim())) {
None => "none",
Some(s) if s.owner.is_empty() => "no_passkey",
Some(_) => "has_passkey",
}
}
/// Pure: the reply for `repository` where the shop is `shop` (empty: none sells here): the
/// site's own grow page for a person who may buy, else the words that an admin buys.
pub fn reply_for(repository: &str, shop: &str, may_buy: bool) -> pb::RoomReply {
if shop.trim().is_empty() || cfg!(not(feature = "metered")) {
// No shop, or an open build, which sells nothing whatever it is handed (A28 M13).
let _ = may_buy;
return pb::RoomReply {
repository: repository.to_owned(),
shop_url: String::new(),
words: SELLS_NOTHING.to_owned(),
};
}
#[cfg(feature = "metered")]
if !may_buy {
return pb::RoomReply {
repository: repository.to_owned(),
shop_url: String::new(),
words: AN_ADMIN_BUYS.to_owned(),
};
}
#[cfg(feature = "metered")]
return pb::RoomReply {
repository: repository.to_owned(),
shop_url: ACCOUNT_PAGE.to_owned(),
words: BUY_HERE.to_owned(),
};
#[cfg(not(feature = "metered"))]
unreachable!("an open build answered above")
}
/// **The actor ticket for `repository`** (metered): monetize-embed's `ActorTicket`,
/// purpose `order`, signed by this box's fleet key under `data_root`, as the hex of its
/// JSON bytes (what the buy relay's order carries as its actor ticket).
#[cfg(feature = "metered")]
pub fn ticket_for(
data_root: &std::path::Path,
repository: &str,
now_unix_ms: i64,
) -> Result<String, String> {
signed_ticket(
data_root,
repository,
monetize_embed::ticket::PURPOSE_ORDER,
now_unix_ms,
)
}
/// **The `estate` ticket for `account`** (metered): the same ticket, purpose `estate`, the
/// tenant the person's principal — what the shop's private-estate page carries (`ticket=`).
#[cfg(feature = "metered")]
pub fn estate_ticket_for(
data_root: &std::path::Path,
account: &str,
now_unix_ms: i64,
) -> Result<String, String> {
signed_ticket(
data_root,
account,
monetize_embed::ticket::PURPOSE_ESTATE,
now_unix_ms,
)
}
/// One ticket for `tenant` and `purpose`, ten minutes, a fresh nonce, signed by this box's
/// fleet key under `data_root`, as the hex of its JSON bytes.
#[cfg(feature = "metered")]
fn signed_ticket(
data_root: &std::path::Path,
tenant: &str,
purpose: &str,
now_unix_ms: i64,
) -> Result<String, String> {
signed_ticket_covering(data_root, tenant, purpose, Default::default(), now_unix_ms)
}
/// [`signed_ticket`] covering `caps` (an `order` names the exact caps the order resolves to).
#[cfg(feature = "metered")]
fn signed_ticket_covering(
data_root: &std::path::Path,
tenant: &str,
purpose: &str,
caps: std::collections::BTreeMap<String, u64>,
now_unix_ms: i64,
) -> Result<String, String> {
use monetize_embed::ticket::{ticket_message, ActorTicket, MAX_LIFE_MS};
let key = holger_fleet::BoxKey::load_or_mint(data_root)
.map_err(|e| format!("this box's fleet key: {e}"))?;
let mut raw = [0u8; 16];
getrandom::fill(&mut raw).map_err(|e| format!("no randomness for the ticket's nonce: {e}"))?;
let mut t = ActorTicket::unsigned(
holger_fleet::PRODUCT,
holger_fleet::PRODUCT,
tenant,
purpose,
holger_fleet::hex(&raw),
now_unix_ms,
MAX_LIFE_MS,
)
.covering(caps);
t.signature = monetize_embed::signing::base64_encode(&key.sign_bytes(&ticket_message(&t)));
serde_json::to_vec(&t)
.map(|b| holger_fleet::hex(&b))
.map_err(|e| e.to_string())
}
/// **This site, as the shop knows it**: the estate this box reports for (its enrolment,
/// `fleet/fleet.json`), else the console's own host.
#[cfg(feature = "metered")]
fn this_site(g: &HolgerGrpc) -> String {
site_from(g.data_root.as_deref(), &g.auth_config)
}
/// Percent-encode everything but the unreserved set.
#[cfg(feature = "metered")]
fn encode(s: &str) -> String {
s.bytes()
.map(|b| match b {
b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'.' | b'_' | b'~' => {
(b as char).to_string()
}
_ => format!("%{b:02X}"),
})
.collect()
}
#[tonic::async_trait]
impl pb::room_service_server::RoomService for Arc<HolgerGrpc> {
async fn room(
&self,
request: Request<pb::RoomRequest>,
) -> Result<Response<pb::RoomReply>, Status> {
let repository = request.get_ref().repository.clone();
// READER on the repository, the gate ReadUsage passes.
self.authorize_read(&request, &repository).await?;
let _ = self.get_repo(&repository)?;
let shop = shop();
let may_buy = self.authorize_admin(&request, &repository).await.is_ok();
Ok(Response::new(reply_for(&repository, &shop, may_buy)))
}
async fn private_estate(
&self,
request: Request<pb::PrivateEstateRequest>,
) -> Result<Response<pb::PrivateEstateReply>, Status> {
let shop = shop();
#[cfg(feature = "metered")]
{
let site = this_site(self);
// ANY person signed in here orders (Gunnar's rule), on the site's private choice.
let signed_in = matches!(
self.authenticate(&request, "order a private Holger").await,
Ok(Some(_))
);
Ok(Response::new(private_reply_for(&shop, &site, signed_in)))
}
#[cfg(not(feature = "metered"))]
{
let _ = request;
Ok(Response::new(private_reply_for(&shop, "", false)))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
/// ★ A12: **the console's way to more room is the site's own grow page**, never the shop's:
/// an admin of the repository is linked there, anyone else told an admin buys.
#[cfg(feature = "metered")]
#[test]
fn the_room_link_is_the_sites_own_grow_page() {
let r = reply_for("my repo", "https://monetize.ignalina.dk/", true);
assert_eq!(
(r.shop_url.as_str(), r.words.as_str(), r.repository.as_str()),
(ACCOUNT_PAGE, BUY_HERE, "my repo")
);
let other = reply_for("my repo", "https://monetize.ignalina.dk/", false);
assert_eq!(
(other.shop_url.as_str(), other.words.as_str()),
("", AN_ADMIN_BUYS)
);
}
#[test]
fn no_shop_sells_nothing() {
let r = reply_for("crates", " ", true);
assert!(r.shop_url.is_empty());
assert_eq!(r.words, SELLS_NOTHING);
}
/// ★ **The ticket is one the ONE shop's own verifier takes**: monetize-embed's
/// `verify_ticket`, under this box's fleet key, for exactly this repository and verb.
#[cfg(feature = "metered")]
#[test]
fn the_minted_ticket_verifies_under_the_boxs_fleet_key_for_this_repository_only() {
let dir = tempfile::tempdir().unwrap();
let now = 1_800_000_000_000;
let hex = ticket_for(dir.path(), "crates", now).unwrap();
let bytes: Vec<u8> = (0..hex.len())
.step_by(2)
.map(|i| u8::from_str_radix(&hex[i..i + 2], 16).unwrap())
.collect();
let key = monetize_embed::VerifyingKey::from_bytes(
&holger_fleet::BoxKey::load_or_mint(dir.path())
.unwrap()
.public(),
)
.unwrap();
let none = std::collections::BTreeMap::new();
let t = monetize_embed::ticket::verify_ticket(
&bytes,
&key,
"holger",
"crates",
"order",
&none,
now + 1,
)
.expect("the shop's verifier takes it");
assert_eq!((t.issuer.as_str(), t.tenant.as_str()), ("holger", "crates"));
assert!(
monetize_embed::ticket::verify_ticket(
&bytes,
&key,
"holger",
"other",
"order",
&none,
now + 1
)
.is_err(),
"another repository: refused"
);
assert!(
monetize_embed::ticket::verify_ticket(
&bytes,
&key,
"holger",
"crates",
"order",
&none,
now + 11 * 60_000
)
.is_err(),
"ten minutes, no more"
);
}
/// ★ **The sign-up page's choices are the site's own pages** (A12, owner 2026-10-23: the
/// buyer stays on the site; the only outside page is the vendor's checkout). A visitor gets
/// the account and the private choice; a signed-in person their ACCOUNT, its plan word, the
/// grow-account page when the account is theirs, and on a sold private estate "Grow
/// <company>'s Holger". No shop or no site: nothing offered. No ticket rides in a link.
#[cfg(feature = "metered")]
#[test]
fn the_signup_page_offers_the_sites_own_pages_and_never_the_shops() {
let visitor = signup_links_for("https://monetize.ignalina.dk/", "holger.rs", None);
assert_eq!(
(visitor.account_url.as_str(), visitor.private_url.as_str()),
(ACCOUNT_PAGE, PRIVATE_CHOICE)
);
assert!(visitor.private_offered && !visitor.signed_in);
assert_eq!(
(
visitor.account.as_str(),
visitor.plan.as_str(),
visitor.grow_account_url.as_str(),
visitor.instance.as_str(),
visitor.grow_instance_url.as_str()
),
("", "", "", "", "")
);
assert_eq!(visitor.words, ORDER_THERE);
let free = Signer {
account: "x14",
holds_plan: false,
can_grow: true,
instance: None,
};
let x14 = signup_links_for("https://monetize.ignalina.dk", "holger.rs", Some(&free));
assert_eq!(x14.private_url, PRIVATE_CHOICE);
assert!(x14.private_offered && x14.signed_in);
assert_eq!(
(
x14.account.as_str(),
x14.plan.as_str(),
x14.grow_account_url.as_str()
),
("x14", PLAN_FREE, ACCOUNT_PAGE)
);
assert_eq!(
(x14.instance.as_str(), x14.grow_instance_url.as_str()),
("", ""),
"no instance owned here: no grow-instance choice"
);
let unbound = Signer {
can_grow: false,
..free.clone()
};
assert_eq!(
signup_links_for("https://monetize.ignalina.dk", "holger.rs", Some(&unbound))
.grow_account_url,
"",
"no account of their own: nothing to grow"
);
// ★ BUY-ONE-2 (owner GO 2026-10-27; Gunnar's `may_buy`, the server decides): the one buy
// page's "New private instance" tab is a buyer's, never a signed-in person with no
// account of their own (the operator).
let none_here =
signup_links_for("https://monetize.ignalina.dk", "holger.rs", Some(&unbound));
assert_eq!(
(none_here.private_offered, none_here.private_url.as_str()),
(false, ""),
"no private tab for the operator"
);
// (A sold private estate's tab is its provider's page, as Gunnar's provided console: not
// this box's ladder; `a_sold_instance_points_at_its_provider_and_sells_nothing_itself`.)
let paid = Signer {
holds_plan: true,
..free.clone()
};
assert_eq!(
signup_links_for("https://monetize.ignalina.dk", "holger.rs", Some(&paid)).plan,
PLAN_HELD
);
for l in [&visitor, &x14] {
assert!(
!serde_json::to_string(l).unwrap().contains("monetize."),
"no link leaves the site: {l:?}"
);
}
for (shop, site) in [("", "holger.rs"), ("https://monetize.ignalina.dk", " ")] {
let none = signup_links_for(shop, site, Some(&free));
assert_eq!(
(
none.account_url.as_str(),
none.private_offered,
none.private_url.as_str(),
none.grow_account_url.as_str()
),
("", false, "", ""),
"{shop:?} {site:?}"
);
assert_eq!(none.words, ORDERS_NOTHING);
}
}
/// ★ **A sold private Holger sells nothing itself: its provider does** (A16, Gunnar's
/// `auth::routes::shop` for a provided console): no account and no grow of the account here,
/// "New instance" and "Grow <company>'s Holger" are the provider's pages (`https://holger.rs/
/// signup?plan=…`), and the line says where it was bought. The provider is the operator's
/// word (`HOLGER_PROVIDER`), else the estate's own name less its company (`acme.holger.rs` →
/// `holger.rs`); none known, nothing is offered.
#[cfg(feature = "metered")]
#[test]
fn a_sold_instance_points_at_its_provider_and_sells_nothing_itself() {
assert_eq!(
provider_named(Some("https://holger.rs/")),
Some("https://holger.rs".to_owned())
);
assert_eq!(provider_named(Some("http://holger.rs")), None);
assert_eq!(
provider_from("acme", "acme.holger.rs"),
Some("https://holger.rs".to_owned())
);
assert_eq!(
provider_from("acme", "git.acme.com"),
None,
"an own domain names no provider"
);
let owner = Signer {
account: "x14",
holds_plan: true,
can_grow: false,
instance: Some("acme"),
};
let acme = sold_links_for(Some("https://holger.rs"), "acme", Some(&owner));
assert_eq!(
(
acme.account_url.as_str(),
acme.grow_account_url.as_str(),
acme.instance.as_str()
),
("", "", ""),
"nothing is bought here"
);
assert!(acme.private_offered && acme.signed_in);
assert_eq!(acme.private_url, "https://holger.rs/signup?plan=private");
assert_eq!(
acme.grow_instance_url,
"https://holger.rs/signup?plan=grow&instance=acme"
);
assert_eq!(
acme.words,
"This Holger was bought at holger.rs. More room for it is bought there."
);
let visitor = sold_links_for(Some("https://holger.rs"), "acme", None);
assert_eq!(
(
visitor.private_url.as_str(),
visitor.grow_instance_url.as_str(),
visitor.account_url.as_str()
),
("https://holger.rs/signup?plan=private", "", "")
);
let unknown = sold_links_for(None, "acme", Some(&owner));
assert_eq!(
(
unknown.private_offered,
unknown.private_url.as_str(),
unknown.grow_instance_url.as_str(),
unknown.words.as_str()
),
(false, "", "", ORDERS_NOTHING)
);
}
/// ★ **The person's account** (A10 row 12): the repository a redeemed sign-up bound their
/// passkey to (its name, `x14`), never the opaque `passkey:<uuid>`; on a sold estate its
/// buyer (the estate's owners' group) by the account name the shop's invite gave them; anybody
/// else by their subject as before. **Its plan**: paid while a non-Free fact
/// is stored for it, else free.
#[test]
fn a_person_is_named_by_the_account_their_passkey_is_bound_to() {
let mut sold = std::collections::BTreeMap::new();
sold.insert(
"x14".to_string(),
crate::sold::SoldRepository {
template: "crates".into(),
signup: "cs_1".into(),
owner: "passkey:aa".into(),
..Default::default()
},
);
sold.insert(
"team".to_string(),
crate::sold::SoldRepository {
template: "crates".into(),
signup: String::new(),
owner: "passkey:aa".into(),
..Default::default()
},
);
let owners = [crate::auth::ESTATE_OWNERS.to_string()];
assert_eq!(
account_for(&sold, "passkey:aa", "nina", &[]),
"x14",
"the signed-up repository names the account"
);
assert_eq!(
account_for(&sold, "passkey:bb", "olle", &[]),
"passkey:bb",
"no bound repository: the subject"
);
assert_eq!(account_for(&sold, "console", "", &[]), "console");
assert_eq!(
account_for(&sold, "passkey:cc", "x14", &owners),
"x14",
"a sold estate's buyer is the account the shop's first-login invite named"
);
assert_eq!(
account_for(&sold, "passkey:cc", "Not A Name", &owners),
"passkey:cc",
"a display that is no account name: the subject"
);
assert_eq!(
sold_company("acme"),
Some("acme"),
"a sold private estate reports as its company"
);
for site in ["holger.rs", "test3.holger.rs", "", "Acme", "1acme"] {
assert_eq!(
sold_company(site),
None,
"{site:?} is a site, not a sold estate's company"
);
}
}
/// ★ A12: **a private Holger is ordered on the site's own private choice**; nobody signed in
/// is told to sign in or buy an account first; no shop or no site, nothing.
#[cfg(feature = "metered")]
#[test]
fn the_private_holger_link_is_the_sites_own_private_choice() {
let r = private_reply_for("https://monetize.ignalina.dk/", "test3-holger-rs", true);
assert_eq!(
(r.shop_url.as_str(), r.words.as_str()),
(PRIVATE_CHOICE, ORDER_THERE)
);
let anyone = private_reply_for("https://monetize.ignalina.dk", "test3.holger.rs", false);
assert_eq!(
(anyone.shop_url.as_str(), anyone.words.as_str()),
(PRIVATE_CHOICE, SIGN_IN_TO_ORDER)
);
for (shop, site) in [
("", "test3-holger-rs"),
("https://monetize.ignalina.dk", " "),
] {
let none = private_reply_for(shop, site, true);
assert!(none.shop_url.is_empty(), "{shop:?} {site:?}");
assert_eq!(none.words, ORDERS_NOTHING);
}
}
/// ★ **The estate ticket is one the ONE box's site check takes**: purpose `estate`, the
/// person's principal, under this box's fleet key — and no order ticket.
#[cfg(feature = "metered")]
#[test]
fn the_estate_ticket_verifies_under_the_boxs_fleet_key_for_this_person_and_the_estate_verb_only(
) {
let dir = tempfile::tempdir().unwrap();
let now = 1_800_000_000_000;
let hex = estate_ticket_for(dir.path(), "nina", now).unwrap();
let bytes: Vec<u8> = (0..hex.len())
.step_by(2)
.map(|i| u8::from_str_radix(&hex[i..i + 2], 16).unwrap())
.collect();
let key = monetize_embed::VerifyingKey::from_bytes(
&holger_fleet::BoxKey::load_or_mint(dir.path())
.unwrap()
.public(),
)
.unwrap();
let none = std::collections::BTreeMap::new();
let t = monetize_embed::ticket::verify_ticket(
&bytes,
&key,
"holger",
"nina",
"estate",
&none,
now + 1,
)
.expect("the shop's verifier takes it");
assert_eq!(
(t.product.as_str(), t.tenant.as_str(), t.purpose.as_str()),
("holger", "nina", "estate")
);
assert!(
monetize_embed::ticket::verify_ticket(
&bytes,
&key,
"holger",
"nina",
"order",
&none,
now + 1
)
.is_err(),
"not an order ticket"
);
assert!(
monetize_embed::ticket::verify_ticket(
&bytes,
&key,
"holger",
"olle",
"estate",
&none,
now + 1
)
.is_err(),
"another person: refused"
);
}
/// ★ A12: **the relay's doors on this box** — who asks, the product's ticket for them (an
/// `order` ticket covers exactly the caps the order resolves to, and only for the account
/// that is theirs), and whether a paid sign-up's account holds its passkey yet.
#[cfg(feature = "metered")]
#[test]
fn the_buy_doors_vouch_for_the_person_and_their_own_account_only() {
let dir = tempfile::tempdir().unwrap();
let root = dir.path();
let now = 1_800_000_000_000;
crate::sold::update(root, |made| {
made.insert(
"x14".into(),
crate::sold::SoldRepository {
template: "crates".into(),
signup: "stripe:cs_test_abcdefghij".into(),
owner: "passkey:aa".into(),
..Default::default()
},
);
made.insert(
"fresh".into(),
crate::sold::SoldRepository {
template: "crates".into(),
signup: "stripe:cs_test_klmnopqrst".into(),
..Default::default()
},
);
Ok(())
})
.unwrap();
let x14 = crate::auth::AuthIdentity {
subject: "passkey:aa".into(),
..Default::default()
};
let olle = crate::auth::AuthIdentity {
subject: "olle".into(),
..Default::default()
};
let me = buy_me(Some(root), Some(&x14));
assert_eq!(
(me.who, me.account.as_str(), me.tenant.as_str(), me.bound),
("person", "x14", "x14", true)
);
assert_eq!(buy_me(Some(root), None).who, "visitor");
assert!(
!buy_me(Some(root), Some(&olle)).bound,
"no account of their own here"
);
let key = monetize_embed::VerifyingKey::from_bytes(
&holger_fleet::BoxKey::load_or_mint(root).unwrap().public(),
)
.unwrap();
let unhex = |s: &str| -> Vec<u8> {
(0..s.len())
.step_by(2)
.map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap())
.collect()
};
let caps = std::collections::BTreeMap::from([("disk_bytes".to_string(), 15u64)]);
let order = buy_ticket(Some(root), Some(&x14), "order", &caps, now)
.expect("their own account's order");
monetize_embed::ticket::verify_ticket(
&unhex(&order),
&key,
"holger",
"x14",
"order",
&caps,
now + 1,
)
.expect("covers exactly the caps");
assert!(
monetize_embed::ticket::verify_ticket(
&unhex(&order),
&key,
"holger",
"x14",
"order",
&std::collections::BTreeMap::new(),
now + 1
)
.is_err(),
"other caps: refused"
);
let estate = buy_ticket(
Some(root),
Some(&olle),
"estate",
&std::collections::BTreeMap::new(),
now,
)
.expect("anyone signed in orders a private Holger");
monetize_embed::ticket::verify_ticket(
&unhex(&estate),
&key,
"holger",
"olle",
"estate",
&std::collections::BTreeMap::new(),
now + 1,
)
.expect("their own estate ticket");
// ★ A16: Gunnar's words and code for a sign-in with no account of its own to buy for
// (gunnar-ui-server `auth::private::ticket_refusal`, NOT_FOUND).
assert_eq!(
buy_ticket(Some(root), Some(&olle), "order", &caps, now).unwrap_err(),
(404, NO_ACCOUNT_TO_BUY_FOR.to_owned()),
"no account of their own: nothing to grow"
);
assert_eq!(NO_ACCOUNT_TO_BUY_FOR, "This sign-in has no account of its own on this Holger to buy a private Holger for. Sign in as the account that buys, or create one first.");
assert_eq!(
buy_ticket(Some(root), None, "estate", &caps, now)
.unwrap_err()
.0,
401
);
assert_eq!(
buy_ticket(Some(root), Some(&x14), "refund", &caps, now)
.unwrap_err()
.0,
400
);
// ★ A16: "Cancel order" (the shared relay's `Purchase.Cancel`) is vouched for with a
// `cancel` ticket that covers no amount, for the account's own orders only.
let cancel = buy_ticket(Some(root), Some(&x14), "cancel", &caps, now)
.expect("their own account's cancel");
monetize_embed::ticket::verify_ticket(
&unhex(&cancel),
&key,
"holger",
"x14",
"cancel",
&std::collections::BTreeMap::new(),
now + 1,
)
.expect("a cancel covers no amount");
assert_eq!(
buy_ticket(Some(root), Some(&olle), "cancel", &caps, now)
.unwrap_err()
.0,
404,
"no account of their own: nothing to cancel"
);
assert_eq!(
(
signup_account(Some(root), "fresh"),
signup_account(Some(root), "x14"),
signup_account(Some(root), "nobody")
),
("no_passkey", "has_passkey", "none")
);
}
/// ★ A16 (owner 2026-10-23: a buyer never sees monetize): **the words a Holger buyer reads
/// name no shop** — room is bought on the account's plan page, a private Holger on this site.
#[cfg(feature = "metered")]
#[test]
fn the_buyers_words_name_no_shop() {
for w in [
BUY_THERE,
AN_ADMIN_BUYS,
ORDER_THERE,
BUY_HERE,
SIGN_IN_TO_ORDER,
ORDERS_NOTHING,
] {
assert!(
!w.contains("shop") && !w.contains("monetize") && !w.contains("ONE box"),
"{w}"
);
}
}
#[test]
fn the_open_build_links_no_shop_by_default() {
assert_eq!(default_shop().is_empty(), !cfg!(feature = "metered"));
}
/// ★ A28 M13: **an open build sells nothing whatever it is handed**: a shop named, a site
/// named, an admin asking, the words are the operator's and no link is given.
#[cfg(not(feature = "metered"))]
#[test]
fn an_open_build_offers_no_room_and_no_private_holger_even_with_a_shop() {
let r = reply_for("crates", "https://shop.mock.invalid/", true);
assert_eq!((r.shop_url.as_str(), r.words.as_str()), ("", SELLS_NOTHING));
let p = private_reply_for("https://shop.mock.invalid/", "holger.mock.invalid", true);
assert_eq!(
(p.shop_url.as_str(), p.words.as_str()),
("", ORDERS_NOTHING)
);
assert_eq!(shop(), "", "an open build reads no HOLGER_SHOP_URL");
}
}