crates/gunnar-ui-web/src/room.rs
//! **The Room table's shapes and its one arithmetic** — the half of the usage
//! page that decides things, kept where `cargo test` can reach it.
//!
//! `pages::usage` is `#[cfg(target_arch = "wasm32")]` like every module that
//! touches the DOM, so nothing inside it is compiled — let alone tested — by
//! `cargo test` on the host. That is fine for a view. It is not fine for the
//! decision this table now makes, which is the one the auditor found wrong:
//! **is this cap a wall or a reading**, and what does the page draw when the
//! box did not say. A test that only ever ran in a browser nobody launches is
//! a test that does not exist, so the types and the rules live here — no
//! leptos, no `web-sys` — and `pages::usage` re-exports them and does nothing
//! but draw. `auth` and `fmt` are here for the same reason and it is the
//! module layout `lib.rs` already describes.
use std::collections::BTreeMap;
use serde::Deserialize;
use crate::fmt;
/// One namespace's meters, exactly as `GET /api/namespaces/{name}/usage` sends
/// them.
///
/// The field names are gunnar's own, and are kept so on purpose: `gunnar
/// control usage` prints these names, and a person comparing the terminal with
/// the browser must not have to work out which of ours is which of its.
#[derive(Debug, Clone, Deserialize, PartialEq)]
pub struct Usage {
pub namespace: String,
pub pack_bytes: u64,
pub cache_bytes: u64,
pub cold_bytes: u64,
pub lfs_bytes: u64,
pub tombstoned_bytes: u64,
pub repos: u64,
pub objects: u64,
pub measured_at_unix_ms: Option<i64>,
// ── FLOW: what moved today ────────────────────────────────────────────
//
// Every meter above is a STOCK meter and reads the same for a namespace
// pushed to this morning as for one last touched in March. These are the
// other kind, and they ride on the same read. `#[serde(default)]` on all of
// them so a console served from a build newer than the server it is talking
// to still renders the page rather than failing to parse it.
#[serde(default)]
pub bytes_in_today: u64,
#[serde(default)]
pub bytes_out_today: u64,
#[serde(default)]
pub pushes_today: u64,
#[serde(default)]
pub fetches_today: u64,
#[serde(default)]
pub refused_today: u64,
/// The last transfer that put bytes in, over the whole kept window.
/// **`None` = never**, and it is `None` rather than 0 because gunnar sends
/// 0 for a namespace nothing has touched — see
/// [`crate::activity::last_seen_line`], which is where that turns into the
/// word.
#[serde(default)]
pub last_push_unix_ms: Option<i64>,
/// The last transfer that took bytes out. Same rule.
#[serde(default)]
pub last_fetch_unix_ms: Option<i64>,
/// Per meter, the cap the box holds. `None` = no cap, never 0.
#[serde(default)]
pub caps: PerMeter,
/// Per meter, what a cap on it DOES on the box that answered:
/// `enforced`, `measured`, `provisioned`.
///
/// **A missing entry — and an empty map — is "the box did not say", not
/// "nothing is enforced".** See [`shape_of`].
#[serde(default)]
pub enforcement: BTreeMap<String, String>,
/// Per meter, what is left — the server's one arithmetic. `None` = nothing
/// this console can read bounds it.
#[serde(default)]
pub remaining: PerMeter,
/// The box, when the console may read it.
#[serde(rename = "box", default)]
pub machine: Option<Machine>,
/// Why there is no box, in gunnar's own words; empty when there is one.
#[serde(default)]
pub box_unreadable: String,
}
/// One optional number per meter, named as gunnar names the meters.
#[derive(Debug, Clone, Default, Deserialize, PartialEq)]
pub struct PerMeter {
#[serde(default)]
pub pack_bytes: Option<u64>,
#[serde(default)]
pub cache_bytes: Option<u64>,
#[serde(default)]
pub cold_bytes: Option<u64>,
#[serde(default)]
pub lfs_bytes: Option<u64>,
#[serde(default)]
pub tombstoned_bytes: Option<u64>,
#[serde(default)]
pub repos: Option<u64>,
#[serde(default)]
pub objects: Option<u64>,
}
/// The box: what `Admin.Capacity` measured.
#[derive(Debug, Clone, Deserialize, PartialEq)]
pub struct Machine {
pub total_bytes: u64,
pub free_bytes: u64,
pub servable_bytes: u64,
/// `sealed`, `operator_only` or `unknown`.
pub growth: String,
/// Space that exists and may never be handed out. `#[serde(default)]` on
/// this and on every field below it, so a console served from a build
/// newer than the server it talks to still renders rather than failing to
/// parse — the same rule the flow half already follows above.
#[serde(default)]
pub floor_bytes: u64,
/// The cores gunnar may run on, ×1000. **`None` = not measured, never 0.**
#[serde(default)]
pub cpu_millicores_total: Option<u64>,
/// The 1-minute load ×1000 — the whole box's runnable tasks, an
/// approximation, and it can exceed [`Machine::cpu_millicores_total`].
#[serde(default)]
pub cpu_millicores_busy: Option<u64>,
/// `MemTotal`, clamped by a cgroup limit. **`None` = not measured.**
#[serde(default)]
pub ram_bytes_total: Option<u64>,
/// `MemAvailable`, clamped by a cgroup limit. **`None` = not measured.**
#[serde(default)]
pub ram_bytes_available: Option<u64>,
/// How the box was measured, and the OS error behind any absence, in
/// gunnar's own words. Empty only on a server too old to send it.
#[serde(default)]
pub machine_reason: String,
}
/// The growth verdict as the box line says it: nothing at all when unknown,
/// because a line that guessed would read as a promise.
pub fn growth_phrase(growth: &str) -> &'static str {
match growth {
"sealed" => "cannot grow while running",
"operator_only" => "an operator can grow it",
_ => "",
}
}
/// **What this row IS**, which is a different question from what its number is.
///
/// The defect this exists to close: `Usage.Read` reports seven caps in one
/// shape, and three of them are a door while four are a reading. A fact
/// carrying `caps["repos"]=5` put a `5` in the Allowed column and nothing on
/// the box refuses the sixth repository. A number shown to a tenant as a limit
/// that no code enforces is a promise the product does not keep, and drawing
/// both kinds identically is what made it one.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Shape {
/// **A WALL.** gunnar refuses when over, so the row is drawn with a bar
/// and a ceiling: the cap is a place the tenant will actually arrive at,
/// and a bar is the only thing on a page that says "and then it stops".
Wall,
/// **A READING.** The number is true and is billed from, and crossing it
/// is not an event on this box. Drawn as a figure and NO bar — a bar
/// implies a ceiling, and inventing one here is exactly the defect.
Reading,
/// **The box did not say.** An older gunnar sends no enforcement at all,
/// and it still cuts a push mid-stream at `pack_bytes`. So this is not
/// [`Shape::Reading`] with a shrug: no bar (this console cannot honestly
/// draw a ceiling it was not told about) and no claim that nothing
/// refuses either.
NotSaid,
}
/// **What a cap on this meter does, in monetize's own words.**
///
/// The two sentences are `monetize_product::Enforcement::describe()` verbatim,
/// including the semicolon. gunnar deliberately put monetize's three names on
/// the wire rather than coining `is_a_wall` or `hard`/`soft`; writing a third
/// phrasing HERE — "hard limit", "advisory", "soft cap" — would put a second
/// vocabulary on the surface a tenant reads, and it is the one that would go
/// stale, because nothing compares a console's prose to another repository's
/// enum.
pub fn describe(word: &str) -> &'static str {
match word {
"enforced" => "the product refuses when over",
"measured" => "counted and reported; nothing refuses",
"provisioned" => "provisioned at the cloud provider; not policed by the product",
// A word this build has not learned. The server already drops the codes
// it cannot name, so this arm is reached only by a console older than
// the gunnar it is talking to — and a guess there would be a sentence
// about a tenant's plan that nobody wrote.
_ => "",
}
}
/// The shape to draw for one meter: gunnar's word, or [`Shape::NotSaid`].
///
/// `provisioned` is a reading here, and gunnar never sends it for a meter in
/// this table — the variant exists so that the enum is monetize's whole
/// vocabulary. If it ever arrives, "bought at the cloud provider" is certainly
/// not a wall this box refuses at, so the reading shape is the safe reading.
pub fn shape_of(enforcement: &BTreeMap<String, String>, meter: &str) -> Shape {
match enforcement.get(meter).map(String::as_str) {
Some("enforced") => Shape::Wall,
Some("measured") | Some("provisioned") => Shape::Reading,
_ => Shape::NotSaid,
}
}
/// **How full the wall is, 0–100**, or `None` when there is no wall to fill.
///
/// Only ever computed for a [`Shape::Wall`] WITH a cap: a bar needs both ends,
/// and a wall with no cap is uncapped — the door exists and nothing has been
/// sold against it, so there is nothing to fill.
///
/// Saturates at 100 rather than running off the end. Over a cap is a real
/// state (a fact can be lowered under a namespace that is already past it) and
/// the honest drawing of it is a full bar; a 140%-wide bar would be a rendering
/// bug that reads as a data bug.
pub fn fill_pct(used: u64, cap: Option<u64>) -> Option<u32> {
let cap = cap?;
if cap == 0 {
// gunnar reports a 0 cap as ABSENT, so this is unreachable from a real
// answer. It is here because the alternative is a division by zero.
return Some(100);
}
Some(((used.min(cap) as u128 * 100) / cap as u128) as u32)
}
/// One row of the Room table: the meter, its three numbers, and what a cap on
/// it does.
pub struct RoomRow {
pub meter: &'static str,
pub used: String,
/// `None` = no cap.
pub allowed: Option<String>,
/// `None` = nothing bounds it that the console could read.
pub remaining: Option<String>,
/// Wall, reading, or a box that did not say.
pub shape: Shape,
/// monetize's own sentence for what a cap here does; empty when unsaid.
pub phrase: &'static str,
/// The bar's fill, 0–100. `Some` only for a wall with a cap in it.
pub fill: Option<u32>,
}
/// The seven rows, bytes rendered as bytes and counts as counts — the same
/// `fmt` the panels below use, so a number is spelled once.
///
/// The label a person reads and the meter gunnar names are both carried: the
/// first is the row heading, the second is the key into the enforcement map,
/// and pairing them here is what stops a renamed heading silently detaching a
/// row from the word that says what it does.
pub fn room_rows(usage: &Usage) -> Vec<RoomRow> {
let row = |label, key: &str, used: u64, cap: Option<u64>, left: Option<u64>, bytes: bool| {
let shape = shape_of(&usage.enforcement, key);
let unit = |n: u64| if bytes { fmt::bytes(n) } else { fmt::count(n) };
RoomRow {
meter: label,
used: unit(used),
allowed: cap.map(unit),
remaining: left.map(unit),
shape,
phrase: usage.enforcement.get(key).map_or("", |word| describe(word)),
// The bar is drawn for a WALL and for nothing else, which is the
// whole point: a reading has no ceiling to draw and a box that did
// not say has none this console may invent.
fill: match shape {
Shape::Wall => fill_pct(used, cap),
Shape::Reading | Shape::NotSaid => None,
},
}
};
let (caps, left) = (&usage.caps, &usage.remaining);
vec![
row(
"Not re-derivable",
"pack_bytes",
usage.pack_bytes,
caps.pack_bytes,
left.pack_bytes,
true,
),
row(
"Read cache",
"cache_bytes",
usage.cache_bytes,
caps.cache_bytes,
left.cache_bytes,
true,
),
row(
"Cold tier",
"cold_bytes",
usage.cold_bytes,
caps.cold_bytes,
left.cold_bytes,
true,
),
row(
"LFS",
"lfs_bytes",
usage.lfs_bytes,
caps.lfs_bytes,
left.lfs_bytes,
true,
),
row(
"Tombstoned",
"tombstoned_bytes",
usage.tombstoned_bytes,
caps.tombstoned_bytes,
left.tombstoned_bytes,
true,
),
row(
"Repositories",
"repos",
usage.repos,
caps.repos,
left.repos,
false,
),
row(
"Indexed objects",
"objects",
usage.objects,
caps.objects,
left.objects,
false,
),
]
}
/// The box line, or `None` when the box could not be read.
pub fn box_line(machine: Option<&Machine>) -> Option<String> {
let machine = machine?;
let mut line = format!(
"This box: {} free of {}",
fmt::bytes(machine.free_bytes),
fmt::bytes(machine.total_bytes)
);
let growth = growth_phrase(&machine.growth);
if !growth.is_empty() {
line.push_str(" \u{b7} ");
line.push_str(growth);
}
Some(line)
}
/// **One resource of the BOX: used, allowed, remaining.**
///
/// The Room table above answers *what is this namespace allowed*. This one
/// answers the other half of the same question — *what room is there at all* —
/// and it is a different table on purpose, because the two have different
/// scopes and merging them is the mistake. `UI.md`'s "Scope is the PRODUCT's
/// call": gunnar meters **disk per tenant and CPU/RAM per product**, a tenant
/// does not own cores, and `Allowance` has no field for either. A cores row
/// inside the Room table would read as a cap somebody was sold.
///
/// Three rows, and no fourth is invented: disk, cores, RAM are exactly what
/// `Admin.Capacity` measures.
pub struct BoxRow {
pub resource: &'static str,
/// `None` = gunnar did not measure it. **Never rendered as 0.**
pub used: Option<String>,
pub allowed: Option<String>,
pub remaining: Option<String>,
/// [`Shape::Wall`] for disk — gunnar's disk gate refuses writes under the
/// floor — and [`Shape::Reading`] for cores and RAM, which nothing in this
/// product refuses on.
pub shape: Shape,
/// monetize's own sentence for what this row's ceiling does, from
/// [`describe`]. Never a phrasing coined here.
///
/// # Why cores and RAM are `measured` here and not `provisioned`
///
/// `provisioned` is monetize's word and its sentence is *"provisioned at
/// the cloud provider; not policed by the product"* — and monetize does
/// file `cpu_millicores` and `ram_bytes` under it, because in monetize's
/// world they were bought from UpCloud. On THIS page that sentence would
/// be a claim about somebody's purchase, on a console that must carry
/// none, and on a self-hoster's own metal it would be plainly false:
/// there is no cloud provider, and the page has to be exactly as useful to
/// that person. `measured` — *"counted and reported; nothing refuses"* —
/// is true on metal and in a cloud alike, is what gunnar actually does
/// with these two numbers, and is still monetize's own word rather than a
/// third vocabulary invented on this surface.
pub phrase: &'static str,
/// The bar's fill. `Some` only on the wall, for the same reason as in
/// [`RoomRow`]: a bar IS a ceiling, and cores and RAM have none this
/// product enforces.
pub fill: Option<u32>,
/// A clause about what the number IS, where the number would otherwise be
/// read as something it is not. Empty on the rows that need none.
pub caveat: &'static str,
}
/// **used · allowed · remaining, from a total, what is left of it, and what is
/// held back and may never be handed out.**
///
/// Split out of [`box_rows`] so the closure can be asserted on the NUMBERS
/// rather than on their rounded words: `142.6 MiB` plus `37.8 GiB` does not
/// visibly equal `37.9 GiB`, and a test that could only read the cells could
/// not see the arithmetic it exists to check.
///
/// `used` is defined as `allowed − remaining` rather than as `total − left`,
/// which is the same number and closes **by construction**: both terms are
/// gunnar's own, and no third reading of the disk can drift from them.
pub fn triple(total: u64, left: u64, held_back: u64) -> (u64, u64, u64) {
let allowed = total.saturating_sub(held_back);
let remaining = left.min(allowed);
(allowed - remaining, allowed, remaining)
}
/// [`triple`] over two measurements that may be absent. `None` in either
/// means the box did not say, and then no column is drawn — not a zero.
fn triple_of(total: Option<u64>, left: Option<u64>, held_back: u64) -> Option<(u64, u64, u64)> {
Some(triple(total?, left?, held_back))
}
/// The three rows of the box table, or `None` when there is no box to read.
///
/// # The arithmetic, and why it closes
///
/// Every number here is `Admin.Capacity`'s own; the only derivation is
/// subtraction, and it is written so that **used + remaining = allowed**
/// exactly, on all three rows. A reader who checks a console's arithmetic and
/// finds it short has been told the console is wrong, and then does not
/// believe the next number either.
///
/// * **Disk** — allowed is `total − floor`, not `total`: the disk floor is
/// space that exists and may never be handed out, and calling it "allowed"
/// would offer 2 GiB that gunnar refuses to sell. Remaining is gunnar's own
/// `servable_bytes`.
/// * **RAM** — allowed is `MemTotal`, remaining is `MemAvailable` as the
/// kernel reports it (page cache counted as free), and nothing is held back.
/// * **Cores** — the ONE row that does not close, and it is written that way
/// deliberately. Allowed is the affinity mask ×1000; used is the one-minute
/// load, which counts every runnable task on the whole box and **can exceed
/// the cores gunnar may run on**; remaining saturates at zero rather than
/// wrapping. A loaded box therefore reads `6.20 used · 4.00 allowed · 0.00
/// remaining`, and that is the true reading — forcing it to add up would
/// mean either inventing cores the box does not have or hiding a load the
/// operator needs to see. [`BoxRow::caveat`] says what the number is.
///
/// # Absence is an answer
///
/// `cpu_*` and `ram_*` are `Option` and a `None` stays `None` all the way to
/// the cell. A row gunnar could not measure carries no number in any column —
/// there is nothing to subtract from — and the page prints gunnar's
/// `machine_reason` rather than drawing an empty machine. The two RAM fields
/// arrive together or not at all (`/proc/meminfo` was read or it was not), so
/// they are treated as one answer; the two CPU fields do not, and a box that
/// measured the mask but not the load keeps the mask.
pub fn box_rows(machine: Option<&Machine>) -> Option<Vec<BoxRow>> {
let machine = machine?;
// Disk. Present unconditionally: `Admin.Capacity` cannot answer at all
// without the `statvfs` these come from — an unmeasured filesystem is
// `CAPACITY_UNMEASURED` and there is no box on this page at all.
let (disk_used, disk_allowed, disk_left) =
triple(machine.total_bytes, machine.servable_bytes, machine.floor_bytes);
let disk = BoxRow {
resource: "Disk",
used: Some(fmt::bytes(disk_used)),
allowed: Some(fmt::bytes(disk_allowed)),
remaining: Some(fmt::bytes(disk_left)),
shape: Shape::Wall,
phrase: describe("enforced"),
fill: fill_pct(disk_used, Some(disk_allowed)),
caveat: "the disk floor is held back out of \u{201c}allowed\u{201d} and is never \
handed to a tenant",
};
let cores = BoxRow {
resource: "Cores",
used: machine.cpu_millicores_busy.map(fmt::cores),
allowed: machine.cpu_millicores_total.map(fmt::cores),
remaining: match (machine.cpu_millicores_total, machine.cpu_millicores_busy) {
(Some(total), Some(busy)) => Some(fmt::cores(total.saturating_sub(busy))),
_ => None,
},
shape: Shape::Reading,
phrase: describe("measured"),
fill: None,
caveat: "\u{201c}used\u{201d} is the one-minute load average of the WHOLE box, not \
Gunnar's own CPU time, and it is an approximation",
};
// RAM has no floor: every byte `MemTotal` counts may be used, so nothing
// is held back out of "allowed".
let ram = triple_of(machine.ram_bytes_total, machine.ram_bytes_available, 0);
let ram = BoxRow {
resource: "RAM",
used: ram.map(|(used, _, _)| fmt::bytes(used)),
allowed: ram.map(|(_, allowed, _)| fmt::bytes(allowed)),
remaining: ram.map(|(_, _, left)| fmt::bytes(left)),
shape: Shape::Reading,
phrase: describe("measured"),
fill: None,
caveat: "\u{201c}remaining\u{201d} is MemAvailable, which counts the page cache as \
free, as the kernel does",
};
Some(vec![disk, cores, ram])
}
#[cfg(test)]
mod tests {
use super::*;
/// gunnar's own table (`gunnar_grpc::service::meters::METERS`) as it
/// reaches this page: three walls, four readings, all seven present on
/// every read whether or not there is a cap to put beside them.
fn table() -> BTreeMap<String, String> {
[
("pack_bytes", "enforced"),
("cache_bytes", "enforced"),
("cold_bytes", "measured"),
("lfs_bytes", "enforced"),
("tombstoned_bytes", "measured"),
("repos", "measured"),
("objects", "measured"),
]
.into_iter()
.map(|(m, w)| (m.to_owned(), w.to_owned()))
.collect()
}
/// A namespace with a cap on a wall and a cap on a reading — the two rows
/// the whole defect is about, side by side, so a renderer that drew them
/// the same could not pass.
fn usage(enforcement: BTreeMap<String, String>) -> Usage {
Usage {
namespace: "alpha".to_owned(),
pack_bytes: 6_000_000,
cache_bytes: 1_572_864,
cold_bytes: 5_242_880,
lfs_bytes: 0,
tombstoned_bytes: 3_145_728,
repos: 9,
objects: 41_237,
measured_at_unix_ms: Some(1_788_000_000_000),
// The flow half. This fixture is about the Room table's shapes and
// nothing here feeds it — the numbers are non-zero anyway, so a row
// that started reading one of them by mistake would be visible.
bytes_in_today: 811_923,
bytes_out_today: 4_402_117,
pushes_today: 3,
fetches_today: 27,
refused_today: 2,
last_push_unix_ms: Some(1_787_999_100_000),
last_fetch_unix_ms: Some(1_787_999_940_000),
caps: PerMeter {
pack_bytes: Some(8_000_000),
// The auditor's example: five bought, nine held, and nothing
// on the box refuses the tenth.
repos: Some(12),
..PerMeter::default()
},
enforcement,
remaining: PerMeter::default(),
machine: None,
box_unreadable: String::new(),
}
}
fn row<'rows>(rows: &'rows [RoomRow], meter: &str) -> &'rows RoomRow {
rows.iter()
.find(|r| r.meter == meter)
.expect("the row is in the table")
}
/// ★ **An enforced cap is a wall; a measured cap is a reading.**
///
/// Seen RED against the table as it stood — every row drawn identically,
/// which is the state this whole change is about:
///
/// ```text
/// ---- pages::usage::tests::a_wall_is_drawn_as_a_wall_and_a_reading_is_not stdout ----
/// `Repositories` is a cap of 12 that nothing on the box refuses the
/// thirteenth of, and it is drawn with a bar: Some(75)
/// ```
#[test]
fn a_wall_is_drawn_as_a_wall_and_a_reading_is_not() {
let rows = room_rows(&usage(table()));
let pack = row(&rows, "Not re-derivable");
assert_eq!(pack.shape, Shape::Wall);
assert_eq!(
pack.fill,
Some(75),
"6 MB of an 8 MB cap gunnar cuts a push at is three quarters of a wall"
);
assert_eq!(
pack.phrase, "the product refuses when over",
"monetize's own sentence, not this console's paraphrase"
);
let repos = row(&rows, "Repositories");
assert_eq!(repos.shape, Shape::Reading);
assert_eq!(
repos.fill, None,
"`Repositories` is a cap of 12 that nothing on the box refuses the thirteenth \
of, and it is drawn with a bar: {:?}",
repos.fill
);
assert_eq!(repos.phrase, "counted and reported; nothing refuses");
// And the number is still SHOWN. Hiding it was the other candidate
// fix and it is the wrong one: the tenant bought it.
assert_eq!(repos.allowed.as_deref(), Some("12"));
}
/// **A wall with no cap has no bar**, because a bar needs both ends. The
/// door on `cache_bytes` exists on every gunnar; on a namespace nothing was
/// sold against, there is no ceiling to fill towards, and a full-width
/// empty track would read as "you have room" — a claim about a cap that is
/// not there.
#[test]
fn a_door_nothing_was_sold_against_draws_no_bar() {
let rows = room_rows(&usage(table()));
let cache = row(&rows, "Read cache");
assert_eq!(
cache.shape,
Shape::Wall,
"the explode budget is a door on every gunnar"
);
assert_eq!(cache.fill, None, "there is no cap here to fill towards");
assert_eq!(cache.allowed, None, "…and the Allowed column says so");
assert_eq!(cache.phrase, "the product refuses when over");
}
/// ★ **A gunnar that said nothing is not a gunnar with no doors.**
///
/// The self-hoster's box: older than `Allowance.enforcement`, sends an
/// empty map, and still cuts a push mid-stream at `pack_bytes`. Drawing
/// that as seven readings would tell its operator their pack quota is a
/// line item; drawing it as seven walls would invent three doors. It gets
/// neither, and the cell says why.
///
/// Seen red against a `shape_of` whose fallback was `Shape::Reading`:
/// *"a box that did not say was read as a box that refuses nothing"*.
#[test]
fn a_box_that_did_not_say_is_neither_a_wall_nor_a_reading() {
let rows = room_rows(&usage(BTreeMap::new()));
for label in ["Not re-derivable", "Read cache", "Repositories"] {
let r = row(&rows, label);
assert_eq!(
r.shape,
Shape::NotSaid,
"a box that did not say was read as a box that refuses nothing: {label}"
);
assert_eq!(
r.fill, None,
"{label}: no ceiling was reported, so none is drawn"
);
assert_eq!(
r.phrase, "",
"{label}: no sentence was sent, so none is quoted"
);
}
// The caps themselves are unaffected. An older gunnar with a signed
// fact still reports the numbers, and the page still shows them.
assert_eq!(
row(&rows, "Not re-derivable").allowed.as_deref(),
Some("7.6 MiB")
);
}
/// The bar's arithmetic, away from the table.
///
/// Over the cap saturates at a full bar rather than running off the end: a
/// fact can be lowered under a namespace that is already past it, which is
/// a real state, and a 140 %-wide bar would be a rendering bug that reads
/// as a data bug.
#[test]
fn the_bar_fills_towards_the_cap_and_stops_there() {
assert_eq!(fill_pct(0, Some(100)), Some(0));
assert_eq!(fill_pct(75, Some(100)), Some(75));
assert_eq!(fill_pct(100, Some(100)), Some(100));
assert_eq!(
fill_pct(140, Some(100)),
Some(100),
"over the cap is a full bar, not 140 %"
);
assert_eq!(fill_pct(7, None), None, "no cap, no bar");
// A byte count large enough to overflow `u64` when multiplied by 100 —
// the arithmetic goes through `u128` for exactly this, and a wrapped
// percentage would draw a nearly-empty bar for a nearly-full disk.
assert_eq!(fill_pct(u64::MAX / 2, Some(u64::MAX)), Some(49));
}
/// The three words gunnar can send and the sentences they carry, held to
/// `monetize_product::Enforcement::describe()` character for character.
/// A word this build has not learned carries no sentence at all rather
/// than a guess about somebody's plan.
#[test]
fn the_words_are_monetizes_own() {
assert_eq!(describe("enforced"), "the product refuses when over");
assert_eq!(
describe("measured"),
"counted and reported; nothing refuses"
);
assert_eq!(
describe("provisioned"),
"provisioned at the cloud provider; not policed by the product"
);
assert_eq!(describe("hard"), "", "a word from nowhere gets no sentence");
let mut sent = BTreeMap::new();
sent.insert("pack_bytes".to_owned(), "enforced".to_owned());
assert_eq!(shape_of(&sent, "pack_bytes"), Shape::Wall);
// A meter the map does not mention is the same silence as no map.
assert_eq!(shape_of(&sent, "repos"), Shape::NotSaid);
// `provisioned` is iron bought at the provider; it is certainly not a
// door this box refuses at, so it draws as a reading.
sent.insert("repos".to_owned(), "provisioned".to_owned());
assert_eq!(shape_of(&sent, "repos"), Shape::Reading);
}
// ── the BOX table: what room there is, beside what a namespace is allowed ──
/// The live appliance, verified against `gunnar control capacity --at
/// 10.13.8.99:50051 --bytes 0` run from the front on 2026-09-20: a
/// 4×10.0G raid0 xfs set, 4 000 millicores of affinity mask, a load of
/// 0.01 and 15.6 GiB of RAM. The numbers are that read's, byte for byte,
/// so a cell that changes shape here can be held against a real answer.
fn the_box() -> Machine {
Machine {
total_bytes: 42_876_272_640,
free_bytes: 42_726_699_008,
servable_bytes: 40_579_215_360,
growth: "at_runtime".to_owned(),
floor_bytes: 2_147_483_648,
cpu_millicores_total: Some(4_000),
cpu_millicores_busy: Some(10),
ram_bytes_total: Some(16_791_379_968),
ram_bytes_available: Some(16_392_073_216),
machine_reason: "This box: 4 CPUs = 4000 millicores gunnar may run on".to_owned(),
}
}
fn box_row<'rows>(rows: &'rows [BoxRow], resource: &str) -> &'rows BoxRow {
rows.iter()
.find(|r| r.resource == resource)
.expect("the row is in the box table")
}
/// ★ **The box's three resources, from the appliance's own answer.**
///
/// Seen RED before `box_rows` existed — `cargo test -p gunnar-ui-web` on
/// oden, `error[E0425]: cannot find function 'box_rows' in this scope`:
/// the page had the box's disk in a sentence and had never been told the
/// cores or the RAM at all, though `Admin.Capacity` has measured both
/// since fields 14–18 landed on `CapacityResponse`.
#[test]
fn the_box_answers_for_disk_cores_and_ram() {
let rows = box_rows(Some(&the_box())).expect("there is a box");
assert_eq!(rows.len(), 3, "disk, cores, RAM — and no fourth invented");
let disk = box_row(&rows, "Disk");
assert_eq!(disk.used.as_deref(), Some("142.6 MiB"));
assert_eq!(
disk.allowed.as_deref(),
Some("37.9 GiB"),
"allowed is the set minus the 2.0 GiB floor, not the 39.9 GiB set"
);
assert_eq!(disk.remaining.as_deref(), Some("37.8 GiB"));
let cores = box_row(&rows, "Cores");
assert_eq!(cores.allowed.as_deref(), Some("4.00 cores"));
assert_eq!(
cores.used.as_deref(),
Some("0.01 cores"),
"a load of 0.01 is a true reading and must not round away to 0.0"
);
assert_eq!(cores.remaining.as_deref(), Some("3.99 cores"));
let ram = box_row(&rows, "RAM");
assert_eq!(ram.allowed.as_deref(), Some("15.6 GiB"));
assert_eq!(ram.remaining.as_deref(), Some("15.3 GiB"));
assert_eq!(ram.used.as_deref(), Some("380.8 MiB"));
}
/// ★ **Disk is a wall; cores and RAM are readings.**
///
/// The same rule the Room table already enforces, and the reason it must
/// hold here too: a bar IS a ceiling. gunnar's disk gate refuses a write
/// under the floor, so the disk row has one. Nothing in this product
/// refuses a push for load or for RAM — they are bought at the cloud
/// provider — so a bar on either would be a door that does not exist.
///
/// Seen red against a first draft that gave every box row `fill_pct`:
/// *"Cores is drawn with a bar: Some(0)"*.
#[test]
fn only_the_disk_row_is_a_wall() {
let rows = box_rows(Some(&the_box())).expect("there is a box");
let disk = box_row(&rows, "Disk");
assert_eq!(disk.shape, Shape::Wall);
assert_eq!(disk.fill, Some(0), "142.6 MiB of 37.9 GiB rounds to 0 %");
assert_eq!(disk.phrase, "the product refuses when over");
for resource in ["Cores", "RAM"] {
let row = box_row(&rows, resource);
assert_eq!(row.shape, Shape::Reading, "{resource}");
assert_eq!(row.fill, None, "{resource} is drawn with a bar: {:?}", row.fill);
// `measured`, NOT `provisioned` — see [`BoxRow::phrase`]. The
// `provisioned` sentence names a cloud provider, which is a claim
// about a purchase on a console that carries none and is simply
// untrue on a self-hoster's own metal.
assert_eq!(
row.phrase, "counted and reported; nothing refuses",
"{resource}: monetize's own sentence, not a phrasing coined here"
);
assert!(
!row.phrase.contains("cloud") && !row.phrase.contains("provider"),
"{resource}: the open console told a self-hoster their cores came from a \
cloud provider: {}",
row.phrase
);
}
}
/// ★ **used + remaining = allowed, on the numbers.**
///
/// Asserted on [`triple`] and not on the cells, because `142.6 MiB` plus
/// `37.8 GiB` does not visibly equal `37.9 GiB` and a test that read the
/// words could not tell a closing sum from a short one.
///
/// Seen red against a draft whose disk allowed was `total_bytes`: *"the
/// floor was offered as room: 149 573 632 + 40 579 215 360 =
/// 40 728 788 992, not 42 876 272 640"*.
#[test]
fn the_three_numbers_close() {
let (used, allowed, left) = triple(42_876_272_640, 40_579_215_360, 2_147_483_648);
assert_eq!(used + left, allowed, "the floor was offered as room");
assert_eq!(allowed, 40_728_788_992);
assert_eq!(used, 149_573_632);
// No floor: allowed is the whole total and the sum still closes.
let (used, allowed, left) = triple(16_791_379_968, 16_392_073_216, 0);
assert_eq!(used + left, allowed);
assert_eq!(used, 399_306_752);
// More left than there is allowed — a floor raised under a set that
// was already fuller than it. `remaining` is clamped to `allowed`
// rather than making `used` wrap to a set-sized number.
let (used, allowed, left) = triple(100, 90, 50);
assert_eq!((used, allowed, left), (0, 50, 50));
}
/// **A box that did not measure a resource says nothing about it**, in
/// every column, and never a zero.
///
/// `Admin.Capacity` sends these fields `optional` for exactly this: a
/// caller that reads a `0` has not been told the box is empty, it has been
/// told nothing. A `0.00 cores` row would say this appliance has no CPU.
///
/// Seen red against a draft that used `unwrap_or_default()`: *"an
/// unmeasured resource was drawn as an empty machine: Some(\"0.00
/// cores\")"*.
#[test]
fn an_unmeasured_resource_is_absent_not_zero() {
let mut machine = the_box();
machine.cpu_millicores_total = None;
machine.cpu_millicores_busy = None;
machine.ram_bytes_total = None;
machine.ram_bytes_available = None;
let rows = box_rows(Some(&machine)).expect("there is still a box");
for resource in ["Cores", "RAM"] {
let row = box_row(&rows, resource);
assert_eq!(
(&row.used, &row.allowed, &row.remaining),
(&None, &None, &None),
"an unmeasured resource was drawn as an empty machine: {resource}"
);
}
// The disk is unaffected: it comes from the `statvfs` without which
// there would be no box on the page at all.
assert_eq!(box_row(&rows, "Disk").allowed.as_deref(), Some("37.9 GiB"));
// Half an answer is still an answer: the mask was read, the load was
// not, and the row keeps the column it has.
let mut machine = the_box();
machine.cpu_millicores_busy = None;
let rows = box_rows(Some(&machine)).expect("there is a box");
let cores = box_row(&rows, "Cores");
assert_eq!(cores.allowed.as_deref(), Some("4.00 cores"));
assert_eq!(cores.used, None, "no load was measured, so none is drawn");
assert_eq!(cores.remaining, None, "and nothing may be subtracted from it");
}
/// **A load above the cores gunnar may run on saturates at no remainder**
/// and is still reported as it was read. The one row that does not close,
/// and the caveat is what makes it honest rather than wrong.
#[test]
fn a_load_above_the_mask_leaves_no_remainder() {
let mut machine = the_box();
machine.cpu_millicores_busy = Some(6_200);
let rows = box_rows(Some(&machine)).expect("there is a box");
let cores = box_row(&rows, "Cores");
assert_eq!(cores.used.as_deref(), Some("6.20 cores"));
assert_eq!(cores.allowed.as_deref(), Some("4.00 cores"));
assert_eq!(
cores.remaining.as_deref(),
Some("0.00 cores"),
"a negative remainder would have wrapped to 18 exa-cores"
);
assert!(
cores.caveat.contains("approximation"),
"the load is not gunnar's CPU time and the row must say so"
);
}
/// No box, no table — and not a table of zeroes. The console that may
/// read one namespace and not the server draws the Room table and nothing
/// here, which is the behaviour `box_line` already had.
#[test]
fn no_box_draws_no_box_table() {
assert!(box_rows(None).is_none());
}
}