gunnar-grpc 3.0.0

The tonic service implementations and the event bus behind gunnar's gRPC control plane.
Documentation
//! **What a cap on each meter actually DOES on this box** — one table, and the
//! only place that answer is written down.
//!
//! # The defect this table exists to close
//!
//! `Usage.Read` has reported seven caps to the tenant since 2026-09-05 and three
//! of them are a door. The other four were reported in the same message, in the
//! same shape, under a field called `Allowance`:
//!
//! > *"If a signed fact carries `caps["repos"]=5`, the console shows the tenant
//! > a limit of 5 and nothing on the box refuses the sixth."*
//!
//! Two surfaces were wrong at once, and they are the two halves of one bug:
//!
//! 1. `Usage.Read` showed a MEASURED number as if it were a wall. Fixed by
//!    [`enforcement`], which puts monetize's own word beside every cap.
//! 2. `Entitlement.Set` accepted a fact carrying a cap on a meter gunnar does
//!    not even REPORT, and then carried it silently forever. Fixed by
//!    [`caps_gunnar_cannot_honour`], which refuses the fact by name.
//!
//! # Why the vocabulary is monetize's and not a new one
//!
//! `monetize_product::Enforcement` already models exactly this distinction —
//! `Enforced` / `Measured` / `Provisioned`, with `Enforcement::is_a_wall()` and a
//! console that renders the three differently — and `products/gunnar`'s `METERS`
//! already carries a row per meter. Coining a second spelling on this side
//! (`is_a_wall`, `hard`/`soft`, a list of enforced names) would have put two
//! vocabularies on the one seam that reports gunnar's numbers to monetize's
//! console, and the day they disagreed there would have been no way to tell
//! which one was stale. The rule: reuse the sibling crates, including their words.
//!
//! # Why gunnar keeps its own copy of the table at all, then
//!
//! Because this one is about the CODE IN THIS BINARY and monetize's is about
//! what may be SOLD, and they are allowed to disagree — in fact they do, and
//! finding that is what this table is for (see [`caps_gunnar_cannot_honour`]).
//! gunnar cannot link `monetize-product`: `gunnar-grpc` compiles on the
//! community appliance, which contains no monetize code at all. A table that
//! says "this build refuses on these three" must live in the build it describes.

use gunnar_proto::Enforcement;

/// **Every meter `Allowance` has a field for, and what a cap on it does here.**
///
/// The order is `Allowance`'s field order, which is `NamespaceUsage`'s, which is
/// the order a console renders. Each `Enforced` row names the door in the
/// comment above it; a row may only claim `Enforced` when a REFUSAL exists on
/// this box, and `tests/allowance_is_labelled.rs` pins the enforced set by name
/// so that promoting a row is a deliberate act with a test to change.
pub const METERS: &[(&str, Enforcement)] = &[
    // `gunnar_wire::receive_pack` counts pack bytes off the wire and cuts the
    // transfer past `PlanGate::pack_ceiling`, and `PlanGate::pack_quota` refuses
    // a namespace already at the cap before a single byte.
    ("pack_bytes", Enforcement::Enforced),
    // `EntitlementSlot::apply_cache_cap` writes it into the namespace's
    // `explode_budget_bytes`, which bounds the exploded read cache.
    ("cache_bytes", Enforcement::Enforced),
    // Reported by `Usage.Read` and billed from. Nothing refuses: the cold tier
    // is where the hot tier goes, not a thing a tenant writes to directly.
    ("cold_bytes", Enforcement::Measured),
    // `gunnar_lfs::Allowance` — an upload past the cap is refused 507 before the
    // object's bytes, on the SSH wire and the HTTP wire alike.
    ("lfs_bytes", Enforcement::Enforced),
    // A soft-deleted repository is charged until it is purged. Nothing refuses a
    // deletion for being over: refusing to DELETE because the tombstone would
    // cost is the one refusal that makes the number grow.
    ("tombstoned_bytes", Enforcement::Measured),
    // **The auditor's example.** `Repos.Create` asks the disk-headroom gate and
    // the grant check and nothing else; a namespace at `caps["repos"]` creates
    // the next repository exactly as before. `products/gunnar`'s `METERS` says
    // the same thing (`Enforcement::Measured`, "not enforced on create yet").
    ("repos", Enforcement::Measured),
    // Indexed objects across the namespace's OPEN stores — a reading of RAM in
    // use, and nothing anywhere refuses on it. See
    // [`caps_gunnar_cannot_honour`] for the second, sharper thing wrong with
    // this row: no fact monetize issues can ever populate it.
    ("objects", Enforcement::Measured),
];

