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

nordisk/gunnar-ui

crates/gunnar-ui-server/src/api/usage.rs

//! **What one namespace holds**, counted by gunnar.
//!
//! # This is not a bill, and it is not behind anything
//!
//! gunnar serves `Usage` unconditionally. It is not behind a feature, not
//! behind a licence, and not behind an edition — deliberately, because a person
//! running gunnar on their own disk wants the same numbers as anyone else, and
//! the counting happens inside the server either way. Everything on this route
//! is the same for a self-hoster who will never pay anyone as it is for
//! anybody else, and it stays that way: a number is a fact about a disk.
//!
//! # Why `Usage.Read` and never `Usage.ReadAll`
//!
//! gunnar splits the two on the grant they take, the same way `Admin` splits
//! `RepoStorage` from `StorageInfo`:
//!
//! * `Usage.Read` names ONE namespace and takes `Read` **on that namespace** —
//!   so an owner can see their own numbers and nothing else.
//! * `Usage.ReadAll` spans every tenant and takes a **server-wide** `Read`.
//!
//! The console asks the narrow question. Reading everything and picking one row
//! out of it would answer the same question by first having every namespace's
//! figures in this process, which is a different thing to have done, and it
//! would make a credential scoped to one namespace fail on a page about that
//! namespace. `namespace_usage_asks_only_about_the_namespace_it_names` holds
//! that: the fake gunnar behind it refuses `ReadAll` exactly as a scoped
//! credential's would.
//!
//! # The refusal is gunnar's, and it is passed through
//!
//! There is no second authorisation rule here. gunnar decides whether the
//! caller may read a namespace's meters, and the console relays its answer
//! verbatim — a `PermissionDenied` becomes a 403 carrying gunnar's own words
//! and **no meters at all**. A rule invented in the console that `gunnar
//! control usage` would not share is the definition of a gate that does not
//! hold; the console's job here is not to have an opinion, it is not to leak
//! one namespace's numbers into another namespace's answer.
//!
//! **Measured, not assumed**: the credential this console signs with today
//! holds a server-wide grant, so on `gunnar.rs` gunnar answers every namespace
//! and the refusal path is not exercised in production. What the tests below
//! prove is that when gunnar DOES refuse — which is what it does for any
//! credential scoped to fewer namespaces than the page asks about — the console
//! refuses too, rather than falling back to a wider call or showing zeroes.
//!
//! # The allowance: what the namespace is allowed, beside what it holds
//!
//! Since 2026-09-05 `Usage.Read` also answers with the namespace's
//! **allowance** — per meter, the cap the box holds for it, ABSENT when there
//! is none. On the community appliance every cap is absent for everyone; on
//! the metered one a cap is there when a signed fact names the meter. The
//! console carries each cap as it came (`null` = no cap, never 0) and derives
//! ONE thing from it, here and nowhere else: `remaining`, per meter —
//!
//! * with a cap: `cap − used`, and for a byte meter no more than the box can
//!   serve (`Admin.Capacity.servable_bytes`, when the console may read it);
//! * without a cap: for a byte meter, what the box can serve — or nothing at
//!   all when the box cannot be read; for a count (`repos`, `objects`),
//!   nothing, because nothing bounds it that the box can say.
//!
//! No price, no plan, no date, no link to anywhere: a cap is a resource
//! number, and this route is exactly as useful to a person who will never pay
//! anyone as it is to anybody else.
//!
//! # A cap is not automatically a wall, and the answer says which
//!
//! Since gunnar grew `Allowance.enforcement` the same read carries, per meter,
//! **what a cap on it DOES on that box**: `enforced` — the product refuses when
//! over — or `measured` — counted and reported; nothing refuses. Those are
//! monetize's own two sentences (`monetize_product::Enforcement::describe`) and
//! they are used verbatim on the page, because gunnar took monetize's
//! vocabulary onto the wire on purpose and a third spelling invented here would
//! be the one that goes stale.
//!
//! It matters because three of the seven meters are a door and four are a
//! reading, and until this field existed all seven arrived in one shape under
//! one message called `Allowance`: a fact carrying `caps["repos"]=5` showed a
//! tenant a limit of 5 that nothing on the box refuses the sixth of.
//!
//! **An EMPTY map is "this box did not say", never "nothing is enforced."** See
//! [`Usage::enforcement`]: an older gunnar sends no map and still cuts a push
//! mid-stream.
//!
//! # The box
//!
//! `Admin.Capacity`, asked with `requested_bytes: 0` and no namespace — the
//! console wants the measurement (total, free, servable, whether the set can
//! grow), not a verdict. It takes a **server-wide** `Read`. When gunnar refuses
//! it, or has no disk gate to measure with, the answer here carries no box and
//! says why in gunnar's own words; the meters and the caps are unaffected, and
//! `remaining` is computed without the box term. A console that can read one
//! namespace and not the box still draws a correct page.
//!
//! The same answer carries the box's OTHER two resources — cores and RAM —
//! and they are carried here too. They are NOT namespace meters: `UI.md`'s
//! *"Scope is the PRODUCT's call"* says gunnar meters disk per tenant and
//! CPU/RAM per product, a tenant does not own cores, and `Allowance` has no
//! field for either. So they never enter the Room table and never enter
//! `remaining`; they are the box's own three-number line, beside disk. Each
//! is `Option` and **absent means gunnar did not measure it, never 0** —
//! `machine_reason` says which, in gunnar's words, and no sentence about it is
//! written here.
//!
//! # FLOW: what moved, beside what is held
//!
//! Every meter above is a **stock** meter. It answers *how much is on the disk
//! right now*, and it reads exactly the same for a namespace that was pushed to
//! this morning and one whose last transfer was in March. That is the defect
//! the flow half closes: nothing on this page could tell a tenant who is using
//! the product from one who has gone silent, and nothing could tell either of
//! them from a tenant who is being turned away at a wall.
//!
//! It arrives in two shapes, and the console carries both:
//!
//! * **The glance** — seven fields on `NamespaceUsage` itself
//!   (`bytes_in_today`, `bytes_out_today`, `pushes_today`, `fetches_today`,
//!   `refused_today`, `last_push_unix_ms`, `last_fetch_unix_ms`), riding on the
//!   same `Usage.Read` the meters do, so a reader gets them without a second
//!   call. "Today" is the current UTC day and the counters reset at that
//!   boundary; they are carried here and NOTHING is derived from them.
//! * **The series** — [`activity`], one row per UTC day that moved something.
//!
//! The two timestamps are `Option<i64>` on this console's JSON where gunnar
//! sends `int64`, and that is not decoration: proto3 cannot tell an unset
//! `int64` from a zero, gunnar sends 0 for *never pushed inside the kept
//! window*, and a 0 rendered as a date puts **1 Jan 1970** — or, relatively,
//! "689 months ago" — beside a namespace that has simply never been pushed to.
//! [`crate::api::recorded`] is the one place that rule is written, and it is
//! the same rule `measured_at_unix_ms` already used.
//!
//! # `Usage.Activity`: the series, and the SAME grant
//!
//! `Usage.Activity` takes `Read` **on the namespace** — the identical grant
//! `Usage.Read` takes, and deliberately not a server-wide one: what a tenant
//! pushed and fetched is that tenant's own business, exactly as their meters
//! are. So there is no second authorisation rule in the console for it either.
//! gunnar decides, and its `PermissionDenied` becomes a 403 in gunnar's own
//! words carrying **no days at all** —
//! `a_namespaces_activity_is_refused_and_leaks_no_days` drives it.
//!
//! The rows are passed through as they came: **oldest first, and a day with no
//! traffic is ABSENT rather than a row of zeroes.** That absence is the point.
//! Zero-filling the gaps here would be inventing measurements — the console
//! would be asserting that a day gunnar never wrote had no traffic, which is a
//! claim only gunnar's ledger can make — and it would destroy the one
//! distinction the page needs: `retained_days` with an empty `days` means
//! *nothing moved*, and a refusal means *this could not be read*. Those are
//! different sentences on screen.
//!
//! `refused_by_reason` is carried the same way. gunnar sends only the reasons
//! that actually happened (`admission`, `cap`, `policy`); a reason it did not
//! send is **absent, never 0**, because "no push was refused by policy today"
//! and "this box does not break refusals down by reason" are different facts
//! and a fabricated `"policy": 0` would flatten them into one.
//!
//! # What is cached, and what is not
//!
//! The box is: one value for the whole server, one caller (this console's
//! key), so [`crate::grpc::Cached`] holds it for a few seconds like `Stats`.
//! The meter read is NOT: the cache holds a value under a fixed name, and the
//! answer depends on the namespace asked for AND on who asked. One shared slot
//! for a per-namespace, per-caller answer is a way to serve `beta`'s numbers
//! to a request for `alpha`. A visit to this page is one `Usage.Read`.
//!
//! `Usage.Activity` is not cached for the same reason, twice over: it is
//! per-namespace and per-caller, and a stale day row is a graph that is wrong
//! about today.

use std::collections::BTreeMap;

use axum::extract::{Path, State};
use axum::Json;
use serde::Serialize;

use crate::api::{ApiError, ApiResult, Reader};
use crate::app::AppState;
use crate::pb;

/// One namespace's meters, as the console shows them.
///
/// The field names are gunnar's own. They are not translated into friendlier
/// words here, because `gunnar control usage` prints those names and an
/// operator comparing the two must not have to guess which of ours is which of
/// its.
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct Usage {
    pub namespace: String,
    /// Objects on disk that are **not** re-derivable: the hot tier minus the
    /// exploded read cache, plus the cold tier.
    pub pack_bytes: u64,
    /// The exploded read cache. Re-derivable, and gunnar will re-derive it —
    /// which is why it is reported apart from `pack_bytes` rather than inside
    /// it.
    pub cache_bytes: u64,
    /// The cold (znippy) tier alone. `0` on an appliance that keeps one archive
    /// per repository, which has no separate cold tier to report — that zero is
    /// the truth of what is measured, not a placeholder.
    pub cold_bytes: u64,
    /// LFS objects. gunnar has no LFS store yet and reports 0 for every
    /// namespace on every server today.
    pub lfs_bytes: u64,
    /// Soft-deleted, not yet purged, and still on the disk. These bytes are in
    /// none of the numbers above: a tombstoned repository has no catalog row.
    pub tombstoned_bytes: u64,
    pub repos: u64,
    /// Indexed objects across the namespace's OPEN stores. The RAM those
    /// stores cost is roughly 382 B each; a closed store reports none, because
    /// nothing is paying for it.
    pub objects: u64,
    /// When gunnar took the measurement. `None` when it did not record one —
    /// proto3 cannot tell "unset" from "zero" and gunnar was not running in
    /// 1970.
    pub measured_at_unix_ms: Option<i64>,

    // ── FLOW: what moved today ────────────────────────────────────────────
    //
    // Seven fields, carried and never derived. See the module doc: 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. "Today" is the current UTC day
    // and gunnar resets the counters at that boundary; the console does not
    // know when that was and must not pretend to.
    /// Wire bytes gunnar ACCEPTED for this namespace today (receive-pack, and
    /// the client half of every other conversation).
    pub bytes_in_today: u64,
    /// Wire bytes gunnar SERVED today (upload-pack, the ref advertisement, the
    /// archive stream).
    pub bytes_out_today: u64,
    /// receive-pack sessions that finished without an error today.
    pub pushes_today: u64,
    /// upload-pack sessions today, clones included. A bare advertisement
    /// (`git ls-remote`) is not one — counting it would make a monitoring
    /// probe look like a customer.
    pub fetches_today: u64,
    /// Transfers turned away today, whatever the reason. The breakdown is on
    /// the day rows ([`Day::refused_by_reason`]); this is the one number a
    /// glance needs — *is this tenant hitting walls*.
    pub refused_today: u64,
    /// The last transfer that put bytes IN — **not restricted to today**, and
    /// `None` when the namespace has never been pushed to inside the kept
    /// window.
    ///
    /// `Option`, where gunnar sends a bare `int64`, for the reason
    /// [`crate::api::recorded`] exists: proto3 cannot tell unset from zero, and
    /// a namespace that has never been pushed to would otherwise be dated
    /// **1 Jan 1970** on a page whose whole job is to say when it was last
    /// seen.
    pub last_push_unix_ms: Option<i64>,
    /// The last transfer that took bytes OUT. Same rule as
    /// [`Usage::last_push_unix_ms`].
    pub last_fetch_unix_ms: Option<i64>,

    /// Per meter, the cap the box holds for this namespace. `null` = no cap,
    /// and it is never 0: gunnar itself reads a 0 cap as unlimited and reports
    /// it absent.
    pub caps: PerMeter,
    /// **Per meter, what a cap on it DOES on the box that answered** —
    /// `enforced` (the product refuses when over) or `measured` (counted and
    /// reported; nothing refuses). See [`enforcement_word`] for why these are
    /// words and not gunnar's wire numbers.
    ///
    /// **EMPTY means the box did not say, and that is not the same as
    /// "nothing is enforced."** gunnar has answered this map on every
    /// `Usage.Read` since it grew `Allowance.enforcement`, always for all
    /// seven meters, capped or not — so an empty map here is a gunnar OLDER
    /// than that field, which is a self-hoster's box and not a box with no
    /// doors. That box refuses on `pack_bytes`, `cache_bytes` and `lfs_bytes`
    /// exactly as this one does; it simply has no way to say so. Rendering it
    /// as seven readings would tell a tenant their pack quota is a line item,
    /// and the push that is cut mid-stream would be the correction.
    pub enforcement: BTreeMap<String, String>,
    /// Per meter, what is left: see the module doc for the one rule. `null` =
    /// nothing bounds it that this console can name.
    pub remaining: PerMeter,
    /// The box, when the console may read it.
    #[serde(rename = "box")]
    pub machine: Option<Machine>,
    /// Why there is no box, in gunnar's own words; empty when there is one.
    pub box_unreadable: String,
}

/// One optional number per meter, named as gunnar names the meters.
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
pub struct PerMeter {
    pub pack_bytes: Option<u64>,
    pub cache_bytes: Option<u64>,
    pub cold_bytes: Option<u64>,
    pub lfs_bytes: Option<u64>,
    pub tombstoned_bytes: Option<u64>,
    pub repos: Option<u64>,
    pub objects: Option<u64>,
}

