gunnar-grpc 1.5.0

The tonic service implementations and the event bus behind gunnar's gRPC control plane.
Documentation
//! `Usage`. Per-namespace meters, read-only.
//!
//! **Open source, always on.** Counting what a tenant holds is not the metered
//! appliance's capability — it is every appliance's, because a self-hoster
//! reading their own disk wants the same numbers a service provider bills
//! from. What is behind the `metered` feature is *acting* on them
//! (`Entitlement`); this service only reports.
//!
//! The grant split is `Admin.StorageInfo`'s: one namespace's meters are that
//! namespace's business (`Read` on the namespace, so an owner can see their own
//! bill), a server-wide read spans tenants and takes a server-wide `Read`.
//!
//! `Read` also carries the namespace's **allowance** — per meter, the cap this
//! box holds for it, absent when there is none (see `usage.proto`). On the
//! metered appliance that is read off the entitlement seam for the one
//! namespace the caller may already read; on the community appliance every
//! cap is absent, because there is nothing to read it from.

use gunnar_proto::usage_server::Usage;
use gunnar_proto::{
    ActivityRequest, ActivityResponse, AdmitPlanRequest, AdmitPlanResponse, Allowance,
    GrantLevel, NamespaceUsage, ReadAllUsageRequest, ReadAllUsageResponse, ReadUsageRequest,
    ReadUsageResponse, Remaining,
};
use tonic::{Request, Response, Status};

use crate::auth::Access;
use crate::service::UsageService;

/// **A signed fact's `caps`, as the allowance `Usage.Read` reports.**
///
/// One rule, the push door's (`gunnar_server::entitlement`, "0 is
/// unlimited"): a meter the fact does not name and a meter it names as `0`
/// are both ABSENT — never `Some(0)`, which a reader would take for "may hold
/// nothing". Keyed by the meter names `NamespaceUsage` reports, which are the
/// keys monetize writes.
///
/// **Every cap goes out labelled** with what this box does at it
/// (`crate::service::meters`), because four of these seven are a reading and
/// three are a door, and reporting a reading in a message called `Allowance`
/// with no other word for it is how `caps["repos"]=5` came to be shown to a
/// tenant as a limit that nothing on the box refuses. See
/// [`crate::service::meters`] for the table and for the two candidate fixes
/// that were not taken.
#[cfg(feature = "metered")]
pub fn allowance_of(caps: &std::collections::BTreeMap<String, u64>) -> Allowance {
    let cap = |meter: &str| caps.get(meter).copied().filter(|cap| *cap > 0);
    Allowance {
        enforcement: crate::service::meters::enforcement(),
        pack_bytes: cap("pack_bytes"),
        cache_bytes: cap("cache_bytes"),
        cold_bytes: cap("cold_bytes"),
        lfs_bytes: cap("lfs_bytes"),
        tombstoned_bytes: cap("tombstoned_bytes"),
        repos: cap("repos"),
        objects: cap("objects"),
    }
}

/// **No cap on anything, and the same seven words about what a cap WOULD do.**
///
/// The answer for a Free namespace and for every namespace on the community
/// appliance. `Allowance::default()` would leave `enforcement` empty, and an
/// empty map is not "nothing is enforced" — it is "this build did not say",
/// which is the one thing a reader must never have to guess. The community
/// appliance holds no caps for anyone, and its doors are the same three doors:
/// both halves of that are true at once and the message says both.
fn uncapped() -> Allowance {
    Allowance {
        enforcement: crate::service::meters::enforcement(),
        ..Allowance::default()
    }
}

/// **What is left under each cap**: cap − held, clamped at 0, ABSENT exactly
/// where the cap is absent (2026-10-04, the thin layer: gunnar's answer, so a
/// console stops computing its own four).
pub fn remaining_of(usage: &NamespaceUsage, allowance: &Allowance) -> Remaining {
    let left = |cap: Option<u64>, held: u64| cap.map(|cap| cap.saturating_sub(held));
    Remaining {
        pack_bytes: left(allowance.pack_bytes, usage.pack_bytes),
        cache_bytes: left(allowance.cache_bytes, usage.cache_bytes),
        cold_bytes: left(allowance.cold_bytes, usage.cold_bytes),
        lfs_bytes: left(allowance.lfs_bytes, usage.lfs_bytes),
        tombstoned_bytes: left(allowance.tombstoned_bytes, usage.tombstoned_bytes),
        repos: left(allowance.repos, usage.repos),
        objects: left(allowance.objects, usage.objects),
    }
}

