crates/monetize-server/tests/pull_holger.rs
//! **Holger is a pulled product, like Gunnar** (5.4, G1; the lifecycle parity of 2026-10-17).
//!
//! `[products.holger] instance = "<estate>"` builds the product (here the Holger-shaped TEST
//! product, `monetize_server::testing::products::HOLGER_FACTORY`, which dials nothing; v2.0 P2
//! 12.0) and wraps it in `monetize_guardian::pulled::Pulled`: every read is answered from the
//! estate main's `Fleet.Report` (a product-generic `monetize_product::pull::ProductReport` — the
//! JSON holger's box-side Fleet client sends), and every change leaves as a signed order the box
//! pulls. The product's own verbs stay: its meters, its guardian and its buyers' tickets.
use monetize_embed::fleet::Report;
use monetize_embed::signing::base64_encode;
use monetize_product::guardian::{role, BoxView, EstateView};
use monetize_product::pull::{order, ProductReport};
use monetize_product::*;
use monetize_server::config::Config;
use monetize_server::registry::{Linked, Registry};
use monetize_server::state::AppState;
const ESTATE: &str = "holger-rs";
const GIB: u64 = 1 << 30;
fn linked() -> Linked {
let mut l = Linked::open();
l.product(monetize_server::testing::products::HOLGER_FACTORY);
l
}
fn config() -> String {
let key = base64_encode(
ed25519_dalek::SigningKey::from_bytes(&[11u8; 32])
.verifying_key()
.as_bytes(),
);
format!("[products.holger]\nfree = true\ninstance = \"{ESTATE}\"\nfleet_keys = {{ main = \"{key}\" }}\n")
}
fn now_ms() -> u64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_millis() as u64
}
/// **The ProductReport a holger main sends**, in the generic shape: its repositories are the
/// tenants, its own `ReadUsage` readings the usage (with the sold `disk_bytes`), its capacity
/// the servable, its metered door's answer, the room a raise would find in the sold meter.
fn holger_report(now: u64) -> String {
let pr = ProductReport {
tenants: vec![
TenantOwner {
tenant: TenantId("crates".into()),
person: false,
seller: false,
},
TenantOwner {
tenant: TenantId("bundles".into()),
person: false,
seller: false,
},
],
usage: [
(
"crates".to_string(),
Usage {
meters: [
("disk_bytes".to_string(), 4_096),
("artifacts".to_string(), 2),
("archive_bytes".to_string(), 2_500),
]
.into(),
measured_at_unix_ms: now,
},
),
(
"bundles".to_string(),
Usage {
meters: [
("disk_bytes".to_string(), 8_192),
("artifacts".to_string(), 2),
]
.into(),
measured_at_unix_ms: now,
},
),
]
.into(),
servable: Some(Servable::Measured {
servable_bytes: 40 * GIB,
total_bytes: 100 * GIB,
growth: Growth::Unknown("holger sent no verdict".into()),
ceilings: Default::default(),
}),
metered: Some(MeteredDoor::Enforcing(
"holger EntitlementService.List answered (0 fact(s) held)".into(),
)),
room: [("disk_bytes".to_string(), 40 * GIB)].into(),
promised: [(
"crates".to_string(),
[("disk_bytes".to_string(), 5 * GIB)].into(),
)]
.into(),
names: vec!["crates".into(), "bundles".into()],
measured_unix_ms: now,
..Default::default()
};
serde_json::to_string(&pr).unwrap()
}
#[test]
fn holger_with_an_instance_is_pulled_and_never_dialled() {
let cfg = Config::from_toml(&config()).expect("a pulled holger loads");
let reg = Registry::from_config_with(&cfg, &linked())
.expect("the Holger-shaped product builds and is never dialled");
let state = AppState::in_memory(reg);
monetize_server::fleet::declare_pulled(&state, &cfg).expect("declared");
let g = state.fleet.guardian().expect("the in-memory guardian");
assert_eq!(g.store.product_of(ESTATE).as_deref(), Some("holger"));
let p = state
.registry
.products()
.find(|p| p.id() == "holger")
.expect("served");
assert!(
matches!(p.list_tenants(), Err(ProductError::Unavailable(w)) if w.contains(ESTATE)),
"before a report: unavailable, never a dial"
);
assert_eq!(
p.meters()
.iter()
.find(|m| m.backing.is_some())
.map(|m| m.name),
Some("disk_bytes"),
"the plugin's own meters: disk_bytes is the sold one"
);
assert!(p.guardian().is_some(), "the plugin's own guardian");
let now = now_ms();
let r = Report {
product: Some(holger_report(now)),
measured_unix_ms: now,
..Default::default()
};
g.store.record_report(ESTATE, role::MAIN, now, &r).unwrap();
assert_eq!(
p.list_tenants().unwrap(),
vec![TenantId("crates".into()), TenantId("bundles".into())]
);
assert_eq!(
p.read_usage(&TenantId("crates".into())).unwrap().meters["disk_bytes"],
4_096
);
assert_eq!(p.metered_door().name(), "enforcing", "the box's own answer");
assert!(
matches!(p.servable(), Servable::Measured { servable_bytes, .. } if servable_bytes == 40 * GIB)
);
let raise = |gib: u64| {
p.can_absorb(
&TenantId("crates".into()),
&[("disk_bytes".to_string(), gib * GIB)].into(),
)
};
assert!(
matches!(raise(40), Ok(Absorb::OnHand)),
"5 GiB promised: a raise to 40 needs 35 of the 40 GiB room"
);
assert!(
raise(50).is_err(),
"45 GiB beyond what is held does not fit the room"
);
// The entitlement and the purge leave as signed orders the main pulls.
let fact = EntitlementFact {
tenant: TenantId("crates".into()),
plan: "holger/crates/10gb-1m".into(),
state: State::Paid,
paid_until_unix_ms: None,
caps: [("disk_bytes".to_string(), 10 * GIB)].into(),
source: "test".into(),
signature: Vec::new(),
issued_unix_ms: Some(now),
issued_signature: Vec::new(),
};
p.push_entitlement(&fact).expect("queued, not dialled");
let purged = p
.purge_tenant(&TenantId("bundles".into()), "nonpay day 30")
.expect("queued, not dialled");
assert!(purged.still_locked, "{}", purged.detail);
let kinds: Vec<String> = g
.store
.orders_for(ESTATE, role::MAIN, 0)
.into_iter()
.map(|o| o.kind)
.collect();
assert_eq!(kinds, [order::ENTITLEMENT, order::PURGE_TENANT]);
}
/// **The guardian reads holger's boxes from the same reports**: a silent main behind a whole
/// twin that cannot see it either is Gunnar's rebuild case (New server), and auto-fix may run
/// it; a held main the rebuild laid is no case of its own (W3, Gunnar's rule).
#[test]
fn the_pulled_holger_hands_its_guardian_to_the_runner() {
let cfg = Config::from_toml(&config()).unwrap();
let reg = Registry::from_config_with(&cfg, &linked()).unwrap();
let p = reg.products().find(|p| p.id() == "holger").unwrap();
let now = now_ms();
let b = |r: &str, words: &[(&str, &str)]| {
(
r.to_string(),
BoxView {
role: r.into(),
heard_unix_ms: now,
health: words
.iter()
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect(),
..Default::default()
},
)
};
let v = EstateView {
product: "holger".into(),
instance: ESTATE.into(),
now_unix_ms: now,
silent_after_ms: 90_000,
boxes: [
b("main", &[("serving", "no"), ("data", "fresh")]),
b("twin", &[("primary", "live"), ("complete", "yes")]),
]
.into_iter()
.collect(),
};
let g = p.guardian().expect("holger's guardian");
assert!(
g.diagnose(&v).is_empty(),
"a held main is the rebuild plan's box, no finding of its own"
);
let mut lost = v.clone();
lost.boxes.get_mut("main").unwrap().heard_unix_ms = now - 600_000;
// GONE at the provider (the runner's word, lane G11): silence alone lays nothing.
lost.boxes.get_mut("main").unwrap().at_provider = "gone".into();
lost.boxes
.get_mut("twin")
.unwrap()
.health
.insert("primary".into(), "silent".into());
let f = g.diagnose(&lost);
assert_eq!(
f.iter()
.map(|f| (f.code.as_str(), f.auto))
.collect::<Vec<_>>(),
[(
"main_lost",
Some(monetize_product::guardian::Remedy::NewServer)
)]
);
}
/// **The key holger's main signs its buyers' tickets with reaches holger's plugin by itself**
/// (2026-10-17): its owner's install notice delivers it (`ticket_key`, the main's fleet key),
/// the guardian takes the joined record, and the HOST checks the tickets of the product whose
/// HOME estate it is under that key (A32-13).
/// Nobody copies it into `[products.holger] appliance_key`.
#[test]
fn the_delivered_ticket_key_vouches_for_a_buyer_of_the_pulled_holger() {
use ed25519_dalek::Signer as _;
use monetize_embed::ticket::{ticket_message, ActorTicket, MAX_LIFE_MS, PURPOSE_ORDER};
let cfg = Config::from_toml(&config()).unwrap();
let reg = Registry::from_config_with(&cfg, &linked()).unwrap();
let state = AppState::in_memory(reg);
monetize_server::fleet::declare_pulled(&state, &cfg).unwrap();
let main = ed25519_dalek::SigningKey::from_bytes(&[12u8; 32]);
let hex: String = main
.verifying_key()
.as_bytes()
.iter()
.map(|b| format!("{b:02x}"))
.collect();
let mint = |nonce: &str| {
let now = now_ms() as i64;
let mut t = ActorTicket::unsigned(
"holger",
"holger",
"crates",
PURPOSE_ORDER,
nonce,
now,
MAX_LIFE_MS,
)
.covering([("disk_bytes".to_string(), 10 * GIB)].into());
t.signature = base64_encode(&main.sign(&ticket_message(&t)).to_bytes());
serde_json::to_vec(&t).unwrap()
};
let plan: std::collections::BTreeMap<String, u64> =
[("disk_bytes".to_string(), 10 * GIB)].into();
let p = state
.registry
.products()
.find(|p| p.id() == "holger")
.unwrap();
assert!(
matches!(
monetize_server::site_ticket::may_act(
&state,
p,
None,
"crates",
"order",
&plan,
&mint("k-0")
)
.unwrap(),
ActorVerdict::Refused(_)
),
"before the key is delivered: nobody is vouched for"
);
let dir = std::env::temp_dir().join(format!("pull-holger-joined-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
let now = now_ms();
let record = serde_json::json!({ "estate": ESTATE, "product": "holger", "box_name": "main", "endpoint": "10.6.0.10:50051", "estate_ca_pem": "", "owner_key": "", "enrolled_unix_ms": now, "roles": ["main"], "installed_unix_ms": now, "appliance_key": hex, "installed_with_ticket_key": true });
std::fs::write(dir.join(format!("{ESTATE}.json")), record.to_string()).unwrap();
let lines = monetize_server::fleet::take_joined(&state, &dir);
let _ = std::fs::remove_dir_all(&dir);
assert_eq!(
monetize_server::site_ticket::may_act(
&state,
p,
None,
"crates",
"order",
&plan,
&mint("k-1")
)
.unwrap(),
ActorVerdict::Allowed,
"{lines:?}"
);
}