/// The box: what `Admin.Capacity` measured, and whether the set can grow.
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct Machine {
    pub total_bytes: u64,
    pub free_bytes: u64,
    /// Free minus the floor gunnar keeps in reserve: what can actually be
    /// served.
    pub servable_bytes: u64,
    /// `sealed` (cannot gain capacity while it runs), `at_restart` (the boot
    /// stack grows it at the next boot), `at_runtime` (grows without a
    /// restart), `operator_only` (an operator can extend the filesystem out
    /// of band), or `unknown`.
    pub growth: &'static str,
    /// The store root the numbers are of (`/data` on the appliance).
    pub root: String,
    /// What the root IS, in gunnar's own words: `4×2.0G raid0 xfs`, `xfs`, or
    /// empty when gunnar could not tell.
    pub data_set: String,
    /// The disk floor in force: space that exists and may never be handed to
    /// a tenant, so that the box can still compact a pack and rewrite its
    /// control documents after it has stopped accepting pushes. Carried
    /// because without it `total`, `free` and `servable` do not add up on the
    /// page, and a reader who checks the arithmetic and finds it short has
    /// been told the console is wrong when it is not.
    pub floor_bytes: u64,

    // ── the box's OTHER two resources ────────────────────────────────────
    //
    // `Admin.Capacity` has measured cores and RAM since the machine half
    // landed on `CapacityResponse` (fields 14–18). They are `Scope::Product`,
    // not per tenant — `UI.md`'s "Scope is the PRODUCT's call": a tenant does
    // not own cores, the box does — so they have no cap in `Allowance` and no
    // row in the Room table. They belong to the box, and the box is where
    // they are carried.
    //
    // **Every one is `Option` and absent means NOT MEASURED, never 0.** That
    // is gunnar's own rule on the wire and it is kept here: a `0` handed to a
    // page would draw an empty machine, which is a different claim from "the
    // kernel would not say". [`Machine::machine_reason`] carries which.
    /// The CPUs gunnar may run on, ×1000 (affinity mask, clamped by a cgroup
    /// `cpu.max` quota when one is set).
    pub cpu_millicores_total: Option<u64>,
    /// `/proc/loadavg`'s 1-minute load ×1000 — the WHOLE box's runnable
    /// tasks, not gunnar's own CPU time, and an approximation that can exceed
    /// `cpu_millicores_total`. The page says so in those words rather than
    /// drawing it as a fraction of a ceiling.
    pub cpu_millicores_busy: Option<u64>,
    /// `/proc/meminfo` `MemTotal`, clamped to a cgroup `memory.max` limit.
    pub ram_bytes_total: Option<u64>,
    /// `/proc/meminfo` `MemAvailable`, clamped under a cgroup limit.
    pub ram_bytes_available: Option<u64>,
    /// **How the box was measured, or why a field above is absent, in
    /// gunnar's own words** — the sources it named, any cgroup clamp that
    /// applied, and the OS error behind every absence. gunnar never sends it
    /// empty. It is carried verbatim and this console writes no sentence of
    /// its own about why a number is missing.
    pub machine_reason: String,
}

/// `Admin.Capacity`'s growth verdict, as one word the page renders.
pub fn growth_word(growth: i32) -> &'static str {
    match pb::SetGrowth::try_from(growth) {
        Ok(pb::SetGrowth::GrowthSealed) => "sealed",
        Ok(pb::SetGrowth::GrowthAtRestart) => "at_restart",
        Ok(pb::SetGrowth::GrowthAtRuntime) => "at_runtime",
        Ok(pb::SetGrowth::GrowthOperatorOnly) => "operator_only",
        _ => "unknown",
    }
}

/// **The data set under the store root**, as `GET /api/dataset` sends it:
/// how big it is, how much is in use and free, how it is laid out, and whether
/// it can grow. Read-only, and the same on every build — a person running
/// gunnar on their own disks wants exactly these numbers.
///
/// Every number is `Admin.Capacity`'s own; nothing is derived except
/// `used_bytes` (`total − free`) and the parse of `layout` into its parts,
/// which never invents a part gunnar's words did not carry.
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct DataSet {
    pub root: String,
    pub total_bytes: u64,
    pub used_bytes: u64,
    pub free_bytes: u64,
    /// gunnar's layout words verbatim (`4×2.0G raid0 xfs`); empty = unknown.
    pub layout: String,
    /// `4` of `4×2.0G`, when the set is an array.
    pub members: Option<u32>,
    /// `2.0G` of `4×2.0G`, as gunnar rounded it.
    pub member_size: Option<String>,
    /// `raid0`, when the set is an array.
    pub level: Option<String>,
    /// `xfs`, the filesystem on top.
    pub filesystem: Option<String>,
    /// [`growth_word`]'s word.
    pub growth: &'static str,
}

/// gunnar's `data_set` words → their parts. `4×75G raid0 xfs` is an array;
/// `xfs` alone is a single filesystem; anything else keeps the verbatim words
/// and no parts, rather than a guess.
pub fn layout_parts(words: &str) -> (Option<u32>, Option<String>, Option<String>, Option<String>) {
    let parts: Vec<&str> = words.split_whitespace().collect();
    match parts.as_slice() {
        [fs] => (None, None, None, Some((*fs).to_owned())),
        [array, level, fs] => match array.split_once('\u{d7}') {
            Some((n, size)) => match n.parse::<u32>() {
                Ok(n) => (Some(n), Some(size.to_owned()), Some((*level).to_owned()), Some((*fs).to_owned())),
                Err(_) => (None, None, None, None),
            },
            None => (None, None, None, None),
        },
        _ => (None, None, None, None),
    }
}

/// The box → the data set.
pub fn render_data_set(machine: &Machine) -> DataSet {
    let (members, member_size, level, filesystem) = layout_parts(&machine.data_set);
    DataSet {
        root: machine.root.clone(),
        total_bytes: machine.total_bytes,
        used_bytes: machine.total_bytes.saturating_sub(machine.free_bytes),
        free_bytes: machine.free_bytes,
        layout: machine.data_set.clone(),
        members,
        member_size,
        level,
        filesystem,
        growth: machine.growth,
    }
}

/// `GET /api/dataset` — the data set, from one `Admin.Capacity` asked for
/// nothing in particular. Not cached: gunnar's refusal must reach the browser
/// as itself (a 403 with gunnar's words), which the box cache flattens to a
/// sentence for the usage page's sake.
/// `GET /api/machine/allocation` — **the box's own allocation**: disk, cores
/// and memory as `Admin.Capacity` measured them, for Administration →
/// Machine. An admin read (`role_floor`'s `/api/machine` prefix): it is the
/// whole box, not anybody's namespace. Not cached, for the reason
/// [`dataset`] gives.
pub async fn allocation(
    reader: crate::api::Reader) -> ApiResult<Machine> {
    let response = reader
        .gunnar
        .admin()
        .capacity(pb::CapacityRequest {
            requested_bytes: 0,
            namespace: String::new(),
            disks: false,
        })
        .await
        .map_err(ApiError::from)?
        .into_inner();
    Ok(Json(render_box(response)))
}

pub async fn dataset(
    reader: crate::api::Reader) -> ApiResult<DataSet> {
    let response = reader
        .gunnar
        .admin()
        .capacity(pb::CapacityRequest {
            requested_bytes: 0,
            namespace: String::new(),
            disks: false,
        })
        .await
        .map_err(ApiError::from)?
        .into_inner();
    Ok(Json(render_data_set(&render_box(response))))
}

/// **What a cap on one meter does, as a WORD** — `enforced`, `measured`,
/// `provisioned` — or `None` for a code this console has no word for.
///
/// # Why a word and not the number gunnar sent
///
/// `Allowance.enforcement` is `map<string, Enforcement>`, and prost hands that
/// over as a `HashMap<String, i32>`. Putting the `i32` on this console's JSON
/// would publish an enum's ORDINAL as an API value: `2` means `ENFORCED` only
/// for as long as nobody renumbers the proto, the browser would carry a magic
/// number nobody can read in a network tab, and the day a variant is inserted
/// the page would silently relabel every wall as a reading. The word is the
/// thing gunnar means; the number is an encoding of it.
///
/// The spelling is the proto variant's own name, lowercased and without its
/// `ENFORCEMENT_` prefix, which is this console's existing idiom for exactly
/// this ([`growth_word`] does the same to `SetGrowth`) and is monetize's too
/// (`Scope::name`, `Growth::name`). One vocabulary, three repositories.
///
/// `None` for `ENFORCEMENT_UNSPECIFIED` and for any future variant: a word this
/// build cannot name is no word at all, and the meter is left OUT of the map
/// rather than given a placeholder. "This box did not say" is the honest
/// reading of both, and it is the one the page already has to handle for an
/// older gunnar.
pub fn enforcement_word(code: i32) -> Option<&'static str> {
    match pb::Enforcement::try_from(code) {
        Ok(pb::Enforcement::Measured) => Some("measured"),
        Ok(pb::Enforcement::Enforced) => Some("enforced"),
        Ok(pb::Enforcement::Provisioned) => Some("provisioned"),
        Ok(pb::Enforcement::Unspecified) | Err(_) => None,
    }
}

/// gunnar's capacity answer → the box.
pub fn render_box(response: pb::CapacityResponse) -> Machine {
    Machine {
        total_bytes: response.total_bytes,
        free_bytes: response.free_bytes,
        servable_bytes: response.servable_bytes,
        growth: growth_word(response.growth),
        root: response.root,
        data_set: response.data_set,
        floor_bytes: response.floor_bytes,
        // Carried, not derived and not defaulted: `prost` decodes an absent
        // `optional` field as `None`, and `None` reaches the page as `null`.
        cpu_millicores_total: response.cpu_millicores_total,
        cpu_millicores_busy: response.cpu_millicores_busy,
        ram_bytes_total: response.ram_bytes_total,
        ram_bytes_available: response.ram_bytes_available,
        machine_reason: response.machine_reason,
    }
}

/// gunnar's message → the console's shape.
///
/// Every field is carried and none is derived. In particular `pack_bytes` is
/// **not** recomputed here from the other three: gunnar's own walk is what
/// `gunnar control usage` prints and what any meter is read from, and a second
/// arithmetic in the console would be a second answer to the same question.
///
/// The allowance is carried the same way: a cap gunnar sent is a cap, a cap
/// it did not send is `None`, and no cap is invented from a plan, a default or
/// the box. **`remaining` is gunnar's too** (thin layer, owner 2026-10-04):
/// `ReadUsageResponse.remaining`, cap − held per meter, absent where nothing
/// caps it. The console used to compute it four different ways (here, clamped
/// to the box; the account pane; "My room"; the import gate); now there is
/// one answer and gunnar gives it. The box is carried beside it, never folded
/// into it.
pub fn render(
    usage: pb::NamespaceUsage,
    allowance: pb::Allowance,
    remaining: Option<pb::Remaining>,
    machine: Result<Machine, String>,
) -> Usage {
    let caps = PerMeter {
        pack_bytes: allowance.pack_bytes,
        cache_bytes: allowance.cache_bytes,
        cold_bytes: allowance.cold_bytes,
        lfs_bytes: allowance.lfs_bytes,
        tombstoned_bytes: allowance.tombstoned_bytes,
        repos: allowance.repos,
        objects: allowance.objects,
    };
    let remaining = remaining.unwrap_or_default();
    let left = PerMeter {
        pack_bytes: remaining.pack_bytes,
        cache_bytes: remaining.cache_bytes,
        cold_bytes: remaining.cold_bytes,
        lfs_bytes: remaining.lfs_bytes,
        tombstoned_bytes: remaining.tombstoned_bytes,
        repos: remaining.repos,
        objects: remaining.objects,
    };
    // Sorted, and by the meter's own name: prost's map arrives in a
    // `HashMap`'s order, which is a different order on every process start,
    // and a JSON body that reshuffles between two reads of the same
    // unchanged namespace is a diff nobody can review.
    let enforcement: BTreeMap<String, String> = allowance
        .enforcement
        .iter()
        .filter_map(|(meter, code)| {
            enforcement_word(*code).map(|word| (meter.clone(), word.to_owned()))
        })
        .collect();
    let (machine, box_unreadable) = match machine {
        Ok(b) => (Some(b), String::new()),
        Err(why) => (None, why),
    };
    Usage {
        namespace: usage.namespace,
        pack_bytes: usage.pack_bytes,
        cache_bytes: usage.cache_bytes,
        cold_bytes: usage.cold_bytes,
        lfs_bytes: usage.lfs_bytes,
        tombstoned_bytes: usage.tombstoned_bytes,
        repos: usage.repos,
        objects: usage.objects,
        measured_at_unix_ms: crate::api::recorded(usage.measured_at_unix_ms),
        // The flow half, carried the same way the stock half is: gunnar's
        // number or nothing. In particular `bytes_in_today` is NOT the sum of
        // the day rows' `bytes_in` for today, and `refused_today` is NOT the
        // sum of `refused_by_reason` — gunnar counts each of them once, at the
        // transport, and a second arithmetic here would be a second answer to
        // the same question that drifts the first time a reason is added.
        bytes_in_today: usage.bytes_in_today,
        bytes_out_today: usage.bytes_out_today,
        pushes_today: usage.pushes_today,
        fetches_today: usage.fetches_today,
        refused_today: usage.refused_today,
        last_push_unix_ms: crate::api::recorded(usage.last_push_unix_ms),
        last_fetch_unix_ms: crate::api::recorded(usage.last_fetch_unix_ms),
        caps,
        enforcement,
        remaining: left,
        machine,
        box_unreadable,
    }
}

/// The box, or gunnar's reason there is none. Cached for a few seconds — for
/// the sessions that sign with the console's own key only.
///
/// A refusal is not an error for this page: a key that may read one namespace
/// and not the server still gets that namespace's meters and caps, with the
/// box term left out of `remaining` and the reason on the wire.
///
/// **Asked as the reader** (thin layer, owner 2026-10-04). Until this line the
/// box — total, free, servable, floor, data set, cores and memory — was read
/// with the console's server-wide key and handed to ANY namespace reader:
/// every customer saw the appliance's capacity, which `/api/dataset` keeps
/// behind the operator floor and `api::subscription` says is "not a tenant's
/// to read". gunnar answers `Admin.Capacity` to a server-wide Read grant, so a
/// customer's own key is refused, `machine` is absent and the reason says so.
async fn read_box(reader: &Reader, state: &AppState) -> Result<Machine, String> {
    state
        .caches
        .capacity
        .get_for(reader.may_cache(), async {
            reader
                .gunnar
                .admin()
                .capacity(pb::CapacityRequest {
                    requested_bytes: 0,
                    namespace: String::new(),
                    disks: false,
                })
                .await
                .map(|response| render_box(response.into_inner()))
                .map_err(|status| {
                    format!(
                        "Gunnar did not answer Admin.Capacity for this reader's key: {}",
                        status.message()
                    )
                })
        })
        .await
}

/// `GET /api/namespaces/{name}/usage`
///
/// One namespace, one `Usage.Read`. gunnar answers `NotFound` for a namespace
/// that is not in its catalog and `PermissionDenied` when the caller may not
/// read it, and both reach the browser as themselves.
pub async fn read(
    reader: Reader,
    State(state): State<AppState>,
    Path(namespace): Path<String>,
) -> ApiResult<Usage> {
    // **The CALLER's credential, not this console's.**
    //
    // The doc above says gunnar answers `PermissionDenied` when the caller may
    // not read this namespace, and until 2026-09-17 it could not: the call was
    // signed with `state.gunnar`, the console's server-wide admin key, and
    // gunnar cannot refuse a caller it cannot narrow. `role_floor` puts a GET
    // at `Viewer` and the namespace comes from the path, so a signed-in viewer
    // read any namespace's meters, caps and activity by spelling the name —
    // how much every other customer stores and what they pay for.
    //
    // `Reader` is what the other nine read modules in this crate already take,
    // and it changes nothing for an operator: it hands an operator or admin
    // session the console's own key, which is what they had. A viewer is
    // answered as themselves and gunnar decides. Pinned by
    // `a_viewers_read_is_not_signed_with_the_consoles_admin_key`.
    let response = reader
        .gunnar
        .usage()
        .read(pb::ReadUsageRequest {
            namespace: namespace.clone(),
        })
        .await
        .map_err(ApiError::from)?
        .into_inner();

    // gunnar cannot send this empty for a namespace it accepted — `Read` either
    // fills the field or answers NotFound — but proto3 makes the message
    // optional on the wire, and a `unwrap_or_default()` here would render an
    // entire namespace as seven zeroes, which is exactly the answer a person
    // reading this page must never be given by mistake.
    let usage = response.usage.ok_or_else(|| {
        ApiError(crate::grpc::Fail {
            code: "internal".to_owned(),
            message: format!(
                "Gunnar accepted the read for namespace {namespace:?} but sent no meters with \
                 it, so there is nothing to show. Showing zeroes here would read as an empty \
                 namespace."
            ),
        })
    })?;
    // A gunnar from before the allowance sends none; that is every cap absent,
    // which is also what it would have said.
    //
    // **The default is NOT the same answer for `enforcement`, and that
    // asymmetry is deliberate.** An absent cap and an absent allowance mean the
    // same thing — nothing caps this meter — so defaulting is lossless there.
    // An absent enforcement map means *"this box did not say"*, and the one
    // reading it must never be given is *"nothing on this box is enforced"*:
    // the appliance being re-imaged tonight answers the map, but a
    // self-hoster's older gunnar does not, and that box still cuts a push
    // mid-stream at `pack_bytes`, still bounds the explode cache at
    // `cache_bytes`, and still refuses an LFS upload at `lfs_bytes`. So the
    // empty map is carried out to the browser AS empty, and
    // `pages::usage::wall_or_reading` renders it as the silence it is rather
    // than as seven readings. See `Usage::enforcement`.
    let allowance = response.allowance.unwrap_or_default();
    let machine = read_box(&reader, &state).await;
    Ok(Json(render(usage, allowance, response.remaining, machine)))
}