impl UsageService {
    /// The namespace's allowance: every cap absent, unless this is the
    /// metered appliance AND it holds a fact for the namespace naming the
    /// meter. The community appliance has no entitlement store to ask, so
    /// the answer is the same [`uncapped`] for every namespace — not a stub,
    /// the truth: nothing on that box caps anyone.
    ///
    /// The caller already holds `Read` on the namespace; nothing here is a
    /// second grant check, and nothing here reaches `Entitlement.Get`'s
    /// server-wide gate — the seam is asked directly, for this one namespace.
    fn allowance(&self, namespace: &str) -> Result<Allowance, Status> {
        #[cfg(feature = "metered")]
        {
            use crate::seam::ControlError;
            // **Clans** (2026-10-05): a clan's room is the sum of what its
            // members give — the number the door holds it to, 0 included (a
            // clan nobody gave room to holds nothing; no fact is signed for a
            // clan) — and a member's own room is its cap less what it gives.
            //
            // Round 2: the room IN FORCE (a lapsed member's share dropped),
            // and ONE room for packs and LFS objects together — so each meter
            // is allowed the room less what the other holds, and what is left
            // under either is what is left of the one room.
            let clans = self.ctx.control.clans();
            if let Some(clan) = clans.map(|p| p.clan_get(namespace)).transpose()?.flatten() {
                let room = clan.room_in_force_bytes;
                let (pack, lfs) = self
                    .ctx
                    .control
                    .usage(Some(namespace))
                    .ok()
                    .and_then(|rows| rows.into_iter().find(|row| row.namespace == namespace))
                    .map_or((0, 0), |row| (row.pack_bytes, row.lfs_bytes));
                return Ok(Allowance {
                    pack_bytes: Some(room.saturating_sub(lfs)),
                    lfs_bytes: Some(room.saturating_sub(pack)),
                    ..uncapped()
                });
            }
            match self.ctx.control.entitlement_get(namespace) {
                Ok(Some(fact)) => {
                    let mut allowance = allowance_of(&fact.caps);
                    if let (Some(cap), Some(plane)) = (allowance.pack_bytes, clans) {
                        allowance.pack_bytes = Some(cap.saturating_sub(plane.clan_given(namespace)?));
                    }
                    return Ok(allowance);
                }
                // No fact = Free = uncapped.
                Ok(None) => {}
                // A metered server started without `--monetize-pubkey` holds
                // no facts at all (`CoreControlPlane::entitlement` says so by
                // name); every namespace is Free there, and a usage read must
                // not fail on it.
                Err(ControlError::FailedPrecondition(_)) => {}
                Err(err) => return Err(err.into()),
            }
        }
        #[cfg(not(feature = "metered"))]
        let _ = namespace;
        Ok(uncapped())
    }
}

#[tonic::async_trait]
impl Usage for UsageService {
    async fn read(
        &self,
        request: Request<ReadUsageRequest>,
    ) -> Result<Response<ReadUsageResponse>, Status> {
        let caller = self.ctx.caller(&request)?;
        let namespace = request.into_inner().namespace;
        if namespace.is_empty() {
            return Err(Status::invalid_argument("namespace is required"));
        }
        self.ctx.require(
            &caller,
            Access::namespace(&namespace),
            GrantLevel::Read,
            "Usage.Read",
        )?;
        let mut rows = self.ctx.control.usage(Some(&namespace))?;
        let usage = rows.pop().ok_or_else(|| {
            Status::not_found(format!("namespace {namespace} is not in the catalog"))
        })?;
        let allowance = self.allowance(&namespace)?;
        let remaining = remaining_of(&usage, &allowance);
        // Where the room went, on the same `Read`. A plane that measures the
        // meters but not the rows answers the meters alone rather than nothing.
        let repos = match self.ctx.control.repo_usage(&namespace) {
            Ok(rows) => rows,
            Err(crate::seam::ControlError::Unimplemented(_)) => Vec::new(),
            Err(err) => return Err(err.into()),
        };
        Ok(Response::new(ReadUsageResponse {
            usage: Some(usage),
            allowance: Some(allowance),
            remaining: Some(remaining),
            repos,
        }))
    }

