crates/monetize-server/tests/tenant_ticket.rs
//! **A tenant renewing their own subscription, over the wire.**
//!
//! `monetize_embed::ticket` proves the ticket itself — what verifies, what is
//! refused, and why a refusal does not name a tenant back. This file proves the
//! thing no unit test on either side can: that `Purchase.Quote` and
//! `Purchase.Start` actually CONSULT the product about a ticket, on the renew
//! path and only there, **before** anything is priced, reserved, written or
//! dialled.
//!
//! The four shapes, and each one is a hole somebody would otherwise dig:
//!
//! | ticket | `renew` | what must happen |
//! |---|---|---|
//! | none | false | an order, exactly as before this path existed |
//! | none | true | the OPERATOR renewal, exactly as before |
//! | renew | true | the product decides, and a no is `permission_denied` |
//! | order | false | the product decides, **and the CAPS must match** |
//! | renew | false | `permission_denied` — a ticket authorises ONE verb |
//! | order | true | `permission_denied` — the other direction of the same rule |
//!
//! **The row that matters most is the one that is not in the table**: a genuine
//! ORDER ticket for the caps the customer agreed to, spent on a BIGGER order.
//! Nothing about that ticket is wrong — the appliance signed it, for the right
//! tenant, product and verb — and it must still be refused, or the appliance is
//! vouching for "this human may buy" and whatever holds the ticket picks the
//! amount. See [`an_order_ticket_cannot_place_a_bigger_order`].
//!
//! Driven over gRPC against [`MockProduct`], which verifies for real (it holds
//! an appliance key and `monetize_embed::verify_ticket`). The PLUGIN's own
//! verifier has its own tests in `products/gunnar/tests/actor_ticket.rs`; what
//! is under test here is the server's use of the seam.
use std::sync::Arc;
use ed25519_dalek::{Signer as _, SigningKey};
use monetize::embed::signing::base64_encode;
use monetize::embed::ticket::{ticket_message, ActorTicket};
use monetize::embed::{PURPOSE_ORDER, PURPOSE_RENEW};
use monetize::testing::{purchase, Answer, MockCloud, MockPay, MockProduct, NOW};
use monetize::transaction::Outcome;
use monetize_proto::products_client::ProductsClient;
use monetize_proto::purchase_client::PurchaseClient;
use monetize_proto::{self as pb, PurchaseState};
use monetize_server::registry::Registry;
use monetize_server::state::AppState;
use monetize_server::testing::TestServer;
use tonic::metadata::MetadataValue;
use tonic::{Code, Request};
use tonic::transport::Channel;
const BEARER: &str = "test-bearer-token-of-sufficient-length";
/// The appliance's key. The product trusts it; the tests mint with it.
fn appliance() -> SigningKey {
SigningKey::from_bytes(&[11u8; 32])
}
/// Mint the way a real appliance does: build unsigned, sign the canonical
/// message, fill in the signature, serialise.
fn mint(key: &SigningKey, ticket: ActorTicket) -> Vec<u8> {
let mut ticket = ticket;
ticket.signature = base64_encode(&key.sign(&ticket_message(&ticket)).to_bytes());
serde_json::to_vec(&ticket).expect("a ticket serialises")
}
/// A genuine, live `renew` ticket for `tenant`, from the appliance the product
/// trusts. `nonce` is the test's, so a test about replay can repeat one.
fn renew_ticket(tenant: &str, nonce: &str) -> Vec<u8> {
mint(&appliance(), ActorTicket::unsigned("gunnar", "gunnar", tenant, PURPOSE_RENEW, nonce, NOW as i64, 60_000))
}
/// The caps an order NAMES: the full target, per meter, which is what the wire
/// carries and what the ticket must cover. `alice` holds 10 GiB from the rig's
/// first sale, so an order for 100 sells the 90 she lacks.
fn caps(gib: u64) -> std::collections::BTreeMap<String, u64> {
[("pack_bytes".to_string(), gib << 30)].into()
}
/// A genuine, live `order` ticket for `tenant`, **covering exactly `for_caps`**
/// — the target the appliance was asked to vouch for.
fn order_ticket(tenant: &str, for_caps: std::collections::BTreeMap<String, u64>, nonce: &str) -> Vec<u8> {
mint(
&appliance(),
ActorTicket::unsigned("gunnar", "gunnar", tenant, PURPOSE_ORDER, nonce, NOW as i64, 60_000).covering(for_caps),
)
}
fn cloud_provisioned() -> MockCloud {
let cloud = MockCloud::new();
cloud.answer_all(Answer::Provisioned);
cloud
}
struct Rig {
state: Arc<AppState>,
product: Arc<MockProduct>,
server: TestServer,
channel: Channel,
/// What alice's first period runs out at, so a test can say the ledger did
/// not move.
paid_until: u64,
}
impl Rig {
/// alice bought a period at [`NOW`] and the product trusts the appliance —
/// a shop customer with a console that can fetch tickets.
async fn up() -> Rig {
Rig::up_with_key(Some(appliance().verifying_key())).await
}
/// The same, with the product's appliance key set or not. `None` is the
/// deployment nobody configured: it must refuse every ticket, and it must
/// not tell a console the route exists.
async fn up_with_key(key: Option<monetize::embed::VerifyingKey>) -> Rig {
let product = Arc::new(MockProduct::gunnar());
// A32-13: the product's main mints tickets, the HOST checks them under the key its
// registry holds, on the rig's fake clock (one clock: a ticket minted at NOW is live).
// The host reads a product's facts ONCE, when it is registered (A32-13 step 3), so the
// product says it mints tickets BEFORE the registry takes it; said after, the host
// would never hear it and would refuse every ticket as one this product cannot verify.
if key.is_some() {
product.mints_tickets();
}
let mut reg = Registry::new().shared_product(product.clone()).sells("gunnar", MockPay::new()).cloud(cloud_provisioned());
if let Some(k) = key {
reg = reg.ticket_key("gunnar", &k.to_bytes().iter().map(|b| format!("{b:02x}")).collect::<String>());
}
let mut state = AppState::in_memory(reg);
*state.terms.write().unwrap() = monetize_server::state::TermsStore::fixture();
state.clock = Box::new(|| NOW);
let state = Arc::new(state);
// The first period, sold directly: this file is about the RENEWAL, and
// an order over the wire to set it up would only add a second thing
// that could fail.
let pay = MockPay::new();
let out = state.transaction(state.registry.products().next().unwrap(), monetize_pay::Vendor::of(&pay)).run(&purchase("alice")).expect("the first period must sell");
assert!(matches!(out, Outcome::Paid { .. }), "{out:?}");
let paid_until = state.ledger.entitlement("gunnar", "alice").unwrap().unwrap().paid_until_unix_ms.expect("a paid period");
let server = TestServer::spawn(state.clone(), BEARER).await;
let channel = Channel::from_shared(server.endpoint()).unwrap().connect().await.expect("connect");
Rig { state, product, server, channel, paid_until }
}
fn authed<T>(&self, req: T) -> Request<T> {
let mut r = Request::new(req);
r.metadata_mut().insert("authorization", MetadataValue::try_from(format!("Bearer {BEARER}")).unwrap());
r
}
/// `Purchase.Start` for a renewal of `tenant`, carrying `ticket` (empty =
/// the operator path).
async fn start_renewal(&self, tenant: &str, ticket: &[u8]) -> Result<pb::StartPurchaseResponse, tonic::Status> {
PurchaseClient::new(self.channel.clone())
.start(self.authed(pb::StartPurchaseRequest {
product: "gunnar".into(),
tenant: tenant.into(),
caps: Default::default(),
vendor: "mock".into(),
payer: String::new(),
starts_unix_ms: 0,
market: 0,
renew: true,
actor_ticket: ticket.to_vec(),
bundle: String::new(),
users: None,
}))
.await
.map(|r| r.into_inner())
}
/// An ORDER — new caps, new money — for the caps it NAMES, carrying
/// `ticket` (empty = the operator path).
async fn start_order(&self, tenant: &str, order_caps: std::collections::BTreeMap<String, u64>, ticket: &[u8]) -> Result<pb::StartPurchaseResponse, tonic::Status> {
PurchaseClient::new(self.channel.clone())
.start(self.authed(pb::StartPurchaseRequest {
product: "gunnar".into(),
tenant: tenant.into(),
caps: order_caps,
vendor: "mock".into(),
payer: String::new(),
starts_unix_ms: NOW,
market: 0,
renew: false,
actor_ticket: ticket.to_vec(),
bundle: String::new(),
users: None,
}))
.await
.map(|r| r.into_inner())
}
async fn quote_renewal(&self, tenant: &str, ticket: &[u8]) -> Result<pb::QuoteResponse, tonic::Status> {
PurchaseClient::new(self.channel.clone())
.quote(self.authed(pb::QuoteRequest {
product: "gunnar".into(),
tenant: tenant.into(),
caps: Default::default(),
market: 0,
starts_unix_ms: 0,
renew: true,
actor_ticket: ticket.to_vec(),
bundle: String::new(),
}))
.await
.map(|r| r.into_inner())
}
async fn quote_order(&self, tenant: &str, order_caps: std::collections::BTreeMap<String, u64>, ticket: &[u8]) -> Result<pb::QuoteResponse, tonic::Status> {
PurchaseClient::new(self.channel.clone())
.quote(self.authed(pb::QuoteRequest {
product: "gunnar".into(),
tenant: tenant.into(),
caps: order_caps,
market: 0,
starts_unix_ms: NOW,
renew: false,
actor_ticket: ticket.to_vec(),
bundle: String::new(),
}))
.await
.map(|r| r.into_inner())
}
fn steps(&self) -> u64 {
self.state.ledger.step_count().expect("the ledger counts its rows")
}
fn paid_until_now(&self) -> Option<u64> {
self.state.ledger.entitlement("gunnar", "alice").unwrap().unwrap().paid_until_unix_ms
}
/// **What the tenant is ALLOWED right now**, off the stored fact. This is the
/// number a blank cheque would move, so every refusal test reads it before
/// and after.
fn caps_now(&self, tenant: &str) -> std::collections::BTreeMap<String, u64> {
self.state.ledger.entitlement("gunnar", tenant).unwrap().map(|f| f.caps).unwrap_or_default()
}
}
/// ★ **A TENANT RENEWS THEMSELVES.** The verb this whole path exists for: a
/// genuine ticket, for the tenant the order names, and the renewal goes through
/// exactly as an operator's would.
#[tokio::test]
async fn a_genuine_ticket_renews_its_own_tenant() {
let rig = Rig::up().await;
let out = rig.start_renewal("alice", &renew_ticket("alice", "n-1")).await.expect("alice's own ticket must renew alice");
assert_eq!(out.state, PurchaseState::Paid as i32, "{}", out.reason);
assert!(rig.paid_until_now().unwrap() > rig.paid_until, "the period moved");
assert_eq!(rig.state.registry.tickets_checked("gunnar"), 1, "the ticket was checked exactly once");
rig.server.stop().await;
}
/// ★ **THE ATTACK, END TO END.** A perfectly genuine ticket — the real
/// appliance signed it, for the bearer's own tenant — spent on somebody else's
/// renewal. Nothing about the signature is wrong. What refuses it is that the
/// server hands the product BOTH the ticket and the tenant from the order, so
/// the comparison cannot be forgotten by any call site.
#[tokio::test]
async fn a_genuine_ticket_for_one_tenant_cannot_renew_another() {
let rig = Rig::up().await;
// beta has a period of their own, so the refusal is about WHO is asking and
// not about a tenant with nothing to renew.
let out = rig.state.transaction(rig.state.registry.products().next().unwrap(), monetize_pay::Vendor::of(&MockPay::new())).run(&purchase("beta")).expect("beta's first period");
assert!(matches!(out, Outcome::Paid { .. }));
let before = rig.steps();
let refused = rig.start_renewal("beta", &renew_ticket("alice", "n-2")).await.expect_err("alice's ticket must not renew beta");
assert_eq!(refused.code(), Code::PermissionDenied, "{refused:?}");
assert!(!refused.message().contains("alice"), "the refusal names the ticket's own tenant to whoever asked about another: {}", refused.message());
assert_eq!(rig.steps(), before, "a refused ticket writes nothing");
rig.server.stop().await;
}
/// ★ **NOTHING IS PRICED, RESERVED OR WRITTEN BEHIND A REFUSED TICKET.**
///
/// The refusal has to come before the transaction, not after it: a ticket
/// checked at the end would mean a stranger's request had already reserved
/// money at a vendor and appended rows that then had to be unwound.
#[tokio::test]
async fn a_refused_ticket_costs_nothing_and_leaves_the_ledger_alone() {
let rig = Rig::up().await;
let before = rig.steps();
let paid_until = rig.paid_until_now();
// Counted from HERE: the rig's own first sale pushed a fact and asked about
// capacity, and a test that compared against zero would be asserting that
// the setup never happened.
let pushes = rig.product.push_attempts().len();
let absorbs = rig.product.absorb_asked().len();
// Signed by an appliance this product holds no key for: genuine-looking
// bytes, a real signature, the wrong signer.
let forged = mint(&SigningKey::from_bytes(&[99u8; 32]), ActorTicket::unsigned("gunnar", "gunnar", "alice", PURPOSE_RENEW, "n-3", NOW as i64, 60_000));
let refused = rig.start_renewal("alice", &forged).await.expect_err("a ticket from another key must not renew");
assert_eq!(refused.code(), Code::PermissionDenied, "{refused:?}");
assert_eq!(rig.steps(), before, "not one ledger row");
assert_eq!(rig.paid_until_now(), paid_until, "the period did not move");
assert_eq!(rig.product.push_attempts().len(), pushes, "no fact was pushed to the product");
assert_eq!(rig.product.absorb_asked().len(), absorbs, "the product was never asked whether it could serve anything");
rig.server.stop().await;
}
/// ★ **THE OPERATOR PATH IS BYTE FOR BYTE WHAT IT WAS.** The regression guard
/// on everything this change did not mean to touch: no ticket, no question
/// asked of the product, and the renewal sells.
#[tokio::test]
async fn a_renewal_with_no_ticket_is_the_operator_path_unchanged() {
let rig = Rig::up().await;
let out = rig.start_renewal("alice", b"").await.expect("the operator renewal must still sell");
assert_eq!(out.state, PurchaseState::Paid as i32, "{}", out.reason);
assert!(rig.paid_until_now().unwrap() > rig.paid_until, "the period moved");
assert_eq!(rig.state.registry.tickets_checked("gunnar"), 0, "an absent ticket is not a question: nothing must be checked at all");
rig.server.stop().await;
}
/// ★ **A TENANT BUYS THEIR OWN ROOM.** The order half of the verb this path
/// exists for: a genuine ORDER ticket, for the tenant the order names and for
/// **exactly the caps it names**, and the order goes through as an operator's
/// would.
#[tokio::test]
async fn a_genuine_order_ticket_places_exactly_that_order() {
let rig = Rig::up().await;
assert_eq!(rig.caps_now("alice"), caps(10), "the rig's first sale is the 10 GiB this order raises");
let out = rig
.start_order("alice", caps(100), &order_ticket("alice", caps(100), "o-1"))
.await
.expect("alice's own order ticket, for the caps she asked for, must sell");
assert_eq!(out.state, PurchaseState::Paid as i32, "{}", out.reason);
assert_eq!(rig.caps_now("alice"), caps(100), "the order she paid for is the order she got");
assert_eq!(rig.state.registry.tickets_checked("gunnar"), 1, "the ticket was checked exactly once");
rig.server.stop().await;
}
/// ★ **THE BLANK CHEQUE, END TO END — the most important refusal on this path.**
///
/// The ticket is GENUINE: the real appliance signed it, for alice's own tenant,
/// for the right product, for the right verb, and it is live. Only the AMOUNT
/// differs — the request asks for ten times what the appliance vouched for.
///
/// Without the caps in the signed message and compared against the ORDER, the
/// appliance would be vouching for "this human may buy" and whatever holds the
/// ticket would choose how much. So: refused, nothing written, the caps not
/// moved, and the product never asked whether it could serve the bigger order.
///
/// **That last assertion is also how this test proves no vendor was dialled.**
/// The vendor is looked up in `Purchase.Start` only AFTER the quote, and the
/// quote is what asks the product `can_absorb`; a product that was never asked
/// is a quote that never ran, and a quote that never ran is a vendor that was
/// never found, let alone called.
#[tokio::test]
async fn an_order_ticket_cannot_place_a_bigger_order() {
let rig = Rig::up().await;
let before = rig.steps();
let held = rig.caps_now("alice");
let absorbs = rig.product.absorb_asked().len();
let pushes = rig.product.push_attempts().len();
// Signed for 100 GiB. Spent on 1000.
let ticket = order_ticket("alice", caps(100), "o-2");
let refused = rig.start_order("alice", caps(1000), &ticket).await.expect_err("a ticket for 100 must not buy 1000");
assert_eq!(refused.code(), Code::PermissionDenied, "{refused:?}");
assert_eq!(rig.steps(), before, "a refused ticket writes nothing");
assert_eq!(rig.caps_now("alice"), held, "the caps did not move");
assert_eq!(rig.product.absorb_asked().len(), absorbs, "the product was never asked whether it could serve the bigger order");
assert_eq!(rig.product.push_attempts().len(), pushes, "no fact was pushed");
// …and the quote is gated identically, so a console cannot even price the
// bigger order it could not buy.
let refused = rig
.start_order("alice", caps(1000), &order_ticket("alice", caps(100), "o-3"))
.await
.expect_err("nor does a fresh ticket for 100 buy 1000");
assert_eq!(refused.code(), Code::PermissionDenied, "{refused:?}");
rig.server.stop().await;
}
/// ★ **ONE TICKET, ONE VERB — both directions, through the service.**
///
/// A renewal ticket may not place an order, and an order ticket may not renew.
/// They fail for different reasons inside `verify_ticket` (the purpose compare,
/// and then the caps compare), so a server that got one right could still wave
/// the other through. Both are `permission_denied`: the request is well formed
/// and the TICKET is what is refused.
#[tokio::test]
async fn a_renewal_ticket_cannot_order_and_an_order_ticket_cannot_renew() {
let rig = Rig::up().await;
let before = rig.steps();
let refused = rig.start_order("alice", caps(100), &renew_ticket("alice", "n-4")).await.expect_err("a renewal ticket bought room");
assert_eq!(refused.code(), Code::PermissionDenied, "{refused:?}");
let refused = rig.quote_order("alice", caps(100), &renew_ticket("alice", "n-4b")).await.expect_err("nor may it price room");
assert_eq!(refused.code(), Code::PermissionDenied, "{refused:?}");
let refused = rig.start_renewal("alice", &order_ticket("alice", caps(100), "o-4")).await.expect_err("an order ticket renewed a period");
assert_eq!(refused.code(), Code::PermissionDenied, "{refused:?}");
// A renewal names no caps on the wire, so the order ticket's caps are
// compared against an empty map and refused there — which is the same rule,
// reached by the other road.
let refused = rig.quote_renewal("alice", &order_ticket("alice", caps(100), "o-5")).await.expect_err("nor may it price one");
assert_eq!(refused.code(), Code::PermissionDenied, "{refused:?}");
assert_eq!(rig.steps(), before, "and not one of the four wrote a row");
assert_eq!(rig.paid_until_now(), Some(rig.paid_until), "the period did not move");
rig.server.stop().await;
}
/// **An order with no ticket is untouched** — the regression guard on the
/// operator path, which is what every caller that predates this field is. The
/// refusals above are about the TICKET and never about orders.
#[tokio::test]
async fn an_order_without_a_ticket_still_goes_through() {
let rig = Rig::up().await;
let out = rig.start_order("alice", caps(100), b"").await.expect("an ordinary order must still sell");
assert_eq!(out.state, PurchaseState::Paid as i32, "{}", out.reason);
assert_eq!(rig.caps_now("alice"), caps(100), "the operator's order landed");
assert_eq!(rig.state.registry.tickets_checked("gunnar"), 0, "an absent ticket is not a question: nothing must be checked at all");
rig.server.stop().await;
}
/// **A refused ORDER ticket leaves the ledger exactly as it found it**, the
/// same guarantee the renewal path has: the gate is before the idempotency
/// read, the quote, the vendor lookup and the transaction, so a refusal has
/// nothing to unwind.
#[tokio::test]
async fn a_refused_order_ticket_leaves_the_ledger_untouched() {
let rig = Rig::up().await;
let before = rig.steps();
let held = rig.caps_now("alice");
let absorbs = rig.product.absorb_asked().len();
// The real appliance's ticket, for the right caps — spent on somebody else.
let refused = rig
.start_order("beta", caps(100), &order_ticket("alice", caps(100), "o-6"))
.await
.expect_err("alice's order ticket must not buy beta's room");
assert_eq!(refused.code(), Code::PermissionDenied, "{refused:?}");
assert!(!refused.message().contains("alice"), "the refusal names the ticket's own tenant to whoever asked about another: {}", refused.message());
assert_eq!(rig.steps(), before, "not one ledger row");
assert_eq!(rig.caps_now("alice"), held, "alice's caps did not move");
assert!(rig.caps_now("beta").is_empty(), "and beta got nothing");
assert_eq!(rig.product.absorb_asked().len(), absorbs, "the product was never asked anything");
rig.server.stop().await;
}
/// ★ **A TICKET IS SPENT ONCE, WHICHEVER CALL SPENDS IT.**
///
/// The replay ring lives in the plugin and cannot tell a `Quote` from a
/// `Start`, so a ticket used to price a renewal is gone. That is deliberate and
/// it is what a console must be built for: one ticket per call.
#[tokio::test]
async fn the_same_ticket_cannot_be_spent_twice() {
let rig = Rig::up().await;
let ticket = renew_ticket("alice", "n-5");
let quote = rig.quote_renewal("alice", &ticket).await.expect("the first spend prices the renewal");
assert!(quote.price.expect("a price").minor > 0, "the fixture list is not free, or this test proves nothing");
let refused = rig.start_renewal("alice", &ticket).await.expect_err("the same ticket must not buy as well as quote");
assert_eq!(refused.code(), Code::PermissionDenied, "{refused:?}");
let refused = rig.quote_renewal("alice", &ticket).await.expect_err("nor quote twice");
assert_eq!(refused.code(), Code::PermissionDenied, "{refused:?}");
rig.server.stop().await;
}
/// **The quote is gated too, and for two reasons neither of which is money.** A
/// quote names a tenant's standing caps and their price, so an ungated one is a
/// read-only enumeration of the customer list; and a console that could quote
/// what it cannot buy shows a customer a price and then a wall.
#[tokio::test]
async fn a_quote_for_a_renewal_is_gated_by_the_same_ticket() {
let rig = Rig::up().await;
let refused = rig.quote_renewal("alice", b"not a ticket at all").await.expect_err("rubbish must not price a renewal");
assert_eq!(refused.code(), Code::PermissionDenied, "{refused:?}");
let priced = rig.quote_renewal("alice", &renew_ticket("alice", "n-6")).await.expect("a genuine ticket prices it");
assert!(priced.can_serve, "a renewal asks the product no capacity question");
rig.server.stop().await;
}
/// ★ **A PRODUCT WITH NO APPLIANCE KEY REFUSES EVERY TICKET — AND SAYS SO
/// BEFORE A CONSOLE DRAWS THE ROUTE.**
///
/// `ProductInfo.tenant_renewal` is how gunnar-ui decides whether self-service
/// exists at all. False here is not a policy, it is the truth: nothing in this
/// deployment could tell a customer from a stranger.
#[tokio::test]
async fn a_product_with_no_appliance_key_offers_nothing_and_refuses_everything() {
let rig = Rig::up_with_key(None).await;
let listed = ProductsClient::new(rig.channel.clone()).list(rig.authed(pb::ListProductsRequest {})).await.expect("list").into_inner();
let gunnar = listed.products.iter().find(|p| p.id == "gunnar").expect("the product is linked in");
assert!(!gunnar.tenant_renewal, "a product that holds no key must not advertise self-service renewal");
let refused = rig.start_renewal("alice", &renew_ticket("alice", "n-7")).await.expect_err("a ticket nothing can check is not a ticket");
assert_eq!(refused.code(), Code::PermissionDenied, "{refused:?}");
// …and the operator still gets through, which is the point of the whole
// "empty changes nothing" rule.
let out = rig.start_renewal("alice", b"").await.expect("the operator path does not need a key");
assert_eq!(out.state, PurchaseState::Paid as i32, "{}", out.reason);
rig.server.stop().await;
}
/// The capability reaches the wire when the key IS configured — the other half
/// of the test above, so neither answer can be the only one this list can give.
#[tokio::test]
async fn a_product_that_holds_the_key_advertises_tenant_renewal() {
let rig = Rig::up().await;
let listed = ProductsClient::new(rig.channel.clone()).list(rig.authed(pb::ListProductsRequest {})).await.expect("list").into_inner();
let gunnar = listed.products.iter().find(|p| p.id == "gunnar").expect("the product is linked in");
assert!(gunnar.tenant_renewal, "a console must be told it may offer the route");
rig.server.stop().await;
}