// ── The series ─────────────────────────────────────────────────────────────

/// **How many UTC days the Activity panel asks for.**
///
/// Thirty, and the number is here rather than in the handler because the page
/// and the empty state both talk about it in words — *"no activity in the last
/// 30 days"* — and a window that said 30 and asked for 14 would put a true
/// sentence over a false graph.
///
/// gunnar keeps 400 (`ActivityResponse::retained_days`), so this is a slice of
/// what it holds and never the whole of it. A console that asked for the lot
/// would pull four hundred rows across the wire to draw thirty bars.
pub const ACTIVITY_DAYS: i64 = 30;

const DAY_MS: i64 = 86_400_000;

/// **The window's first millisecond**: midnight UTC, `days − 1` days before the
/// UTC day `now_unix_ms` falls in.
///
/// Floored to a day boundary rather than measured back from the current clock,
/// because gunnar's rows ARE whole UTC days: asking for "the last 30 × 24
/// hours" at 14:00 would land mid-day and make the oldest bar a partial day
/// drawn at full height beside twenty-nine whole ones. `days − 1` because the
/// window is inclusive at both ends — thirty days ending today is today plus
/// the twenty-nine before it, and an off-by-one here is a thirty-first bar.
///
/// `div_euclid`, not `/`: a pre-1970 timestamp is not a real input, but integer
/// division rounds towards zero and would floor a negative millisecond to the
/// day AFTER the one it is in. It costs nothing and it is the kind of thing
/// that is wrong for six months before anybody notices.
pub fn window_start(now_unix_ms: i64, days: i64) -> i64 {
    let midnight = now_unix_ms.div_euclid(DAY_MS) * DAY_MS;
    midnight - (days - 1) * DAY_MS
}

/// One UTC day's flow, exactly as gunnar counted it.
///
/// A day with no traffic HAS NO ROW — see [`activity`]. Nothing in this shape
/// is derived: `refused` is gunnar's own total and not the sum of
/// [`Day::refused_by_reason`], because a reason gunnar has not learned to name
/// yet would silently make the two disagree.
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct Day {
    /// Midnight UTC of the day, in Unix milliseconds. The key, and the x axis.
    pub day_unix_ms: i64,
    /// Wire bytes accepted that day.
    pub bytes_in: u64,
    /// Wire bytes served that day.
    pub bytes_out: u64,
    pub pushes: u64,
    pub fetches: u64,
    /// LFS object bytes as the LFS session counted them. **Also inside
    /// `bytes_in`/`bytes_out`** as wire bytes: this is the applied figure and
    /// that is the wire figure, two numbers about one transfer, so subtracting
    /// one from the other is not a thing to do with them.
    pub lfs_bytes_in: u64,
    pub lfs_bytes_out: u64,
    /// Transfers turned away that day, whatever the reason.
    pub refused: u64,
    /// **Only the reasons that actually happened** — `admission` (the box was
    /// full), `cap` (the plan refused before a byte), `policy` (the push gate
    /// refused). A reason gunnar did not send is ABSENT and is never written
    /// out as 0: "nothing was refused by policy" and "this box does not break
    /// refusals down" are different facts, and a fabricated zero merges them.
    ///
    /// A `BTreeMap` because prost hands the map over as a `HashMap`, whose
    /// iteration order changes on every process start; a JSON body that
    /// reshuffles between two reads of one unchanged day is a diff nobody can
    /// review. Same rule as [`Usage::enforcement`].
    pub refused_by_reason: BTreeMap<String, u64>,
    /// Principals that authenticated and then held no grant on what they asked
    /// for. A key that resolves to nobody is NOT here — it happens before any
    /// repository is named, so it belongs to no namespace.
    pub auth_failures: u64,
    /// Distinct principals seen that day: an exact count, bounded at 10 000.
    pub distinct_principals: u64,
    /// The count above is a FLOOR, not a total. Never true on a real namespace;
    /// it exists so that if it ever is, the number says so instead of quietly
    /// flattening.
    pub distinct_principals_capped: bool,
    /// The last push OF THAT DAY. `None` when the day had none — the same
    /// zero-is-not-1970 rule as everywhere else on this route.
    pub last_push_unix_ms: Option<i64>,
    /// The last fetch of that day. `None` when the day had none.
    pub last_fetch_unix_ms: Option<i64>,
}

/// The series for one namespace, as the console sends it.
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct Activity {
    pub namespace: String,
    /// How many days this gunnar keeps (400 today). The page needs it to tell
    /// an empty answer that means *nothing moved* from one that means *you
    /// asked about a time this box no longer remembers*.
    pub retained_days: i64,
    /// **Oldest first, gaps left as gaps.** See [`activity`].
    pub days: Vec<Day>,
}

/// gunnar's day row → the console's shape. Carried, not computed.
pub fn render_day(day: pb::DayActivity) -> Day {
    Day {
        day_unix_ms: day.day_unix_ms,
        bytes_in: day.bytes_in,
        bytes_out: day.bytes_out,
        pushes: day.pushes,
        fetches: day.fetches,
        lfs_bytes_in: day.lfs_bytes_in,
        lfs_bytes_out: day.lfs_bytes_out,
        refused: day.refused,
        // Sorted, and filtered by NOTHING: every key gunnar sent is carried
        // and no key it did not send is invented. gunnar already omits the
        // reasons with no refusals behind them.
        refused_by_reason: day.refused_by_reason.into_iter().collect(),
        auth_failures: day.auth_failures,
        distinct_principals: day.distinct_principals,
        distinct_principals_capped: day.distinct_principals_capped,
        last_push_unix_ms: crate::api::recorded(day.last_push_unix_ms),
        last_fetch_unix_ms: crate::api::recorded(day.last_fetch_unix_ms),
    }
}

/// `GET /api/namespaces/{name}/activity`
///
/// One namespace, one `Usage.Activity`, the last [`ACTIVITY_DAYS`] UTC days.
///
/// **The grant is the same one `/usage` needs** — `Read` on the namespace —
/// and, as there, the console invents no second rule: gunnar's
/// `PermissionDenied` reaches the browser as a 403 in gunnar's own words
/// carrying no day rows at all, and its `NotFound` reaches it as a 404.
///
/// **The rows are passed through untouched: oldest first, and a day with no
/// traffic is absent rather than zero-filled.** Filling the gaps in here is the
/// tempting bug — a graph with a bar for every day is easier to draw — and it
/// would make this console assert a measurement gunnar never took. It also
/// destroys the distinction the page is built on: an empty `days` beside a
/// non-zero `retained_days` means *nothing moved*, and only a refusal means
/// *this could not be read*.
pub async fn activity(
    reader: Reader,
    Path(namespace): Path<String>,
) -> ApiResult<Activity> {
    let now = std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .map(|d| d.as_millis() as i64)
        .unwrap_or_default();

    // The caller's own credential — see `read` above. A day-by-day series of
    // what a namespace moved is as much that tenant's business as the totals.
    let response = reader
        .gunnar
        .usage()
        .activity(pb::ActivityRequest {
            namespace: namespace.clone(),
            since_unix_ms: window_start(now, ACTIVITY_DAYS),
            // 0 is gunnar's own word for "now". Sending this console's clock
            // instead would drop today's row whenever the two machines
            // disagree by a few seconds across midnight, and the console's
            // clock is the one with no reason to be right.
            until_unix_ms: 0,
        })
        .await
        .map_err(ApiError::from)?
        .into_inner();

    Ok(Json(Activity {
        namespace,
        retained_days: response.retained_days,
        days: response.days.into_iter().map(render_day).collect(),
    }))
}

/// One kind of work's admission plan, as the console shows it.
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct AdmitKind {
    /// `clone`, `fetch`, `push`, `request`.
    pub kind: String,
    /// `Full`, `Reduced`, `Floor` or `Queued` — gunnar's words.
    pub class: String,
    /// gunnar's sentence, verbatim: "100 GB/30d used 63 GB; this clone ~1.2 GB; Full".
    pub why: String,
    /// Learnt bytes (in + out) per transfer of this kind. `None` when gunnar
    /// has no estimate yet — a 0 would read as "costs nothing".
    pub estimate_bytes: Option<u64>,
    /// Finished transfers the estimate learnt from since the server started.
    pub samples: u64,
}

/// `GET /api/namespaces/{name}/admit` — the answer, as the console sends it.
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct AdmitPlan {
    pub namespace: String,
    /// `pass-through` (no budget; every kind Full) or `usage-share`.
    pub policy: String,
    pub window_days: u32,
    /// Bytes moved (in + out) over the window, today included.
    pub used_bytes: u64,
    /// `None` = no budget on this box. Never 0 for "none".
    pub limit_bytes: Option<u64>,
    /// `None` when there is no budget.
    pub budget_left_bytes: Option<u64>,
    pub kinds: Vec<AdmitKind>,
}

/// gunnar's plan → the console's shape. Carried, not computed.
pub fn render_admit(namespace: String, plan: pb::AdmitPlanResponse) -> AdmitPlan {
    AdmitPlan {
        namespace,
        policy: plan.policy,
        window_days: plan.window_days,
        used_bytes: plan.used_bytes,
        limit_bytes: plan.limit_bytes,
        budget_left_bytes: plan.budget_left_bytes,
        kinds: plan
            .kinds
            .into_iter()
            .map(|k| AdmitKind {
                kind: k.kind,
                class: k.admit_class,
                why: k.why,
                estimate_bytes: (k.estimate_bytes > 0).then_some(k.estimate_bytes),
                samples: k.samples,
            })
            .collect(),
    }
}

