nordisk/monetize
crates/monetize-ui-web/src/fmt.rs
//! Pure formatting and validation — no `web-sys`, tested on the host.
//!
//! Numbers stay integers in state; only the rendered string carries a decimal.
use std::collections::BTreeMap;
// The arithmetic of a daily counter and of "how long since we saw them" lives
// in the product crate, beside the `Usage` it reads, and is tested there. This
// file decides the WORDS; it does not own a second copy of the fold.
use monetize_product::flow;
use crate::model::{
ActivityDayJson, CloudJson, CloudOwnedJson, FleetJson, FleetMeterJson, HoldingJson, MeterInfo,
PriceListJson, QuoteJson, RefusalJson, RowJson, SaleJson, SignupJson, SpendJson, TenantRow,
TrendJson,
};
const KIB: u64 = 1 << 10;
const MIB: u64 = 1 << 20;
const GIB: u64 = 1 << 30;
const TIB: u64 = 1 << 40;
const DAY_MS: u64 = 86_400_000;
/// A refusal, as the console shows it: the status, monetize's code, the
/// server's words — never reworded.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Fail {
/// The HTTP status, or 0 when the request never reached the server.
pub status: u16,
pub code: String,
pub message: String,
}
impl Fail {
pub fn summary(&self) -> &'static str {
match (self.status, self.code.as_str()) {
(_, NO_PASSKEY) => "No passkey answered.",
(_, STALE_PASSKEY) => "This passkey is not registered on this box.",
(_, ALREADY_ENROLLED) => "This key is already enrolled.",
(_, BROWSER_PASSKEY) => "The browser did not create a passkey.",
(0, _) => "The console could not reach its server.",
(401, _) => "Your session is no longer valid.",
(403, "stepup") => {
"This moves money or keys: touch your passkey again to confirm it is you."
}
(403, _) => "Your role does not allow this.",
// **A sale that cannot be DELIVERED is not a malformed request.**
// `failed_precondition` is what an order gets when the product
// itself says no — before the reserve, so nothing has been spent. The detail below carries the product's own sentence, and on
// a sealed gunnar that sentence says a bought disk would be billed
// and invisible. "Invalid request" over that paragraph sends an
// operator to look for a typo instead of reading it.
(400, "failedprecondition") => {
"This cannot be delivered, so monetize refused it before any money moved."
}
(400, _) => "monetize refused the request as invalid.",
(404, _) => "It does not exist.",
(409, _) => "That already exists.",
(502, _) => "monetize answered with an error.",
(503, _) => "monetize is not responding right now.",
(504, _) => "monetize did not answer in time.",
_ => "The request failed.",
}
}
/// The server's step-up refusal (`auth::STEP_UP_CODE`): answered with a
/// fresh passkey touch and the same request again.
pub fn wants_step_up(&self) -> bool {
self.status == 403 && self.code == "stepup"
}
pub fn worth_retrying(&self) -> bool {
!matches!(
self.code.as_str(),
NO_PASSKEY | ALREADY_ENROLLED | BROWSER_PASSKEY
) && matches!(self.status, 0 | 502 | 503 | 504)
}
/// The footer under the words: the HTTP status and monetize's code.
pub fn footer(&self) -> Option<String> {
if matches!(
self.code.as_str(),
NO_PASSKEY | STALE_PASSKEY | ALREADY_ENROLLED | BROWSER_PASSKEY
) {
return None;
}
Some(if self.code.is_empty() {
format!("HTTP {}", self.status)
} else {
format!("HTTP {} · monetize: {}", self.status, self.code)
})
}
/// **The browser's passkey call failed** (NotAllowedError, no passkey answered): the
/// server was never asked. `host` is the console's own (`location.hostname`).
pub fn no_passkey(host: &str, browser: &str) -> Fail {
let _ = browser;
Fail { status: 0, code: NO_PASSKEY.into(), message: format!("No passkey for {host} answered on this device. Try another device, or ask the operator for a break-glass reset.") }
}
/// **A login the server refused**, in the words a person acts on: a passkey this box does
/// not know (`credential is not registered here`) names the host and the way out.
pub fn login_refused(self, host: &str) -> Fail {
if self.status == 401 && self.message.contains("not registered here") {
return Fail { status: 401, code: STALE_PASSKEY.into(), message: format!("This passkey is not registered on this box — pick another passkey for {host} or delete the stale one.") };
}
self
}
}
/// The code a browser-side passkey failure carries (never sent by a server).
pub const NO_PASSKEY: &str = "no-passkey";
/// The code a passkey the box does not know carries, once the console said it.
pub const STALE_PASSKEY: &str = "stale-passkey";
/// The four states anything in this console can be in, plus "not a status".
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Tone {
Pass,
Warn,
Bad,
Live,
Plain,
}
impl Tone {
pub fn class(self) -> &'static str {
match self {
Tone::Pass => "badge pass",
Tone::Warn => "badge warn",
Tone::Bad => "badge bad",
Tone::Live => "badge live",
Tone::Plain => "badge plain",
}
}
/// The bare colour word, for a dot that is not a badge.
pub fn word(self) -> &'static str {
match self {
Tone::Pass => "pass",
Tone::Warn => "warn",
Tone::Bad => "bad",
Tone::Live => "live",
Tone::Plain => "plain",
}
}
}
/// The ladder's colour. Free is not a status: nothing was paid for.
pub fn state_tone(state: &str) -> Tone {
match state {
"paid" => Tone::Pass,
"grace" => Tone::Warn,
"suspended" | "retention" => Tone::Bad,
_ => Tone::Plain,
}
}
pub fn state_label(state: &str) -> &'static str {
match state {
"free" => "Free",
"paid" => "Paid",
"grace" => "Grace",
"suspended" => "Suspended",
"retention" => "Retention",
_ => "Unknown",
}
}
/// **Bytes in the largest unit that still leaves a digit in front of the
/// decimal point**: `10.0 GiB`, `64.0 MiB`, `999 B`.
///
/// Re-exported, not written here. This file had its own copy that divided by
/// 1 GiB unconditionally, which is a lie about small numbers rather than a
/// rounding nicety: a real 64 MiB `pack_bytes` cap printed as `0.0 GiB`, and
/// `0.0` beside a quota is read as *there is no quota*. Core and the gunnar
/// plugin each had the same six lines with the same bug, so the fix is one
/// writer rather than three that agree.
pub use monetize_product::bytes;
/// A meter's value in its own unit: `*_bytes` meters as GiB, the rest as counts.
pub fn meter_value(name: &str, value: u64) -> String {
if name.ends_with("_bytes") {
bytes(value)
} else {
value.to_string()
}
}
/// **The word beside a USING row** (`docs/v2/07-console.md` rule 3, the tenant screen): what
/// a cap on this meter IS to the tenant, in plain words, decided from two
/// facts and nothing invented — whether the product polices it, and whether
/// the deployment's price list charges for it.
///
/// * `wall` — [`Enforcement`] says `enforced`: the product refuses over it,
/// whatever the list says. A wall is a wall whether or not it is billed.
/// * `billed` — priced above zero on the list, and not a wall: counted, and
/// the customer pays for it.
/// * `not billed` — priced at zero, or not on the list at all, or a meter
/// with no unit. The open-source deployment's answer for everything.
/// * `still costs` — `tombstoned_bytes` when it is priced: bytes a tenant
/// deleted and has not purged, which the list charges for all the same.
///
/// There is no billing attribute on a meter; **the price list IS it**. A
/// console that said `billed` from a word of its own would be saying money
/// moves where the ledger would show none.
pub fn billing_word(meter: &str, enforcement: &str, unit_price: Option<u64>) -> &'static str {
if enforcement == "enforced" {
return "wall";
}
match unit_price {
Some(p) if p > 0 => {
if meter == "tombstoned_bytes" {
"still costs"
} else {
"billed"
}
}
_ => "not billed",
}
}
/// The list's unit price for `meter`: what the tenant's market charges per
/// unit-month under the meter's [`MeterInfo::unit`]. `None` when the meter has
/// no unit at all (a count) or there is no list (free); `Some(0)` when the list
/// exists and does not charge for it. Both read `not billed`.
pub fn unit_price(meter: &MeterInfo, list: Option<&PriceListJson>) -> Option<u64> {
if meter.unit.is_empty() {
return None;
}
Some(list?.units.get(&meter.unit).copied().unwrap_or(0))
}
/// **The list the order form and the USING words read**: the product's list
/// for `market`, or its first list when none is named, or nothing — free.
pub fn list_for<'a>(
prices: &'a [PriceListJson],
product: &str,
market: Option<&str>,
) -> Option<&'a PriceListJson> {
let mine = prices.iter().filter(|l| l.product == product);
match market {
Some(m) => mine.clone().find(|l| l.market.eq_ignore_ascii_case(m)),
None => None,
}
.or_else(|| prices.iter().find(|l| l.product == product))
}
/// **A customer price, in words**: `free` at zero — FREE is a kind of
/// deployment, not a price of nothing — else `1980.00 SEK`.
pub fn price_words(minor: u64, currency: &str) -> String {
if minor == 0 {
return "free".to_owned();
}
money(minor, currency)
}
/// **The provider's cost, in words**: what we pay for the delta's iron.
/// Zero with iron behind it is the box's own disk — served from what was
/// already paid for — and zero with none is an order that buys no iron.
pub fn cost_words(cost_minor: u64, currency: &str, buys_iron: bool) -> String {
match (cost_minor, buys_iron) {
(0, true) => "this box · nothing to buy".to_owned(),
(0, false) => "no iron bought".to_owned(),
(minor, _) => money(minor, currency),
}
}
/// **Can it be served?** The badge and the sentence: `yes`, or `no` with
/// monetize's reason whole — the sentence an operator reads back to the
/// customer, never shortened here.
pub fn can_serve_words(q: &QuoteJson) -> (Tone, String) {
if q.can_serve {
(Tone::Pass, "can serve: yes".to_owned())
} else {
(Tone::Bad, format!("can serve: no — {}", q.reason))
}
}
/// **The order form's number for a meter**: whole GiB for a bytes meter
/// (rounded UP, so what the form shows is never less than what the tenant
/// holds), the count itself otherwise.
pub fn form_value(meter: &str, bytes: u64) -> u64 {
if meter.ends_with("_bytes") {
bytes.div_ceil(GIB)
} else {
bytes
}
}
/// The inverse of [`form_value`]: what a typed number means in the meter's
/// own unit.
pub fn form_bytes(meter: &str, typed: u64) -> u64 {
if meter.ends_with("_bytes") {
typed.saturating_mul(GIB)
} else {
typed
}
}
/// **The caps an order sends**: only the meters whose typed value differs
/// from what the tenant holds — a meter left alone is left out, and the server
/// carries it forward. Pure, so the arithmetic that decides what is asked for
/// is testable on its own.
pub fn order_caps(
current: &BTreeMap<String, u64>,
typed: &BTreeMap<String, u64>,
) -> BTreeMap<String, u64> {
typed
.iter()
.map(|(m, v)| (m.clone(), form_bytes(m, *v)))
.filter(|(m, v)| current.get(m).copied().unwrap_or(0) != *v)
.collect()
}
/// `meter=value,meter=value` — the `caps=` query `GET /api/quote` takes.
pub fn caps_query(caps: &BTreeMap<String, u64>) -> String {
caps.iter()
.map(|(k, v)| format!("{k}={v}"))
.collect::<Vec<_>>()
.join(",")
}
/// Minor units to `49.00 SEK`. Never a float.
/// **The line behind a sale's price**: what the customer was charged, what the iron
/// cost, and — when the vendor said — what it kept and what actually arrived.
///
/// The net is folded here rather than carried: `price − fee` is the same kind of
/// number as the margin, and `docs/v2/06-data-model.md` §6.1 rule 3 keeps those out of storage.
///
/// A sale nobody reported a fee for reads exactly as it did before. Rendering
/// "vendor kept 0" for a vendor that never told us would turn an absence into a
/// claim — and the absence is the common case: a free sale never reached a vendor,
/// `invoice` has none, `klarna` settles in a portal.
pub fn sale_money_line(sale: &SaleJson) -> String {
let mut line = format!(
"price {} · cost {}",
price_words(sale.price_minor, &sale.currency),
price_words(sale.cost_minor, &sale.cost_currency)
);
if let Some(fee) = sale.fee_minor {
line.push_str(&format!(
" · vendor kept {} · net {}",
money(fee, &sale.fee_currency),
money(sale.price_minor.saturating_sub(fee), &sale.currency)
));
}
line
}
/// **The VAT per order** (owner 2026-09-28), for the row: `VAT 55.00 SEK
/// (25 %)`, or `(as the vendor charged)` when the figure is the vendor's own —
/// led by the buyer's country and the vendor's rate for it when the row names
/// one (`VAT 1.90 EUR · DE 19 % (as the vendor charged)`).
/// `None` when the row carries no tax — nothing is said rather than a zero.
pub fn sale_tax_line(sale: &SaleJson) -> Option<String> {
let minor = sale.tax_minor?;
let currency = if sale.tax_currency.is_empty() {
sale.currency.as_str()
} else {
sale.tax_currency.as_str()
};
let bp = sale.vat_basis_points;
let rate = if bp % 100 == 0 {
format!("{} %", bp / 100)
} else {
format!(
"{} %",
format!("{:.2}", bp as f64 / 100.0)
.trim_end_matches('0')
.trim_end_matches('.')
)
};
let country = sale.customer_country.trim();
// The buyer's country (owner 2026-10-11: Stripe decides per country, each
// payment is a row) leads the rate when the vendor said it.
Some(match (country.is_empty(), sale.tax_from_vendor) {
(true, true) => format!("VAT {} (as the vendor charged)", money(minor, currency)),
(true, false) => format!("VAT {} ({rate})", money(minor, currency)),
(false, true) => format!(
"VAT {} · {country} {rate} (as the vendor charged)",
money(minor, currency)
),
(false, false) => format!("VAT {} · {country} ({rate})", money(minor, currency)),
})
}
/// `path?estate=<label>` (percent-encoded) for a site; `path` alone for none.
pub fn site_path(path: &str, estate: Option<&str>) -> String {
match estate.filter(|e| !e.is_empty()) {
Some(e) => format!("{path}?estate={}", percent_encode(e)),
None => path.to_owned(),
}
}
/// **What a page says when the box answered from its kept state** (PROD-4, owner 2026-10-26:
/// the Overview and the Guardian page spun for ever). `None`: the state is fresh.
pub fn stale_note(stale_secs: Option<u64>) -> Option<String> {
let s = stale_secs?;
let age = if s < 90 {
format!("{s} s")
} else if s < 5400 {
format!("{} min", s / 60)
} else {
format!("{} h", s / 3600)
};
Some(format!("The box did not answer within 2 s: this is its state from {age} ago. It is asked again in the background; reload to see it."))
}
#[cfg(test)]
mod site_path_tests {
#[test]
fn a_sites_money_panels_ask_for_that_site_only() {
assert_eq!(
super::site_path("/api/signups", Some("test2-gunnar-rs")),
"/api/signups?estate=test2-gunnar-rs"
);
assert_eq!(super::site_path("/api/owed-back", None), "/api/owed-back");
}
}
pub fn money(minor: u64, currency: &str) -> String {
format!("{} {currency}", money_amount(minor))
}
/// Minor units to `49.00`, for a figure whose currency the wire may not carry
/// (`CloudJson::monthly_minor` from a server older than its `currency` field).
/// Still never a float.
pub fn money_amount(minor: u64) -> String {
format!("{}.{:02}", minor / 100, minor % 100)
}
/// Percent of `cap` used, clamped to 100. `None` without a cap.
pub fn percent(value: u64, cap: Option<u64>) -> Option<u8> {
let cap = cap.filter(|c| *c > 0)?;
Some(((value.min(cap) as u128 * 100) / cap as u128) as u8)
}
/// [`percent`] rounded UP: for money against a ceiling, where drawing spend
/// short is the direction that costs (`SpendCap::reconcile_total` corrects
/// upward only, for the same reason). `226 of 1000` is 23, not 22.
pub fn percent_up(value: u64, cap: Option<u64>) -> Option<u8> {
let cap = cap.filter(|c| *c > 0)?;
Some((value.min(cap) as u128 * 100).div_ceil(cap as u128) as u8)
}
fn civil_from_days(z: i64) -> (i64, u32, u32) {
let z = z + 719_468;
let era = z.div_euclid(146_097);
let doe = z.rem_euclid(146_097);
let yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let d = (doy - (153 * mp + 2) / 5 + 1) as u32;
let m = if mp < 10 { mp + 3 } else { mp - 9 } as u32;
(yoe + era * 400 + i64::from(m <= 2), m, d)
}
fn days_from_civil(y: i64, m: u32, d: u32) -> i64 {
let y = if m <= 2 { y - 1 } else { y };
let era = y.div_euclid(400);
let yoe = y.rem_euclid(400);
let mp = if m > 2 { m - 3 } else { m + 9 } as i64;
let doy = (153 * mp + 2) / 5 + i64::from(d) - 1;
let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
era * 146_097 + doe - 719_468
}
/// `YYYY-MM-DD` (UTC) of a millisecond timestamp.
pub fn date(unix_ms: u64) -> String {
let (y, m, d) = civil_from_days((unix_ms / DAY_MS) as i64);
format!("{y:04}-{m:02}-{d:02}")
}
/// `YYYY-MM-DD` → the millisecond timestamp of that midnight (UTC).
pub fn parse_date_ms(s: &str) -> Option<u64> {
let mut it = s.trim().split('-');
let y: i64 = it.next()?.parse().ok()?;
let m: u32 = it.next()?.parse().ok()?;
let d: u32 = it.next()?.parse().ok()?;
if it.next().is_some() || !(1..=12).contains(&m) || !(1..=31).contains(&d) {
return None;
}
u64::try_from(days_from_civil(y, m, d))
.ok()
.map(|days| days * DAY_MS)
}
/// How long ago, in the coarsest unit that is not zero.
pub fn since(then_ms: u64, now_ms: u64) -> String {
since_ms(now_ms.saturating_sub(then_ms))
}
/// [`since`] over an AGE that has already been worked out — the words alone,
/// with no second subtraction.
///
/// Split out for [`last_seen_line`], whose age comes from
/// `monetize_product::flow::quiet_for`: that function owns the arm this file
/// must not re-derive (`0` is NEVER, a clock that runs ahead is "just now"),
/// and a `since(seen, now)` beside it would be a second, disagreeing
/// subtraction of the same two numbers.
pub fn since_ms(age_ms: u64) -> String {
let s = age_ms / 1000;
match s {
0..=59 => "just now".into(),
60..=3599 => format!("{} min ago", s / 60),
3600..=86_399 => format!("{} h ago", s / 3600),
_ => format!("{} d ago", s / 86_400),
}
}
/// An order may only cap a meter its product declares. Names the meter.
pub fn validate_caps(
product: &str,
declared: &[String],
caps: &BTreeMap<String, u64>,
) -> Result<(), String> {
match caps.keys().find(|k| !declared.contains(*k)) {
Some(m) => Err(format!(
"cap on {m:?}: product {product} does not declare that meter"
)),
None => Ok(()),
}
}
/// The caps editor's text: one `meter = value` per line, where value is an
/// integer with an optional `GiB` / `MiB` suffix (`pack_bytes = 10 GiB`).
pub fn parse_caps(text: &str) -> Result<BTreeMap<String, u64>, String> {
let mut caps = BTreeMap::new();
for line in text.lines().map(str::trim).filter(|l| !l.is_empty()) {
let (name, value) = line
.split_once('=')
.ok_or_else(|| format!("{line:?}: expected `meter = value`"))?;
let value = value.trim();
let (digits, unit) = match value.find(|c: char| !c.is_ascii_digit()) {
Some(i) => (&value[..i], value[i..].trim()),
None => (value, ""),
};
let n: u64 = digits
.parse()
.map_err(|_| format!("{line:?}: {value:?} is not a number"))?;
let scaled = match unit {
"" => n,
// The same ladder [`bytes`] prints in, so a number the console
// showed can be typed straight back into this box.
"TiB" => n
.checked_mul(TIB)
.ok_or_else(|| format!("{line:?}: too large"))?,
"GiB" => n
.checked_mul(GIB)
.ok_or_else(|| format!("{line:?}: too large"))?,
"MiB" => n
.checked_mul(MIB)
.ok_or_else(|| format!("{line:?}: too large"))?,
"KiB" => n
.checked_mul(KIB)
.ok_or_else(|| format!("{line:?}: too large"))?,
other => {
return Err(format!(
"{line:?}: unit {other:?} is not KiB, MiB, GiB or TiB"
))
}
};
caps.insert(name.trim().to_owned(), scaled);
}
Ok(caps)
}
/// The inverse of [`parse_caps`], for the editor: the largest unit that divides
/// the value exactly, and raw bytes when none does.
///
/// It only offered GiB, so `rehearsal-free`'s 64 MiB cap came back as
/// `pack_bytes = 67108864` — a number an operator has to do arithmetic on before
/// they can tell whether it is the one they meant.
pub fn caps_text(caps: &BTreeMap<String, u64>) -> String {
caps.iter()
.map(|(k, v)| {
match [("TiB", TIB), ("GiB", GIB), ("MiB", MIB), ("KiB", KIB)]
.iter()
.find(|(_, per)| *v > 0 && v % per == 0)
{
Some((unit, per)) => format!("{k} = {} {unit}", v / per),
None => format!("{k} = {v}"),
}
})
.collect::<Vec<_>>()
.join("\n")
}
/// Percent-encode a query value.
/// **One estate's part of a product** (MAP-SEG 1): `/api/products/<p>/tenants?estate=<e>`;
/// no estate: the rows of no ONE estate.
pub fn tenants_path(product: &str, estate: &str) -> String {
let base = format!("/api/products/{}/tenants", percent_encode(product));
if estate.is_empty() {
base
} else {
format!("{base}?estate={}", percent_encode(estate))
}
}
pub fn percent_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()
}
other => format!("%{other:02X}"),
})
.collect()
}
/// **What the serving badge says when the health read FAILED**, decided by the
/// HTTP status alone.
///
/// It exists because the badge used to render ONE label for every `Err` —
/// "console server unreachable" — and `/api/health` sits behind the auth gate. So
/// an operator who had simply not signed in yet was told, in red, that the server
/// was down. Seen on the live console 2026-09-04, minutes after the name went
/// public, while `/api/health` was answering `serving: true, products:
/// ["gunnar"]` to any request that carried a credential.
///
/// `status == 0` is the console never reaching the server at all (`api::Fail`
/// documents that), and it is the ONLY shape entitled to the word "unreachable".
/// Every other status already has an honest sentence from `Fail::summary`; this
/// only has to stop overwriting it — and it carries the number, because one word
/// for nine causes is not something an operator can act on.
///
/// Red is a claim about the SYSTEM. A login state is not a fault, so it is not red.
pub fn health_failure_badge(status: u16, summary: &str) -> (Tone, String) {
match status {
0 => (Tone::Bad, "console server unreachable".to_owned()),
401 => (Tone::Plain, "sign in to see monetize".to_owned()),
403 => (Tone::Warn, "your session may not read this".to_owned()),
_ => (Tone::Bad, format!("console server: {summary} ({status})")),
}
}
/// ★ **A 401 on a page is not a page's error — it is the login's cue.**
///
/// Seen on monetize.vetra.se 2026-09-08: with no session the navigation is
/// shown (so a visitor can see what the console is), and clicking any entry
/// answered a red "Your session is no longer valid" box in the middle of the
/// page. Red is a claim about the system, and nothing is wrong with the
/// system — the visitor is simply not logged in. The ruling: send them
/// to the login and flash a red bulb THERE. So a 401 anywhere but `/login`
/// itself becomes this destination, carrying `why=session` so the login page
/// knows it was reached by a refusal and not by the menu. `None` means "stay
/// and render the failure as before": a 401 on `/login` would loop, and every
/// other status is a real answer the page should show.
pub fn login_redirect_for(status: u16, current_path: &str) -> Option<&'static str> {
(status == 401 && current_path != "/login").then_some("/login?why=session")
}
/// The one line the login page adds when it was reached through
/// [`login_redirect_for`], keyed on the `why` it carried. `None` for a visit
/// from the menu, where a warning would be a warning about nothing.
pub fn login_prompt(why: Option<&str>) -> Option<&'static str> {
match why {
Some("session") => Some("That page needs a login."),
_ => None,
}
}
/// **What to say beside a cap**, given whether the product enforces it.
///
/// `None` only when there is no cap: the number is a reading and nothing
/// bounds it. Every cap gets a word, because `pack_bytes = 10 GiB` and
/// `cpu_millicores = 2000` look like the same kind of promise as numbers and
/// only one of them is a wall — and `docs/v2/07-console.md` rule 3 wants the wall SAID, in
/// that word, rather than left as the unmarked default.
pub fn cap_note(has_cap: bool, enforcement: &str) -> Option<&'static str> {
if !has_cap {
return None;
}
match enforcement {
"enforced" => Some("wall"),
"provisioned" => Some("provisioned"),
"measured" => Some("not enforced"),
_ => Some("unknown"),
}
}
/// **What a usage bar is actually SAYING**, from the reading, the cap and
/// whether the product polices that cap.
///
/// It exists because colour on this page is a claim about whether a tenant is in
/// trouble, and the bar used to make that claim from the percentage alone. Two
/// consequences, both wrong at 3am:
///
/// * a `measured` meter at 97% went RED. Nothing refuses on it — the tenant is
/// not in trouble, they are using a thing that is counted. An operator who
/// learns that red sometimes means nothing stops reading red.
/// * a meter with NO cap was drawn against the largest number on the page, so
/// the tenant's biggest uncapped meter was always a FULL bar. A full bar is
/// the strongest thing this page can say and it was being said about a
/// quantity nobody had set a limit on.
///
/// So the escalation belongs to `enforced` meters and only to them: those are
/// the caps that are walls, where 100% means the next push is refused.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BarState {
/// No cap (or a cap of zero, which is how a product spells "unlimited").
/// Not 0%, not 100% — there is no percentage, and the bar must not draw one.
Uncapped,
/// A wall, with room.
Ok,
/// A wall, and the tenant is close to it.
Near,
/// A wall, and the tenant is AT it: the product refuses now.
AtLimit,
/// Counted and billed, never refused. Fills honestly and does not escalate.
Metered,
/// Bought at the provider. The number is what was BOUGHT, so the bar is a
/// picture of a purchase, not of a budget being consumed.
Provisioned,
/// This monetize did not say whether the cap is enforced. Said, not guessed.
Unknown,
}
/// Where the escalation starts, for a cap that is a wall.
pub const NEAR_PERCENT: u8 = 80;
/// The bar's state. Pure, and the ONE place the decision is made — `ui::Bars`
/// renders whatever this says.
pub fn bar_state(value: u64, cap: Option<u64>, enforcement: &str) -> BarState {
bar_state_at(percent(value, cap), enforcement)
}
/// [`bar_state`] from an already-computed percentage, so a bar drawn from a
/// differently-rounded figure ([`percent_up`]) escalates at the width it draws,
/// never a point behind it.
pub fn bar_state_at(pct: Option<u8>, enforcement: &str) -> BarState {
let Some(pct) = pct else {
return BarState::Uncapped;
};
match enforcement {
"enforced" if pct >= 100 => BarState::AtLimit,
"enforced" if pct >= NEAR_PERCENT => BarState::Near,
"enforced" => BarState::Ok,
"measured" => BarState::Metered,
"provisioned" => BarState::Provisioned,
_ => BarState::Unknown,
}
}
impl BarState {
/// The fill's class. `uncapped` gets its own so the track can be drawn as a
/// track with no reading in it rather than as an empty or a full bar.
pub fn class(self) -> &'static str {
match self {
BarState::Uncapped => "fill uncapped",
BarState::Ok => "fill ok",
BarState::Near => "fill near",
BarState::AtLimit => "fill at-limit",
BarState::Metered => "fill metered",
BarState::Provisioned => "fill provisioned",
BarState::Unknown => "fill unknown",
}
}
/// How wide to draw the fill, given the percentage of the cap. An uncapped
/// meter has no percentage at all — drawing one against the biggest number
/// on the page is what made every tenant's largest meter look full.
pub fn width_percent(self, pct: Option<u8>) -> u8 {
match self {
BarState::Uncapped => 0,
_ => pct.unwrap_or(0),
}
}
/// What the state means, for the bar's `title`. An operator hovering a
/// colour must be able to find out what it claims.
pub fn meaning(self) -> &'static str {
match self {
BarState::Uncapped => "no cap: this is a reading, and nothing bounds it",
BarState::Ok => "the product refuses over this cap; there is room",
BarState::Near => "the product refuses over this cap, and the tenant is close to it",
BarState::AtLimit => "at the cap: the product is refusing now",
BarState::Metered => "counted and billed; the product never refuses on it",
BarState::Provisioned => {
"bought at the provider; this is what the tenant has, not a budget being spent"
}
BarState::Unknown => "this monetize did not say whether the cap is enforced",
}
}
}
#[cfg(test)]
mod tests {
#[test]
fn a_kept_state_is_said_with_its_age_and_a_fresh_one_says_nothing() {
assert_eq!(stale_note(None), None);
assert!(stale_note(Some(12)).unwrap().contains("from 12 s ago"));
assert!(stale_note(Some(600)).unwrap().contains("from 10 min ago"));
assert!(stale_note(Some(7200)).unwrap().contains("from 2 h ago"));
}
use super::*;
/// ★ **A passkey the browser did not give is not a server that cannot be reached**
/// (owner 2026-10-17): the console says which host's passkey did not answer and what to do,
/// never "could not reach its server … HTTP 0"; a passkey this box does not know is said
/// as such, with the host.
#[test]
fn a_passkey_that_did_not_answer_or_is_stale_is_said_in_words_never_http_0() {
let f = Fail::no_passkey(
"monetize.ignalina.dk",
"The operation either timed out or was not allowed.",
);
assert_eq!(f.message, "No passkey for monetize.ignalina.dk answered on this device. Try another device, or ask the operator for a break-glass reset.");
assert_eq!(f.code, NO_PASSKEY);
assert!(
!f.summary().contains("could not reach its server"),
"{}",
f.summary()
);
assert_eq!(f.footer(), None, "no HTTP 0: the server was never asked");
assert!(
!f.worth_retrying(),
"trying the same device again is not the way out"
);
let refused = Fail {
status: 401,
code: "unauthenticated".into(),
message: "this credential is not registered here".into(),
}
.login_refused("monetize.ignalina.dk");
assert_eq!(refused.message, "This passkey is not registered on this box — pick another passkey for monetize.ignalina.dk or delete the stale one.");
assert_eq!(refused.code, STALE_PASSKEY);
assert_eq!(refused.footer(), None);
// Any other refusal is said as the server said it.
let other = Fail {
status: 401,
code: "unauthenticated".into(),
message: "the ceremony expired".into(),
};
assert_eq!(other.clone().login_refused("monetize.ignalina.dk"), other);
assert_eq!(
other.footer().as_deref(),
Some("HTTP 401 · monetize: unauthenticated")
);
}
/// ★ **A step-up is not a role refusal.** The server's `403 stepup` is
/// the one refusal the console answers by asking for a passkey touch and
/// sending the request again; an ordinary 403 is shown as it came.
#[test]
fn a_step_up_refusal_asks_for_a_touch_and_a_role_refusal_does_not() {
let step = Fail {
status: 403,
code: "stepup".into(),
message: "touch your passkey".into(),
};
assert!(step.wants_step_up());
assert!(step.summary().contains("passkey"), "{}", step.summary());
assert!(
!step.worth_retrying(),
"a step-up is answered with a touch, not a blind retry"
);
let role = Fail {
status: 403,
code: String::new(),
message: "this needs the admin role".into(),
};
assert!(!role.wants_step_up());
assert_eq!(role.summary(), "Your role does not allow this.");
assert!(
!Fail {
status: 400,
code: "stepup".into(),
message: String::new()
}
.wants_step_up(),
"only a 403 is a step-up"
);
}
#[test]
fn a_401_off_the_login_page_is_sent_to_the_login_with_a_reason() {
assert_eq!(
login_redirect_for(401, "/tenants"),
Some("/login?why=session")
);
assert_eq!(login_redirect_for(401, "/"), Some("/login?why=session"));
// On the login page itself a redirect would loop.
assert_eq!(login_redirect_for(401, "/login"), None);
// Every other status is an answer the page shows.
for status in [0u16, 400, 403, 404, 409, 503] {
assert_eq!(login_redirect_for(status, "/tenants"), None, "{status}");
}
}
#[test]
fn the_login_page_flashes_only_when_it_was_reached_by_a_refusal() {
assert_eq!(
login_prompt(Some("session")),
Some("That page needs a login.")
);
assert_eq!(login_prompt(None), None);
assert_eq!(login_prompt(Some("anything-else")), None);
}
/// RED before the fix: every failed health read rendered `Tone::Bad`
/// "console server unreachable", so a 401 on the login page accused a healthy
/// server of being down.
#[test]
fn only_a_request_that_never_arrived_may_say_unreachable() {
// The one true unreachable: the request never left.
assert_eq!(
health_failure_badge(0, "The console could not reach its server."),
(Tone::Bad, "console server unreachable".to_owned())
);
// Not signed in. Not a fault in anything, so not red, and it must not
// claim anything about the server.
let (tone, label) = health_failure_badge(401, "Your session is no longer valid.");
assert_eq!(
tone,
Tone::Plain,
"a login state is not a system fault: {label}"
);
assert!(!label.contains("unreachable"), "{label}");
// Signed in, wrong role: a warning about the SESSION.
let (tone, label) = health_failure_badge(403, "Your role does not allow this.");
assert_eq!(tone, Tone::Warn);
assert!(!label.contains("unreachable"), "{label}");
// A real server-side failure keeps its own sentence AND its number.
let (tone, label) = health_failure_badge(503, "monetize is not responding right now.");
assert_eq!(tone, Tone::Bad);
assert!(
label.contains("503")
&& label.contains("not responding")
&& !label.contains("unreachable"),
"{label}"
);
// And an unmapped status still says something specific rather than a guess.
let (_, label) = health_failure_badge(418, "The request failed.");
assert!(label.contains("418"), "{label}");
}
/// **A meter reading that is not zero must never render as zero.**
///
/// RED before the fix: this crate's own `gib` divided by 1 GiB and printed
/// one decimal, full stop. `rehearsal-free` caps `pack_bytes` at 67108864 —
/// a real 64 MiB quota, small enough that one push crosses it — and the
/// Plans page rendered it `0.0 GiB`, which an operator reads as *no quota at
/// all*. The same formatter draws the tenant page's meter bars, so a tenant
/// sitting on 300 MiB of a 10 GiB cap read as using nothing.
///
/// The formatter itself is `monetize_product::bytes` and is tested there,
/// over its whole range. What is asserted here is that the meter renderer
/// this console actually calls goes through it.
#[test]
fn a_meter_reading_that_exists_never_renders_as_zero() {
for n in [1u64, 512, 1 << 20, 67_108_864, GIB - 1] {
let s = meter_value("pack_bytes", n);
assert!(
!s.starts_with("0.0 ") && !s.starts_with("0 "),
"{n} bytes rendered as {s:?}, which reads as nothing"
);
}
// The rehearsal cap, in the unit an operator can act on.
assert_eq!(meter_value("pack_bytes", 67_108_864), "64.0 MiB");
assert_eq!(
meter_value("pack_bytes", 0),
"0 B",
"only a genuine zero says zero"
);
assert_eq!(meter_value("pack_bytes", 2 << 30), "2.0 GiB");
assert_eq!(
meter_value("pushes", 42),
"42",
"a count is not bytes and gets no unit"
);
}
#[test]
fn money_and_percent_stay_integer_arithmetic() {
assert_eq!(money(4900, "SEK"), "49.00 SEK");
assert_eq!(money(5, "EUR"), "0.05 EUR");
assert_eq!(percent(3 << 30, Some(10 << 30)), Some(30));
assert_eq!(percent(20 << 30, Some(10 << 30)), Some(100));
assert_eq!(percent(1, None), None);
}
/// **The USING word is read off the price list, not invented.** Four arms,
/// on a fixture where the cache is unpriced and the tombstoned bytes are
/// priced — the pair that tells `not billed` from `still costs`.
///
/// RED first: the console had no billing word at all, so the browser
/// matrix's `screen3-plain-words-on-the-surface` row could not find
/// `not billed` or `still costs` on the tenant screen.
#[test]
fn the_using_word_is_wall_billed_not_billed_or_still_costs_from_the_price() {
let list = PriceListJson {
product: "gunnar".into(),
market: "SE".into(),
currency: "SEK".into(),
units: [
("disk_gib_month".to_string(), 2200u64),
("cpu_core_month".to_string(), 0),
]
.into(),
};
let meter = |name: &str, enforcement: &str, unit: &str| MeterInfo {
name: name.into(),
meaning: String::new(),
enforcement: enforcement.into(),
scope: "tenant".into(),
unit: unit.into(),
};
let word = |m: &MeterInfo, list: Option<&PriceListJson>| {
billing_word(&m.name, &m.enforcement, unit_price(m, list))
};
// A wall is a wall, priced or not.
assert_eq!(
word(
&meter("pack_bytes", "enforced", "disk_gib_month"),
Some(&list)
),
"wall"
);
assert_eq!(
word(&meter("pack_bytes", "enforced", "disk_gib_month"), None),
"wall"
);
// Priced above zero and not a wall: billed.
assert_eq!(
word(
&meter("lfs_bytes", "measured", "disk_gib_month"),
Some(&list)
),
"billed"
);
// The cache has no unit: not billed, whatever the list says.
assert_eq!(
word(&meter("cache_bytes", "measured", ""), Some(&list)),
"not billed"
);
// A zero unit on the list is free, said as not billed.
assert_eq!(
word(
&meter("cpu_millicores", "provisioned", "cpu_core_month"),
Some(&list)
),
"not billed"
);
// A unit the list does not name is free too.
assert_eq!(
word(
&meter("ram_bytes", "provisioned", "ram_gib_month"),
Some(&list)
),
"not billed"
);
// Deleted-not-purged bytes the list charges for: still costs.
assert_eq!(
word(
&meter("tombstoned_bytes", "measured", "disk_gib_month"),
Some(&list)
),
"still costs"
);
// …and NOT when there is no list: the open-source deployment bills nothing.
assert_eq!(
word(
&meter("tombstoned_bytes", "measured", "disk_gib_month"),
None
),
"not billed"
);
assert_eq!(
unit_price(
&meter("tombstoned_bytes", "measured", "disk_gib_month"),
Some(&list)
),
Some(2200)
);
assert_eq!(
unit_price(&meter("repos", "measured", ""), Some(&list)),
None,
"a count has no unit"
);
// The list picked for a product: the market's, else its first, else none.
let fi = PriceListJson {
product: "gunnar".into(),
market: "FI".into(),
currency: "EUR".into(),
units: [("disk_gib_month".to_string(), 200u64)].into(),
};
let other = PriceListJson {
product: "skade".into(),
..fi.clone()
};
let all = vec![list.clone(), fi.clone(), other];
assert_eq!(
list_for(&all, "gunnar", Some("fi")).map(|l| l.currency.as_str()),
Some("EUR")
);
assert_eq!(
list_for(&all, "gunnar", None).map(|l| l.market.as_str()),
Some("SE"),
"the first of the product's lists"
);
assert_eq!(
list_for(&all, "gunnar", Some("NO")).map(|l| l.market.as_str()),
Some("SE"),
"an unknown market falls back rather than pricing nothing"
);
assert!(list_for(&all, "idun", None).is_none(), "no list is free");
}
/// **The form's arithmetic**: GiB up, GiB back, only what changed is sent.
/// 10 GiB held, 100 typed: one cap of 100 GiB; the cache typed unchanged is
/// left out, and the server carries it forward.
#[test]
fn the_order_form_sends_only_what_changed_in_bytes() {
assert_eq!(form_value("pack_bytes", 10 * GIB), 10);
assert_eq!(
form_value("pack_bytes", 10 * GIB + 1),
11,
"rounded UP: never less than the tenant holds"
);
assert_eq!(form_value("repos", 26), 26, "a count is itself");
assert_eq!(form_bytes("pack_bytes", 100), 100 * GIB);
assert_eq!(form_bytes("repos", 50), 50);
let current: BTreeMap<String, u64> = [
("pack_bytes".to_string(), 10 * GIB),
("cache_bytes".to_string(), 20 * GIB),
]
.into();
let typed: BTreeMap<String, u64> = [
("pack_bytes".to_string(), 100u64),
("cache_bytes".to_string(), 20),
]
.into();
let caps = order_caps(¤t, &typed);
assert_eq!(
caps,
[("pack_bytes".to_string(), 100 * GIB)].into(),
"the unchanged cache is not re-ordered"
);
assert_eq!(caps_query(&caps), format!("pack_bytes={}", 100 * GIB));
assert!(
order_caps(¤t, &[("pack_bytes".to_string(), 10u64)].into()).is_empty(),
"nothing changed, nothing sent"
);
// A typed downgrade IS sent — the server refuses it by name, and the
// console shows that sentence rather than silently dropping the field.
assert_eq!(
order_caps(¤t, &[("pack_bytes".to_string(), 1u64)].into())["pack_bytes"],
GIB
);
// The words on the quote.
assert_eq!(
price_words(0, "SEK"),
"free",
"free is a kind of deployment, not 0.00"
);
assert_eq!(price_words(198_000, "SEK"), "1980.00 SEK");
assert_eq!(cost_words(0, "EUR", true), "this box · nothing to buy");
assert_eq!(cost_words(0, "EUR", false), "no iron bought");
assert_eq!(cost_words(2_070, "EUR", true), "20.70 EUR");
let yes = QuoteJson {
can_serve: true,
..QuoteJson::default()
};
assert_eq!(can_serve_words(&yes), (Tone::Pass, "can serve: yes".into()));
let no = QuoteJson { can_serve: false, reason: "short 5 GiB. Serving more needs a fresh install onto a larger set, not another disk".into(), ..QuoteJson::default() };
let (tone, line) = can_serve_words(&no);
assert_eq!(tone, Tone::Bad);
assert!(
line.starts_with("can serve: no — ") && line.ends_with("not another disk"),
"the reason, whole: {line}"
);
}
#[test]
fn the_ladder_maps_to_colour_and_label() {
assert_eq!(
(state_tone("paid"), state_label("paid")),
(Tone::Pass, "Paid")
);
assert_eq!(
(state_tone("grace"), state_label("grace")),
(Tone::Warn, "Grace")
);
assert_eq!(state_tone("suspended"), Tone::Bad);
assert_eq!(state_tone("retention"), Tone::Bad);
assert_eq!(
(state_tone("free"), state_label("free")),
(Tone::Plain, "Free")
);
assert_eq!(state_label("banana"), "Unknown");
assert_eq!(Tone::Warn.class(), "badge warn");
}
#[test]
fn dates_round_trip_and_since_picks_the_coarsest_unit() {
assert_eq!(date(0), "1970-01-01");
assert_eq!(
date(1_788_436_800_000),
"2026-09-03",
"monetize's own test fixture"
);
assert_eq!(parse_date_ms("2026-09-03"), Some(1_788_393_600_000));
assert_eq!(date(parse_date_ms("2024-02-29").unwrap()), "2024-02-29");
assert_eq!(parse_date_ms("2026-13-01"), None);
assert_eq!(parse_date_ms("soon"), None);
assert_eq!(since(1_000, 30_000), "just now");
assert_eq!(since(0, 5 * 60_000), "5 min ago");
assert_eq!(since(0, 3 * 3_600_000), "3 h ago");
assert_eq!(since(0, 2 * DAY_MS), "2 d ago");
}
#[test]
fn caps_parse_validate_and_name_the_undeclared_meter() {
let caps = parse_caps("pack_bytes = 10 GiB\nlfs_bytes=5GiB\n\npushes = 100").unwrap();
assert_eq!(caps["pack_bytes"], 10 << 30);
assert_eq!(caps["lfs_bytes"], 5 << 30);
assert_eq!(caps["pushes"], 100);
assert_eq!(
caps_text(&caps),
"lfs_bytes = 5 GiB\npack_bytes = 10 GiB\npushes = 100"
);
assert!(parse_caps("pack_bytes 10").is_err());
assert!(parse_caps("pack_bytes = 10 GB")
.unwrap_err()
.contains("KiB, MiB, GiB or TiB"));
// **The editor round-trips a cap that is not a whole GiB.** It used to
// print `pack_bytes = 67108864` for the 64 MiB rehearsal cap, so the one
// plan whose number is easy to get wrong was the one shown in raw bytes.
let small = parse_caps("pack_bytes = 64 MiB").unwrap();
assert_eq!(small["pack_bytes"], 67_108_864);
assert_eq!(caps_text(&small), "pack_bytes = 64 MiB");
assert_eq!(
parse_caps(&caps_text(&small)).unwrap(),
small,
"what the editor shows must parse back to what it was given"
);
// Every unit the display can produce is a unit the editor accepts.
for (text, want) in [("a = 2 TiB", 2 << 40), ("a = 3 KiB", 3 << 10), ("a = 7", 7)] {
let one = parse_caps(text).unwrap();
assert_eq!(one["a"], want, "{text}");
assert_eq!(parse_caps(&caps_text(&one)).unwrap(), one, "{text}");
}
let declared = vec!["pack_bytes".to_string(), "lfs_bytes".to_string()];
assert!(validate_caps("gunnar", &declared, &caps)
.unwrap_err()
.contains("\"pushes\""));
let mut ok = caps.clone();
ok.remove("pushes");
assert!(validate_caps("gunnar", &declared, &ok).is_ok());
assert_eq!(percent_encode("team/sub x"), "team%2Fsub%20x");
}
}
#[cfg(test)]
mod sell_tests {
use super::*;
/// A cap is only a limit when something enforces it, and the console must
/// say which — otherwise two identical-looking numbers make two different
/// promises and the operator cannot tell.
#[test]
fn a_cap_says_whether_it_is_a_wall() {
// No cap: nothing to qualify.
assert_eq!(cap_note(false, "enforced"), None);
assert_eq!(cap_note(false, "provisioned"), None);
// Enforced: the number IS the limit, and `docs/v2/07-console.md` rule 3 wants that
// said in a plain word. (It used to be None — "the number speaks for
// itself" — which left the one cap that matters as the unmarked one.)
assert_eq!(cap_note(true, "enforced"), Some("wall"));
// The two that would otherwise read as walls.
assert_eq!(cap_note(true, "provisioned"), Some("provisioned"));
assert_eq!(cap_note(true, "measured"), Some("not enforced"));
// A server older than the field, or a value this console does not know:
// say so rather than defaulting to the reassuring answer.
assert_eq!(cap_note(true, ""), Some("unknown"));
assert_eq!(cap_note(true, "something-new"), Some("unknown"));
}
}
#[cfg(test)]
mod bar_tests {
use super::*;
const GIB: u64 = 1 << 30;
/// **Only a WALL may escalate.** A cap the product does not police is not a
/// cliff a tenant is walking towards, and colouring it as one teaches an
/// operator that red means nothing.
#[test]
fn colour_escalates_only_where_something_actually_refuses() {
// Enforced: the three rungs, checked AT the boundary and one below it.
assert_eq!(bar_state(0, Some(10 * GIB), "enforced"), BarState::Ok);
assert_eq!(
bar_state(7 * GIB, Some(10 * GIB), "enforced"),
BarState::Ok,
"70% is not a warning"
);
assert_eq!(
bar_state(8 * GIB, Some(10 * GIB), "enforced"),
BarState::Near,
"80% is where it starts"
);
assert_eq!(
bar_state(10 * GIB - 1, Some(10 * GIB), "enforced"),
BarState::Near,
"one byte short is not at the limit"
);
assert_eq!(
bar_state(10 * GIB, Some(10 * GIB), "enforced"),
BarState::AtLimit
);
assert_eq!(
bar_state(99 * GIB, Some(10 * GIB), "enforced"),
BarState::AtLimit,
"over the cap is still at the cap"
);
// Measured: full, and STILL not an alarm. This is the one the old
// percentage-only rule got wrong.
for used in [0, 8 * GIB, 10 * GIB, 99 * GIB] {
assert_eq!(
bar_state(used, Some(10 * GIB), "measured"),
BarState::Metered,
"at {used} bytes"
);
}
// Provisioned: the number is what was bought.
assert_eq!(
bar_state(2000, Some(2000), "provisioned"),
BarState::Provisioned
);
// A word this console does not know is reported AS unknown rather than
// defaulted to the reassuring answer.
assert_eq!(bar_state(1, Some(2), ""), BarState::Unknown);
assert_eq!(bar_state(1, Some(2), "something-new"), BarState::Unknown);
}
/// **Uncapped is its own state, at every enforcement.** It is not 0% and it
/// is not 100%, and a zero cap is how a product spells "unlimited"
/// (gunnar's `explode_budget_bytes`), so it is uncapped too.
#[test]
fn a_meter_with_no_cap_is_uncapped_and_draws_no_fill() {
for enforcement in ["enforced", "measured", "provisioned", ""] {
assert_eq!(
bar_state(5 * GIB, None, enforcement),
BarState::Uncapped,
"{enforcement}"
);
assert_eq!(
bar_state(5 * GIB, Some(0), enforcement),
BarState::Uncapped,
"a cap of 0 is unlimited: {enforcement}"
);
}
// Even with a percentage handed to it, an uncapped bar draws nothing —
// the old code drew it against the biggest meter on the page, so the
// largest number a tenant had was always a FULL bar.
assert_eq!(BarState::Uncapped.width_percent(Some(100)), 0);
assert_eq!(BarState::AtLimit.width_percent(Some(100)), 100);
assert_eq!(BarState::Metered.width_percent(Some(95)), 95);
assert_eq!(BarState::Ok.width_percent(None), 0);
}
/// Every state has its own class and its own sentence, so a colour can
/// always be asked what it claims — and no two states are the same colour.
#[test]
fn every_state_says_something_different() {
let all = [
BarState::Uncapped,
BarState::Ok,
BarState::Near,
BarState::AtLimit,
BarState::Metered,
BarState::Provisioned,
BarState::Unknown,
];
let mut classes: Vec<&str> = all.iter().map(|s| s.class()).collect();
let n = classes.len();
classes.sort();
classes.dedup();
assert_eq!(
classes.len(),
n,
"two states share a class and are therefore the same colour"
);
let mut meanings: Vec<&str> = all.iter().map(|s| s.meaning()).collect();
meanings.sort();
meanings.dedup();
assert_eq!(meanings.len(), n);
assert!(all
.iter()
.all(|s| s.class().starts_with("fill ") && !s.meaning().is_empty()));
}
}
#[cfg(test)]
mod fail_tests {
use super::*;
/// **A sale that cannot be DELIVERED is not an invalid request**, and the
/// heading over the refusal must not say it is.
///
/// An order is refused with `failed_precondition` when the product says it
/// cannot absorb the caps — before the reserve, so nothing has been spent. This BFF maps that to 400, which used to put "monetize refused the
/// request as invalid." above a paragraph explaining that gunnar's data set
/// cannot grow. The operator's next move differs entirely between the two:
/// one is a typo, the other is a conversation with a customer about a
/// reinstall.
#[test]
fn a_refusal_to_deliver_does_not_read_as_a_malformed_request() {
let cannot_deliver = Fail {
status: 400,
code: "failedprecondition".into(),
message: "gunnar can serve the pack_bytes cap of 100.0 GiB, but … AND THIS SET CANNOT GROW WHILE \
GUNNAR RUNS: … a disk … would be BILLED AND INVISIBLE. Serving more needs a fresh install \
onto a larger set, not another disk"
.into(),
};
let malformed = Fail {
status: 400,
code: "invalidargument".into(),
message: "tenant is required".into(),
};
assert_ne!(
cannot_deliver.summary(),
malformed.summary(),
"a product that cannot deliver reads as a malformed request, and the operator is sent to fix the form"
);
assert!(
!cannot_deliver.summary().contains("invalid"),
"{}",
cannot_deliver.summary()
);
assert!(
malformed.summary().contains("invalid"),
"{}",
malformed.summary()
);
// The message itself is NEVER reworded — it is what the operator reads
// back to the customer, and the remediation is inside it.
assert!(
cannot_deliver.message.contains("fresh install"),
"the sentence must survive whole"
);
}
/// A refusal that cannot be delivered is not worth retrying: nothing about
/// it will change by asking again, and a "Try again" button beside it is an
/// invitation to spend the operator's afternoon.
#[test]
fn only_a_transport_or_upstream_fault_offers_a_retry() {
for status in [0, 502, 503, 504] {
assert!(
Fail {
status,
code: String::new(),
message: String::new()
}
.worth_retrying(),
"{status}"
);
}
for status in [400, 401, 403, 404, 409] {
assert!(
!Fail {
status,
code: "failedprecondition".into(),
message: String::new()
}
.worth_retrying(),
"{status}"
);
}
}
}
// ── The screens (`docs/v2/07-console.md`) ───────────────────────────────────────────────
//
// Every decision a screen makes is a function here, over the shapes in
// `crate::model`, and has a host-run test. The DOM layer only calls them.
/// **The dot on screen 1.** `(tone, glyph, word)`; the word is empty for a
/// product that answers, because the row then carries its numbers instead.
///
/// `unknown` is NOT down. A server that has not finished its first poll tick
/// has asked nobody, and painting that red would be a claim nobody made.
pub fn reach_dot(state: &str) -> (Tone, &'static str, &'static str) {
match state {
"up" => (Tone::Pass, "●", ""),
"down" => (Tone::Bad, "○", "not connected"),
"silent" => (Tone::Warn, "○", "not reporting"),
"not-installed" => (Tone::Plain, "○", "not installed"),
_ => (Tone::Plain, "○", "not checked yet"),
}
}
/// **A product line on a SITE's page**: the site's own fleet reports decide it
/// (owner 2026-10-12: "gunnar — not connected" while test2's main reported to
/// the ONE box every 30 s). The ONE box dials no estate, so the product's own
/// reachability (`down`: nothing dialled) says nothing about a site. `reached`
/// / `installed` are the site summary's; `None` (not read yet, or no site)
/// keeps the product's word.
pub fn site_reach(product_state: &str, reached: Option<bool>, installed: Option<bool>) -> &str {
match (reached, installed) {
(Some(true), _) => "up",
(Some(false), Some(false)) => "not-installed",
(Some(false), _) => "silent",
(None, _) => product_state,
}
}
#[cfg(test)]
mod site_reach_tests {
use super::*;
/// ★ test2's main reports in: the line is up, never "not connected".
#[test]
fn a_sites_product_line_follows_its_fleet_reports() {
assert_eq!(
reach_dot(site_reach("down", Some(true), Some(true))).2,
"",
"reporting: up, no word"
);
assert_eq!(
reach_dot(site_reach("down", Some(false), Some(true))).2,
"not reporting"
);
assert_eq!(
reach_dot(site_reach("down", Some(false), Some(false))).2,
"not installed"
);
assert_eq!(
reach_dot(site_reach("down", None, None)).2,
"not connected",
"no site read: the product's own word"
);
}
}
/// **The meter a product's screens lead with.** When the BFF sent a trend, its
/// `meter` is the authority (the server chose it, and the rate is OF it);
/// otherwise the same rule the BFF applies — the first `enforced` meter, the
/// first cap that is a wall. `None` for a product that declares no wall: no
/// headline rather than a headline about whichever meter sorted first.
pub fn primary_meter<'a>(trend: Option<&'a TrendJson>, meters: &'a [MeterInfo]) -> Option<&'a str> {
match trend {
Some(t) if !t.meter.is_empty() => Some(t.meter.as_str()),
_ => meters
.iter()
.find(|m| m.enforcement == "enforced")
.map(|m| m.name.as_str()),
}
}
/// **The fleet's headline number — including the two shapes that are not a
/// number.** `docs/v2/06-data-model.md` §6.1 §console 3: an unanswered capacity question is a
/// measurement that did not happen, never a full disk and never an empty one.
#[derive(Debug, Clone, PartialEq)]
pub enum Figure {
/// `used` of `capacity`; `is_a_floor` when some tenant went unmeasured.
Measured {
used: u64,
capacity: u64,
is_a_floor: bool,
},
/// The tenants' sum is known; what the box can serve is not.
Unmeasured {
used: u64,
is_a_floor: bool,
why: String,
},
/// The product declares no per-tenant meter to lead with.
NoMeter,
}
pub fn fleet_figure(fleet: &FleetJson, meter: Option<&str>) -> Figure {
let Some(m) = meter.and_then(|name| fleet.meters.iter().find(|m| m.meter == name)) else {
return Figure::NoMeter;
};
match m.capacity {
Some(capacity) => Figure::Measured {
used: m.used,
capacity,
is_a_floor: m.is_a_floor,
},
None => Figure::Unmeasured {
used: m.used,
is_a_floor: m.is_a_floor,
why: if m.oversell_reason.is_empty() {
fleet.servable_reason.clone()
} else {
m.oversell_reason.clone()
},
},
}
}
impl Figure {
/// The words: `184.0 GiB of 300.0 GiB`, or the honest unmeasured state.
pub fn text(&self) -> String {
let floor = |f: bool| if f { "at least " } else { "" };
match self {
Figure::Measured {
used,
capacity,
is_a_floor,
} => format!(
"{}{} of {}",
floor(*is_a_floor),
bytes(*used),
bytes(*capacity)
),
// Owner 2026-10-12 ("at least 0 B · capacity unmeasured"): nothing
// until there is data, then one short line.
Figure::Unmeasured { used: 0, .. } => String::new(),
Figure::Unmeasured {
used, is_a_floor, ..
} => format!("{}{} used", floor(*is_a_floor), bytes(*used)),
Figure::NoMeter => "no per-tenant wall declared".to_owned(),
}
}
/// The bar, when there is a denominator: the same [`bar_state`] the tenant
/// bars use, as a wall — a full disk is the one cap the product cannot
/// help refusing on. `None` draws no bar at all, never an empty one.
pub fn bar(&self) -> Option<(BarState, u8)> {
match self {
Figure::Measured { used, capacity, .. } => {
// An exhausted box with nothing on it has capacity 0; that is
// full, not uncapped.
let cap = Some((*capacity).max(1));
let state = bar_state(*used, cap, "enforced");
Some((state, state.width_percent(percent(*used, cap))))
}
_ => None,
}
}
/// The reason there is no denominator, for the row's title.
pub fn why(&self) -> &str {
match self {
Figure::Unmeasured { why, .. } => why,
_ => "",
}
}
}
/// **A rate as one phrase, or nothing.** `▲ 12.0 GiB/wk`, `▼ ~2.0 GiB/wk`.
/// `None` when the rate is not known — a screen then shows NOTHING where the
/// rate would be, never a flat line: "not enough history" is not zero.
pub fn rate_line(trend: Option<&TrendJson>) -> Option<String> {
let t = trend?;
let per_week = t.per_week.filter(|_| t.known)?;
let arrow = if per_week < 0.0 { "▼" } else { "▲" };
let magnitude = per_week.abs();
let amount = if t.meter.ends_with("_bytes") {
bytes(magnitude.round() as u64)
} else {
format!("{magnitude:.0}")
};
Some(format!("{arrow} {}{amount}/wk", t.marker))
}
/// Whole weeks until `used` reaches `capacity` at `per_week`, rounded UP.
/// `None` when there is no capacity, or the rate is not positive — a shrinking
/// fleet never fills. `Some(0)` when it is already there.
pub fn weeks_until_full(used: u64, capacity: Option<u64>, per_week: f64) -> Option<u64> {
let capacity = capacity?;
if used >= capacity {
return Some(0);
}
if per_week <= 0.0 {
return None;
}
Some(((capacity - used) as f64 / per_week).ceil() as u64)
}
/// **The trend as ONE line** (`docs/v2/07-console.md` rule 4), or `None` when the product has
/// no meter to lead with.
///
/// A projection is only rendered when the server said it may be
/// (`may_project`), and when it is rendered it ALWAYS carries `~` — git
/// storage is bursty, and a linear fit stated as a plain date is a confident
/// wrong day someone plans a re-image around (`docs/v2/06-data-model.md` §6.1 "never grow").
/// Everything else renders the rate and says, in words, why there is no date.
pub fn trend_line(trend: Option<&TrendJson>, used: u64, capacity: Option<u64>) -> Option<String> {
let t = trend?;
if !t.known {
// Nothing until there is history to say something about.
return None;
}
let rate = rate_line(Some(t))?;
let per_week = t.per_week?;
let window = format!("last {} d", t.window_ms / DAY_MS);
let line = match (capacity, t.may_project) {
(None, _) => format!("{rate} ({window}) · capacity unmeasured, no projection"),
(Some(c), _) if used >= c => format!("At capacity · {rate} ({window})"),
(Some(_), false) => format!("{rate} ({window}), bursty — no projection"),
(Some(c), true) => match weeks_until_full(used, Some(c), per_week) {
Some(w) if w <= 1 => format!("Full within a week ({rate}, {window})"),
Some(w) => format!("Full in ~{w} weeks ({rate}, {window})"),
None => format!("{rate} ({window}) · not filling"),
},
};
let floor = if t.unmeasured_tenants > 0 {
format!(
" · a floor: {} of {} tenants unmeasured",
t.unmeasured_tenants,
t.measured_tenants + t.unmeasured_tenants
)
} else {
String::new()
};
Some(line + &floor)
}
#[cfg(test)]
mod screen_one_tests {
use super::*;
use crate::model::FleetMeterJson;
const GIB: u64 = 1 << 30;
fn trend(known: bool, per_week: f64, confidence: &str) -> TrendJson {
TrendJson {
meter: "pack_bytes".into(),
known,
per_week: known.then_some(per_week),
confidence: confidence.into(),
marker: if confidence == "firm" { "" } else { "~" }.into(),
may_project: matches!(confidence, "firm" | "approximate"),
readings: 20,
span_ms: 10 * DAY_MS,
needed_span_ms: 3 * DAY_MS + DAY_MS / 2,
window_ms: 30 * DAY_MS,
..TrendJson::default()
}
}
fn fleet(capacity: Option<u64>, is_a_floor: bool) -> FleetJson {
FleetJson {
meters: vec![FleetMeterJson {
meter: "pack_bytes".into(),
tenants: 9,
used: 184 * GIB,
capacity,
is_a_floor,
oversell: if capacity.is_some() {
"fits"
} else {
"unmeasured"
}
.into(),
oversell_reason: if capacity.is_some() {
String::new()
} else {
"no Admin.Capacity RPC".into()
},
..FleetMeterJson::default()
}],
servable_measured: capacity.is_some(),
..FleetJson::default()
}
}
/// **Unknown is not down.** RED against the two-state shortcut: a server on
/// its first tick would have shown every product as unreachable.
#[test]
fn the_dot_has_three_states_and_only_down_is_red() {
assert_eq!(reach_dot("up"), (Tone::Pass, "●", ""));
assert_eq!(reach_dot("down"), (Tone::Bad, "○", "not connected"));
let (tone, _, word) = reach_dot("unknown");
assert_eq!(tone, Tone::Plain, "nobody asked yet is not a fault");
assert_ne!(word, "not connected", "and must not be reported as one");
assert_eq!(
reach_dot(""),
reach_dot("unknown"),
"an older server that sent nothing is the same silence"
);
}
/// The trend's meter is the authority; without one, the first wall; without
/// a wall, nothing — never the first meter that happened to sort.
#[test]
fn the_headline_meter_is_the_trends_or_the_first_wall_or_nothing() {
let meters = vec![
MeterInfo {
name: "lfs_bytes".into(),
enforcement: "measured".into(),
..MeterInfo::default()
},
MeterInfo {
name: "pack_bytes".into(),
enforcement: "enforced".into(),
..MeterInfo::default()
},
];
assert_eq!(
primary_meter(None, &meters),
Some("pack_bytes"),
"the first WALL, not the first meter"
);
let t = TrendJson {
meter: "cache_bytes".into(),
..TrendJson::default()
};
assert_eq!(
primary_meter(Some(&t), &meters),
Some("cache_bytes"),
"the server's choice wins"
);
let no_wall = vec![MeterInfo {
name: "artifacts".into(),
enforcement: "measured".into(),
..MeterInfo::default()
}];
assert_eq!(
primary_meter(None, &no_wall),
None,
"holger declares no wall: no headline"
);
}
/// **An unmeasured capacity is not a full disk and not an empty one.** RED
/// against rendering `capacity.unwrap_or(0)`: the bar would have been full.
#[test]
fn the_figure_says_unmeasured_and_draws_no_bar_without_a_denominator() {
let measured = fleet_figure(&fleet(Some(300 * GIB), false), Some("pack_bytes"));
assert_eq!(measured.text(), "184.0 GiB of 300.0 GiB");
let (state, width) = measured.bar().expect("a denominator draws a bar");
assert_eq!((state, width), (BarState::Ok, 61));
let unmeasured = fleet_figure(&fleet(None, true), Some("pack_bytes"));
assert_eq!(unmeasured.text(), "at least 184.0 GiB used");
// ★ Owner 2026-10-12: no "at least 0 B · capacity unmeasured" — nothing until there is data.
let empty = Figure::Unmeasured {
used: 0,
is_a_floor: true,
why: String::new(),
};
assert_eq!(empty.text(), "");
assert_eq!(
unmeasured.bar(),
None,
"no denominator, no bar — not an empty one, not a full one"
);
assert!(
unmeasured.why().contains("Admin.Capacity"),
"the product's reason travels: {}",
unmeasured.why()
);
// A floor is SAID, on a measured figure too.
assert_eq!(
fleet_figure(&fleet(Some(300 * GIB), true), Some("pack_bytes")).text(),
"at least 184.0 GiB of 300.0 GiB"
);
// The wall's escalation applies to the fleet: at capacity is red.
let full = Figure::Measured {
used: 300 * GIB,
capacity: 300 * GIB,
is_a_floor: false,
};
assert_eq!(full.bar(), Some((BarState::AtLimit, 100)));
// An exhausted, empty box is full, not uncapped.
assert_eq!(
Figure::Measured {
used: 0,
capacity: 0,
is_a_floor: false
}
.bar()
.map(|b| b.0),
Some(BarState::Ok)
);
// No meter to lead with: said, and no bar.
assert_eq!(
fleet_figure(&fleet(Some(GIB), false), None),
Figure::NoMeter
);
assert_eq!(
fleet_figure(&fleet(Some(GIB), false), Some("lfs_bytes")),
Figure::NoMeter,
"a meter the fleet did not fold is no figure"
);
assert_eq!(Figure::NoMeter.bar(), None);
}
/// **Not enough history is nothing, not zero.** RED against
/// `per_week.unwrap_or(0.0)`: the row would have read `▲ 0 B/wk`.
#[test]
fn the_rate_is_a_phrase_with_its_marker_or_nothing_at_all() {
assert_eq!(rate_line(None), None);
assert_eq!(
rate_line(Some(&trend(false, 0.0, ""))),
None,
"unknown renders nothing"
);
assert_eq!(
rate_line(Some(&trend(true, 12.0 * GIB as f64, "firm"))).as_deref(),
Some("▲ 12.0 GiB/wk")
);
assert_eq!(
rate_line(Some(&trend(true, 12.0 * GIB as f64, "approximate"))).as_deref(),
Some("▲ ~12.0 GiB/wk")
);
assert_eq!(
rate_line(Some(&trend(true, -2.0 * GIB as f64, "bursty"))).as_deref(),
Some("▼ ~2.0 GiB/wk")
);
let count = TrendJson {
meter: "repos".into(),
..trend(true, 3.4, "firm")
};
assert_eq!(
rate_line(Some(&count)).as_deref(),
Some("▲ 3/wk"),
"a count is not bytes"
);
// A known rate whose number the server nevertheless withheld renders nothing.
let withheld = TrendJson {
per_week: None,
..trend(true, 1.0, "firm")
};
assert_eq!(rate_line(Some(&withheld)), None);
}
#[test]
fn weeks_until_full_rounds_up_and_a_shrinking_fleet_never_fills() {
assert_eq!(
weeks_until_full(184 * GIB, Some(300 * GIB), 12.9 * GIB as f64),
Some(9),
"116 / 12.9 = 8.99 → 9"
);
assert_eq!(
weeks_until_full(184 * GIB, Some(300 * GIB), 116.0 * GIB as f64),
Some(1)
);
assert_eq!(weeks_until_full(300 * GIB, Some(300 * GIB), 1.0), Some(0));
assert_eq!(
weeks_until_full(301 * GIB, Some(300 * GIB), 1.0),
Some(0),
"over is at"
);
assert_eq!(weeks_until_full(1, Some(300 * GIB), 0.0), None);
assert_eq!(weeks_until_full(1, Some(300 * GIB), -5.0), None);
assert_eq!(weeks_until_full(1, None, 5.0), None);
}
/// **A projection is never a date without `~`, and never rendered when the
/// server said it may not be.** RED against two shortcuts: rendering
/// `Full in 9 weeks` for a firm series (a plain number nobody should plan
/// around), and rendering a date for a bursty one at all.
#[test]
fn the_trend_line_projects_only_when_allowed_and_always_with_its_uncertainty() {
let cap = Some(300 * GIB);
let firm = trend_line(
Some(&trend(true, 12.9 * GIB as f64, "firm")),
184 * GIB,
cap,
)
.unwrap();
assert_eq!(firm, "Full in ~9 weeks (▲ 12.9 GiB/wk, last 30 d)");
assert!(
firm.contains('~'),
"a firm fit is still a projection: {firm}"
);
let approx = trend_line(
Some(&trend(true, 12.9 * GIB as f64, "approximate")),
184 * GIB,
cap,
)
.unwrap();
assert!(
approx.starts_with("Full in ~9 weeks (▲ ~12.9 GiB/wk"),
"{approx}"
);
let bursty = trend_line(
Some(&trend(true, 12.9 * GIB as f64, "bursty")),
184 * GIB,
cap,
)
.unwrap();
assert!(
!bursty.contains("Full in"),
"bursty forbids a date: {bursty}"
);
assert!(
bursty.contains("▲ ~12.9 GiB/wk") && bursty.contains("no projection"),
"but the rate is still said: {bursty}"
);
// ★ Owner 2026-10-12: "not enough history yet: 0 d of 3 d measured" is noise —
// nothing is said until there is history (and never a rate of zero).
assert_eq!(
trend_line(Some(&trend(false, 0.0, "")), 184 * GIB, cap),
None
);
let no_cap = trend_line(
Some(&trend(true, 12.9 * GIB as f64, "firm")),
184 * GIB,
None,
)
.unwrap();
assert!(
no_cap.contains("capacity unmeasured") && !no_cap.contains("Full in"),
"{no_cap}"
);
let full = trend_line(
Some(&trend(true, 12.9 * GIB as f64, "firm")),
300 * GIB,
cap,
)
.unwrap();
assert!(full.starts_with("At capacity"), "{full}");
let shrinking =
trend_line(Some(&trend(true, -(GIB as f64), "firm")), 184 * GIB, cap).unwrap();
assert!(
shrinking.contains("not filling") && !shrinking.contains("Full in"),
"{shrinking}"
);
let soon = trend_line(
Some(&trend(true, 200.0 * GIB as f64, "firm")),
184 * GIB,
cap,
)
.unwrap();
assert!(soon.starts_with("Full within a week"), "{soon}");
// A floor is said on the line, not hidden in the number.
let floor = TrendJson {
measured_tenants: 7,
unmeasured_tenants: 2,
..trend(true, 12.9 * GIB as f64, "firm")
};
assert!(trend_line(Some(&floor), 184 * GIB, cap)
.unwrap()
.ends_with("a floor: 2 of 9 tenants unmeasured"));
assert_eq!(
trend_line(None, 1, cap),
None,
"no meter to lead with: no line"
);
}
#[test]
fn a_tones_word_is_its_class_without_the_badge() {
for t in [Tone::Pass, Tone::Warn, Tone::Bad, Tone::Live, Tone::Plain] {
assert_eq!(t.class(), format!("badge {}", t.word()));
}
}
}
// ── Screen 2: a product ─────────────────────────────────────────────────
/// **One tenant's standing on ONE meter**: the reading, the cap, and what the
/// bar may claim about them. Absence is kept as absence — a tenant the poller
/// has not read is not at zero, and a fact naming no cap is not a cap of zero.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Standing {
/// The meter, so the text can format bytes as bytes and counts as counts.
pub meter: String,
pub used: Option<u64>,
pub cap: Option<u64>,
pub state: BarState,
pub width: u8,
/// [`cap_note`]'s word for the cap, or `None` without one.
pub note: Option<&'static str>,
}
/// A tenant row's standing on `meter`. Reads what the row carries and nothing
/// else — no reading is `None`, and a cap of zero is no cap.
pub fn standing(row: &TenantRow, meter: &str, enforcement: &str) -> Standing {
standing_of(
meter,
row.meters.get(meter).copied(),
row.caps.get(meter).copied(),
enforcement,
)
}
/// The same decision from bare parts, for the bars on screen 3 — one writer of
/// "what does a reading against a cap claim" (one implementation).
pub fn standing_of(
meter: &str,
used: Option<u64>,
cap: Option<u64>,
enforcement: &str,
) -> Standing {
let cap = cap.filter(|c| *c > 0);
let state = match used {
Some(u) => bar_state(u, cap, enforcement),
None => BarState::Uncapped,
};
let width = match used {
Some(u) => state.width_percent(percent(u, cap)),
None => 0,
};
Standing {
meter: meter.to_owned(),
used,
cap,
state,
width,
note: cap_note(cap.is_some(), enforcement),
}
}
impl Standing {
/// `10.2 GiB of 10.0 GiB` · `10.2 GiB` (no cap) · `26 of 50` for a count ·
/// `not measured yet` — never a zero for a reading that did not happen.
pub fn text(&self) -> String {
match (self.used, self.cap) {
(Some(u), Some(c)) => format!(
"{} of {}",
meter_value(&self.meter, u),
meter_value(&self.meter, c)
),
(Some(u), None) => meter_value(&self.meter, u),
(None, _) => "not measured yet".to_owned(),
}
}
/// The word beside the bar: `AT CAP` when a WALL is being refused on, a
/// percentage otherwise, and the two absences said as absences. A measured
/// (not enforced) meter at 100 % is `100 %`, never `AT CAP` — the note
/// beside it says "not enforced", and nothing is being refused.
pub fn word(&self) -> String {
match (self.used, self.cap) {
(None, _) => "not read yet".into(),
(Some(_), None) => "no cap".into(),
(Some(u), Some(c)) => match self.state {
BarState::AtLimit => "AT CAP".into(),
_ => format!("{} %", percent(u, Some(c)).unwrap_or(0)),
},
}
}
/// **Sort key: lower first.** Capped and measured tenants first, closest to
/// their cap on top; then measured tenants nobody capped; then tenants
/// nobody has read. `docs/v2/07-console.md` rule 5 — the tenant at their cap is the first
/// row, and a tenant with no percentage cannot outrank one with one.
pub fn attention_key(&self) -> (u8, u8) {
match (self.used, self.cap) {
(Some(u), Some(c)) => (0, 100 - percent(u, Some(c)).unwrap_or(0)),
(Some(_), None) => (1, 0),
(None, _) => (2, 0),
}
}
}
/// Tenants in attention order, stable within a rank so the server's order
/// (alphabetical by tenant) breaks ties.
pub fn sort_closest(rows: &mut [TenantRow], meter: &str, enforcement: &str) {
rows.sort_by_key(|r| standing(r, meter, enforcement).attention_key());
}
/// How many tenants screen 2 shows before `+ N more`.
pub const SHOWN_TENANTS: usize = 5;
/// How many outcome lines screen 2's RECENTLY BOUGHT shows before `all activity →`.
pub const SHOWN_SALES: usize = 5;
/// `(shown, hidden)` for a list of `total` folded at `shown_max`. Hidden is 0
/// rather than 1 when everything fits: a fold that hides one row is a fold
/// that costs a click to save nothing.
pub fn fold(total: usize, shown_max: usize) -> (usize, usize) {
if total <= shown_max {
(total, 0)
} else {
(shown_max, total - shown_max)
}
}
/// **The growth badge**: `⚠ cannot grow` for a SEALED box, and a plain word for
/// a box that grows — or nothing. Only `sealed` earns the WARNING: a box that
/// can be grown by an operator is not a refusal, and one that could not tell
/// (`unknown`) must not be sold as sealed — a reinstall planned on a guess is
/// the wrong kind of certainty. `at_runtime` says the set grows in place (the
/// Data set panel is the way), `at_restart` that it grows at the next boot.
/// The empty state (never measured) is nothing.
pub fn growth_badge(state: &str) -> Option<(&'static str, Tone)> {
match state {
"sealed" => Some(("⚠ cannot grow", Tone::Warn)),
"at_runtime" => Some(("grows in place at runtime", Tone::Pass)),
"at_restart" => Some(("grows at restart", Tone::Plain)),
_ => None,
}
}
/// **`oversold by 4.0 GiB — promised 16.0 GiB, can serve 12.0 GiB`**, or
/// nothing. The one number that stops you selling air: the sum of what the
/// tenants were promised against what the box can serve (`docs/v2/07-console.md` "What must
/// be BUILT"). Only the `oversold` arm draws it — `fits` is headroom, not a
/// task; `unmeasured` already reads as such in the big number; `not_storage`
/// promises no bytes at all.
///
/// Keyed on the WORD, never on `oversell_bytes`: the same field carries a
/// `fits` fleet's headroom, and a reader that took the number alone would
/// announce 94 GiB of spare room as 94 GiB oversold.
pub fn oversell_line(m: &FleetMeterJson) -> Option<String> {
if m.oversell != "oversold" {
return None;
}
// The comparand is the capacity the server subtracted from; an oversold
// verdict always carries one, and the derivation below is the same
// subtraction run backwards, so an older wire without it still reads true.
let can_serve = m
.capacity
.unwrap_or(m.allowance.saturating_sub(m.oversell_bytes));
Some(format!(
"oversold by {} — promised {}, can serve {}",
bytes(m.oversell_bytes),
bytes(m.allowance),
bytes(can_serve)
))
}
/// Whether the ladder state is one an operator must not scroll past.
pub fn needs_attention_state(state: &str) -> bool {
matches!(state, "grace" | "suspended" | "retention")
}
// ── Screen 3: a tenant ──────────────────────────────────────────────────
/// **Which meters a tenant screen lists.** A `product`-scoped meter (gunnar's
/// CPU and RAM) belongs on the product screen once and in no per-tenant list —
/// a tenant does not own cores. `unknown` (an older server) stays listed: this
/// console hides nothing on a guess.
pub fn on_tenant_screen(scope: &str) -> bool {
scope != "product"
}
/// **BACKED BY, one line per thing the tenant stands on** — `docs/v2/06-data-model.md` §6.1
/// rule 4 made visible: what was BOUGHT, distinct from what is allowed.
/// `bought` is iron monetize paid for and a purge gives back; `on hand` is
/// capacity the pool already had, real to the tenant and not monetize's to
/// return. A kind this console does not know is shown as itself.
pub fn holding_line(h: &HoldingJson) -> String {
let how = if h.provisioned { "bought" } else { "on hand" };
format!("{} {} · {how}", h.detail, h.kind)
}
#[cfg(test)]
mod screen_three_tests {
use super::*;
/// Sold is not bought: the line says which of the two a holding is.
#[test]
fn a_holding_says_whether_monetize_bought_it() {
let bought = HoldingJson {
reference: "gunnar/alice/2026-09-04".into(),
kind: "disk".into(),
detail: "10 GiB on gunnar-se-sto1".into(),
provisioned: true,
id: "upcloud:abc".into(),
};
assert_eq!(
holding_line(&bought),
"10 GiB on gunnar-se-sto1 disk · bought"
);
let pool = HoldingJson {
provisioned: false,
id: String::new(),
..bought.clone()
};
assert_eq!(
holding_line(&pool),
"10 GiB on gunnar-se-sto1 disk · on hand"
);
assert_ne!(
holding_line(&bought),
holding_line(&pool),
"the same iron, two different claims about who paid"
);
}
#[test]
fn a_product_scoped_meter_is_not_in_a_tenants_list() {
assert!(on_tenant_screen("tenant"));
assert!(!on_tenant_screen("product"), "a tenant does not own cores");
assert!(
on_tenant_screen("unknown") && on_tenant_screen(""),
"an older server's silence hides nothing"
);
}
}
// ── FLOW: what MOVED, as against what is held (screens 2 and 3) ─────────
//
// Every meter USING draws is a STOCK reading — how much is on the disk — and
// it reads the same for a tenant who pushed this morning and one who stopped
// in March. The meters below are the other kind, and the arithmetic over them
// is `monetize_product::flow`'s, not this file's: `quiet_for` owns "0 means
// NEVER", `last_seen` owns "a fetch counts as being seen", and `daily_totals`
// owns "a `*_today` counter is read once per day however often it is polled".
// Nothing here re-derives one of those three.
/// **A meter that belongs to the Activity panel and not to USING.**
///
/// The flow meters arrive in the same `Usage` as the stock ones and are
/// declared in the same list, so USING drew them the moment the product
/// gained them: `bytes_in_today` as a bar with no cap, and — the defect that
/// makes this function exist — `last_push_unix_ms` as a thirteen-digit COUNT
/// with `no cap` beside it. A Unix millisecond is not a quantity a tenant is
/// using any amount of, and a bar is a claim that it is.
///
/// Keyed on the two suffixes the product declares them under (`*_today` for a
/// daily counter, `*_unix_ms` for an instant) rather than on a list of gunnar's
/// seven names, so a second product's flow meters land in the right panel
/// without this console learning their names.
pub fn is_flow_meter(name: &str) -> bool {
name.ends_with("_today") || name.ends_with("_unix_ms")
}
/// **The four day series the Activity panel draws, in order**: the product's
/// wire name, and the word on the page.
///
/// In one place because the panel draws them, the titles name them and the
/// axis labels them — three surfaces that must not come to disagree about
/// whether the second row is "out" or "bytes out". The product's own day keys
/// are richer than this (`lfs_bytes_in`, `auth_failures`, `distinct_principals`
/// and the `refused_*` reasons); those are not four bars, they are the detail,
/// and the panel does not draw them.
pub const ACTIVITY_SERIES: [(&str, &str); 4] = [
("bytes_in", "in"),
("bytes_out", "out"),
("pushes", "pushes"),
("fetches", "fetches"),
];
/// The day meter that marks a day RED: the product said no that day.
pub const REFUSED_METER: &str = "refused";
/// The page word for a day meter, or the wire name when the panel does not
/// have one — never an empty label.
pub fn series_word(meter: &str) -> &str {
ACTIVITY_SERIES
.iter()
.find(|(name, _)| *name == meter)
.map(|(_, word)| *word)
.unwrap_or(meter)
}
/// **A day meter's value in its own unit.** Not [`meter_value`]: the stock
/// meters are named `*_bytes` and the day meters are named `bytes_in`, so the
/// suffix rule reads every one of them as a count and would print 4 294 967 296
/// where the panel means `4.0 GiB`.
pub fn day_meter_value(meter: &str, value: u64) -> String {
if meter.contains("bytes") {
bytes(value)
} else {
value.to_string()
}
}
/// One day's bar in one series.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DayBar {
pub day_unix_ms: u64,
/// `Sep 4` — the x-axis label, from [`month_day`]. One date formatter.
pub label: String,
/// **`None` when the product did not count this meter that day.** Absence,
/// not zero: a product that stopped reporting and a day with no traffic
/// are different facts, and only the second is a quiet day.
pub value: Option<u64>,
/// 0..=100, against the WINDOW's own maximum.
pub width: u8,
/// This day had refusals. The count is in [`DayBar::title`]; the flag is
/// what the mark is drawn from.
pub refused: bool,
/// The whole row in words, for the bar's `title` — the only place a
/// thirty-bar strip can say what any one bar is.
pub title: String,
}
/// **A window of days as bars, scaled against its OWN maximum.**
///
/// Two things it refuses to do, both of which a naive scaling does:
///
/// * **A window whose maximum is 0 draws NO bars.** Dividing by the maximum
/// makes every day of a zero window full width (`0/0`, or the `max(1)`
/// spelling of it), so a customer who moved nothing for a month would be
/// drawn as a full, busy strip — the exact opposite of what happened, in the
/// one panel that exists to show that it happened.
/// * **A day the product did not count is not a bar of zero.** It draws
/// nothing and says so in its title.
///
/// A non-zero day is rounded UP to at least one percent, because a real day
/// that rounds to 0 % and a day that did not happen are the same empty column
/// on the screen.
pub fn day_bars(days: &[ActivityDayJson], meter: &str) -> Vec<DayBar> {
let word = series_word(meter);
let peak = days
.iter()
.filter_map(|d| d.meters.get(meter).copied())
.max()
.unwrap_or(0);
days.iter()
.map(|day| {
let value = day.meters.get(meter).copied();
let refused = day.meters.get(REFUSED_METER).copied().unwrap_or(0);
let label = month_day(day.day_unix_ms);
let width = match (value, peak) {
// Nothing in the window: no bar is drawn at all. See above.
(_, 0) | (None, _) => 0,
(Some(0), _) => 0,
(Some(v), p) => (v as u128 * 100).div_ceil(p as u128).min(100) as u8,
};
let title = match value {
Some(v) => format!("{label} · {word} {}", day_meter_value(meter, v)),
None => format!("{label} · {word} not counted"),
};
let title = match refused {
0 => title,
1 => format!("{title} · 1 refused"),
n => format!("{title} · {n} refused"),
};
DayBar {
day_unix_ms: day.day_unix_ms,
label,
value,
width,
refused: refused > 0,
title,
}
})
.collect()
}
/// One row of the Activity panel: a meter, its bars, and the window's total.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DaySeries {
pub meter: &'static str,
pub word: &'static str,
pub bars: Vec<DayBar>,
/// The window's total, added over the days the product COUNTED. Adding
/// days is right here and only here: the product already folded each day
/// (`monetize_product::flow::daily_totals`), so these are day totals and
/// not repeated readings of one counter.
pub total: u64,
}
impl DaySeries {
/// The total in its own unit — bytes as bytes, events as a count.
pub fn total_text(&self) -> String {
day_meter_value(self.meter, self.total)
}
/// Did anything at all move in this series' window? A series of nothing
/// draws its row and no bars, rather than being dropped: a missing row
/// reads as a meter the product does not have.
pub fn moved(&self) -> bool {
self.total > 0
}
}
/// The four rows the Activity panel draws, in [`ACTIVITY_SERIES`] order.
pub fn activity_series(days: &[ActivityDayJson]) -> Vec<DaySeries> {
ACTIVITY_SERIES
.iter()
.map(|(meter, word)| DaySeries {
meter,
word,
bars: day_bars(days, meter),
total: days
.iter()
.filter_map(|d| d.meters.get(*meter).copied())
.fold(0u64, |acc, n| acc.saturating_add(n)),
})
.collect()
}
/// **`last seen 3 d ago`, or `never seen`.**
///
/// The two `last_*_unix_ms` meters as one line. Both decisions belong to
/// `monetize_product::flow` and neither is made here: which of the two counts
/// (`last_seen` — a customer who clones every morning and never pushes is
/// using the product) and what `0` means (`quiet_for` — NEVER, and rendering
/// it as a date would put 1970 on a console beside real dates, which reads as
/// a very old customer rather than as one nobody has ever used).
pub fn last_seen_line(last_push_unix_ms: u64, last_fetch_unix_ms: u64, now_unix_ms: u64) -> String {
let usage = monetize_product::Usage {
meters: BTreeMap::from([
("last_push_unix_ms".to_owned(), last_push_unix_ms),
("last_fetch_unix_ms".to_owned(), last_fetch_unix_ms),
]),
measured_at_unix_ms: now_unix_ms,
};
match flow::quiet_for(flow::last_seen(&usage), now_unix_ms) {
// Unreachable from the tenant page, which does not call this at all
// when neither meter is present — but the arm is real and is answered
// with the true sentence rather than with `never seen`, so that a
// second caller cannot reintroduce the claim by forgetting the guard.
flow::Quiet::NoData => "no data".to_owned(),
flow::Quiet::Never => "never seen".to_owned(),
flow::Quiet::For(ms) => format!("last seen {}", since_ms(ms)),
}
}
/// **The line naming the tenants nobody measured**, under the SILENT panel.
///
/// The third fact on that panel and the one the page used to tell as the first:
/// a product that reports neither `last_*_unix_ms` meter says nothing about
/// whether its tenants are being used, and folding that into SILENT published
/// `never seen` about real customers on the strength of a gap in a plugin.
///
/// They are NAMED rather than counted-and-dropped. An operator who reads "3
/// tenants" has to go and find out which; an operator who reads the names knows
/// immediately whether it is one odd account or a whole product build that
/// stopped reporting — which is the difference between an afternoon and a
/// glance.
///
/// `None` when the list is empty: a panel that says "no data for 0 tenants"
/// every time is a line that gets stopped being read.
pub fn no_flow_data_line(tenants: &[String]) -> Option<String> {
match tenants {
[] => None,
[one] => Some(format!(
"NO DATA for {one} — this product does not report flow, so nothing here says whether \
it is being used. Not counted as silent."
)),
many => {
// The names, capped, because a product with 400 unreporting tenants
// must not push the measured findings off the screen.
const SHOWN: usize = 6;
let head = many
.iter()
.take(SHOWN)
.cloned()
.collect::<Vec<_>>()
.join(", ");
let rest = many.len().saturating_sub(SHOWN);
let names = if rest > 0 {
format!("{head} and {rest} more")
} else {
head
};
Some(format!(
"NO DATA for {} tenants ({names}) — this product does not report flow, so nothing \
here says whether they are being used. Not counted as silent.",
many.len()
))
}
}
}
/// **The words on a SILENT row.** `None` is `never seen` and is never a
/// number: the wire sends `null` for a tenant nobody has ever used, and
/// "quiet 0 d" is the most reassuring thing this console could put beside the
/// least reassuring row on the page.
pub fn quiet_line(quiet_days: Option<u64>) -> String {
match quiet_days {
None => "never seen".to_owned(),
// Not a row the server puts on this list, and rendered honestly if it
// ever is: a tenant seen today is not quiet for no days.
Some(0) => "seen today".to_owned(),
Some(1) => "quiet 1 d".to_owned(),
Some(n) => format!("quiet {n} d"),
}
}
/// **Why the Activity panel is empty**, which is two different facts and never
/// one sentence for both.
///
/// * The product remembers the whole window: nothing moved, and that is a
/// finding about the customer.
/// * The product remembers less than the window: the panel asked for thirty
/// days and this box keeps seven, so an empty strip says nothing about the
/// weeks before that and must not be read as if it did.
/// * `retained_days == 0`: the product would not say. Not a memory of no days
/// — a refusal to answer, and the line says so rather than picking whichever
/// of the two above happens to be more comfortable.
pub fn activity_empty_line(days_asked: u64, retained_days: u64) -> String {
if retained_days == 0 {
return format!("Nothing moved in the last {days_asked} days. This product does not say how far back it remembers, so an empty window here may be a short memory rather than a quiet month.");
}
if retained_days < days_asked {
return format!("Nothing moved in the last {retained_days} days. This product remembers no further back than that; the panel asked for {days_asked}.");
}
format!("Nothing moved in the last {days_asked} days.")
}
/// **The words on a FRICTION row**: `4 refusals in the last 7 days`.
pub fn friction_line(refused: u64, days: u64) -> String {
let window = if days == 1 {
"the last day".to_owned()
} else {
format!("the last {days} days")
};
match refused {
// Not a row the server sends — it drops a tenant with no refusals —
// and said plainly if one ever arrives, rather than as "0 refusals",
// which reads as a measurement of nothing.
0 => format!("nothing refused in {window}"),
1 => format!("1 refusal in {window}"),
n => format!("{n} refusals in {window}"),
}
}
/// **Whether the fleet page draws one of the two flow panels at all.**
///
/// The window is the SERVER's, and `0` is the one value it never sends: it is
/// what a `#[serde(default)]` leaves behind when the field is not on the wire
/// at all — a monetize older than the lists. Drawn anyway, that panel would
/// say "every tenant has been seen inside the last 0 days", which is an
/// all-clear about a question nobody asked. No window, no panel.
pub fn shows_flow_panel(window_days: u64) -> bool {
window_days > 0
}
/// The SILENT panel's caption: the threshold, from the wire.
pub fn silent_window_note(after_days: u64) -> String {
format!("no push and no fetch for {after_days} days")
}
/// The FRICTION panel's one short line — and it says what this page
/// deliberately does NOT do.
///
/// The fleet page folds refusals from the readings the server already holds,
/// never by asking the product once per tenant: a page that made N calls would
/// hang on the slowest box in the estate. The cost of that choice is that the
/// REASON breakdown is not here, and the line names where it is instead of
/// leaving an operator to wonder why a count has no cause beside it.
pub fn friction_window_note(days: u64) -> String {
format!(
"refused in the last {days} days · the reason breakdown is on each tenant's Activity panel"
)
}
/// The SILENT panel with nothing in it: an answer, not a blank.
pub fn silent_empty_line(after_days: u64) -> String {
format!("Every tenant has been pushed to or fetched from inside the last {after_days} days. Nothing to act on.")
}
/// The FRICTION panel with nothing in it.
pub fn friction_empty_line(days: u64) -> String {
format!("No tenant has been refused in the last {days} days. Nothing to act on.")
}
#[cfg(test)]
mod flow_tests {
use super::*;
const GIB: u64 = 1 << 30;
const DAY: u64 = 86_400_000;
const NOON: u64 = 1_789_041_600_000;
fn day(n: u64, meters: &[(&str, u64)]) -> ActivityDayJson {
ActivityDayJson {
day_unix_ms: NOON - (n * DAY),
meters: meters.iter().map(|(k, v)| ((*k).to_string(), *v)).collect(),
}
}
/// ★ **A month of nothing draws NO bars.**
///
/// RED before the `(_, 0)` arm: scaling against the window's maximum with
/// the usual `max(1)` guard makes every day of an all-zero window full
/// width, so the one panel that exists to show a customer stopped would
/// have drawn them as busy — thirty full bars for thirty empty days.
#[test]
fn a_window_that_holds_nothing_draws_no_bars_at_all() {
let quiet: Vec<ActivityDayJson> = (0..5).map(|n| day(n, &[("pushes", 0)])).collect();
let bars = day_bars(&quiet, "pushes");
assert_eq!(bars.len(), 5);
assert!(
bars.iter().all(|b| b.width == 0),
"a window whose maximum is 0 must draw nothing: {bars:?}"
);
assert!(
bars.iter().all(|b| b.value == Some(0)),
"the days were measured, and measured zero"
);
}
/// ★ **Scaled against the window's own maximum, and a real day is never
/// rounded away.**
#[test]
fn the_tallest_day_is_the_scale_and_the_smallest_still_draws() {
let days = vec![
day(2, &[("bytes_in", 10 * GIB)]),
day(1, &[("bytes_in", 5 * GIB)]),
day(0, &[("bytes_in", 1)]),
];
let bars = day_bars(&days, "bytes_in");
assert_eq!(bars[0].width, 100, "the peak is the scale");
assert_eq!(bars[1].width, 50);
assert_eq!(
bars[2].width, 1,
"one byte against ten gibibytes is a hair, not nothing: a real day must draw"
);
}
/// **A day the product did not count is absent, not a zero.** The bar is
/// empty either way; the TITLE is what keeps the two facts apart, and
/// `value` is `Option` so nothing downstream can add them together.
#[test]
fn a_day_the_product_did_not_count_is_not_a_day_of_none() {
let days = vec![day(1, &[("pushes", 4)]), day(0, &[("fetches", 9)])];
let bars = day_bars(&days, "pushes");
assert_eq!(
bars[1].value, None,
"the second day carries no `pushes` at all"
);
assert_eq!(bars[1].width, 0);
assert!(
bars[1].title.ends_with("pushes not counted"),
"{:?}",
bars[1].title
);
assert!(
bars[0].title.starts_with(&month_day(days[0].day_unix_ms)),
"the label is the x-axis label, one date formatter: {:?}",
bars[0].title
);
assert_eq!(
activity_series(&days)
.iter()
.find(|s| s.meter == "pushes")
.unwrap()
.total,
4,
"an uncounted day adds nothing rather than zero"
);
}
/// ★ **A day the product said no on is marked, and the title carries the
/// count.** The mark is a flag and not the number, because a red tick is
/// legible at one bar in thirty and a number is not.
#[test]
fn a_day_with_refusals_is_marked_on_every_series() {
let days = vec![
day(1, &[("pushes", 3), ("refused", 2)]),
day(0, &[("pushes", 3)]),
];
for meter in ["pushes", "bytes_in"] {
let bars = day_bars(&days, meter);
assert!(
bars[0].refused,
"{meter}: the refusal is a fact about the DAY, not about one series"
);
assert!(!bars[1].refused);
}
assert!(
day_bars(&days, "pushes")[0].title.ends_with("· 2 refused"),
"{:?}",
day_bars(&days, "pushes")[0].title
);
assert!(
day_bars(&[day(0, &[("pushes", 1), ("refused", 1)])], "pushes")[0]
.title
.ends_with("· 1 refused"),
"one refusal is not `1 refusals`"
);
}
/// **Bytes are bytes and events are counts**, on names that carry neither
/// suffix `meter_value` keys on. RED against reusing `meter_value` here:
/// `bytes_in` does not end in `_bytes`, so 4 GiB of traffic printed as
/// `4294967296`.
#[test]
fn a_day_meter_says_bytes_in_bytes_and_events_in_counts() {
assert_eq!(day_meter_value("bytes_in", 4 * GIB), "4.0 GiB");
assert_eq!(day_meter_value("lfs_bytes_out", 64 * (1 << 20)), "64.0 MiB");
assert_eq!(day_meter_value("pushes", 12), "12");
assert_eq!(day_meter_value("refused", 0), "0");
assert_eq!(
meter_value("bytes_in", 4 * GIB),
"4294967296",
"the stock rule keys on the SUFFIX and cannot read a day meter"
);
}
/// The four rows are named once, in order, and the words are the page's.
#[test]
fn the_four_series_are_declared_in_one_place_and_keep_their_order() {
let series = activity_series(&[day(
0,
&[
("bytes_in", 2),
("bytes_out", 3),
("pushes", 4),
("fetches", 5),
],
)]);
assert_eq!(
series.iter().map(|s| s.word).collect::<Vec<_>>(),
["in", "out", "pushes", "fetches"]
);
assert_eq!(
series.iter().map(|s| s.total).collect::<Vec<_>>(),
[2, 3, 4, 5]
);
assert_eq!(series[0].total_text(), "2 B", "the byte rows read as bytes");
assert_eq!(series[2].total_text(), "4");
assert_eq!(series_word("bytes_in"), "in");
assert_eq!(
series_word("auth_failures"),
"auth_failures",
"a meter the panel has no word for keeps its own name rather than going blank"
);
assert!(series[0].moved(), "a series with a total moved");
// A series the tenant never used keeps its ROW: a missing row reads as
// a meter the product does not have, which is a different claim.
let one_sided = activity_series(&[day(0, &[("fetches", 5)])]);
assert_eq!(one_sided.len(), 4);
assert!(
!one_sided[2].moved() && one_sided[2].bars.iter().all(|b| b.width == 0),
"nothing was pushed, so nothing is drawn"
);
assert!(one_sided[3].moved());
}
/// ★ **`never seen` is never a date, and a fetch counts as being seen.**
///
/// Both decisions are `monetize_product::flow`'s; this test is here to
/// prove the console asks it rather than re-deriving them. `0` read as a
/// timestamp is 1970, which on a console beside real dates reads as a very
/// old customer rather than as one nobody has ever used.
#[test]
fn the_last_seen_line_says_never_rather_than_nineteen_seventy() {
assert_eq!(last_seen_line(0, 0, NOON), "never seen");
assert_eq!(last_seen_line(NOON - 3 * DAY, 0, NOON), "last seen 3 d ago");
assert_eq!(
last_seen_line(0, NOON - 3 * DAY, NOON),
"last seen 3 d ago",
"a customer who only ever clones is using the product"
);
assert_eq!(
last_seen_line(NOON - 40 * DAY, NOON - 2 * DAY, NOON),
"last seen 2 d ago",
"the LATER of the two"
);
assert_eq!(last_seen_line(NOON, 0, NOON), "last seen just now");
assert_eq!(
last_seen_line(NOON + DAY, 0, NOON),
"last seen just now",
"a clock that runs ahead is a disagreement, not a visit that has not happened"
);
}
/// ★ **A tenant nobody has ever used never reads as `quiet 0 d`.**
///
/// RED against a defaulted `0` on the wire field: `null` is what the
/// server sends for never, and the reassuring number is exactly the wrong
/// thing to print on the least reassuring row on the page.
#[test]
fn the_silent_row_says_never_seen_rather_than_a_number_of_days() {
assert_eq!(quiet_line(None), "never seen");
assert_eq!(quiet_line(Some(21)), "quiet 21 d");
assert_eq!(quiet_line(Some(1)), "quiet 1 d");
assert_eq!(
quiet_line(Some(0)),
"seen today",
"not a row the server sends, and not rendered as quiet for no time either"
);
assert_ne!(
quiet_line(None),
quiet_line(Some(0)),
"never and today are the two ends of this list, not the same words"
);
}
/// ★ **A tenant nobody MEASURED reads as NO DATA, by name — never as zero
/// and never as `never seen`.**
///
/// The third fact on the SILENT panel. `never seen` is a MEASUREMENT: the
/// product counts flow and counted none. NO DATA is a GAP — an older
/// product build, the `Admin.StorageInfo` fallback, a tenant the poller has
/// never read — and it says nothing at all about the customer.
///
/// Until 2026-09-10 the fleet page could not tell them apart:
/// `flow::last_seen` folded an absent meter into a meter reading `0`, so an
/// unreporting tenant was drawn `never seen` AND sorted to the top of the
/// page by `quiet_for_ms`'s `u64::MAX`. The most alarming row on the screen
/// was manufactured from a plugin gap. `flow::Quiet::NoData` now keeps the
/// two apart in the type and `silent_tenants` cannot list one.
///
/// The names are ON the line: an operator who reads a count has to go and
/// find out which tenants, and one who reads the names can see at a glance
/// whether it is one odd account or a whole product that stopped reporting.
#[test]
fn no_data_is_named_and_is_not_never_seen_and_is_not_zero() {
// Nothing to say, so nothing is said.
assert_eq!(
no_flow_data_line(&[]),
None,
"a panel must not say `no data for 0 tenants` every time"
);
let one = no_flow_data_line(&["acme".to_owned()]).expect("a line");
assert!(one.contains("NO DATA"), "the fact is named: {one}");
assert!(one.contains("acme"), "and the tenant is named: {one}");
assert!(
one.contains("Not counted as silent"),
"and it says where it is NOT: {one}"
);
assert!(
!one.contains('0'),
"NO DATA must never be rendered as a zero: {one}"
);
assert!(
!one.contains("never seen"),
"a gap is not a measurement: {one}"
);
// Many are named too, and capped so they cannot push the measured
// findings off the screen.
let names: Vec<String> = (0..9).map(|n| format!("t{n}")).collect();
let many = no_flow_data_line(&names).expect("a line");
assert!(many.contains("9 tenants"), "{many}");
assert!(
many.contains("t0, t1, t2, t3, t4, t5"),
"the first six by name: {many}"
);
assert!(many.contains("and 3 more"), "and the rest counted: {many}");
assert!(!many.contains("t8"), "the list is capped: {many}");
// THE CONFLATION THIS EXISTS TO PREVENT: three different sentences for
// three different facts, and no two of them equal.
let no_data = one;
let never = quiet_line(None);
let idle = quiet_line(Some(0));
assert_ne!(no_data, never);
assert_ne!(never, idle);
assert_ne!(no_data, idle);
// And the one-line renderer answers the arm rather than falling through
// to `never seen`, so a second caller cannot reintroduce the claim.
let unmeasured = monetize_product::Usage {
meters: BTreeMap::new(),
measured_at_unix_ms: NOON,
};
assert!(monetize_product::flow::last_seen(&unmeasured).is_none());
assert_eq!(
monetize_product::flow::quiet_for(monetize_product::flow::last_seen(&unmeasured), NOON),
monetize_product::flow::Quiet::NoData
);
}
/// ★ **An empty window says WHICH silence it is.**
///
/// *Nothing moved in thirty days* is a finding about a customer.
/// *This box keeps seven days* is a fact about the box, and reading the
/// second as the first is how a console reports a quiet month that nobody
/// measured.
#[test]
fn an_empty_activity_window_tells_a_quiet_month_from_a_short_memory() {
let whole = activity_empty_line(30, 400);
assert_eq!(whole, "Nothing moved in the last 30 days.");
let short = activity_empty_line(30, 7);
assert!(
short.contains("last 7 days") && short.contains("asked for 30"),
"{short:?}"
);
assert_ne!(short, whole);
let silent = activity_empty_line(30, 0);
assert!(
silent.contains("does not say how far back"),
"0 retained days is a refusal to answer, not a memory of no days: {silent:?}"
);
assert_ne!(silent, short);
assert_ne!(silent, whole);
}
/// **The friction row counts refusals and names its window** — one is not
/// `1 refusals`, and a count with no window is a number nobody can act on.
#[test]
fn the_friction_line_counts_refusals_and_states_the_window_it_counted_over() {
assert_eq!(friction_line(4, 7), "4 refusals in the last 7 days");
assert_eq!(friction_line(1, 7), "1 refusal in the last 7 days");
assert_eq!(friction_line(1, 1), "1 refusal in the last day");
assert_eq!(
friction_line(0, 7),
"nothing refused in the last 7 days",
"not `0 refusals`, which reads as a measurement of nothing"
);
}
/// **An empty list says there is nothing to act on**, and both captions
/// carry the window the SERVER applied rather than a number this console
/// picked.
#[test]
fn an_empty_list_says_nothing_to_act_on_and_the_captions_carry_the_servers_window() {
assert!(
silent_empty_line(14).contains("14 days")
&& silent_empty_line(14).ends_with("Nothing to act on.")
);
assert!(
friction_empty_line(7).contains("7 days")
&& friction_empty_line(7).ends_with("Nothing to act on.")
);
assert!(
silent_window_note(21).contains("21"),
"the threshold is the wire's, so a server that changes it changes the caption"
);
let note = friction_window_note(7);
assert!(note.contains("7 days") && note.contains("Activity panel"), "the fleet page must say where the reasons are, since it deliberately does not fetch them: {note:?}");
}
/// ★ **A server too old to have the lists draws no panel, rather than an
/// all-clear over a window of no days.**
///
/// RED against drawing the panels unconditionally: the window arrives
/// `#[serde(default)]`, so an older monetize leaves 0 behind, and the
/// empty state would then have read "every tenant has been pushed to or
/// fetched from inside the last 0 days" — a reassurance nobody measured.
#[test]
fn a_window_of_no_days_is_a_missing_field_and_draws_no_panel() {
assert!(shows_flow_panel(14) && shows_flow_panel(7) && shows_flow_panel(1));
assert!(
!shows_flow_panel(0),
"0 is the absent field, never a threshold"
);
}
/// ★ **A flow meter is not a bar in USING.**
///
/// RED before `is_flow_meter`: the seven flow meters are declared beside
/// the stock ones and arrive in the same reading, so USING drew
/// `last_push_unix_ms` as a thirteen-digit count with `no cap` beside it —
/// a bar claiming a tenant is using 1 789 041 600 000 of something.
#[test]
fn a_flow_meter_belongs_to_the_activity_panel_and_not_to_using() {
for flow in [
"bytes_in_today",
"bytes_out_today",
"pushes_today",
"fetches_today",
"refused_today",
"last_push_unix_ms",
"last_fetch_unix_ms",
] {
assert!(
is_flow_meter(flow),
"{flow} is flow and must not be a USING bar"
);
}
for stock in [
"pack_bytes",
"cache_bytes",
"cold_bytes",
"lfs_bytes",
"tombstoned_bytes",
"repos",
"objects",
"open_store_ram_bytes",
"cpu_millicores",
"ram_bytes",
"concurrent_transfers",
] {
assert!(
!is_flow_meter(stock),
"{stock} is a stock meter and must keep its bar"
);
}
}
}
// ── Sales: one attempt as one line (screens 2 and 4) ────────────────────
const MONTHS: [&str; 12] = [
"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
];
/// `Sep 4` — the day, as a row reads it. Year-less on purpose: the ledger's
/// full date is in the disclosure, and a list of outcomes is read for recency.
pub fn month_day(unix_ms: u64) -> String {
let (_, m, d) = civil_from_days((unix_ms / DAY_MS) as i64);
format!("{} {d}", MONTHS[(m - 1) as usize])
}
/// What a sale promised, in one phrase: `10.0 GiB` for a plan capping bytes,
/// `10.0 GiB + 50 repos` when it caps more, `no caps` when it caps nothing.
pub fn caps_phrase(caps: &BTreeMap<String, u64>) -> String {
if caps.is_empty() {
return "no caps".to_owned();
}
caps.iter()
.map(|(k, v)| {
if k.ends_with("_bytes") {
bytes(*v)
} else {
format!("{v} {}", plain_meter(k).to_lowercase())
}
})
.collect::<Vec<_>>()
.join(" + ")
}
/// **The outcome line** (`docs/v2/07-console.md` rule 6): `Sold bob 90.0 GiB`,
/// `Refunded zz-visibility 1.0 GiB`, `Declined bob 10.0 GiB`, `Open: bob
/// 10.0 GiB`. The verb is the outcome; the steps live behind the disclosure.
/// What is named is the DELTA — what was sold — not the tenant's whole
/// allowance afterwards; a monetize too old to send one is read by its caps.
pub fn sale_line(s: &SaleJson) -> String {
let what = caps_phrase(if s.delta.is_empty() {
&s.caps
} else {
&s.delta
});
match s.outcome.as_str() {
"granted" => format!("Sold {} {what}", s.tenant),
"refunded" => format!("Refunded {} {what}", s.tenant),
"declined" => format!("Declined {} {what}", s.tenant),
"open" => format!("Open: {} {what}", s.tenant),
other => format!("{other}: {} {what}", s.tenant),
}
}
/// The mark at the end of the row, and its colour. **Declined is not a
/// refund**: nothing was locked, and rendering it with the refund's arrow
/// invents money that moved. A refund that never drew is said so too.
pub fn sale_mark(outcome: &str, drawn: bool) -> (Tone, &'static str) {
match (outcome, drawn) {
("granted", _) => (Tone::Pass, "✓"),
("refunded", true) => (Tone::Warn, "↩ refunded"),
("refunded", false) => (Tone::Warn, "↩ released, nothing drawn"),
("declined", _) => (Tone::Bad, "✗ declined"),
("open", _) => (Tone::Live, "open"),
_ => (Tone::Plain, "unknown outcome"),
}
}
/// **NEEDS YOU's line**: the tenant and the server's reason, or the one
/// sentence that is true of every open attempt when the server gave none.
pub fn needs_you_line(s: &SaleJson) -> String {
let why = if s.reason.is_empty() {
"money reserved, not yet captured or released"
} else {
s.reason.as_str()
};
format!("{} — {why}", s.tenant)
}
/// Open attempts first — the only rows that are tasks — then history. Both
/// halves keep the server's order (newest first).
pub fn split_needs_you(sales: Vec<SaleJson>) -> (Vec<SaleJson>, Vec<SaleJson>) {
sales.into_iter().partition(|s| s.needs_you)
}
// ── Screen 4: activity ──────────────────────────────────────────────────
/// **A ledger step in plain words** (`docs/v2/07-console.md` rule 3): `reserved → bought →
/// charged → granted`. The `Step` variant name stays in the title. A kind this
/// console does not know is shown as itself — visible, never guessed.
pub fn plain_step(kind: &str) -> String {
match kind {
"Reserved" => "reserved",
"Declined" => "declined",
"Ensured" => "bought",
"Destroyed" => "gave back",
"Captured" => "charged",
"Released" => "refunded",
"EntitlementPushed" => "granted",
"Reclaimed" => "reclaimed",
// The data set enlarged in place by the outside actor: a standing
// cost, neither bought nor given back.
"Grown" => "grew the set",
other => other,
}
.to_owned()
}
/// The step's colour: money drawn is pass, money released is warn, a refusal
/// is bad, everything else is a fact with no colour.
pub fn step_tone(kind: &str) -> Tone {
match kind {
"Captured" | "EntitlementPushed" => Tone::Pass,
"Released" | "Destroyed" | "Reclaimed" => Tone::Warn,
"Declined" => Tone::Bad,
_ => Tone::Plain,
}
}
/// **The steps behind one outcome line**, out of the ledger's recent rows, in
/// the order they were written. Empty when the attempt is older than the
/// recent window — the disclosure then fetches them by reference instead.
pub fn steps_of(recent: &[RowJson], reference: &str) -> Vec<RowJson> {
let mut rows: Vec<RowJson> = recent
.iter()
.filter(|r| r.reference == reference)
.cloned()
.collect();
rows.sort_by_key(|r| r.seq);
rows
}
/// Activity scoped to one tenant (`History →` on screen 3), or everything.
pub fn sales_for(sales: Vec<SaleJson>, tenant: Option<&str>) -> Vec<SaleJson> {
match tenant.filter(|t| !t.is_empty()) {
Some(t) => sales.into_iter().filter(|s| s.tenant == t).collect(),
None => sales,
}
}
#[cfg(test)]
mod screen_four_tests {
use super::*;
fn row(reference: &str, seq: u32, kind: &str) -> RowJson {
RowJson {
reference: reference.into(),
seq,
at_unix_ms: 1_000 + u64::from(seq),
kind: kind.into(),
json: "{}".into(),
}
}
/// **The six steps read as a sentence, not as variant names.** RED against
/// an identity mapping: `EntitlementPushed` would sit on the surface.
#[test]
fn every_ledger_step_has_plain_words_and_an_unknown_one_keeps_its_name() {
let timeline: Vec<String> = ["Reserved", "Ensured", "Captured", "EntitlementPushed"]
.iter()
.map(|k| plain_step(k))
.collect();
assert_eq!(
timeline.join(" → "),
"reserved → bought → charged → granted"
);
for kind in [
"Reserved",
"Declined",
"Ensured",
"Destroyed",
"Captured",
"Released",
"EntitlementPushed",
"Reclaimed",
"Grown",
] {
let word = plain_step(kind);
assert_ne!(word, kind, "{kind} still shows its variant name");
assert_eq!(word, word.to_lowercase(), "{kind} → {word} is not plain");
}
assert_eq!(
plain_step("SomethingNew"),
"SomethingNew",
"unmapped is shown as itself"
);
assert_eq!(
(
step_tone("Captured"),
step_tone("Released"),
step_tone("Declined"),
step_tone("Reserved")
),
(Tone::Pass, Tone::Warn, Tone::Bad, Tone::Plain)
);
}
/// The disclosure shows THIS attempt's rows, in write order, whatever
/// order the recent list arrived in — and nothing from its neighbours.
#[test]
fn the_steps_behind_a_line_are_its_own_in_write_order() {
let recent = vec![
row("b", 1, "Captured"),
row("a", 2, "Captured"),
row("b", 0, "Reserved"),
row("a", 0, "Reserved"),
row("a", 1, "Ensured"),
];
let a: Vec<(u32, String)> = steps_of(&recent, "a")
.into_iter()
.map(|r| (r.seq, r.kind))
.collect();
assert_eq!(
a,
[
(0, "Reserved".to_string()),
(1, "Ensured".to_string()),
(2, "Captured".to_string())
]
);
assert_eq!(steps_of(&recent, "b").len(), 2);
assert!(
steps_of(&recent, "zzz").is_empty(),
"older than the window: nothing, and the page fetches by reference"
);
}
#[test]
fn history_for_one_tenant_keeps_only_that_tenants_attempts() {
let sale = |t: &str| SaleJson {
tenant: t.into(),
..SaleJson::default()
};
let all = vec![sale("a"), sale("b"), sale("a")];
assert_eq!(sales_for(all.clone(), Some("a")).len(), 2);
assert_eq!(sales_for(all.clone(), None).len(), 3);
assert_eq!(
sales_for(all.clone(), Some("")).len(),
3,
"an empty query is no filter"
);
assert!(sales_for(all, Some("nobody")).is_empty());
}
}
/// **Plain words on the surface** (`docs/v2/07-console.md` rule 3): the meter's name goes in
/// the title, this goes on the page. A meter this console has no word for is
/// shown by its raw name — said, not hidden, and never a guess.
pub fn plain_meter(name: &str) -> String {
match name {
"pack_bytes" => "Repository data",
"cache_bytes" => "Cache",
"cold_bytes" => "Cold tier",
"lfs_bytes" => "Large files",
"tombstoned_bytes" => "Deleted, not purged",
"repos" => "Repositories",
"open_store_ram_bytes" => "RAM for open stores",
"concurrent_transfers" => "Simultaneous transfers",
"cpu_millicores" => "CPU",
"ram_bytes" => "RAM",
"artifacts" => "Artifacts",
"archive_bytes" => "Archive",
"bytes_served" => "Bytes served",
"requests" => "Requests",
"proxy_upstream_hits" => "Upstream fetches",
other => other,
}
.to_owned()
}
#[cfg(test)]
mod screen_two_tests {
use super::*;
const GIB: u64 = 1 << 30;
fn row(tenant: &str, used: Option<u64>, cap: Option<u64>) -> TenantRow {
TenantRow {
tenant: tenant.into(),
meters: used
.map(|u| [("pack_bytes".to_string(), u)].into())
.unwrap_or_default(),
caps: cap
.map(|c| [("pack_bytes".to_string(), c)].into())
.unwrap_or_default(),
measured_at_unix_ms: used.map(|_| 1),
..TenantRow::default()
}
}
/// **The tenant at their cap is the first row.** RED against alphabetical
/// (the server's order): `bob` would sit above `alice`. And RED against
/// sorting by bytes used: an uncapped tenant with 50 GiB would outrank the
/// one being refused at 10.
#[test]
fn tenants_sort_by_closeness_to_cap_and_the_unmeasured_go_last() {
let mut rows = vec![
row("aaa-unread", None, Some(10 * GIB)),
row("big-uncapped", Some(50 * GIB), None),
row("bob", Some(3 * GIB + GIB / 8), Some(10 * GIB)),
row("alice", Some(10 * GIB + GIB / 4), Some(10 * GIB)),
row("near", Some(9 * GIB), Some(10 * GIB)),
];
sort_closest(&mut rows, "pack_bytes", "enforced");
let order: Vec<&str> = rows.iter().map(|r| r.tenant.as_str()).collect();
assert_eq!(
order,
["alice", "near", "bob", "big-uncapped", "aaa-unread"]
);
// Ties keep the server's order rather than reshuffling.
let mut tie = vec![
row("b", Some(GIB), Some(10 * GIB)),
row("a", Some(GIB), Some(10 * GIB)),
];
sort_closest(&mut tie, "pack_bytes", "enforced");
assert_eq!(tie[0].tenant, "b");
}
/// The standing's words at every shape: over the cap is AT CAP, a measured
/// (not enforced) cap at 100 % is NOT, no cap is said, no reading is said.
#[test]
fn a_standing_says_at_cap_only_for_a_wall_and_never_invents_a_zero() {
let at = standing(
&row("r", Some(10 * GIB + GIB / 4), Some(10 * GIB)),
"pack_bytes",
"enforced",
);
assert_eq!(
(at.text().as_str(), at.word().as_str(), at.state, at.width),
("10.2 GiB of 10.0 GiB", "AT CAP", BarState::AtLimit, 100)
);
let part = standing(
&row("h", Some(3 * GIB + GIB / 8), Some(10 * GIB)),
"pack_bytes",
"enforced",
);
assert_eq!(
(part.text().as_str(), part.word().as_str(), part.width),
("3.1 GiB of 10.0 GiB", "31 %", 31)
);
let metered = standing(
&row("m", Some(10 * GIB), Some(10 * GIB)),
"pack_bytes",
"measured",
);
assert_eq!(metered.word(), "100 %", "full and NOT refused");
assert_eq!(
metered.note,
Some("not enforced"),
"the note beside it says why 100 % is not AT CAP"
);
assert_ne!(metered.state, BarState::AtLimit);
assert_eq!(at.note, Some("wall"), "and a wall is called one");
// A count formats as a count, through the same formatter the bars use.
let repos = standing_of("repos", Some(26), Some(50), "measured");
assert_eq!(
(repos.text().as_str(), repos.word().as_str()),
("26 of 50", "52 %")
);
assert_eq!(
standing_of("repos", Some(26), None, "measured").text(),
"26"
);
let uncapped = standing(&row("u", Some(5 * GIB), None), "pack_bytes", "enforced");
assert_eq!(
(
uncapped.text().as_str(),
uncapped.word().as_str(),
uncapped.state
),
("5.0 GiB", "no cap", BarState::Uncapped)
);
let zero_cap = standing(&row("z", Some(5 * GIB), Some(0)), "pack_bytes", "enforced");
assert_eq!(
zero_cap.cap, None,
"a cap of 0 is how a product spells unlimited"
);
let unread = standing(&row("n", None, Some(10 * GIB)), "pack_bytes", "enforced");
assert_eq!(
(unread.text().as_str(), unread.word().as_str(), unread.width),
("not measured yet", "not read yet", 0)
);
assert!(!unread.text().contains("0 B"), "no reading is not zero");
}
#[test]
fn the_fold_never_hides_a_single_row() {
assert_eq!(fold(9, 5), (5, 4));
assert_eq!(fold(5, 5), (5, 0));
assert_eq!(
fold(6, 5),
(5, 1),
"six rows: five and one more — the fold saves a screen, not a click"
);
assert_eq!(fold(0, 5), (0, 0));
assert_eq!(fold(2, 5), (2, 0));
}
/// **Only SEALED earns the badge.** RED against `state != ""`: UNKNOWN would
/// have read as "cannot grow", and an operator would plan a reinstall on a
/// box nobody measured.
#[test]
fn only_a_sealed_box_says_cannot_grow() {
assert_eq!(growth_badge("sealed"), Some(("⚠ cannot grow", Tone::Warn)));
assert_eq!(
growth_badge("unknown"),
None,
"not a promise either way, so not a refusal either"
);
assert_eq!(
growth_badge("operator_only"),
None,
"it can be grown, by hand"
);
assert_eq!(growth_badge(""), None, "never measured");
}
/// **A box that grows at runtime does not wear `cannot grow`.** RED on the
/// live front 2026-09-15: the warning sat above a Data set panel offering
/// Grow. The words follow the growth word the product reported.
#[test]
fn a_box_that_grows_at_runtime_says_so_and_warns_nothing() {
let (word, tone) = growth_badge("at_runtime").expect("growth at runtime is worth saying");
assert!(!word.contains("cannot"), "{word}");
assert!(word.contains("runtime"), "{word}");
assert_eq!(tone, Tone::Pass);
let (restart, tone) = growth_badge("at_restart").unwrap();
assert!(
!restart.contains("cannot") && tone != Tone::Warn,
"{restart}"
);
}
/// **Only the OVERSOLD arm draws the line, and it draws the number.** RED
/// against keying on `oversell_bytes > 0`: the same field carries a `fits`
/// fleet's headroom, and 94 GiB of spare room rendered as "oversold by
/// 94.0 GiB" — the wrong task, stated with confidence. The string is
/// asserted whole: `is_some()` cannot tell `promised` from `can serve`.
#[test]
fn only_an_oversold_fleet_says_so_and_it_says_the_number() {
let meter = |oversell: &str, oversell_bytes: u64, capacity: Option<u64>| FleetMeterJson {
meter: "store_bytes".into(),
tenants: 2,
used: 8 * GIB + 800 * (1 << 20),
allowance: 16 * GIB,
oversell: oversell.into(),
oversell_bytes,
capacity,
..FleetMeterJson::default()
};
assert_eq!(
oversell_line(&meter("oversold", 4 * GIB, Some(12 * GIB))).as_deref(),
Some("oversold by 4.0 GiB — promised 16.0 GiB, can serve 12.0 GiB"),
"two tenants promised 16 GiB on a 12 GiB box"
);
assert_eq!(
oversell_line(&meter("fits", 94 * GIB, Some(110 * GIB))),
None,
"headroom is not a task, and `oversell_bytes` is the headroom here"
);
assert_eq!(
oversell_line(&meter("unmeasured", 0, None)),
None,
"the big number already says capacity unmeasured"
);
assert_eq!(
oversell_line(&meter("not_storage", 0, None)),
None,
"a meter that promises no bytes cannot oversell them"
);
}
#[test]
fn the_attention_states_are_the_middle_rungs() {
assert!(
needs_attention_state("grace")
&& needs_attention_state("suspended")
&& needs_attention_state("retention")
);
assert!(
!needs_attention_state("paid")
&& !needs_attention_state("free")
&& !needs_attention_state("")
);
}
}
#[cfg(test)]
mod sales_tests {
use super::*;
const GIB: u64 = 1 << 30;
/// **What a sale actually netted, and what an unreported fee must not become.**
#[test]
fn the_money_line_folds_the_net_and_stays_quiet_when_no_fee_was_reported() {
let quiet = SaleJson {
price_minor: 12_500,
currency: "SEK".into(),
cost_minor: 1_000,
cost_currency: "EUR".into(),
..SaleJson::default()
};
assert_eq!(sale_money_line(&quiet), "price 125.00 SEK · cost 10.00 EUR");
// Stripe kept 5.74 of 125.00 — measured, not computed from a rate card.
let charged = SaleJson {
fee_minor: Some(574),
fee_currency: "SEK".into(),
..quiet.clone()
};
assert_eq!(
sale_money_line(&charged),
"price 125.00 SEK · cost 10.00 EUR · vendor kept 5.74 SEK · net 119.26 SEK"
);
// A vendor that says it kept nothing is not the same as one that said
// nothing, and the line has to show the difference.
let free = SaleJson {
fee_minor: Some(0),
fee_currency: "SEK".into(),
..quiet.clone()
};
assert!(
sale_money_line(&free).contains("vendor kept 0.00 SEK"),
"{}",
sale_money_line(&free)
);
assert!(!sale_money_line(&quiet).contains("vendor kept"));
}
/// **The VAT per order** (owner 2026-09-28): the figure, its currency and
/// the rate; the vendor's own figure says so; a sale that never carried
/// tax (declined) shows nothing rather than a zero.
#[test]
fn a_sale_says_its_vat_and_whose_figure_it_is() {
let base = SaleJson {
price_minor: 22_000,
currency: "SEK".into(),
..SaleJson::default()
};
assert_eq!(
sale_tax_line(&base),
None,
"no tax on the row: nothing is said"
);
let folded = SaleJson {
tax_minor: Some(5_500),
tax_currency: "SEK".into(),
vat_basis_points: 2_500,
..base.clone()
};
assert_eq!(
sale_tax_line(&folded).as_deref(),
Some("VAT 55.00 SEK (25 %)")
);
let stripe = SaleJson {
tax_minor: Some(5_499),
tax_from_vendor: true,
..folded.clone()
};
assert_eq!(
sale_tax_line(&stripe).as_deref(),
Some("VAT 54.99 SEK (as the vendor charged)")
);
let odd = SaleJson {
vat_basis_points: 1_250,
..folded
};
assert_eq!(
sale_tax_line(&odd).as_deref(),
Some("VAT 55.00 SEK (12.5 %)")
);
// Stripe decides per country, each payment a row (owner 2026-10-11): the
// buyer's country and the rate Stripe computed for it are on the row.
let de = SaleJson {
tax_minor: Some(190),
tax_currency: "EUR".into(),
vat_basis_points: 1_900,
tax_from_vendor: true,
customer_country: "DE".into(),
..base.clone()
};
assert_eq!(
sale_tax_line(&de).as_deref(),
Some("VAT 1.90 EUR · DE 19 % (as the vendor charged)")
);
let se = SaleJson {
customer_country: "SE".into(),
tax_from_vendor: false,
..odd
};
assert_eq!(
sale_tax_line(&se).as_deref(),
Some("VAT 55.00 SEK · SE (12.5 %)")
);
}
fn sale(tenant: &str, outcome: &str, drawn: bool, needs_you: bool) -> SaleJson {
SaleJson {
reference: format!("gunnar/{tenant}/2026-09-04"),
tenant: tenant.into(),
price_minor: 22_000,
currency: "SEK".into(),
caps: [("pack_bytes".to_string(), 10 * GIB)].into(),
at_unix_ms: 1_788_868_800_000,
outcome: outcome.into(),
drawn,
needs_you,
..SaleJson::default()
}
}
/// **Outcomes, not steps** — and DECLINED is not a refund. RED against a
/// two-arm mark (`granted` → ✓, else ↩): a vendor's "no" would have shown
/// the refund arrow over money that was never locked.
#[test]
fn a_sale_is_one_line_and_declined_is_not_a_refund() {
assert_eq!(
sale_line(&sale("bob", "granted", true, false)),
"Sold bob 10.0 GiB"
);
// The DELTA is what was sold: 90 GiB, on a tenant now allowed 100.
let more = SaleJson {
caps: [("pack_bytes".to_string(), 100 * GIB)].into(),
delta: [("pack_bytes".to_string(), 90 * GIB)].into(),
..sale("alice", "granted", true, false)
};
assert_eq!(
sale_line(&more),
"Sold alice 90.0 GiB",
"the delta, never the hundred"
);
assert_eq!(
sale_line(&sale("zz-visibility", "refunded", true, false)),
"Refunded zz-visibility 10.0 GiB"
);
assert_eq!(
sale_line(&sale("bob", "declined", false, false)),
"Declined bob 10.0 GiB"
);
assert_eq!(
sale_line(&sale("bob", "open", false, true)),
"Open: bob 10.0 GiB"
);
assert_eq!(
sale_line(&sale("x", "banana", false, false)),
"banana: x 10.0 GiB",
"an outcome this console does not know is shown, not guessed"
);
assert_eq!(sale_mark("granted", true), (Tone::Pass, "✓"));
assert_eq!(sale_mark("refunded", true), (Tone::Warn, "↩ refunded"));
let (tone, word) = sale_mark("refunded", false);
assert_eq!(tone, Tone::Warn);
assert!(
word.contains("nothing drawn"),
"a released reservation moved no money: {word}"
);
let (tone, word) = sale_mark("declined", false);
assert_eq!(tone, Tone::Bad);
assert!(
!word.contains('↩'),
"declined must not wear the refund arrow: {word}"
);
assert_eq!(sale_mark("open", false).0, Tone::Live);
assert_eq!(sale_mark("weird", false).0, Tone::Plain);
}
#[test]
fn needs_you_floats_the_open_attempts_and_keeps_the_servers_order() {
let (needs, rest) = split_needs_you(vec![
sale("a", "granted", true, false),
sale("b", "open", false, true),
sale("c", "refunded", true, false),
sale("d", "open", false, true),
]);
assert_eq!(
needs.iter().map(|s| s.tenant.as_str()).collect::<Vec<_>>(),
["b", "d"]
);
assert_eq!(
rest.iter().map(|s| s.tenant.as_str()).collect::<Vec<_>>(),
["a", "c"]
);
assert_eq!(
needs_you_line(&sale("b", "open", false, true)),
"b — money reserved, not yet captured or released"
);
let mut said = sale("b", "open", false, true);
said.reason = "paid, never delivered".into();
assert_eq!(
needs_you_line(&said),
"b — paid, never delivered",
"the server's words win when it gave any"
);
}
#[test]
fn the_day_and_the_caps_read_as_a_row() {
assert_eq!(month_day(1_788_868_800_000), "Sep 8");
assert_eq!(month_day(0), "Jan 1");
assert_eq!(month_day(parse_date_ms("2026-12-31").unwrap()), "Dec 31");
assert_eq!(
caps_phrase(&[("pack_bytes".to_string(), 10 * GIB)].into()),
"10.0 GiB"
);
assert_eq!(
caps_phrase(
&[
("pack_bytes".to_string(), 10 * GIB),
("repos".to_string(), 50)
]
.into()
),
"10.0 GiB + 50 repositories"
);
assert_eq!(caps_phrase(&BTreeMap::new()), "no caps");
}
/// **Raw meter names leave the surface.** Every meter gunnar and holger
/// declare has a word; a name nobody mapped comes back as itself, so it is
/// visible rather than swallowed. RED against a mapping that returned the
/// name for `pack_bytes` — the row would still say `pack_bytes`.
#[test]
fn every_declared_meter_has_plain_words_and_an_unknown_one_keeps_its_name() {
assert_eq!(plain_meter("pack_bytes"), "Repository data");
assert_eq!(plain_meter("cache_bytes"), "Cache");
assert_eq!(plain_meter("tombstoned_bytes"), "Deleted, not purged");
assert_eq!(plain_meter("repos"), "Repositories");
for name in [
"pack_bytes",
"cache_bytes",
"cold_bytes",
"lfs_bytes",
"tombstoned_bytes",
"repos",
"open_store_ram_bytes",
"concurrent_transfers",
"cpu_millicores",
"ram_bytes",
"artifacts",
"archive_bytes",
"bytes_served",
"requests",
"proxy_upstream_hits",
] {
let word = plain_meter(name);
assert_ne!(word, name, "{name} still shows its wire name");
assert!(!word.contains('_'), "{name} → {word} is still jargon");
}
assert_eq!(
plain_meter("something_new"),
"something_new",
"unmapped is shown as itself, never guessed"
);
}
}
// ── Screen 2: IRON — what we actually own out there, and the box itself ──
/// **The panel's title: `Iron · <provider>`.** It said `Cloud ·` until
/// 2026-09-05, when the `server` provider arrived — the iron a self-hoster
/// already has is not in any cloud, and a panel that called the box "cloud"
/// would be naming a vendor that is not there. Iron is what both are.
pub fn cloud_title(c: &CloudJson) -> String {
format!("Iron · {}", c.provider)
}
/// **The three states the IRON panel can be in.** `Hidden` is a box with no
/// cloud at all — the server sent no block, or the registry's `none` stub is
/// the only "provider" — and the panel does not render. `Unavailable` is a
/// cloud that exists and could not be listed: the sentence renders where the
/// list would, because absence is not zero. `Show` is a listing.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CloudPanel {
Hidden,
Unavailable(String),
Show,
}
pub fn cloud_panel_state(cloud: Option<&CloudJson>) -> CloudPanel {
match cloud {
// No block, a server older than the field (the BFF writes `provider: ""`),
// or the registry's `none` stub: this box has no cloud.
None => CloudPanel::Hidden,
Some(c) if c.provider.is_empty() || c.provider == "none" => CloudPanel::Hidden,
Some(c) if !c.available => CloudPanel::Unavailable(if c.error.is_empty() {
format!("{} could not be listed", c.provider)
} else {
format!("{} could not be listed: {}", c.provider, c.error)
}),
Some(_) => CloudPanel::Show,
}
}
/// **The panel's one line**: `10.0 GiB owned · ~2.26 EUR/month`. `unpriced > 0`
/// makes the money a floor and the line says `at least`. The currency is the
/// provider's own, as the wire carries it; a server older than the field sends
/// none, and the amount then stands alone rather than beside a guessed word.
pub fn cloud_summary_line(c: &CloudJson) -> String {
if c.owned.is_empty() {
return "nothing owned".to_owned();
}
let floor = if c.unpriced > 0 { "at least " } else { "" };
let cost = if c.currency.is_empty() {
money_amount(c.monthly_minor)
} else {
money(c.monthly_minor, &c.currency)
};
format!("{} owned · {floor}~{cost}/month", bytes(c.total_bytes))
}
/// One owned resource, as a row: every field is a string the page can place.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CloudRow {
pub id: String,
pub size: String,
pub zone: String,
/// `on <server>`, or `detached` — the proto's own word for an empty
/// `attached_to`.
pub attached: String,
pub tenant: String,
pub reference: String,
/// `provenance == "sandbox"`: fake iron, and the row wears a badge so a
/// tenant on it is distinguishable forever.
pub play_iron: bool,
/// `provenance == "server"`: the box itself — the product's own data set,
/// never bought, never an orphan. Wears `this box` instead of a play badge.
pub this_box: bool,
}
/// A row the provider lists as a SERVER (MAP-SEG 2): a machine, not a volume.
pub fn is_server(o: &CloudOwnedJson) -> bool {
o.kind == "server"
}
fn cloud_row(o: &CloudOwnedJson) -> CloudRow {
// MAP-SEG 2 (owner 2026-10-26: "server/… 0 B detached"): a server has no size of its
// own and is attached to nothing — it IS what disks attach to. It reads as a server.
let server = is_server(o);
CloudRow {
id: o.id.clone(),
size: if server {
"server".to_owned()
} else {
bytes(o.gib << 30)
},
zone: o.zone.clone(),
attached: if server {
String::new()
} else if o.attached_to.is_empty() {
"detached".to_owned()
} else {
format!("on {}", o.attached_to)
},
tenant: o.tenant.clone(),
reference: o.reference.clone(),
play_iron: o.provenance == "sandbox",
this_box: o.provenance == "server",
}
}
/// **One row per OWNED resource** — at the provider AND in this product's
/// ledger. Orphans are not here: they are tasks, and `cloud_needs_you` has them.
/// The servers first, then the disks (MAP-SEG 2): a machine, then what it holds.
pub fn cloud_rows(c: &CloudJson) -> Vec<CloudRow> {
c.owned
.iter()
.filter(|o| is_server(o))
.chain(c.owned.iter().filter(|o| !is_server(o)))
.map(cloud_row)
.collect()
}
/// **Where a row stands**: `se-sto1 · on <server>` for a disk, the zone alone for a server.
pub fn cloud_where(r: &CloudRow) -> String {
if r.attached.is_empty() {
r.zone.clone()
} else {
format!("{} · {}", r.zone, r.attached)
}
}
/// **A cloud task**: one of the two bugs that cost money, in plain words.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NeedsYou {
/// `orphan` | `missing`.
pub what: &'static str,
/// The resource id for an orphan; the tenant for a missing resource.
pub subject: String,
/// `10.0 GiB se-sto1` for an orphan; the fingerprint for a missing one.
pub detail: String,
pub why: &'static str,
}
impl NeedsYou {
pub fn line(&self) -> String {
format!(
"{} {} {} — {}",
self.what, self.subject, self.detail, self.why
)
}
}
/// **Orphans, then missing.** An orphan is at the provider and in no order:
/// we pay for it and nothing owns it. A missing resource is in the ledger and
/// not at the provider: sold, and not there. Both are tasks, not history.
pub fn cloud_needs_you(c: &CloudJson) -> Vec<NeedsYou> {
// MAP-3: an orphan BACKUP (its disk gone, or itself an orphan) says it is one, by its title.
let orphans = c.orphans.iter().map(|o| {
let backup = o.kind == "backup";
let title = if o.title.is_empty() {
String::new()
} else {
format!(" {:?}", o.title)
};
NeedsYou {
what: "orphan",
subject: o.id.clone(),
detail: format!(
"{}{} {}{title}",
if backup { "backup " } else { "" },
The file goes on; the console shows all of it.