/// **The FLOW meters, and what a cap on one of them would do here: nothing at
/// all, because there is no field to put one in.**
///
/// `Usage.Read` has reported these seven since the flow ledger landed
/// (`gunnar_core::flow`) and every one of them is a count of something that
/// HAPPENED — bytes off the wire, sessions, refusals, a timestamp. They are the
/// answer to *is this tenant using the product*, which no meter in [`METERS`]
/// can give: every one of those is a STOCK meter and reads the same for a
/// tenant who pushed this morning and a tenant who last touched this box in
/// March.
///
/// # Why they are a second table and not seven more rows above
///
/// [`METERS`] is defined as *"every meter `Allowance` has a field for"*, and
/// these have none. That is not an oversight to be corrected later: a cap on a
/// count of pushes is a promise nothing on this box could keep, `Allowance`
/// mirrors `NamespaceUsage`'s field numbers 2–8 by construction, and adding an
/// eighth cap field for a meter no door reads would be exactly the defect
/// [`METERS`]'s own doc comment exists to describe — a MEASURED number reported
/// in a message called `Allowance`.
///
/// The consequence is deliberate and is the correct one: because they are in
/// neither [`METERS`] nor [`PROVISIONED_ELSEWHERE`],
/// [`caps_gunnar_cannot_honour`] REFUSES a signed fact that tries to cap one,
/// by name, at the moment of the bad sale.
///
/// # `Measured` is not a stage on the way to `Enforced`
///
/// Every row here is `Enforcement::Measured` and is expected to stay that way.
/// A row may only claim `Enforced` when a REFUSAL exists on this box; there is
/// none for any of these, and building one would mean deciding to cut a tenant
/// off mid-month for having fetched too often — a door nobody has asked for and
/// which this table is not the place to invent.
pub const ACTIVITY_METERS: &[(&str, Enforcement)] = &[
    // `gunnar_server::transport::serve_blocking`'s metered reader, the one
    // place a byte arriving is counted, on every transport.
    ("bytes_in_today", Enforcement::Measured),
    // The same transfer's metered writer, plus `Repos.ArchiveOut`'s own receipt.
    ("bytes_out_today", Enforcement::Measured),
    // receive-pack sessions that finished. Nothing refuses on the count.
    ("pushes_today", Enforcement::Measured),
    // upload-pack sessions that asked for objects. The bare advertisement is
    // not one; see `usage.proto`.
    ("fetches_today", Enforcement::Measured),
    // Transfers turned away. **A count of doors met, not a door.** The doors
    // themselves are `pack_bytes`, `cache_bytes` and `lfs_bytes` above; this
    // number is how many times one of them was reached, which is a reading of
    // the refusals and cannot itself refuse anything.
    ("refused_today", Enforcement::Measured),
    // Timestamps. Reported as meters so that every existing consumer of
    // `Usage.Read` sees them with no new call, and so that "last seen" is one
    // number rather than something each console folds for itself.
    ("last_push_unix_ms", Enforcement::Measured),
    ("last_fetch_unix_ms", Enforcement::Measured),
];

/// **Cap keys gunnar neither enforces nor reports, but which a plan may legally
/// sell** — the provider's iron.
///
/// `monetize_product::Enforcement::Provisioned` is the honest name for these:
/// CPU and RAM are bought with real money at the cloud provider, they change
/// what the whole appliance can do, and no git server polices them per
/// namespace. They are `Scope::Product` on monetize's side — the box owns the
/// cores, not the tenant — so they have no `Allowance` field and never will.
///
/// They are listed here for one reason: [`caps_gunnar_cannot_honour`] must not
/// refuse a fact for carrying them. A cap gunnar deliberately leaves to the
/// provider's invoice is not a broken promise; a cap gunnar has simply never
/// heard of is.
pub const PROVISIONED_ELSEWHERE: &[&str] = &["cpu_millicores", "ram_bytes"];

/// The word for one meter, or `None` for a name that is not one of
/// [`METERS`].
pub fn enforcement_of(meter: &str) -> Option<Enforcement> {
    METERS
        .iter()
        .find(|(name, _)| *name == meter)
        .map(|(_, word)| *word)
}