    async fn read_all(
        &self,
        request: Request<ReadAllUsageRequest>,
    ) -> Result<Response<ReadAllUsageResponse>, Status> {
        let caller = self
            .ctx
            .authorize(&request, Access::Server, GrantLevel::Read, "Usage.ReadAll")?;
        let usages = self.ctx.control.usage(None)?;
        // **An account's size, never its insides** (2026-10-05): the row of a
        // customer's account itself is the operator's aggregate (what it
        // holds, what it was sold); a namespace NESTED in it names the
        // account's structure and is not shown to a server grant that does
        // not reach inside.
        let usages = self.ctx.retain_unshielded(&caller, usages, |row| {
            row.namespace.contains('/').then(|| row.namespace.clone())
        })?;
        Ok(Response::new(ReadAllUsageResponse { usages }))
    }

    /// **The flow series for one namespace.**
    ///
    /// The grant is `Read` on the NAMESPACE, deliberately the same one
    /// [`Usage::read`] takes and not the server-wide grant `ReadAll` takes:
    /// what a tenant pushed and fetched is that tenant's own business, and a
    /// tenant who can read their meters can read how they got them. Making it
    /// server-wide would have put a tenant's own traffic behind an operator
    /// credential, which is the shape of gate that ends with a console reading
    /// everyone's numbers to show one person theirs.
    ///
    /// Nothing here re-derives the window: the control plane resolves `0` to
    /// "the whole kept window" / "now" and clamps to whole UTC days, because it
    /// is the half that knows what this server actually keeps. A second
    /// arithmetic here would be a second answer to the same question.
    async fn activity(
        &self,
        request: Request<ActivityRequest>,
    ) -> Result<Response<ActivityResponse>, Status> {
        let caller = self.ctx.caller(&request)?;
        let request = request.into_inner();
        if request.namespace.is_empty() {
            return Err(Status::invalid_argument("namespace is required"));
        }
        self.ctx.require(
            &caller,
            Access::namespace(&request.namespace),
            GrantLevel::Read,
            "Usage.Activity",
        )?;
        let (days, retained_days) = self.ctx.control.activity(
            &request.namespace,
            request.since_unix_ms,
            request.until_unix_ms,
        )?;
        Ok(Response::new(ActivityResponse {
            days,
            retained_days,
        }))
    }

    /// `Usage.AdmitPlan` — the SAME grant as `Activity`: `Read` on the
    /// namespace. A tenant may read their own budget and what it buys.
    async fn admit_plan(
        &self,
        request: Request<AdmitPlanRequest>,
    ) -> Result<Response<AdmitPlanResponse>, Status> {
        let caller = self.ctx.caller(&request)?;
        let request = request.into_inner();
        if request.namespace.is_empty() {
            return Err(Status::invalid_argument("namespace is required"));
        }
        self.ctx.require(
            &caller,
            Access::namespace(&request.namespace),
            GrantLevel::Read,
            "Usage.AdmitPlan",
        )?;
        Ok(Response::new(self.ctx.control.admit_plan(&request.namespace)?))
    }
}

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

    fn held() -> NamespaceUsage {
        NamespaceUsage {
            namespace: "acme".into(),
            pack_bytes: 700,
            cache_bytes: 50,
            lfs_bytes: 0,
            repos: 3,
            ..Default::default()
        }
    }

    /// ★ cap − held under the cap; 0 — never a wrapped huge number — over it;
    /// absent where the cap is absent, and a 0 held under a cap is the whole cap.
    #[test]
    fn what_is_left_is_cap_minus_held_clamped_at_zero_and_absent_where_uncapped() {
        let allowance = Allowance {
            pack_bytes: Some(1_000),
            cache_bytes: Some(40),
            lfs_bytes: Some(9),
            repos: None,
            ..Allowance::default()
        };
        let left = remaining_of(&held(), &allowance);
        assert_eq!(left.pack_bytes, Some(300), "under the cap");
        assert_eq!(left.cache_bytes, Some(0), "over the cap is 0, not a wrap");
        assert_eq!(left.lfs_bytes, Some(9), "nothing held: the whole cap");
        assert_eq!(left.repos, None, "uncapped is absent, never a number");
        assert_eq!(left.cold_bytes, None);
        assert_eq!(left.tombstoned_bytes, None);
        assert_eq!(left.objects, None);
    }
}