/// `GET /api/namespaces/{name}/admit`
///
/// One namespace, one `Usage.AdmitPlan`: what the namespace has moved over
/// gunnar's rolling window, its budget, and per kind of work the class the
/// admission gate would give the next transfer and gunnar's sentence saying
/// why. For the admin view and for the tenant alike.
///
/// **The grant is the same one `/usage` and `/activity` need** — `Read` on the
/// namespace — and the console invents no second rule: gunnar's refusal
/// reaches the browser in gunnar's own words and carries no plan.
pub async fn admit_plan(
    reader: Reader,
    State(_state): State<AppState>,
    Path(namespace): Path<String>,
) -> ApiResult<AdmitPlan> {
    let response = reader
        .gunnar
        .usage()
        .admit_plan(pb::AdmitPlanRequest {
            namespace: namespace.clone(),
        })
        .await
        .map_err(ApiError::from)?
        .into_inner();
    Ok(Json(render_admit(namespace, response)))
}

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

    use std::collections::BTreeMap;
    use std::sync::Arc;

    use tower::ServiceExt as _;

    use crate::pb::admin_server::{Admin as AdminService, AdminServer};
    use crate::pb::usage_server::{Usage as UsageService, UsageServer};

    /// The meters of one namespace, with **seven different numbers**.
    ///
    /// Every value is distinct and none is a round multiple of another, so a
    /// field crossed with its neighbour is visible. A fixture of zeroes — or of
    /// one repeated number — would pass against a handler that read the wrong
    /// field, against one that read the same field seven times, and against one
    /// that returned a default: the identity value that proves nothing, and a
    /// live hazard rather than a hypothetical, because `lfs_bytes` really is 0
    /// on every gunnar there is.
    fn meters(namespace: &str) -> pb::NamespaceUsage {
        pb::NamespaceUsage {
            namespace: namespace.to_owned(),
            pack_bytes: 7_340_032,
            cache_bytes: 1_572_864,
            cold_bytes: 5_242_880,
            // The one that is genuinely 0 everywhere today, kept 0 so the
            // rendering of a real zero is exercised beside seven non-zeroes.
            lfs_bytes: 0,
            tombstoned_bytes: 3_145_728,
            repos: 9,
            objects: 41_237,
            measured_at_unix_ms: 1_788_000_000_000,
            // ── the flow half ────────────────────────────────────────────
            //
            // Distinct again, and for the same reason: five counters that a
            // handler could cross with each other or with the stock meters
            // above. `refused_today` is deliberately NOT the sum of anything
            // else here, so a console that computed it instead of carrying it
            // could not pass.
            bytes_in_today: 811_923,
            bytes_out_today: 4_402_117,
            pushes_today: 3,
            fetches_today: 27,
            refused_today: 2,
            last_push_unix_ms: 1_787_999_100_000,
            last_fetch_unix_ms: 1_787_999_940_000,
        }
    }

    /// **The allowance fixture: every cap DIFFERENT from every meter, and each
    /// arm of the `remaining` rule reachable.**
    ///
    /// Against [`meters`] and [`the_box`] (servable 2 MiB):
    ///
    /// * `pack_bytes` cap 8 MiB, 7 MiB used → 1 MiB left, UNDER the box: the
    ///   cap is what binds;
    /// * `tombstoned_bytes` cap 100 MB, 3 MiB used → 96 MB left, OVER the box:
    ///   the box is what binds, and `remaining` is the box's 2 MiB;
    /// * `cold_bytes` cap == used → 0 left, a real zero, the cap reached;
    /// * `cache_bytes`, `lfs_bytes`: NO cap → `remaining` is the box alone;
    /// * `repos` cap 12, 9 used → 3, a count and not bytes, so the box does
    ///   not enter; `objects`: no cap on a count → nothing bounds it.
    ///
    /// The enforcement map is gunnar's own table
    /// (`gunnar_grpc::service::meters::METERS`): all seven meters, capped or
    /// not, three walls and four readings. It is spelled out here rather than
    /// derived so that a change on gunnar's side has to be copied in
    /// deliberately, the way [`super::enforcement_word`]'s vocabulary is.
    fn caps() -> pb::Allowance {
        pb::Allowance {
            enforcement: table(),
            pack_bytes: Some(8_388_608),
            cache_bytes: None,
            cold_bytes: Some(5_242_880),
            lfs_bytes: None,
            tombstoned_bytes: Some(100_000_000),
            repos: Some(12),
            objects: None,
        }
    }

    /// gunnar's seven meters and what a cap on each does there. `repos` is the
    /// auditor's own example — a cap of five with nothing refusing the sixth —
    /// and `pack_bytes` its opposite, so a fixture that flattened the two
    /// could not pass anything below.
    fn table() -> std::collections::HashMap<String, i32> {
        [
            ("pack_bytes", pb::Enforcement::Enforced),
            ("cache_bytes", pb::Enforcement::Enforced),
            ("cold_bytes", pb::Enforcement::Measured),
            ("lfs_bytes", pb::Enforcement::Enforced),
            ("tombstoned_bytes", pb::Enforcement::Measured),
            ("repos", pb::Enforcement::Measured),
            ("objects", pb::Enforcement::Measured),
        ]
        .into_iter()
        .map(|(meter, word)| (meter.to_owned(), word as i32))
        .collect()
    }

    /// The box: a 100 GiB set with 1 GiB and 2 MiB free, a 1 GiB floor, so
    /// exactly 2 MiB servable — SEALED, the appliance's answer.
    fn the_box() -> pb::CapacityResponse {
        pb::CapacityResponse {
            verdict: pb::CapacityVerdict::CapacityAvailable as i32,
            growth: pb::SetGrowth::GrowthSealed as i32,
            root: "/var/lib/gunnar".to_owned(),
            total_bytes: 107_374_182_400,
            free_bytes: 1_075_838_976,
            floor_bytes: 1_073_741_824,
            servable_bytes: 2_097_152,
            reason: "YES".to_owned(),
            ..Default::default()
        }
    }

    /// A gunnar that counts, and that refuses the way a real one does.
    ///
    /// The grant table is the point. gunnar's `Usage.Read` takes `Read` on the
    /// namespace it names, and `Usage.ReadAll` takes a server-wide `Read`; this
    /// answers `PermissionDenied` for anything outside `readable`, in gunnar's
    /// own words, so the console's refusal path can be driven without a server.
    ///
    /// `Admin.Capacity` is served too — or refused, when `capacity` is an
    /// error — because the box line and the box term of `remaining` come from
    /// it and the page must be right both with and without it.
    #[derive(Clone)]
    struct FakeGunnar {
        /// **Every fingerprint that signed a `Usage.Read`**, in order.
        ///
        /// The only way a test can see WHICH of the console's credentials made
        /// a call. `readable` proves gunnar's refusal reaches the browser; this
        /// proves the console asked as the right principal in the first place
        /// — and those are two different questions, because a console that
        /// asks with a server-wide admin key is never refused at all, so a
        /// `readable` that excluded the namespace would still let the read
        /// through.
        signed_by: std::sync::Arc<std::sync::Mutex<Vec<String>>>,
        readable: Vec<&'static str>,
        rows: BTreeMap<&'static str, pb::NamespaceUsage>,
        allowances: BTreeMap<&'static str, pb::Allowance>,
        capacity: Result<pb::CapacityResponse, tonic::Code>,
        /// The ledger `Usage.Activity` answers from, per namespace, **oldest
        /// first with the quiet days already missing** — which is the shape
        /// gunnar sends and therefore the only shape worth testing against.
        days: BTreeMap<&'static str, Vec<pb::DayActivity>>,
    }

    impl FakeGunnar {
        /// Counts `rows`, caps nobody, and cannot say what the box holds — the
        /// community appliance seen through a key with no server-wide grant.
        fn counting(
            readable: Vec<&'static str>,
            rows: BTreeMap<&'static str, pb::NamespaceUsage>,
        ) -> FakeGunnar {
            FakeGunnar {
                signed_by: Default::default(),
                readable,
                rows,
                allowances: BTreeMap::new(),
                capacity: Err(tonic::Code::PermissionDenied),
                days: BTreeMap::new(),
            }
        }
    }

    #[tonic::async_trait]
    impl AdminService for FakeGunnar {
        async fn capacity(
            &self,
            request: tonic::Request<pb::CapacityRequest>,
        ) -> Result<tonic::Response<pb::CapacityResponse>, tonic::Status> {
            let asked = request.into_inner();
            // The console wants the measurement, not a verdict about a raise:
            // a request naming bytes or a namespace here would be asking the
            // seller's question on a page that has no seller.
            assert_eq!(
                asked.requested_bytes, 0,
                "the page asks for the box, not for a raise"
            );
            assert!(
                asked.namespace.is_empty(),
                "the page asks for the box, not for a tenant"
            );
            match &self.capacity {
                Ok(answer) => Ok(tonic::Response::new(answer.clone())),
                Err(code) => Err(tonic::Status::new(
                    *code,
                    "principal gunnar-ui has no server-wide read grant (Admin.Capacity)",
                )),
            }
        }
        async fn health(
            &self,
            _: tonic::Request<pb::HealthRequest>,
        ) -> Result<tonic::Response<pb::HealthResponse>, tonic::Status> {
            Err(tonic::Status::unimplemented("not this page's"))
        }
        async fn stats(
            &self,
            _: tonic::Request<pb::StatsRequest>,
        ) -> Result<tonic::Response<pb::StatsResponse>, tonic::Status> {
            Err(tonic::Status::unimplemented("not this page's"))
        }
        async fn storage_info(
            &self,
            _: tonic::Request<pb::StorageInfoRequest>,
        ) -> Result<tonic::Response<pb::StorageInfoResponse>, tonic::Status> {
            Err(tonic::Status::unimplemented("not this page's"))
        }
        async fn verify_chain(
            &self,
            _: tonic::Request<pb::VerifyChainRequest>,
        ) -> Result<tonic::Response<pb::VerifyChainResponse>, tonic::Status> {
            Err(tonic::Status::unimplemented("not this page's"))
        }
        async fn grow_start(
            &self,
            _: tonic::Request<pb::GrowStartRequest>,
        ) -> Result<tonic::Response<pb::GrowStatusResponse>, tonic::Status> {
            Err(tonic::Status::unimplemented("not this page's"))
        }
        async fn grow_status(
            &self,
            _: tonic::Request<pb::GrowStatusRequest>,
        ) -> Result<tonic::Response<pb::GrowStatusResponse>, tonic::Status> {
            Err(tonic::Status::unimplemented("not this page's"))
        }
        async fn grow_resume(
            &self,
            _: tonic::Request<pb::GrowResumeRequest>,
        ) -> Result<tonic::Response<pb::GrowStatusResponse>, tonic::Status> {
            Err(tonic::Status::unimplemented("not this page's"))
        }
    }

    // ── the data set (`GET /api/dataset`) ───────────────────────────────────

    /// The appliance's shape: four 2 GiB members striped, xfs on top.
    fn the_data_set_box() -> pb::CapacityResponse {
        pb::CapacityResponse {
            verdict: pb::CapacityVerdict::CapacityAvailable as i32,
            growth: pb::SetGrowth::GrowthAtRestart as i32,
            root: "/data".to_owned(),
            total_bytes: 8_578_400_256,
            free_bytes: 8_294_146_048,
            floor_bytes: 2_147_483_648,
            servable_bytes: 6_146_662_400,
            data_set: "4\u{d7}2.0G raid0 xfs".to_owned(),
            reason: "YES".to_owned(),
            ..Default::default()
        }
    }

    fn box_only(capacity: Result<pb::CapacityResponse, tonic::Code>) -> FakeGunnar {
        FakeGunnar {
            signed_by: Default::default(),
            readable: Vec::new(),
            rows: BTreeMap::new(),
            allowances: BTreeMap::new(),
            capacity,
            days: BTreeMap::new(),
        }
    }

    #[test]
    fn the_layout_words_split_into_their_parts_and_never_into_a_guess() {
        assert_eq!(
            layout_parts("4\u{d7}75G raid0 xfs"),
            (Some(4), Some("75G".to_owned()), Some("raid0".to_owned()), Some("xfs".to_owned()))
        );
        assert_eq!(layout_parts("xfs"), (None, None, None, Some("xfs".to_owned())));
        assert_eq!(layout_parts(""), (None, None, None, None));
        // A shape this console does not know keeps no parts at all.
        assert_eq!(layout_parts("x\u{d7}2G raid0 xfs"), (None, None, None, None));
        assert_eq!(layout_parts("4 2G raid0 xfs"), (None, None, None, None));
    }

    #[test]
    fn every_growth_answer_gunnar_can_give_has_a_word() {
        assert_eq!(growth_word(pb::SetGrowth::GrowthSealed as i32), "sealed");
        assert_eq!(growth_word(pb::SetGrowth::GrowthAtRestart as i32), "at_restart");
        assert_eq!(growth_word(pb::SetGrowth::GrowthAtRuntime as i32), "at_runtime");
        assert_eq!(growth_word(pb::SetGrowth::GrowthOperatorOnly as i32), "operator_only");
        assert_eq!(growth_word(pb::SetGrowth::GrowthUnknown as i32), "unknown");
        assert_eq!(growth_word(99), "unknown");
    }

    /// ★ **The data set on the wire is gunnar's measurement, field for field.**
    ///
    /// End to end: a real `Admin` service on a real port, the console's router,
    /// its gRPC client, its JSON. Total and free are gunnar's; used is the one
    /// derived number; the layout is gunnar's words and their parts.
    #[tokio::test]
    async fn the_data_set_is_the_box_gunnar_measured() {
        let app = console(box_only(Ok(the_data_set_box()))).await;
        let (status, body) = get(&app, "/api/dataset").await;
        assert_eq!(status, http::StatusCode::OK, "{body}");
        let json: serde_json::Value = serde_json::from_str(&body).expect("json");
        assert_eq!(json["root"], "/data");
        assert_eq!(json["total_bytes"], 8_578_400_256u64);
        assert_eq!(json["free_bytes"], 8_294_146_048u64);
        assert_eq!(json["used_bytes"], 8_578_400_256u64 - 8_294_146_048u64);
        assert_eq!(json["layout"], "4\u{d7}2.0G raid0 xfs");
        assert_eq!(json["members"], 4);
        assert_eq!(json["member_size"], "2.0G");
        assert_eq!(json["level"], "raid0");
        assert_eq!(json["filesystem"], "xfs");
        assert_eq!(json["growth"], "at_restart");
        // The open console's own JSON: no money anywhere in it, keys or values.
        let lower = body.to_lowercase();
        for word in ["price", "buy", "sell", "sold", "quota", "plan", "billing", "billed", "pay"] {
            assert!(
                !lower
                    .split(|c: char| !c.is_ascii_alphanumeric())
                    .any(|w| w == word),
                "`{word}` in the data-set JSON: {body}"
            );
        }
    }

    /// ★ **The box's own allocation is Administration → Machine's read**
    /// (regression audit 2026-10-01: lane-allocbuy e349616 drew disk, cores and
    /// RAM on a tenant's usage page and never reached a release; the owner's
    /// 2026-10-02 rule puts the machine under Administration). Cores and memory
    /// are gunnar's measurement, field for field; a field gunnar did not
    /// measure stays absent, never 0.
    #[tokio::test]
    async fn the_box_allocation_is_an_administrators_read_with_cores_and_memory() {
        let measured = pb::CapacityResponse {
            cpu_millicores_total: Some(4000),
            cpu_millicores_busy: Some(10),
            ram_bytes_total: Some(16_000_000_000),
            ram_bytes_available: None,
            ..the_data_set_box()
        };
        let app = console(box_only(Ok(measured))).await;
        let (status, body) = get(&app, "/api/machine/allocation").await;
        assert_eq!(status, http::StatusCode::OK, "{body}");
        let json: serde_json::Value = serde_json::from_str(&body).expect("json");
        assert_eq!(json["total_bytes"], 8_578_400_256u64);
        assert_eq!(json["cpu_millicores_total"], 4000);
        assert_eq!(json["cpu_millicores_busy"], 10);
        assert_eq!(json["ram_bytes_total"], 16_000_000_000u64);
        assert!(json["ram_bytes_available"].is_null(), "unmeasured is absent, never 0: {body}");
    }

    /// gunnar's refusal reaches the browser as itself — and no numbers with it.
    #[tokio::test]
    async fn a_key_without_a_server_wide_read_gets_gunnars_refusal_and_no_numbers() {
        let app = console(box_only(Err(tonic::Code::PermissionDenied))).await;
        let (status, body) = get(&app, "/api/dataset").await;
        assert_eq!(status, http::StatusCode::FORBIDDEN, "{body}");
        assert!(body.contains("no server-wide read grant"), "{body}");
        assert!(!body.contains("total_bytes"), "{body}");
    }

    #[tonic::async_trait]
    impl UsageService for FakeGunnar {
        async fn read(
            &self,
            request: tonic::Request<pb::ReadUsageRequest>,
        ) -> Result<tonic::Response<pb::ReadUsageResponse>, tonic::Status> {
            // Recorded BEFORE the grant check, so a test can see which key
            // asked even when the answer is a refusal.
            // A person's call carries their identity-core session instead of a
            // signature (IDENTITY-HOST): recorded as `session:<token>`.
            let session = request
                .metadata()
                .get(crate::grpc::SESSION_METADATA_KEY)
                .and_then(|v| v.to_str().ok())
                .map(|token| format!("session:{token}"));
            self.signed_by.lock().expect("the fingerprint log").push(session.unwrap_or_else(|| {
                request
                    .metadata()
                    .get(crate::signer::CREDENTIAL_METADATA_KEY)
                    .and_then(|v| v.to_str().ok())
                    .and_then(|header| header.split_whitespace().nth(1))
                    .unwrap_or("<none>")
                    .to_owned()
            }));
            let namespace = request.into_inner().namespace;
            if !self.readable.iter().any(|n| *n == namespace) {
                return Err(tonic::Status::permission_denied(format!(
                    "principal gunnar-ui has no read grant on namespace:{namespace}"
                )));
            }
            match self.rows.get(namespace.as_str()) {
                Some(row) => {
                    // Absent for a namespace with no entry, as gunnar sends it
                    // for a namespace with no fact: every cap absent.
                    let allowance = self
                        .allowances
                        .get(namespace.as_str())
                        .cloned()
                        .unwrap_or_default();
                    // gunnar's own `remaining`: cap − held, clamped at 0,
                    // absent where nothing caps the meter.
                    let left = |held: u64, cap: Option<u64>| cap.map(|c| c.saturating_sub(held));
                    let remaining = pb::Remaining {
                        pack_bytes: left(row.pack_bytes, allowance.pack_bytes),
                        cache_bytes: left(row.cache_bytes, allowance.cache_bytes),
                        cold_bytes: left(row.cold_bytes, allowance.cold_bytes),
                        lfs_bytes: left(row.lfs_bytes, allowance.lfs_bytes),
                        tombstoned_bytes: left(row.tombstoned_bytes, allowance.tombstoned_bytes),
                        repos: left(row.repos, allowance.repos),
                        objects: left(row.objects, allowance.objects),
                    };
                    Ok(tonic::Response::new(pb::ReadUsageResponse {
                        usage: Some(row.clone()),
                        allowance: Some(allowance),
                        remaining: Some(remaining),
                        repos: Vec::new(),
                    }))
                }
                None => Err(tonic::Status::not_found(format!(
                    "namespace {namespace} is not in the catalog"
                ))),
            }
        }

        async fn read_all(
            &self,
            _request: tonic::Request<pb::ReadAllUsageRequest>,
        ) -> Result<tonic::Response<pb::ReadAllUsageResponse>, tonic::Status> {
            // A credential that may read one namespace may NOT read every
            // namespace, and gunnar says so. If the console ever answers this
            // page by asking for everything and filtering, the row that reaches
            // for it fails here rather than quietly working on an
            // admin-credentialled deployment and breaking on a scoped one.
            Err(tonic::Status::permission_denied(
                "principal gunnar-ui has no server-wide read grant",
            ))
        }

        /// The series, behind the **same** grant `read` is behind.
        ///
        /// That sameness is asserted rather than assumed: a fake that let
        /// `Activity` through for a namespace `Read` refuses would make
        /// `a_namespaces_activity_is_refused_and_leaks_no_days` unable to fail.
        async fn activity(
            &self,
            request: tonic::Request<pb::ActivityRequest>,
        ) -> Result<tonic::Response<pb::ActivityResponse>, tonic::Status> {
            let asked = request.into_inner();
            if !self.readable.iter().any(|n| *n == asked.namespace) {
                return Err(tonic::Status::permission_denied(format!(
                    "principal gunnar-ui has no read grant on namespace:{}",
                    asked.namespace
                )));
            }
            // The window the console asked for, checked here because it is the
            // only place it can be: the handler builds the request and nothing
            // downstream would notice a console that asked for four hundred
            // days to draw thirty bars, or that sent its own clock where
            // gunnar's word for "now" belongs.
            assert_eq!(
                asked.until_unix_ms, 0,
                "0 is gunnar's own word for now; sending the console's clock drops today's \
                 row whenever the two machines disagree across midnight"
            );
            assert!(
                asked.since_unix_ms > 0,
                "a `since` of 0 asks for the WHOLE kept window — four hundred rows to draw \
                 thirty bars"
            );
            assert_eq!(
                asked.since_unix_ms % 86_400_000,
                0,
                "the window must start at midnight UTC, or the oldest bar is a part-day drawn \
                 at full height beside twenty-nine whole ones"
            );
            Ok(tonic::Response::new(pb::ActivityResponse {
                days: self
                    .days
                    .get(asked.namespace.as_str())
                    .cloned()
                    .unwrap_or_default(),
                retained_days: 400,
            }))
        }

        /// The admission plan, behind the **same** grant `read` is behind.
        async fn admit_plan(
            &self,
            request: tonic::Request<pb::AdmitPlanRequest>,
        ) -> Result<tonic::Response<pb::AdmitPlanResponse>, tonic::Status> {
            let asked = request.into_inner();
            if !self.readable.iter().any(|n| *n == asked.namespace) {
                return Err(tonic::Status::permission_denied(format!(
                    "principal gunnar-ui has no read grant on namespace:{}",
                    asked.namespace
                )));
            }
            Ok(tonic::Response::new(admit_answer()))
        }
    }

    /// A usage-share plan with distinct numbers, and one kind with no estimate yet.
    fn admit_answer() -> pb::AdmitPlanResponse {
        pb::AdmitPlanResponse {
            policy: "usage-share".into(),
            window_days: 30,
            used_bytes: 63_000_000_017,
            limit_bytes: Some(100_000_000_000),
            budget_left_bytes: Some(36_999_999_983),
            kinds: vec![
                pb::AdmitPlanKind {
                    kind: "clone".into(),
                    admit_class: "Full".into(),
                    why: "100 GB/30d used 63 GB; this clone ~1.2 GB; Full".into(),
                    estimate_bytes: 1_200_000_003,
                    estimate_cpu_ms: 0,
                    samples: 41,
                },
                pb::AdmitPlanKind {
                    kind: "push".into(),
                    admit_class: "Full".into(),
                    why: "100 GB/30d used 63 GB; this push ~? (no estimate yet); Full".into(),
                    estimate_bytes: 0,
                    estimate_cpu_ms: 0,
                    samples: 0,
                },
            ],
        }
    }

    /// ★ **The plan is carried as gunnar sent it**: the policy, the window,
    /// the budget, gunnar's own sentence per kind — and a kind with no estimate
    /// is `null`, never 0, which would read as "costs nothing".
    #[tokio::test]
    async fn the_admission_plan_is_gunnars_and_an_unlearnt_estimate_is_null() {
        let app = console(FakeGunnar::counting(
            vec!["alpha"],
            BTreeMap::from([("alpha", meters("alpha"))]),
        ))
        .await;
        let (status, body) = get(&app, "/api/namespaces/alpha/admit").await;
        assert_eq!(status, http::StatusCode::OK, "{body}");
        let seen: serde_json::Value = serde_json::from_str(&body).expect("json");
        assert_eq!(seen["namespace"], "alpha");
        assert_eq!(seen["policy"], "usage-share");
        assert_eq!(seen["window_days"], 30);
        assert_eq!(seen["used_bytes"], 63_000_000_017u64);
        assert_eq!(seen["limit_bytes"], 100_000_000_000u64);
        assert_eq!(seen["budget_left_bytes"], 36_999_999_983u64);
        assert_eq!(seen["kinds"][0]["kind"], "clone");
        assert_eq!(seen["kinds"][0]["class"], "Full");
        assert_eq!(seen["kinds"][0]["why"], "100 GB/30d used 63 GB; this clone ~1.2 GB; Full");
        assert_eq!(seen["kinds"][0]["estimate_bytes"], 1_200_000_003u64);
        assert_eq!(seen["kinds"][0]["samples"], 41);
        assert!(seen["kinds"][1]["estimate_bytes"].is_null(), "{body}");
    }

    /// A pass-through box has no budget, and says so with `null`, not 0.
    #[test]
    fn no_budget_is_null_and_not_zero() {
        let plan = render_admit(
            "alpha".into(),
            pb::AdmitPlanResponse {
                policy: "pass-through".into(),
                window_days: 30,
                used_bytes: 5,
                limit_bytes: None,
                budget_left_bytes: None,
                kinds: vec![],
            },
        );
        let json = serde_json::to_value(&plan).unwrap();
        assert!(json["limit_bytes"].is_null() && json["budget_left_bytes"].is_null(), "{json}");
    }

    /// The plan of a namespace the caller cannot read is gunnar's refusal and
    /// carries no number.
    #[tokio::test]
    async fn a_namespaces_admission_plan_is_refused_and_leaks_nothing() {
        let app = console(FakeGunnar::counting(
            vec!["alpha"],
            BTreeMap::from([("alpha", meters("alpha")), ("beta", meters("beta"))]),
        ))
        .await;
        let (status, body) = get(&app, "/api/namespaces/beta/admit").await;
        assert_eq!(status, http::StatusCode::FORBIDDEN, "{body}");
        assert!(body.contains("no read grant on namespace:beta"), "{body}");
        assert!(!body.contains("63000000017") && !body.contains("used_bytes"), "{body}");
    }

    /// Stand `gunnar` up on loopback and point a console at it.
    ///
    /// Returns the console's router. The server lives as a detached task for
    /// the process's lifetime, which is what a `#[tokio::test]` wants: nothing
    /// to join, nothing to leak past the test binary.
    async fn console(fake: FakeGunnar) -> axum::Router {
        let listener = tokio::net::TcpListener::bind("127.0.0.1:0")
            .await
            .expect("a loopback port");
        let addr = listener.local_addr().expect("its address");
        tokio::spawn(async move {
            tonic::transport::Server::builder()
                .add_service(UsageServer::new(fake.clone()))
                .add_service(AdminServer::new(fake))
                .serve_with_incoming(tokio_stream::wrappers::TcpListenerStream::new(listener))
                .await
        });

        let signer = Arc::new(
            crate::signer::Signer::new(ssh_key::PrivateKey::from(
                ssh_key::private::Ed25519Keypair::from_seed(&[7u8; 32]),
            ))
            .expect("a fixed seed makes a signer"),
        );
        let endpoint = format!("http://{addr}");
        let gunnar = crate::grpc::Gunnar::connect_lazy(&endpoint, signer).expect("a lazy channel");
        let store = crate::state::StateStore::open(
            tempfile::Builder::new()
                .suffix(".redb")
                .tempfile()
                .expect("a temp file")
                // Kept: the store outlives the `NamedTempFile` guard, and the
                // test binary's temp directory is cleaned by the OS. Dropping
                // the guard here would delete the file redb is holding open.
                .into_temp_path()
                .keep()
                .expect("keep the path"),
        )
        .expect("a state store");

        crate::serve::router(Arc::new(crate::app::App {
            webauthn: None,
            #[cfg(feature = "metered")]
            monetize: None,
            admin_token: None,
            events: crate::sse::EventHub::detached(),
            gunnar,
            // No guest identity: these tests predate the anonymous surface and
            // are about a logged-in console.
            twin: None,
            guest: None,
            reviewers: Arc::new(crate::reviewer::ReviewerKeys::new(
                endpoint.clone(),
                store.clone(),
            )),
            tenants: Arc::new(crate::tenant::TenantKeys::new(endpoint, store)),
            forges: Default::default(),
            imports: Default::default(),
            forge_apps: Default::default(),
            ui_dir: None,
            caches: crate::api::Caches::default(),
            objects: Default::default(),
            readmes: Default::default(),
        }))
    }

    /// `GET` a path off the console and read the status and the body.
    async fn get(app: &axum::Router, uri: &str) -> (http::StatusCode, String) {
        let response = app
            .clone()
            .oneshot(
                http::Request::builder()
                    .uri(uri)
                    .body(axum::body::Body::empty())
                    .expect("a request"),
            )
            .await
            .expect("the console answers");
        let status = response.status();
        let body = axum::body::to_bytes(response.into_body(), 1 << 20)
            .await
            .expect("a readable body");
        (status, String::from_utf8_lossy(&body).into_owned())
    }

    /// ★ **The numbers on the wire are gunnar's, field for field.**
    ///
    /// End to end: a real `Usage` service on a real port, the console's own
    /// router, its own gRPC client, its own JSON. Every meter is asserted
    /// against the exact value the service sent, and the seven values are
    /// different from each other so a crossed field cannot hide.
    ///
    /// Seen red twice, both on the applied output:
    ///
    /// * with `cold_bytes` written into `cache_bytes` in [`render`] —
    ///   *"assertion `left == right` failed: cache_bytes … left: 5242880 right:
    ///     1572864"*;
    /// * with the whole body replaced by `Usage::default()`-shaped zeroes —
    ///   *"pack_bytes … left: 0 right: 7340032"*, which is the failure a page
    ///   wired to nothing would produce and the one a fixture of zeroes could
    ///   never produce.
    #[tokio::test]
    async fn a_namespaces_meters_are_the_numbers_gunnar_counted() {
        let app = console(FakeGunnar::counting(
            vec!["alpha"],
            BTreeMap::from([("alpha", meters("alpha"))]),
        ))
        .await;

        let (status, body) = get(&app, "/api/namespaces/alpha/usage").await;
        assert_eq!(status, http::StatusCode::OK, "{body}");

        // Read as JSON rather than back through this module's own struct: the
        // browser sees field NAMES, and a rename that a `serde_json::from_str`
        // into `Usage` would follow silently is a page of blanks.
        let seen: serde_json::Value = serde_json::from_str(&body).expect("the console sends JSON");
        let sent = meters("alpha");
        assert_eq!(seen["namespace"], "alpha");
        for (name, value) in [
            ("pack_bytes", sent.pack_bytes),
            ("cache_bytes", sent.cache_bytes),
            ("cold_bytes", sent.cold_bytes),
            ("tombstoned_bytes", sent.tombstoned_bytes),
            ("repos", sent.repos),
            ("objects", sent.objects),
        ] {
            assert_eq!(
                seen[name].as_u64(),
                Some(value),
                "{name} is not the number gunnar counted: {body}"
            );
        }
        assert_eq!(
            seen["measured_at_unix_ms"].as_i64(),
            Some(sent.measured_at_unix_ms),
            "measured_at"
        );
        // The real zero, carried as a zero. gunnar has no LFS store, so this
        // meter is 0 on every server there is — which is why the six above are
        // not, and why this one is asserted separately rather than being the
        // test's only evidence.
        assert_eq!(seen["lfs_bytes"].as_u64(), Some(0), "lfs_bytes");
        // And no meter was invented: `pack_bytes` is gunnar's own walk, not the
        // sum of the tiers beside it. If the console ever starts computing it,
        // this is what says so.
        assert_ne!(
            sent.pack_bytes,
            sent.cache_bytes + sent.cold_bytes,
            "the fixture itself must not make the sum accidentally true"
        );
    }

    /// ★ **A namespace the caller may not read yields no numbers.**
    ///
    /// The refusal red. gunnar answers `PermissionDenied` for a namespace
    /// outside the caller's grants, and the console must relay that as a
    /// refusal — since 2026-09-28 the one 404 `api::absent` gives a name that
    /// does not exist, so the refusal says nothing about the name — not fall back to a wider call, not answer
    /// 200 with zeroes, and above all not let a single byte of that namespace's
    /// figures into the response.
    ///
    /// Seen red by having the handler swallow the refusal and answer
    /// `Json(Usage { namespace, ..zeroes })` on error: *"a refused namespace
    /// answered 200 OK"*. Seen red a second way by pointing the handler at
    /// `read_all` and filtering, which the fake refuses server-wide: the row
    /// then fails on the ALLOWED namespace too, which is the point — a console
    /// that reads everything cannot serve a scoped credential at all.
    #[tokio::test]
    async fn a_namespace_the_caller_cannot_read_is_refused_and_leaks_nothing() {
        let app = console(FakeGunnar::counting(
            // `beta` exists and is counted. It is simply not this caller's.
            vec!["alpha"],
            BTreeMap::from([("alpha", meters("alpha")), ("beta", meters("beta"))]),
        ))
        .await;

        // The control: the same console, the same call, a namespace the caller
        // MAY read. Without this the test below would pass against a console
        // that refuses everything.
        let (allowed, _) = get(&app, "/api/namespaces/alpha/usage").await;
        assert_eq!(
            allowed,
            http::StatusCode::OK,
            "the caller's own namespace must still be readable, or this proves nothing"
        );

        let (status, body) = get(&app, "/api/namespaces/beta/usage").await;
        // Since 2026-09-28 a namespace the caller may not read is answered
        // EXACTLY as one that does not exist (`api::absent`): 404 and one body.
        // gunnar's words ("no read grant on namespace:beta") would tell the
        // caller the name is real.
        assert_eq!(
            status,
            http::StatusCode::NOT_FOUND,
            "a refused namespace answered {status}: {body}"
        );
        assert_eq!(body, crate::api::absent::body().to_string());
        // The numbers themselves. Rendered as JSON they would appear as bare
        // decimal integers, so each is looked for as it would be written.
        let sent = meters("beta");
        for (name, value) in [
            ("pack_bytes", sent.pack_bytes),
            ("cache_bytes", sent.cache_bytes),
            ("cold_bytes", sent.cold_bytes),
            ("tombstoned_bytes", sent.tombstoned_bytes),
            ("objects", sent.objects),
        ] {
            assert!(
                !body.contains(&value.to_string()),
                "the refusal carries beta's {name} ({value}): {body}"
            );
        }
    }

    /// ★ **The console asks about ONE namespace.**
    ///
    /// `Usage.ReadAll` takes a server-wide `Read` and spans every tenant.
    /// Answering this page from it would work perfectly on a console whose
    /// credential is server-wide admin — which is the credential `gunnar.rs`
    /// runs today — and fail on any narrower one, while having handed this
    /// process every namespace's figures on the way.
    ///
    /// The fake refuses `ReadAll` exactly as gunnar would for a scoped
    /// credential, so a console that reached for it could not answer at all.
    /// This asserts the positive: with `ReadAll` denied server-wide, the page's
    /// call still succeeds and still carries the right namespace's numbers.
    #[tokio::test]
    async fn namespace_usage_asks_only_about_the_namespace_it_names() {
        let app = console(FakeGunnar::counting(
            vec!["alpha", "beta"],
            BTreeMap::from([("alpha", meters("alpha")), ("beta", meters("beta"))]),
        ))
        .await;

        for namespace in ["alpha", "beta"] {
            let (status, body) = get(&app, &format!("/api/namespaces/{namespace}/usage")).await;
            assert_eq!(status, http::StatusCode::OK, "{namespace}: {body}");
            let seen: serde_json::Value =
                serde_json::from_str(&body).expect("the console sends JSON");
            assert_eq!(
                seen["namespace"], namespace,
                "the answer must be about the namespace that was asked for"
            );
        }
    }

    /// A namespace gunnar does not have is a 404, not a page of zeroes.
    ///
    /// The failure this rules out is the friendly one: an empty answer rendered
    /// as an existing namespace holding nothing. A person reading this page to
    /// find out where their disk went must be able to tell "no such namespace"
    /// from "this namespace is empty".
    #[tokio::test]
    async fn a_namespace_that_is_not_in_the_catalog_is_not_shown_as_an_empty_one() {
        let app = console(FakeGunnar::counting(
            // Readable, so the refusal cannot be what answers.
            vec!["ghost"],
            BTreeMap::new(),
        ))
        .await;

        let (status, body) = get(&app, "/api/namespaces/ghost/usage").await;
        assert_eq!(status, http::StatusCode::NOT_FOUND, "{body}");
        // Not zeroes, and — since `api::absent` — the one body every hidden
        // name gets, so "not in the catalog" and "not yours" read the same.
        assert_eq!(body, crate::api::absent::body().to_string());
    }

    /// gunnar accepting the read and sending no meters is a fault, said out
    /// loud, rather than a namespace rendered as seven zeroes.
    #[tokio::test]
    async fn an_answer_with_no_meters_in_it_is_not_rendered_as_zeroes() {
        struct Empty;

        #[tonic::async_trait]
        impl UsageService for Empty {
            async fn read(
                &self,
                _request: tonic::Request<pb::ReadUsageRequest>,
            ) -> Result<tonic::Response<pb::ReadUsageResponse>, tonic::Status> {
                Ok(tonic::Response::new(pb::ReadUsageResponse {
                    usage: None,
                    ..Default::default()
                }))
            }
            async fn read_all(
                &self,
                _request: tonic::Request<pb::ReadAllUsageRequest>,
            ) -> Result<tonic::Response<pb::ReadAllUsageResponse>, tonic::Status> {
                unreachable!("this page never asks for every namespace")
            }
            async fn activity(
                &self,
                _request: tonic::Request<pb::ActivityRequest>,
            ) -> Result<tonic::Response<pb::ActivityResponse>, tonic::Status> {
                unreachable!("this fixture is about the meter read answering with no meters")
            }
            async fn admit_plan(
                &self,
                _request: tonic::Request<pb::AdmitPlanRequest>,
            ) -> Result<tonic::Response<pb::AdmitPlanResponse>, tonic::Status> {
                unreachable!("this fixture is about the meter read answering with no meters")
            }
        }

        let listener = tokio::net::TcpListener::bind("127.0.0.1:0")
            .await
            .expect("a loopback port");
        let addr = listener.local_addr().expect("its address");
        tokio::spawn(async move {
            tonic::transport::Server::builder()
                .add_service(UsageServer::new(Empty))
                .serve_with_incoming(tokio_stream::wrappers::TcpListenerStream::new(listener))
                .await
        });
        let signer = Arc::new(
            crate::signer::Signer::new(ssh_key::PrivateKey::from(
                ssh_key::private::Ed25519Keypair::from_seed(&[8u8; 32]),
            ))
            .expect("a signer"),
        );
        let gunnar = crate::grpc::Gunnar::connect_lazy(&format!("http://{addr}"), signer)
            .expect("a lazy channel");
        let store = crate::state::StateStore::open(
            tempfile::Builder::new()
                .suffix(".redb")
                .tempfile()
                .expect("a temp file")
                .into_temp_path()
                .keep()
                .expect("keep the path"),
        )
        .expect("a state store");
        let state = Arc::new(crate::app::App {
            webauthn: None,
            #[cfg(feature = "metered")]
            monetize: None,
            admin_token: None,
            events: crate::sse::EventHub::detached(),
            gunnar,
            // No guest identity: these tests predate the anonymous surface and
            // are about a logged-in console.
            twin: None,
            guest: None,
            reviewers: Arc::new(crate::reviewer::ReviewerKeys::new(
                format!("http://{addr}"),
                store.clone(),
            )),
            tenants: Arc::new(crate::tenant::TenantKeys::new(
                format!("http://{addr}"),
                store,
            )),
            forges: Default::default(),
            imports: Default::default(),
            forge_apps: Default::default(),
            ui_dir: None,
            caches: crate::api::Caches::default(),
            objects: Default::default(),
            readmes: Default::default(),
        });

        // The console's own credential, which is what `Reader` hands an
        // operator — this test is about a malformed ANSWER, not about who
        // asked.
        let reader = Reader {
            gunnar: state.gunnar.clone(),
            guest: false,
            tenant: false,
            anonymous: false,
        };
        let failed = read(reader, State(state), Path("alpha".to_owned()))
            .await
            .expect_err("no meters is not an answer");
        assert!(
            failed.0.message.contains("no meters"),
            "the fault must name what was missing: {}",
            failed.0.message
        );
    }

    /// The mapping itself, field by field, away from the wire.
    ///
    /// The end-to-end rows above would catch a crossed field too; this says
    /// which one in one line when they do, and it is where the timestamp rule
    /// is pinned: gunnar leaves several of its timestamps at zero and a zero
    /// rendered relatively reads as "689 months ago".
    #[test]
    fn a_measurement_gunnar_never_took_is_absent_rather_than_1970() {
        let rendered = render(
            pb::NamespaceUsage {
                measured_at_unix_ms: 0,
                ..meters("alpha")
            },
            pb::Allowance::default(),
            None,
            Err("no box".to_owned()),
        );

        assert_eq!(rendered.measured_at_unix_ms, None);
        // …and a real one survives, or the rule above would be "always None".
        assert_eq!(
            render(
                meters("alpha"),
                pb::Allowance::default(),
                None,
                Err("no box".to_owned())
            )
            .measured_at_unix_ms,
            Some(1_788_000_000_000)
        );
    }

    // ── the allowance, and what is left ───────────────────────────────────

    /// The JSON of one meter's three numbers, as the browser reads them.
    fn meter(seen: &serde_json::Value, name: &str) -> (Option<u64>, Option<u64>, Option<u64>) {
        (
            seen[name].as_u64(),
            seen["caps"][name].as_u64(),
            seen["remaining"][name].as_u64(),
        )
    }

    /// ★ **Used, allowed, remaining — with the box: the cap is carried as it
    /// came, `remaining` is gunnar's own answer (thin layer, owner
    /// 2026-10-04), and the box is carried beside it, never folded in.**
    ///
    /// Every arm of the rule, each on a meter whose numbers no other meter
    /// shares (see [`caps`]). Seen red three ways on the applied JSON: with
    /// `remaining` computed as `cap − used` and the box ignored —
    /// *"tombstoned_bytes remaining: left Some(96854272) right Some(2097152)"*;
    /// with a `None` cap rendered as `0` —
    /// *"cache_bytes cap: left Some(0) right None"*; and with the box's
    /// `free_bytes` sent where `servable_bytes` belongs —
    /// *"cache_bytes remaining: left Some(1075838976) right Some(2097152)"*.
    #[tokio::test]
    async fn remaining_is_gunnars_answer_and_the_box_is_carried_beside_it() {
        let app = console(FakeGunnar {
            signed_by: Default::default(),
            readable: vec!["alpha"],
            rows: BTreeMap::from([("alpha", meters("alpha"))]),
            allowances: BTreeMap::from([("alpha", caps())]),
            capacity: Ok(the_box()),
            days: BTreeMap::new(),
        })
        .await;

        let (status, body) = get(&app, "/api/namespaces/alpha/usage").await;
        assert_eq!(status, http::StatusCode::OK, "{body}");
        let seen: serde_json::Value = serde_json::from_str(&body).expect("JSON");

        // (used, cap, remaining) per meter — every triple different, every
        // arm of the rule on its own line.
        // `remaining` is gunnar's (cap − held, absent where uncapped) and the
        // box is NOT folded into it: the box is its own field, below.
        let expect = [
            ("pack_bytes", 7_340_032, Some(8_388_608), Some(1_048_576)),
            ("cache_bytes", 1_572_864, None, None),
            ("cold_bytes", 5_242_880, Some(5_242_880), Some(0)),
            ("lfs_bytes", 0, None, None),
            ("tombstoned_bytes", 3_145_728, Some(100_000_000), Some(96_854_272)),
            ("repos", 9, Some(12), Some(3)),
            ("objects", 41_237, None, None),
        ];
        for (name, used, cap, left) in expect {
            let (u, c, l) = meter(&seen, name);
            assert_eq!(u, Some(used), "{name} used: {body}");
            assert_eq!(c, cap, "{name} cap: {body}");
            assert_eq!(l, left, "{name} remaining: {body}");
        }
        // `null`, not 0, not missing: the browser tells "no cap" from "a cap
        // of nothing" by the field being present and null.
        assert!(seen["caps"]["cache_bytes"].is_null(), "{body}");
        assert!(
            seen["caps"].get("cache_bytes").is_some(),
            "the field is present: {body}"
        );
        assert!(seen["remaining"]["objects"].is_null(), "{body}");

        // The box itself, field for field, with the growth word.
        assert_eq!(
            seen["box"]["total_bytes"].as_u64(),
            Some(107_374_182_400),
            "{body}"
        );
        assert_eq!(
            seen["box"]["free_bytes"].as_u64(),
            Some(1_075_838_976),
            "{body}"
        );
        assert_eq!(
            seen["box"]["servable_bytes"].as_u64(),
            Some(2_097_152),
            "{body}"
        );
        assert_eq!(seen["box"]["growth"], "sealed", "{body}");
        assert_eq!(seen["box_unreadable"], "", "{body}");
    }

    /// ★ **Without the box — a key that may read the namespace and not the
    /// server — the caps are unchanged, `remaining` is `cap − used` alone, no
    /// cap on a byte meter leaves nothing to say, and the page is still 200
    /// with gunnar's reason for the missing box on it.**
    ///
    /// Seen red with the refusal propagated as the route's error — *"a
    /// refused box answered 403 Forbidden"* — and with `remaining` for an
    /// uncapped meter filled with 0 in place of `null` —
    /// *"cache_bytes remaining: left Some(0) right None"*.
    #[tokio::test]
    async fn without_the_box_remaining_is_what_the_cap_leaves_and_the_reason_is_on_the_wire() {
        let app = console(FakeGunnar {
            signed_by: Default::default(),
            readable: vec!["alpha"],
            rows: BTreeMap::from([("alpha", meters("alpha"))]),
            allowances: BTreeMap::from([("alpha", caps())]),
            capacity: Err(tonic::Code::PermissionDenied),
            days: BTreeMap::new(),
        })
        .await;

        let (status, body) = get(&app, "/api/namespaces/alpha/usage").await;
        assert_eq!(
            status,
            http::StatusCode::OK,
            "a refused box answered {status}: {body}"
        );
        let seen: serde_json::Value = serde_json::from_str(&body).expect("JSON");
        let expect = [
            ("pack_bytes", Some(8_388_608), Some(1_048_576)),
            ("cache_bytes", None, None),
            ("cold_bytes", Some(5_242_880), Some(0)),
            ("lfs_bytes", None, None),
            ("tombstoned_bytes", Some(100_000_000), Some(96_854_272)),
            ("repos", Some(12), Some(3)),
            ("objects", None, None),
        ];
        for (name, cap, left) in expect {
            let (_, c, l) = meter(&seen, name);
            assert_eq!(c, cap, "{name} cap: {body}");
            assert_eq!(l, left, "{name} remaining: {body}");
        }
        assert!(seen["box"].is_null(), "{body}");
        assert!(
            seen["box_unreadable"]
                .as_str()
                .is_some_and(|why| why.contains("no server-wide read grant")),
            "gunnar's own words for the missing box: {body}"
        );
    }

    /// A gunnar from before the allowance sends none, and that is every cap
    /// absent — the same answer it would give if asked. Not an error, not 0.
    #[test]
    fn a_gunnar_that_sends_no_allowance_is_every_cap_absent() {
        let rendered = render(
            meters("alpha"),
            pb::Allowance::default(),
            None,
            Err("no box".to_owned()),
        );
        assert_eq!(rendered.caps, PerMeter::default());
        assert_eq!(rendered.remaining, PerMeter::default());
        assert_eq!(rendered.machine, None);
        assert_eq!(rendered.box_unreadable, "no box");
    }

    /// ★ **A cap goes out with the word for what it DOES, and the word is a
    /// word.**
    ///
    /// The defect: `Usage.Read` has reported seven caps in one shape under one
    /// message called `Allowance`, and three of them are a door. gunnar closed
    /// its half on 2026-09-07 (`Allowance.enforcement`); this console's mirror
    /// predated it, so the field arrived and nothing carried it to the browser.
    ///
    /// Seen RED against the tree as it stood — the compiler first, because the
    /// re-vendored proto grew a field the fixture did not fill:
    ///
    /// ```text
    /// error[E0063]: missing field `enforcement` in initializer of `Allowance`
    ///    --> crates/gunnar-ui-server/src/api/usage.rs:374:9
    /// ```
    ///
    /// and then, with the fixture filled and [`render`] still ignoring it —
    /// note `"enforcement":{}` in the body, which is precisely the shape an
    /// older gunnar's answer has and the reason the two cases needed
    /// separating:
    ///
    /// ```text
    /// ---- api::usage::tests::a_cap_carries_the_word_for_what_it_does stdout ----
    /// assertion `left == right` failed: pack_bytes is a cap and the console says
    /// nothing about what happens at it: {"namespace":"alpha",… ,"caps":
    /// {"pack_bytes":8388608,…},"enforcement":{},"remaining":{…}}
    ///   left: None
    ///  right: Some("enforced")
    /// ```
    ///
    /// The assertion is on the JSON, not on the struct: the browser reads field
    /// names and values, and a `serde_json::from_str` back into [`Usage`] would
    /// follow a rename this console made on both sides at once.
    #[tokio::test]
    async fn a_cap_carries_the_word_for_what_it_does() {
        let app = console(FakeGunnar {
            signed_by: Default::default(),
            readable: vec!["alpha"],
            rows: BTreeMap::from([("alpha", meters("alpha"))]),
            allowances: BTreeMap::from([("alpha", caps())]),
            capacity: Ok(the_box()),
            days: BTreeMap::new(),
        })
        .await;

        let (status, body) = get(&app, "/api/namespaces/alpha/usage").await;
        assert_eq!(status, http::StatusCode::OK, "{body}");
        let seen: serde_json::Value = serde_json::from_str(&body).expect("JSON");

        for (meter, word) in [
            ("pack_bytes", "enforced"),
            ("cache_bytes", "enforced"),
            ("cold_bytes", "measured"),
            ("lfs_bytes", "enforced"),
            ("tombstoned_bytes", "measured"),
            // The auditor's example: a cap of 12 repositories that nothing on
            // the box refuses the thirteenth of.
            ("repos", "measured"),
            ("objects", "measured"),
        ] {
            assert_eq!(
                seen["enforcement"][meter].as_str(),
                Some(word),
                "{meter} is a cap and the console says nothing about what happens at it: \
                 {body}"
            );
        }

        // **A WORD, never the wire number.** `2` on this JSON would be an
        // enum's ordinal published as an API value, and renumbering the proto
        // would relabel every wall a reading with nothing failing.
        assert!(
            seen["enforcement"]["pack_bytes"].is_string(),
            "the enforcement is gunnar's enum ORDINAL on the console's own JSON: {body}"
        );
        // And the word travels beside the cap, not instead of it: the tenant
        // bought the number.
        assert_eq!(seen["caps"]["repos"].as_u64(), Some(12), "{body}");
    }

    /// ★ **An older gunnar says nothing, and nothing is not "nothing is
    /// enforced".**
    ///
    /// The appliance is being re-imaged and will answer the map. A
    /// self-hoster's box will not — and that box still cuts a push mid-stream
    /// at `pack_bytes`. Rendering its silence as seven readings would tell its
    /// operator their pack quota is a line item, and the truth would arrive as
    /// a refused push.
    ///
    /// So the empty map is carried out EMPTY. It is the page's job to say "not
    /// said" (`pages::usage::wall_or_reading`), and it can only do that if the
    /// server does not fill the silence in for it.
    ///
    /// Seen red against a [`render`] that defaulted the missing map to the
    /// seven words this build happens to know:
    /// *"an allowance with no enforcement in it was rendered as 7 words; a box
    /// that did not say must not be quoted"*.
    #[test]
    fn a_gunnar_that_says_nothing_about_enforcement_is_not_read_as_nothing_enforced() {
        let silent = pb::Allowance {
            // It DOES hold a cap — this is not the uncapped case. An older
            // gunnar with a signed fact reports the number and no word for it.
            pack_bytes: Some(8_388_608),
            ..pb::Allowance::default()
        };
        let rendered = render(meters("alpha"), silent, None, Err("no box".to_owned()));

        assert_eq!(
            rendered.caps.pack_bytes,
            Some(8_388_608),
            "the cap is still carried"
        );
        assert!(
            rendered.enforcement.is_empty(),
            "an allowance with no enforcement in it was rendered as {} words; a box that \
             did not say must not be quoted: {:?}",
            rendered.enforcement.len(),
            rendered.enforcement
        );
    }

    /// An enforcement code this build has no word for is left OUT rather than
    /// given a placeholder, which puts it in the same box as an older gunnar's
    /// silence — the one case the page already renders honestly.
    ///
    /// `ENFORCEMENT_UNSPECIFIED` is a field nobody filled. A future variant is
    /// a word this build has not learned. Neither is something to show a
    /// tenant as a fact about their plan.
    #[test]
    fn a_word_this_build_does_not_know_is_left_unsaid() {
        assert_eq!(
            enforcement_word(pb::Enforcement::Enforced as i32),
            Some("enforced")
        );
        assert_eq!(
            enforcement_word(pb::Enforcement::Measured as i32),
            Some("measured")
        );
        assert_eq!(
            enforcement_word(pb::Enforcement::Provisioned as i32),
            Some("provisioned"),
            "gunnar answers only two of the three, but the vocabulary is monetize's whole \
             one and a console that could not name the third would be a console that \
             breaks on the day gunnar starts using it"
        );
        assert_eq!(enforcement_word(pb::Enforcement::Unspecified as i32), None);
        assert_eq!(enforcement_word(99), None);

        let rendered = render(
            meters("alpha"),
            pb::Allowance {
                enforcement: [("repos".to_owned(), 99), ("pack_bytes".to_owned(), 2)]
                    .into_iter()
                    .collect(),
                ..pb::Allowance::default()
            },
            None,
            Err("no box".to_owned()),
        );
        assert_eq!(
            rendered.enforcement,
            BTreeMap::from([("pack_bytes".to_owned(), "enforced".to_owned())]),
            "a code with no word must be absent, not a placeholder"
        );
    }

    /// gunnar's `remaining` is carried meter by meter, untouched — an absent
    /// one stays absent, a 0 stays 0, and nothing is computed beside it.
    #[test]
    fn remaining_is_carried_from_gunnar_untouched() {
        let rendered = render(
            meters("alpha"),
            caps(),
            Some(pb::Remaining {
                pack_bytes: Some(11),
                cold_bytes: Some(0),
                repos: Some(3),
                ..Default::default()
            }),
            Err("no box".to_owned()),
        );
        assert_eq!(rendered.remaining.pack_bytes, Some(11));
        assert_eq!(rendered.remaining.cold_bytes, Some(0));
        assert_eq!(rendered.remaining.repos, Some(3));
        assert_eq!(rendered.remaining.cache_bytes, None);
        assert_eq!(rendered.remaining.tombstoned_bytes, None, "gunnar sent none: none");
    }

    // ── the flow half: what moved ─────────────────────────────────────────

    /// ★ **The five counters and the two "last seen" stamps are gunnar's, and
    /// a namespace nothing has ever pushed to is not dated 1970.**
    ///
    /// The defect this closes is the whole reason the flow fields exist: every
    /// meter on this route before them was a STOCK meter, and a namespace
    /// pushed to this morning read identically to one last touched in March.
    ///
    /// The timestamp half is the subtle one. gunnar sends a bare `int64` and
    /// uses 0 for *never pushed to inside the kept window*; proto3 cannot tell
    /// that from an unset field, so a console that carried the number would put
    /// **1 Jan 1970** — "689 months ago", on a page that renders ages
    /// relatively — beside a namespace that has simply never been pushed to.
    /// That is not a cosmetic wrong date: it is the difference between "quiet
    /// since March" and "never used", which is the exact question the field was
    /// added to answer.
    ///
    /// Seen red twice on the applied JSON: with `pushes_today` read out of
    /// `fetches_today` — *"pushes_today … left: Some(27) right: Some(3)"* — and
    /// with the raw stamp carried instead of [`crate::api::recorded`] —
    /// *"a namespace never pushed to is dated 1970: Some(0)"*.
    #[tokio::test]
    async fn what_moved_today_is_carried_and_a_namespace_never_pushed_to_is_not_dated_1970() {
        let app = console(FakeGunnar::counting(
            vec!["alpha", "quiet"],
            BTreeMap::from([
                ("alpha", meters("alpha")),
                // The namespace nothing has ever touched: every flow counter 0
                // AND both stamps 0, which is the shape a brand-new namespace
                // has and the shape an old gunnar sends for every namespace.
                (
                    "quiet",
                    pb::NamespaceUsage {
                        bytes_in_today: 0,
                        bytes_out_today: 0,
                        pushes_today: 0,
                        fetches_today: 0,
                        refused_today: 0,
                        last_push_unix_ms: 0,
                        last_fetch_unix_ms: 0,
                        ..meters("quiet")
                    },
                ),
            ]),
        ))
        .await;

        let (status, body) = get(&app, "/api/namespaces/alpha/usage").await;
        assert_eq!(status, http::StatusCode::OK, "{body}");
        let seen: serde_json::Value = serde_json::from_str(&body).expect("JSON");
        let sent = meters("alpha");
        for (name, value) in [
            ("bytes_in_today", sent.bytes_in_today),
            ("bytes_out_today", sent.bytes_out_today),
            ("pushes_today", sent.pushes_today),
            ("fetches_today", sent.fetches_today),
            ("refused_today", sent.refused_today),
        ] {
            assert_eq!(
                seen[name].as_u64(),
                Some(value),
                "{name} is not the number gunnar counted: {body}"
            );
        }
        assert_eq!(
            seen["last_push_unix_ms"].as_i64(),
            Some(sent.last_push_unix_ms),
            "a real stamp must survive, or the rule below would be \"always null\": {body}"
        );
        assert_eq!(
            seen["last_fetch_unix_ms"].as_i64(),
            Some(sent.last_fetch_unix_ms),
            "{body}"
        );
        // And nothing was derived: `refused_today` is gunnar's own count and
        // not a sum of anything on this page.
        assert_ne!(
            sent.refused_today,
            sent.pushes_today + sent.fetches_today,
            "the fixture itself must not make a derived answer accidentally true"
        );

        let (status, body) = get(&app, "/api/namespaces/quiet/usage").await;
        assert_eq!(status, http::StatusCode::OK, "{body}");
        let seen: serde_json::Value = serde_json::from_str(&body).expect("JSON");
        assert!(
            seen["last_push_unix_ms"].is_null(),
            "a namespace never pushed to is dated 1970: {:?} — {body}",
            seen["last_push_unix_ms"]
        );
        assert!(
            seen["last_fetch_unix_ms"].is_null(),
            "a namespace never fetched from is dated 1970: {body}"
        );
        // The counters themselves are REAL zeroes and stay zeroes. A quiet day
        // moved nothing, and that is a measurement, not a missing one.
        assert_eq!(seen["pushes_today"].as_u64(), Some(0), "{body}");
        assert_eq!(seen["bytes_in_today"].as_u64(), Some(0), "{body}");
    }

    // ── the series ────────────────────────────────────────────────────────

    const DAY: i64 = 86_400_000;
    /// Midnight UTC of an arbitrary but real day; the fixture's rows hang off
    /// it. A multiple of [`DAY`] on purpose — a "day" that is not a midnight
    /// would make the ordering assertions pass for the wrong reason.
    const D0: i64 = 20_694 * DAY;

    /// **Three days out of six, with the quiet days MISSING** — the shape
    /// gunnar actually sends, and therefore the only shape worth a fixture.
    ///
    /// `D0` is an ordinary day. `D0 + 2` is the day the tenant hit walls, and
    /// its breakdown names `admission` and `cap` and **not `policy`**, so a
    /// console that zero-filled the third reason has something to fail on.
    /// `D0 + 5` is a fetch-only day, so its `last_push_unix_ms` is gunnar's
    /// genuine 0 and the 1970 rule is exercised on a day row rather than only
    /// on the glance.
    ///
    /// Every number is distinct from every other, including across rows: a
    /// handler that read day two's `bytes_out` into day one's `bytes_in` is
    /// visible, and a fixture of round repeated numbers could not show it.
    fn ledger() -> Vec<pb::DayActivity> {
        vec![
            pb::DayActivity {
                day_unix_ms: D0,
                bytes_in: 1_048_576,
                bytes_out: 5_242_880,
                pushes: 4,
                fetches: 11,
                lfs_bytes_in: 262_144,
                lfs_bytes_out: 131_072,
                refused: 0,
                refused_by_reason: std::collections::HashMap::new(),
                auth_failures: 0,
                last_push_unix_ms: D0 + 41_000_000,
                last_fetch_unix_ms: D0 + 62_000_000,
                distinct_principals: 3,
                distinct_principals_capped: false,
            },
            // The gap: `D0 + 1` has no row at all, because nothing moved.
            pb::DayActivity {
                day_unix_ms: D0 + 2 * DAY,
                bytes_in: 3_145_728,
                bytes_out: 786_432,
                pushes: 2,
                fetches: 7,
                lfs_bytes_in: 0,
                lfs_bytes_out: 65_536,
                refused: 5,
                // TWO reasons of the three. `policy` never happened, so gunnar
                // does not send it, and neither may this console.
                refused_by_reason: [("admission".to_owned(), 1u64), ("cap".to_owned(), 4u64)]
                    .into_iter()
                    .collect(),
                auth_failures: 1,
                last_push_unix_ms: D0 + 2 * DAY + 33_000_000,
                last_fetch_unix_ms: D0 + 2 * DAY + 55_000_000,
                distinct_principals: 2,
                distinct_principals_capped: false,
            },
            pb::DayActivity {
                day_unix_ms: D0 + 5 * DAY,
                bytes_in: 0,
                bytes_out: 9_437_184,
                // Nobody pushed. gunnar's `last_push_unix_ms` for the day is
                // its genuine 0.
                pushes: 0,
                fetches: 19,
                lfs_bytes_in: 0,
                lfs_bytes_out: 0,
                refused: 0,
                refused_by_reason: std::collections::HashMap::new(),
                auth_failures: 0,
                last_push_unix_ms: 0,
                last_fetch_unix_ms: D0 + 5 * DAY + 71_000_000,
                distinct_principals: 5,
                distinct_principals_capped: false,
            },
        ]
    }

    /// A gunnar that counts `alpha` and holds [`ledger`] for it.
    fn with_ledger(readable: Vec<&'static str>) -> FakeGunnar {
        FakeGunnar {
            signed_by: Default::default(),
            days: BTreeMap::from([("alpha", ledger())]),
            ..FakeGunnar::counting(
                readable,
                BTreeMap::from([("alpha", meters("alpha")), ("beta", meters("beta"))]),
            )
        }
    }

    /// ★ **The days arrive oldest first, untouched, gaps and all.**
    ///
    /// Every field of every row against the exact value the service sent, in
    /// the exact order it sent them — and the gap at `D0 + 1` is asserted as a
    /// gap, because zero-filling it is the tempting bug: a bar for every day is
    /// easier to draw, and it would make this console assert a measurement
    /// gunnar never took. The page needs the absence to tell *nothing moved*
    /// from *this could not be read*.
    ///
    /// Seen red on the applied JSON with the rows sorted newest-first —
    /// *"day 0 is the wrong day: left Some(1788393600000) right
    /// Some(1787961600000)"* — and with a synthetic zero row inserted for the
    /// quiet day, *"3 rows became 6; a day gunnar never wrote is not a day with
    /// no traffic"*.
    #[tokio::test]
    async fn the_days_come_back_oldest_first_exactly_as_gunnar_sent_them() {
        let app = console(with_ledger(vec!["alpha"])).await;

        let (status, body) = get(&app, "/api/namespaces/alpha/activity").await;
        assert_eq!(status, http::StatusCode::OK, "{body}");
        let seen: serde_json::Value = serde_json::from_str(&body).expect("JSON");

        assert_eq!(seen["namespace"], "alpha", "{body}");
        assert_eq!(
            seen["retained_days"].as_i64(),
            Some(400),
            "the window gunnar keeps is what tells an empty answer from an unanswerable \
             one: {body}"
        );

        let days = seen["days"].as_array().expect("an array of days");
        assert_eq!(
            days.len(),
            3,
            "{} rows became {}; a day gunnar never wrote is not a day with no traffic: {body}",
            ledger().len(),
            days.len()
        );
        for (i, sent) in ledger().into_iter().enumerate() {
            let got = &days[i];
            assert_eq!(
                got["day_unix_ms"].as_i64(),
                Some(sent.day_unix_ms),
                "day {i} is the wrong day: {body}"
            );
            for (name, value) in [
                ("bytes_in", sent.bytes_in),
                ("bytes_out", sent.bytes_out),
                ("pushes", sent.pushes),
                ("fetches", sent.fetches),
                ("lfs_bytes_in", sent.lfs_bytes_in),
                ("lfs_bytes_out", sent.lfs_bytes_out),
                ("refused", sent.refused),
                ("auth_failures", sent.auth_failures),
                ("distinct_principals", sent.distinct_principals),
            ] {
                assert_eq!(
                    got[name].as_u64(),
                    Some(value),
                    "day {i} {name} is not the number gunnar counted: {body}"
                );
            }
            assert_eq!(
                got["distinct_principals_capped"].as_bool(),
                Some(sent.distinct_principals_capped),
                "day {i}: a floor that stopped growing must say so: {body}"
            );
        }
        // The gap, named: the day between the first two rows has no row, and
        // nothing in the answer claims otherwise.
        assert!(
            !days
                .iter()
                .any(|d| d["day_unix_ms"].as_i64() == Some(D0 + DAY)),
            "a day with no traffic was invented as a row: {body}"
        );
    }

    /// ★ **A refusal reason gunnar did not send is ABSENT, never 0.**
    ///
    /// The fixture's walled day was refused by `admission` and by `cap`, and
    /// never by `policy`. Writing `"policy": 0` out would say *nothing was
    /// refused by the push gate today*, which this console did not measure and
    /// gunnar did not claim — and the reader who most needs the difference is
    /// the tenant deciding whether their problem is money or a protected
    /// branch.
    ///
    /// Seen red against a render that pre-seeded the three known reasons:
    /// *"the console invented a reason gunnar did not send: policy"*.
    #[tokio::test]
    async fn a_refusal_reason_gunnar_did_not_send_is_absent_rather_than_zero() {
        let app = console(with_ledger(vec!["alpha"])).await;

        let (status, body) = get(&app, "/api/namespaces/alpha/activity").await;
        assert_eq!(status, http::StatusCode::OK, "{body}");
        let seen: serde_json::Value = serde_json::from_str(&body).expect("JSON");
        let walled = &seen["days"][1];

        assert_eq!(
            walled["refused_by_reason"]["admission"].as_u64(),
            Some(1),
            "{body}"
        );
        assert_eq!(
            walled["refused_by_reason"]["cap"].as_u64(),
            Some(4),
            "{body}"
        );
        assert!(
            walled["refused_by_reason"].get("policy").is_none(),
            "the console invented a reason gunnar did not send: policy — {body}"
        );
        // And the total is gunnar's own, not the sum of the breakdown: a reason
        // gunnar has not learned to name yet would make the two disagree, and
        // the number a tenant reads must be the one the server counted.
        assert_eq!(walled["refused"].as_u64(), Some(5), "{body}");
        // A day nothing was refused on carries an EMPTY map, not the previous
        // day's keys and not three zeroes.
        assert_eq!(
            seen["days"][0]["refused_by_reason"]
                .as_object()
                .map(|m| m.len()),
            Some(0),
            "a day with no refusals must carry no reasons at all: {body}"
        );
    }

    /// ★ **A day nobody pushed on renders `null`, not an epoch date.**
    ///
    /// The third row is fetch-only, so gunnar's `last_push_unix_ms` for it is a
    /// genuine 0. Carried as a number it would be drawn as 1 Jan 1970 — or,
    /// on this console, as "689 months ago" beside a bar for last Tuesday.
    ///
    /// Seen red with the stamp carried raw instead of through
    /// [`crate::api::recorded`]: *"a day with no push is dated 1970:
    /// Number(0)"*.
    #[tokio::test]
    async fn a_day_with_no_push_renders_null_and_not_an_epoch_date() {
        let app = console(with_ledger(vec!["alpha"])).await;

        let (status, body) = get(&app, "/api/namespaces/alpha/activity").await;
        assert_eq!(status, http::StatusCode::OK, "{body}");
        let seen: serde_json::Value = serde_json::from_str(&body).expect("JSON");

        let fetch_only = &seen["days"][2];
        assert!(
            fetch_only["last_push_unix_ms"].is_null(),
            "a day with no push is dated 1970: {:?} — {body}",
            fetch_only["last_push_unix_ms"]
        );
        // The field is PRESENT and null rather than missing: the browser tells
        // "no push that day" from "this build does not report it" by the field
        // being there.
        assert!(
            fetch_only.get("last_push_unix_ms").is_some(),
            "the field must be present and null: {body}"
        );
        // …and the fetch of that same day, which really happened, survives —
        // or the rule above would be "always null".
        assert_eq!(
            fetch_only["last_fetch_unix_ms"].as_i64(),
            Some(D0 + 5 * DAY + 71_000_000),
            "{body}"
        );
        // The count beside it is a real zero and stays one: nobody pushed is a
        // measurement, unlike a stamp that was never written.
        assert_eq!(fetch_only["pushes"].as_u64(), Some(0), "{body}");
    }

    /// ★ **A namespace the caller may not read yields no days.**
    ///
    /// `Usage.Activity` takes `Read` on the namespace, the same grant
    /// `Usage.Read` takes, and the console invents no second rule: gunnar's
    /// `PermissionDenied` is relayed as a 403 in gunnar's own words carrying
    /// not one byte of `beta`'s traffic.
    ///
    /// The control comes first, as it does for `/usage`: without it this would
    /// pass against a console that refused everything, which is not the feature.
    #[tokio::test]
    async fn a_namespaces_activity_is_refused_and_leaks_no_days() {
        let fake = FakeGunnar {
            signed_by: Default::default(),
            // `beta` is counted, and its ledger is here to be leaked. It is
            // simply not this caller's.
            days: BTreeMap::from([("alpha", ledger()), ("beta", ledger())]),
            ..FakeGunnar::counting(
                vec!["alpha"],
                BTreeMap::from([("alpha", meters("alpha")), ("beta", meters("beta"))]),
            )
        };
        let app = console(fake).await;

        let (allowed, body) = get(&app, "/api/namespaces/alpha/activity").await;
        assert_eq!(
            allowed,
            http::StatusCode::OK,
            "the caller's own namespace must still be readable, or this proves nothing: {body}"
        );

        let (status, body) = get(&app, "/api/namespaces/beta/activity").await;
        // As for `/usage`: refused reads the same as absent (`api::absent`).
        assert_eq!(
            status,
            http::StatusCode::NOT_FOUND,
            "a refused namespace answered {status}: {body}"
        );
        assert_eq!(body, crate::api::absent::body().to_string());
        assert!(
            !body.contains("\"days\""),
            "the refusal carries a days array: {body}"
        );
        // The numbers themselves, as they would be written in JSON.
        for day in ledger() {
            for value in [day.bytes_in, day.bytes_out, day.fetches] {
                if value == 0 {
                    // 0 appears in an HTTP status and proves nothing.
                    continue;
                }
                assert!(
                    !body.contains(&value.to_string()),
                    "the refusal carries beta's traffic ({value}): {body}"
                );
            }
        }
    }

    /// **An empty series is not a refusal, and it says how far back it looked.**
    ///
    /// `days: []` with a non-zero `retained_days` is *nothing moved*. That is a
    /// 200 with an answer in it, and the page renders it as a quiet tenant. A
    /// console that turned it into an error — or into the same blank the
    /// refusal draws — would make "silent" and "not allowed to look"
    /// indistinguishable, which is the pair this whole route exists to tell
    /// apart.
    #[tokio::test]
    async fn a_namespace_that_moved_nothing_answers_with_no_days_and_not_an_error() {
        let app = console(FakeGunnar::counting(
            vec!["alpha"],
            BTreeMap::from([("alpha", meters("alpha"))]),
        ))
        .await;

        let (status, body) = get(&app, "/api/namespaces/alpha/activity").await;
        assert_eq!(status, http::StatusCode::OK, "{body}");
        let seen: serde_json::Value = serde_json::from_str(&body).expect("JSON");
        assert_eq!(
            seen["days"].as_array().map(Vec::len),
            Some(0),
            "a quiet namespace must answer with an empty series: {body}"
        );
        assert_eq!(
            seen["retained_days"].as_i64(),
            Some(400),
            "…and still say how far back the box remembers, or the page cannot tell \
             \"nothing moved\" from \"you asked about a time this box forgot\": {body}"
        );
        assert_eq!(seen["namespace"], "alpha", "{body}");
    }

    /// **Thirty WHOLE UTC days, ending with today.**
    ///
    /// The window is floored to midnight rather than measured back from the
    /// clock: gunnar's rows are whole UTC days, so a `since` at 14:00 would
    /// make the oldest bar a part-day drawn at full height beside twenty-nine
    /// whole ones. And it is `days − 1` back, because the window is inclusive
    /// at both ends — a bare `days` is a thirty-first bar.
    ///
    /// The fake asserts the same three properties against the live request (see
    /// `FakeGunnar::activity`); this pins the arithmetic in one line so a
    /// failure there says which half is wrong.
    #[test]
    fn the_window_is_thirty_whole_utc_days_ending_with_today() {
        // Midday of a day, and midnight of the same day, must produce the same
        // window: the clock's time of day may not move the x axis.
        let midnight = 20_694 * DAY;
        let midday = midnight + 14 * 3_600_000;
        assert_eq!(
            window_start(midnight, ACTIVITY_DAYS),
            window_start(midday, ACTIVITY_DAYS)
        );
        // Inclusive at both ends: today plus the twenty-nine before it.
        assert_eq!(window_start(midday, ACTIVITY_DAYS), midnight - 29 * DAY);
        assert_eq!(window_start(midday, 1), midnight, "one day is today alone");
        // It IS a midnight, which is what the fake checks on the wire.
        assert_eq!(window_start(midday, ACTIVITY_DAYS) % DAY, 0);
        // A millisecond before the epoch floors to the day it is IN, not the
        // one after: `/` rounds towards zero and would be off by a whole day.
        assert_eq!(window_start(-1, 1), -DAY);
        assert_eq!(
            ACTIVITY_DAYS, 30,
            "the page says \"the last 30 days\" in words"
        );
    }

    /// The day mapping itself, away from the wire — the timestamp rule and the
    /// reason map in one line each, so the end-to-end rows above say WHICH half
    /// broke when they go red.
    #[test]
    fn a_day_row_is_carried_field_for_field() {
        let sent = ledger().remove(1);
        let got = render_day(sent.clone());

        assert_eq!(got.day_unix_ms, sent.day_unix_ms);
        assert_eq!(got.bytes_in, sent.bytes_in);
        assert_eq!(got.bytes_out, sent.bytes_out);
        assert_eq!(got.lfs_bytes_in, sent.lfs_bytes_in);
        assert_eq!(got.lfs_bytes_out, sent.lfs_bytes_out);
        assert_eq!(got.refused, sent.refused);
        assert_eq!(got.auth_failures, sent.auth_failures);
        assert_eq!(got.distinct_principals, sent.distinct_principals);
        assert_eq!(
            got.refused_by_reason,
            BTreeMap::from([("admission".to_owned(), 1u64), ("cap".to_owned(), 4u64)]),
            "every key gunnar sent, and no key it did not"
        );
        assert_eq!(got.last_push_unix_ms, Some(sent.last_push_unix_ms));

        // The zero rule, on a row of its own.
        let quiet = render_day(pb::DayActivity {
            last_push_unix_ms: 0,
            last_fetch_unix_ms: 0,
            ..ledger().remove(0)
        });
        assert_eq!(quiet.last_push_unix_ms, None);
        assert_eq!(quiet.last_fetch_unix_ms, None);
    }

    /// The growth word: the two verdicts the page renders, and everything
    /// else is `unknown` — including UNSPECIFIED, which is a field nobody
    /// filled and must not read as a promise.
    #[test]
    fn the_growth_word_names_sealed_and_operator_only_and_nothing_else() {
        assert_eq!(growth_word(pb::SetGrowth::GrowthSealed as i32), "sealed");
        assert_eq!(
            growth_word(pb::SetGrowth::GrowthOperatorOnly as i32),
            "operator_only"
        );
        assert_eq!(growth_word(pb::SetGrowth::GrowthUnknown as i32), "unknown");
        assert_eq!(growth_word(pb::SetGrowth::Unspecified as i32), "unknown");
        assert_eq!(growth_word(99), "unknown");
    }

    // ── Whose key answers a namespace's meters ─────────────────────────────
    //
    // `serve.rs` says of this route: *"reading your own meters is not an act of
    // administration, and gunnar's own grant on the namespace is what decides
    // whether the call is answered at all (see `api::usage`)."*
    //
    // That described the intent and, until this test, not the code. Every other
    // read surface in this crate takes `api::Reader` — nine modules do — and
    // `api::usage` was the one that did not: it signed with `state.gunnar`, the
    // console's SERVER-WIDE ADMIN key. gunnar can only refuse a caller it can
    // narrow, and that caller could never be narrowed, so the grant the comment
    // relies on was never consulted.
    //
    // In practice: `role_floor` puts a GET at `Viewer`, the namespace comes
    // from the path, and the credential was an admin's — so a signed-in viewer
    // could read any namespace's `pack_bytes`, its caps and its day-by-day
    // activity by spelling the name. How much every other customer stores, and
    // what they pay for.

    /// A console with a real passkey subsystem, so a viewer session is the real
    /// thing and `Reader` runs its real ladder instead of the open-on-loopback
    /// bypass.
    async fn gated(
        fake: FakeGunnar,
    ) -> (
        axum::Router,
        Arc<crate::auth::Webauthn>,
        String,
        crate::state::StateStore,
    ) {
        let listener = tokio::net::TcpListener::bind("127.0.0.1:0")
            .await
            .expect("a loopback port");
        let addr = listener.local_addr().expect("its address");
        tokio::spawn(async move {
            tonic::transport::Server::builder()
                .add_service(UsageServer::new(fake.clone()))
                .add_service(AdminServer::new(fake))
                .serve_with_incoming(tokio_stream::wrappers::TcpListenerStream::new(listener))
                .await
        });
        let dir = tempfile::tempdir().expect("a temp dir").keep();
        let store = crate::state::StateStore::open(dir.join("state.redb")).expect("a store");
        let webauthn = Arc::new(
            crate::auth::Webauthn::new(
                crate::auth::RelyingParty {
                    rp_id: "console.example".to_owned(),
                    origin: "https://console.example".to_owned(),
                    rp_name: "gunnar".to_owned(),
                },
                store.clone(),
            )
            .expect("a subsystem"),
        );
        let signer = Arc::new(
            crate::signer::Signer::new(ssh_key::PrivateKey::from(
                ssh_key::private::Ed25519Keypair::from_seed(&[7u8; 32]),
            ))
            .expect("a fixed seed makes a signer"),
        );
        let console_fingerprint = signer.fingerprint().to_owned();
        let endpoint = format!("http://{addr}");
        let router = crate::serve::router(Arc::new(crate::app::App {
            webauthn: Some(webauthn.clone()),
            admin_token: None,
            #[cfg(feature = "metered")]
            monetize: None,
            events: crate::sse::EventHub::detached(),
            gunnar: crate::grpc::Gunnar::connect_lazy(&endpoint, signer).expect("a lazy handle"),
            twin: None,
            guest: None,
            reviewers: Arc::new(crate::reviewer::ReviewerKeys::new(
                endpoint.clone(),
                store.clone(),
            )),
            tenants: Arc::new(crate::tenant::TenantKeys::new(endpoint, store.clone())),
            forges: Default::default(),
            imports: Default::default(),
            forge_apps: Default::default(),
            ui_dir: None,
            caches: crate::api::Caches::default(),
            objects: Default::default(),
            readmes: Default::default(),
        }));
        (router, webauthn, console_fingerprint, store)
    }

    /// ★ **A viewer's read of somebody else's namespace is not signed with the
    /// console's admin key.**
    ///
    /// Asserted on the FINGERPRINT in the `gunnar-sshsig-v2` credential,
    /// because that is the only thing that can tell the console's key from the
    /// tenant's from outside this process — and because asserting on the HTTP
    /// status would not have caught this at all: with an admin credential
    /// gunnar answers 200, so the route looked like it worked.
    #[tokio::test]
    async fn a_viewers_read_is_not_signed_with_the_consoles_admin_key() {
        let fake = FakeGunnar::counting(
            // The fake would happily serve `beta` to an admin, which is the
            // point: the refusal must come from the CREDENTIAL being narrow,
            // not from this list.
            vec!["alice", "beta"],
            BTreeMap::from([
                ("alice", pb::NamespaceUsage::default()),
                ("beta", pb::NamespaceUsage::default()),
            ]),
        );
        let signed_by = fake.signed_by.clone();
        let (router, webauthn, console_fingerprint, store) = gated(fake).await;
        // **vera's identity-core session** — what a passkey login through the
        // core leaves (IDENTITY-HOST). This fake gunnar serves only `Usage`
        // and `Admin`, so no login can run against it; the session is planted
        // as the core would have said it, and her reads carry its token.
        let _ = &store;
        let session = webauthn.plant_core_session_for_tests("vera", crate::role::Role::Viewer);
        let fingerprint = format!("session:{session}");

        let response = router
            .oneshot(
                http::Request::builder()
                    .uri("/api/namespaces/beta/usage")
                    .header(
                        http::header::COOKIE,
                        format!("{}={session}", crate::auth::SESSION_COOKIE),
                    )
                    .body(axum::body::Body::empty())
                    .expect("a request"),
            )
            .await
            .expect("the router answers");
        let _ = response.status();

        let signed = signed_by.lock().expect("the fingerprint log").clone();
        assert!(
            !signed.is_empty(),
            "the read never reached gunnar, so this test proves nothing"
        );
        assert!(
            signed.contains(&fingerprint),
            "the read did not carry the person's own session"
        );
        assert!(
            !signed.contains(&console_fingerprint),
            "a viewer's read of another namespace was signed with the CONSOLE's server-wide \
             admin key ({console_fingerprint}). gunnar cannot refuse a caller it cannot narrow, \
             so every namespace on the box is readable by anyone who can spell its name."
        );
    }
}