/// **The whole table, as `Allowance.enforcement` carries it**: all seven
/// meters, every read, capped or not.
///
/// Built rather than kept as a constant because prost's map field is a
/// `HashMap<String, i32>` and a `const` cannot be one. Seven entries of a short
/// `&'static str` and an `i32`, once per `Usage.Read` — the read walks every
/// repository in the namespace off the disk immediately afterwards, so this is
/// not the cost on that path.
pub fn enforcement() -> std::collections::HashMap<String, i32> {
    METERS
        .iter()
        .map(|(meter, word)| ((*meter).to_owned(), *word as i32))
        .collect()
}

/// **Cap keys in a signed fact that this appliance can neither refuse on nor
/// report** — the reason `Entitlement.Set` turns a fact away, sorted, or empty
/// when the fact is honourable.
///
/// # What is NOT here, deliberately
///
/// A cap on a `Measured` meter of [`METERS`] is fine and is accepted. `repos`,
/// `cold_bytes`, `tombstoned_bytes` and `objects` are real numbers that
/// `Usage.Read` reports and monetize bills from; selling one is selling a
/// measurement, which is honest as long as it is LABELLED as one — and since
/// `Allowance.enforcement` it is. Refusing them would refuse an honest sale.
///
/// So is a cap on [`PROVISIONED_ELSEWHERE`]: iron bought at the provider is not
/// gunnar's to police and never was.
///
/// # What IS here, and why refusing is the right answer
///
/// A key that is in neither list is a number that reaches NO surface on this
/// box. It is not enforced, it is not reported by `Usage.Read`, it does not
/// appear in `Admin.StorageInfo`, and the only trace it leaves is the
/// attestation line that recorded the sale. The operator finds out three months
/// later from a tenant who noticed, or never. `INVALID_ARGUMENT` naming the key
/// puts that discovery at the moment of the bad sale, which is the only moment
/// anyone can act on it.
///
/// **Two real keys land here today**, and both are a disagreement worth being
/// loud about rather than a hypothetical typo:
///
/// * `concurrent_transfers` — `products/gunnar`'s `METERS` declares it
///   `Enforcement::Enforced`. Nothing in gunnar reads it.
///   `SshConfig::max_concurrent_transfers` is an OPERATOR flag, one number for
///   the whole box, and it has never been per namespace or read off a fact.
///   monetize claims a wall gunnar does not have.
/// * `open_store_ram_bytes` — monetize's name for the quantity gunnar reports
///   as `objects` (`products/gunnar`'s own conversion is
///   `meters["open_store_ram_bytes"] / RAM_BYTES_PER_OBJECT`). Two names for one
///   number across one seam, so a cap written under monetize's name is invisible
///   to `allowance_of`, which looks up `caps["objects"]` — a key monetize never
///   writes. The `objects` allowance is unreachable from any fact monetize can
///   issue, and this refusal is what makes that say so out loud.
///
/// # The blast radius, measured before this landed
///
/// A refusal here CANNOT demote a tenant that is already paid up. It is reached
/// only from the `Entitlement.Set` handler; the boot path
/// (`gunnar_server::entitlement::EntitlementSlot::open`) pushes stored facts
/// straight into the cache and runs no shape check at all, so a restart re-loads
/// every stored fact exactly as before. And `INVALID_ARGUMENT` means the fact is
/// not stored, which leaves the PREVIOUS fact in force — the tenant keeps the
/// plan they have until it expires, and the operator has until then to correct
/// the order. Nothing in gunnar re-pushes a stored fact through `Set`, so no
/// internal loop can be caught by this either.
pub fn caps_gunnar_cannot_honour<'caps>(
    caps: impl IntoIterator<Item = &'caps String>,
) -> Vec<&'caps str> {
    let mut unknown: Vec<&str> = caps
        .into_iter()
        .map(String::as_str)
        .filter(|key| enforcement_of(key).is_none() && !PROVISIONED_ELSEWHERE.contains(key))
        .collect();
    unknown.sort_unstable();
    unknown.dedup();
    unknown
}

/// Every cap key this appliance accepts, for the refusal's own message: an
/// operator told "`concurrent_transfers` is not a cap this appliance can
/// honour" and nothing else has to go and read a proto file to find out what is.
pub fn honourable_caps() -> Vec<&'static str> {
    let mut names: Vec<&'static str> = METERS
        .iter()
        .map(|(meter, _)| *meter)
        .chain(PROVISIONED_ELSEWHERE.iter().copied())
        .collect();
    names.sort_unstable();
    names
}