crates/gunnar-grpc/tests/entitlement_caps_door.rs
//! **A fact may not carry a cap this appliance has never heard of.**
//!
//! The other half of the `caps["repos"]` defect. `tests/allowance_is_labelled.rs`
//! fixes what the tenant is SHOWN; this one fixes what the box AGREES TO. Until
//! it existed, `Entitlement.Set` looked at five things — namespace, plan, state,
//! source, a 64-byte signature — and never once at `caps`. A signed fact could
//! name any key at all and gunnar stored it, logged an attestation line for it,
//! and then did nothing with it for the life of the plan.
//!
//! A cap on a MEASURED meter is fine and is accepted: `repos`, `cold_bytes`,
//! `tombstoned_bytes` and `objects` are real numbers `Usage.Read` reports and
//! monetize bills from, and since `Allowance.enforcement` they go out labelled
//! as readings. Selling a labelled measurement is honest. So is a cap on the
//! iron the provider sells (`cpu_millicores`, `ram_bytes`) — that is
//! `Enforcement::Provisioned` and never was gunnar's to police.
//!
//! What is refused is a key that reaches **no surface on this box at all**:
//! not enforced, not reported by `Usage.Read`, not in `Admin.StorageInfo`, and
//! traceable only in the attestation line that recorded the sale. That is a
//! promise nobody can even observe being broken. `INVALID_ARGUMENT` naming the
//! key moves the discovery from "three months later, from a tenant who noticed"
//! to the moment of the bad sale.
//!
//! # Red first — MEASURED 2026-09-07
//!
//! ```text
//! ---- a_cap_this_box_has_never_heard_of_is_refused_by_name stdout ----
//! thread 'a_cap_this_box_has_never_heard_of_is_refused_by_name' panicked at
//! crates/gunnar-grpc/tests/entitlement_caps_door.rs:78:5:
//! a fact selling `concurrent_transfers` was accepted; nothing on this box
//! reads that key
//! ```
//!
//! # The two real keys this catches, which is why it is not a typo guard
//!
//! * **`concurrent_transfers`** — `products/gunnar`'s `METERS` declares it
//! `Enforcement::Enforced`. Nothing in gunnar reads it. The only
//! `max_concurrent_transfers` on the box is an OPERATOR flag
//! (`SshConfig::max_concurrent_transfers`), one number for the whole
//! appliance, never per namespace and never off a fact. monetize claims a wall
//! gunnar does not have, and this is the test that says so.
//! * **`open_store_ram_bytes`** — monetize's name for the number gunnar reports
//! as `objects` (`products/gunnar` converts with `/ RAM_BYTES_PER_OBJECT`).
//! One quantity, two names, one seam. `allowance_of` looks up `caps["objects"]`
//! — a key monetize never writes — so the `objects` allowance cannot be
//! populated by any fact monetize can issue.
//!
//! # Why this cannot take a paying tenant down
//!
//! Measured before it landed, and it is the reason this is a refusal rather than
//! a log line:
//!
//! * The boot path does not come through here. `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 it did before.
//! * `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` — no renewal loop,
//! no reconcile job, no standby replay — so no internal loop can trip on it.
#![cfg(feature = "metered")]
use std::collections::BTreeMap;
use gunnar_grpc::service::entitlement::well_formed;
use gunnar_proto::{EntitlementFact, EntitlementState};
/// A fact that is well formed in every OTHER respect, so a refusal here can only
/// be about `caps`.
fn fact(caps: &[(&str, u64)]) -> EntitlementFact {
EntitlementFact {
namespace: "team".to_owned(),
plan: "10gb".to_owned(),
state: EntitlementState::Paid as i32,
paid_until_unix_ms: Some(1),
caps: caps
.iter()
.map(|(name, cap)| ((*name).to_owned(), *cap))
.collect::<BTreeMap<_, _>>(),
source: "operator".to_owned(),
// Never checked here — `well_formed` only counts the bytes, and the real
// Ed25519 verification is the control plane's.
signature: vec![0u8; 64],
issued_unix_ms: Some(1_789_000_000_000),
issued_signature: vec![0u8; 64],
}
}
#[test]
fn a_cap_this_box_has_never_heard_of_is_refused_by_name() {
let refusal = well_formed(&fact(&[
("pack_bytes", 10 << 30),
("concurrent_transfers", 8),
]))
.expect_err(
"a fact selling `concurrent_transfers` was accepted; nothing on this box reads \
that key",
);
assert_eq!(refusal.code(), tonic::Code::InvalidArgument);
assert!(
refusal.message().contains("concurrent_transfers"),
"the refusal must name the cap the operator has to remove: {}",
refusal.message()
);
assert!(
refusal.message().contains("pack_bytes"),
"…and say what this appliance CAN honour, so the fix does not need a \
proto file: {}",
refusal.message()
);
}
/// monetize's name for the number gunnar calls `objects`. Two names for one
/// quantity is not a typo and the refusal is what makes the seam say so.
#[test]
fn monetizes_name_for_a_meter_gunnar_calls_something_else_is_refused() {
let refusal = well_formed(&fact(&[("open_store_ram_bytes", 1 << 30)]))
.expect_err("`open_store_ram_bytes` reaches no surface on this box");
assert!(refusal.message().contains("open_store_ram_bytes"));
}
/// **A measured cap is a legitimate sale.** The auditor's own example: five
/// repositories, nothing refuses the sixth, and that is now SAID
/// (`Allowance.enforcement`) rather than refused. Refusing it would refuse an
/// honest order for a number monetize prices and `Usage.Read` reports.
#[test]
fn a_cap_on_a_measured_meter_is_accepted() {
well_formed(&fact(&[("repos", 5), ("tombstoned_bytes", 1 << 30)]))
.expect("a labelled measurement is an honest thing to sell");
}
/// CPU and RAM are bought at the provider with real money and no git server
/// polices them per namespace. `Enforcement::Provisioned` is the honest name and
/// a refusal here would break a real sale.
#[test]
fn iron_the_provider_sells_is_not_gunnars_to_refuse() {
well_formed(&fact(&[("cpu_millicores", 8000), ("ram_bytes", 32 << 30)]))
.expect("provisioned at the cloud provider; not policed by the product");
}
/// Every key gunnar enforces or reports, in one fact, accepted. The list is
/// `service::meters::METERS` and this is what pins that the door and the
/// reporter read ONE table: a meter added to `Allowance` and forgotten here
/// would be refused at the sale that sells it.
#[test]
fn the_seven_reported_meters_are_all_honourable() {
well_formed(&fact(&[
("pack_bytes", 1),
("cache_bytes", 1),
("cold_bytes", 1),
("lfs_bytes", 1),
("tombstoned_bytes", 1),
("repos", 1),
("objects", 1),
]))
.expect("these are exactly the meters `Usage.Read` reports");
}
/// A fact with no caps at all is a plan that sells the defaults, and every other
/// test in `gunnar-server/tests/entitlement.rs` pushes one. This is the
/// regression guard on the cheapest way to get this wrong.
#[test]
fn a_fact_with_no_caps_is_still_well_formed() {
well_formed(&fact(&[])).expect("no caps is a plan on the server's defaults");
}
/// **The stamp is both halves or neither**, and the refusal names the field.
/// An issue time with nothing signing it is one anybody could have written; the
/// control plane refuses it as forged a moment later, but a caller is better
/// told WHICH field it got wrong.
#[test]
fn refuse_twin_half_a_stamp_is_refused_by_field_name_and_a_whole_one_and_none_are_not() {
let whole = fact(&[("pack_bytes", 1 << 30)]);
well_formed(&whole).expect("a stamped fact");
// The OLD form: neither half. Every fact signed before 2026-09-17 is this
// shape, and it must go on being well formed.
let mut unstamped = whole.clone();
unstamped.issued_unix_ms = None;
unstamped.issued_signature = Vec::new();
well_formed(&unstamped).expect("a fact signed before the stamp existed is still well formed");
let mut no_signature = whole.clone();
no_signature.issued_signature = Vec::new();
let refusal = well_formed(&no_signature).expect_err("an unsigned issue time");
assert!(
refusal.message().contains("issued_signature is 0 byte(s)"),
"{}",
refusal.message()
);
let mut no_time = whole;
no_time.issued_unix_ms = None;
let refusal = well_formed(&no_time).expect_err("a signature over nothing");
assert!(
refusal.message().contains("issued_unix_ms is absent"),
"{}",
refusal.message()
);
}