crates/holger-monetize-guest/src/lib.rs
//! **The holger product, as a WASM guest** — the six ops of the product contract
//! (monetize-product-guest 0.2, v2.0 P2 T13): the manifest with Holger's meter table, its estate
//! lifecycle (facts and plan, [`estate`]) and its estate guardian ([`guardian`]).
//!
//! It opens no door (`grants = []`): the ONE box never calls into a Holger estate. The boxes
//! report their repositories and usage and pull their orders (monetize's pulled product), so
//! the gRPC half this guest once had (the reads, the entitlement push, the grow lane, the purge)
//! is the ONE box's and holger-fleet's, never the guest's. Holger's main mints no actor tickets
//! the ONE box could check ([`lifecycle`] says `tickets: false`).
pub const PRODUCT: &str = "holger";
// `BUILD_REF`, and the custom section `holger_build_ref` holger-server `metered`
// reads back before it registers this module (`build.rs`).
include!(concat!(env!("OUT_DIR"), "/build_ref.rs"));
/// Holger's estate as the ONE box lays it (op 91).
pub mod estate;
/// Holger's estate guardian as the ONE box runs it (`guardian.*`, lane W3).
pub mod guardian;
use std::collections::BTreeMap;
use monetize_product_guest::{Enforcement, Guest, Host, Manifest, ProductError, WireMeter};
/// The sold meter's name — holger-server-lib `metering::METER_DISK_BYTES`.
pub const DISK_BYTES: &str = "disk_bytes";
/// **holger's meters**, the linked plugin's table as wire meters. Exactly ONE is
/// backed and sold (owner 2026-09-27: "bill disk bytes"): `disk_bytes`, what the
/// repository OCCUPIES on holger's disk — its archive or its loose files, counted
/// once by holger itself (`ReadUsage`, whose `source` says where). The rest are
/// information: `artifact_bytes` and `archive_bytes` describe the same bytes in
/// two shapes, and backing either beside `disk_bytes` would buy a disk twice.
pub fn meters() -> Vec<WireMeter> {
vec![
WireMeter::new("artifacts", "artifacts in the repository, as ListArtifacts enumerates them", Enforcement::Measured),
WireMeter::new("artifact_bytes", "sum of the repository's artifact sizes, uncompressed", Enforcement::Measured),
WireMeter::new(
DISK_BYTES,
"what the repository occupies on holger's disk (archive or loose files, allocated, counted once) — holger's own ReadUsage reading, which says where",
Enforcement::Enforced,
)
.with_backing(monetize_product_guest::Backing::DiskBytes),
WireMeter::new(
"archive_bytes",
"uncompressed bytes in the znippy archive behind the repository (ArchiveInfo); absent when it has no archive",
Enforcement::Measured,
),
WireMeter::new(
"archive_files",
"files in that archive, artifacts and index files alike (ArchiveInfo); absent when it has no archive",
Enforcement::Measured,
),
]
}
/// **Holger's test buttons**: a 10 GB repository on the shop's hosted Holger, and a private
/// Holger at the ladder's smallest sizes. The room and the shape are Holger's to name; the
/// plan, the price and the vendor are the shop's.
pub fn test_actions() -> Vec<monetize_product_guest::TestAction> {
use monetize_product_guest::{TestAction, TestActionKind};
vec![
TestAction {
id: "repository-10gb".into(),
label: "Test: 10 GB repository".into(),
kind: TestActionKind::HostedPlan { cap_gib: 10 },
},
TestAction {
id: "private-small".into(),
label: "Test: private Holger small".into(),
kind: TestActionKind::PrivateInstance {
front_plan: String::new(),
main_plan: String::new(),
main_data_gib: 0,
},
},
]
}
/// The holger guest. Stateless and pure.
#[derive(Default, Debug)]
pub struct HolgerGuest;
/// **Holger's estate lifecycle, as facts** (op 90, `lifecycle.facts`): exactly monetize's
/// native `estate_order::lifecycle::Holger` (lane L7) — updated twin → main → front, keep-only
/// (main and twin from `holger-keep`, the front from `holger-front-keep`; a live slot, L20), main
/// and twin (holger-server, the twin in twin mode) laid from the `holger` medium and the front
/// (holger-web-ui) from Holger's OWN `holger-front` medium — never Gunnar's `front` — main's
/// disks kept at a terminate, the twin's disk mirroring main's set.
///
/// Its `default_shape` is Holger's own ([`estate::default_shape`], the ladder inside it), as
/// **Holger's own order kinds, as data** (monetize `Facts::orders`): each runs on main through the
/// same hub its `/-/sync` doors use; `bundle_put` takes its file pulled from the ONE box and
/// writes it into the chain's bundles door as the verified order (verify-bundle first).
pub fn order_kinds() -> Vec<monetize_product_guest::facts::OrderKindFacts> {
use monetize_product_guest::facts::{OrderKindFacts, OrderParamFacts};
let p = |name: &str, words: &str, required: bool| OrderParamFacts {
name: name.into(),
words: words.into(),
required,
};
let kind = |kind: &str, words: &str, params: Vec<OrderParamFacts>, file: &str| OrderKindFacts {
kind: kind.into(),
role: "main".into(),
words: words.into(),
params,
file: file.into(),
};
vec![
kind(
"downstream_enrol",
"enrol a downstream (its name, sync key, front or wireguard)",
vec![
p("name", "the downstream's name", true),
p("key", "its sync key (hex)", false),
p("via", "front | wireguard", false),
p("wireguard_key", "its WireGuard public key", false),
],
"",
),
kind(
"downstream_invite",
"a downstream's signed invite",
vec![
p("name", "the downstream's name", true),
p("door", "its direct door", false),
p("hub_key", "the hub's WireGuard key", false),
p("endpoint", "the front's WireGuard endpoint", false),
],
"",
),
kind(
"downstream_revoke",
"revoke a downstream",
vec![p("name", "the downstream's name", true)],
"",
),
kind(
"downstream_list",
"the downstreams, each with its via",
Vec::new(),
"",
),
kind(
"sync_board",
"the sync board: each downstream's generation and lag",
Vec::new(),
"",
),
kind("sync_key", "main's sync name and key", Vec::new(), ""),
kind(
"estate_board",
"the estate board (front, main, twin, upstreams, downstreams)",
Vec::new(),
"",
),
kind(
"bundle_put",
"a signed Holger bundle into main's bundles door (verified against its trust policy)",
vec![
p(
"repository",
"the bundles door (the default; any other repository is refused)",
false,
),
p("path", "its name in the bundles door", true),
p("bundle", "the signed bundle", true),
],
"bundle",
),
]
}
/// Gunnar's guest names Gunnar's: the ONE box links no product and lays from what this says.
pub fn lifecycle() -> monetize_product_guest::facts::Facts {
use monetize_product_guest::facts::{
Billing, Facts, GrowRule, PortFacts, RoleFacts, TwinFacts,
};
use monetize_product_guest::wire::{WireDataRole, WireWords};
let s = |v: &[&str]| v.iter().map(|x| x.to_string()).collect::<Vec<_>>();
Facts {
kind: PRODUCT.into(),
update_order: s(&["twin", "main", "front"]),
app_medium: PRODUCT.into(),
front_medium: format!("{PRODUCT}-front"),
front_role: "front".into(),
keep_medium: format!("{PRODUCT}-keep"),
front_keep_medium: format!("{PRODUCT}-front-keep"),
upgraded_by_shop: s(&["main", "twin"]),
medium_of: BTreeMap::from([
("front".to_string(), format!("{PRODUCT}-front")),
("main".to_string(), PRODUCT.to_string()),
("twin".to_string(), PRODUCT.to_string()),
]),
other_medium: PRODUCT.into(),
backup_roles: s(&["main"]),
words: WireWords {
name: PRODUCT.into(),
title: "Holger".into(),
account_home: String::new(),
},
default_shape: estate::default_shape(),
data_roles: vec![
WireDataRole {
role: "main".into(),
mirrors_primary: false,
set_suffix: String::new(),
},
WireDataRole {
role: "twin".into(),
mirrors_primary: true,
set_suffix: "-twin".into(),
},
],
domain_proof_name: format!("{PRODUCT}-proof"),
// A live slot (L20): main, twin and front take the release's live apps (holger-live
// over korp `live`); the keep media stay the fallback.
live_slot: true,
// Holger's main mints no actor tickets the ONE box checks (op 40, merged).
tickets: false,
// Holger's test buttons (op 54, merged).
test_actions: test_actions(),
// Holger's estate guardian (op 60's guardian lane, merged; lane W3).
guardian: true,
// ── What the shop's pairing speaks for a sold Holger (v2.0 P3: the ONE box's core
// holds no Holger contract of its own).
sold_variant: Some(format!("{PRODUCT}-twin-monetize")),
site_variant: Some(format!("{PRODUCT}-twin-one")),
box_kind: Some(PRODUCT.into()),
free_feed: Some(format!("plugin.{PRODUCT}.free")),
// Holger makes no welcome repository.
welcome: Some(false),
// The front serves no pay page: it is fed the one its site row carries.
billing: Some(Billing::SitePayPage),
// private-holger-ops 6b2a0f7: Holger's kit feeds `holger_console_token` only.
console_token_feed: Some("holger_console_token".into()),
// Holger's main ships an off-site copy (server-lib `offsite`, lane O1/M5).
offsite: Some(true),
// Sold media must not break silently (test3-holger-rs 2026-10-22): a box of a release
// before the per-box key waits for the front's bearer, so main and twin are fed it.
boxes_hold_console_token: Some(true),
// server-cli `paired::PASSKEY_RP_FEED`: the console host a box binds its passkeys to.
passkey_rp_feed: Some("passkey_rp_id".into()),
// HOL-PUBSITE-FEED (Gunnar's GRS-PUBLIC): main and twin take the site's public-site
// switch in a refeed (holger-fleet `order::refeed_key::PUBLIC_SITE`) and keep it as the
// console's setting `console.public-site`, which the console follows.
public_site: Some(true),
proof: Some(sale_proof()),
// ── What a Holger estate IS, as data (monetize v2.0 P3-FACTS, A24 §2.2): the ONE box's
// core decides none of it by Gunnar's shape any more.
// Main holds the tenants (the off-site copy, the report the shop meters by).
primary_role: "main".into(),
// Each role once. The front opens its WireGuard downstreams' interface to anyone (a
// downstream outside the estate dials it from its signed invite; the front's relay answers
// only the keys main lists); main and the twin open nothing more (lane OX-WGDS5).
roles: ["front", "main", "twin"]
.iter()
.map(|r| RoleFacts {
role: r.to_string(),
open: if *r == "front" {
vec![PortFacts {
protocol: "udp".into(),
port: crate::DOWNSTREAM_UDP,
why:
"a WireGuard downstream's handshake with Holger's downstream interface"
.into(),
}]
} else {
Vec::new()
},
..RoleFacts::default()
})
.collect(),
// The twin mirrors main (holger-server in twin mode).
twin: Some(TwinFacts {
role: "twin".into(),
mirrors: "main".into(),
}),
// Main's data never shrinks; a plan change resizes the three machines, adds none.
grow: Some(GrowRule::sizes()),
// Holger's own order kinds (monetize MONETIZE-FIX 2026-11-01): the operator queues them
// on his site, main PULLS and runs them (holger-fleet `order::holger_kind::PRODUCT`); the
// ONE box never calls into the estate.
orders: order_kinds(),
..Facts::default()
}
}
/// Holger's own doors the sale check reads through the thin console (holger-ui's thin server:
/// the bundle's `/login`, and `/-/*` passed to Holger's HTTP door; no `/auth/*`, no `/api/*`).
pub mod doors {
pub const LOGIN_PATH: &str = "/login";
/// The thin console's login page loads its passkey script.
pub const LOGIN_PAGE_MARK: &str = "/login.js";
/// `{enabled, bootstrap_spent}`.
pub const CONFIG_PATH: &str = "/-/auth/passkey/config";
/// `POST {invite, display}`: a challenge, nothing spent until `finish`.
pub const REGISTER_START_PATH: &str = "/-/auth/passkey/register/start";
/// The front door's roster (`{repositories: [{name, format, packages}], …}`).
pub const REPOS_PATH: &str = "/-/releases";
}
/// Pure: **what proves a sale of Holger** (A28 H12), over the sold console's public
/// `https://<host>`: the thin console's login page; Holger's passkey config (offered, its
/// first-admin door unspent); a register ceremony for the ready mail's invite (a challenge,
/// nothing spent); and at least one repository on the front door's roster, read with the
/// console's bearer. Holger makes no welcome repository.
pub fn sale_proof() -> monetize_product_guest::facts::SaleProof {
use doors::*;
use monetize_product_guest::facts::{
FirstDoorProof, ProofAsk, ProofCheck, ProofExpect, ProofMethod, SaleProof,
};
let get = |path: &str, bearer: bool| ProofAsk {
method: ProofMethod::Get,
path: path.into(),
body: None,
bearer,
};
let is = |field: &str, value: bool| ProofExpect::Equals {
field: field.into(),
value: serde_json::Value::Bool(value),
};
let start = ProofAsk {
method: ProofMethod::Post,
path: REGISTER_START_PATH.into(),
body: Some(serde_json::json!({ "invite": "{invite}", "display": "{account}" })),
bearer: false,
};
let challenge = "/publicKey/challenge";
SaleProof {
checks: vec![
ProofCheck {
what: "the login page".into(),
ask: get(LOGIN_PATH, false),
expect: vec![ProofExpect::Contains {
text: LOGIN_PAGE_MARK.into(),
}],
},
ProofCheck {
what: "the first-login door".into(),
ask: get(CONFIG_PATH, false),
expect: vec![is("/enabled", true), is("/bootstrap_spent", false)],
},
ProofCheck {
what: "the first-login door".into(),
ask: start.clone(),
expect: vec![ProofExpect::Text {
field: challenge.into(),
}],
},
ProofCheck {
what: "the repository list".into(),
ask: get(REPOS_PATH, true),
expect: vec![ProofExpect::NotEmpty {
list: "/repositories".into(),
name_field: "name".into(),
}],
},
],
needs_account: true,
first_door: Some(FirstDoorProof {
status: get(CONFIG_PATH, false),
spent: "/bootstrap_spent".into(),
enabled: "/enabled".into(),
start,
challenge: challenge.into(),
}),
}
}
impl Guest for HolgerGuest {
/// No grants: the guest opens no door. The meter table rides in the manifest (op 1).
fn manifest(&self) -> Manifest {
Manifest::new(PRODUCT, &[]).with_meters(meters())
}
fn lifecycle_facts(
&self,
_host: &dyn Host,
) -> Result<monetize_product_guest::wire::LifecycleFacts, ProductError> {
Ok(lifecycle())
}
/// **Op 91**: the plan a Holger estate is laid as ([`estate::lay_plan`]), as Gunnar's guest
/// answers it — the front, main and the twin from the shape the request carries.
fn lifecycle_lay_plan(
&self,
_host: &dyn Host,
req: &monetize_product_guest::wire::LayPlanReq,
) -> Result<serde_json::Value, ProductError> {
estate::lay_plan(req).map_err(ProductError::Refused)
}
fn guardian_diagnose(
&self,
view: &monetize_product_guest::guardian::EstateView,
) -> Result<Vec<monetize_product_guest::guardian::Finding>, ProductError> {
use monetize_product_guest::guardian::EstateGuardian as _;
Ok(guardian::GUARDIAN.diagnose(view))
}
fn guardian_plan(
&self,
remedy: monetize_product_guest::guardian::Remedy,
view: &monetize_product_guest::guardian::EstateView,
) -> Result<Result<Vec<monetize_product_guest::guardian::Step>, String>, ProductError> {
use monetize_product_guest::guardian::EstateGuardian as _;
Ok(guardian::GUARDIAN.plan(remedy, view))
}
fn guardian_observe(
&self,
role: &str,
what: &str,
view: &monetize_product_guest::guardian::EstateView,
) -> Result<monetize_product_guest::guardian::StepState, ProductError> {
use monetize_product_guest::guardian::EstateGuardian as _;
Ok(guardian::GUARDIAN.observe(role, what, view))
}
}
/// The udp port Holger's front holds its WireGuard downstreams' interface on (holger
/// `server/downstream` `via::HUB_UDP_PORT`).
pub const DOWNSTREAM_UDP: u16 = 51830;
monetize_product_guest::export_product!(HolgerGuest, grants = []);
#[cfg(test)]
mod lifecycle_tests {
/// ★ **Holger's front declares its downstream interface, and nothing else opens** (lane
/// OX-WGDS5, 2026-10-29): the ONE box's cloud opens to anyone only the ports a role declares.
/// The front holds the WireGuard downstreams' interface on udp/51830 (`via::HUB_UDP_PORT`);
/// main and the twin open nothing more. The port is the downstream crate's own number.
#[test]
fn the_front_declares_its_downstream_interface_and_main_and_the_twin_nothing() {
let f = super::lifecycle();
let open = |role: &str| {
f.roles
.iter()
.find(|r| r.role == role)
.map(|r| {
r.open
.iter()
.map(|p| (p.protocol.clone(), p.port))
.collect::<Vec<_>>()
})
.unwrap_or_default()
};
assert_eq!(open("front"), [("udp".to_string(), super::DOWNSTREAM_UDP)]);
assert!(open("main").is_empty() && open("twin").is_empty());
let via = include_str!("../../../server/downstream/src/via.rs");
assert!(
via.contains(&format!(
"pub const HUB_UDP_PORT: u16 = {};",
super::DOWNSTREAM_UDP
)),
"the downstream crate's HUB_UDP_PORT is the declared port"
);
}
/// ★ **HOL-PUBSITE-FEED (Gunnar's GRS-PUBLIC): Holger's facts say its boxes take the site's
/// public-site switch in a refeed** (`console_public_site`, holger-fleet
/// `order::refeed_key::PUBLIC_SITE`, kept as the console's setting `console.public-site`), so
/// `site refeed <site> --only public_site` reaches main and twin instead of being refused.
#[test]
fn holgers_boxes_take_the_public_site_switch_in_a_refeed() {
let f = super::lifecycle();
assert_eq!(f.public_site, Some(true));
assert!(f.takes_public_site());
}
/// ★ The facts are monetize's native `lifecycle::Holger` (L7), word for word.
#[test]
fn the_lifecycle_is_monetizes_native_holger() {
let f = super::lifecycle();
let s = |v: &[&str]| v.iter().map(|x| x.to_string()).collect::<Vec<_>>();
assert_eq!(f.kind, "holger");
assert_eq!(f.update_order, s(&["twin", "main", "front"]));
assert_eq!(
(
f.app_medium.as_str(),
f.front_medium.as_str(),
f.front_role.as_str()
),
("holger", "holger-front", "front"),
"the front is Holger's own holger-web-ui medium, never Gunnar's `front`"
);
assert_eq!(
(f.keep_medium.as_str(), f.front_keep_medium.as_str()),
("holger-keep", "holger-front-keep")
);
assert_eq!(f.upgraded_by_shop, s(&["main", "twin"]));
assert_eq!(
f.medium_of.get("front").map(String::as_str),
Some("holger-front")
);
assert_eq!(
(
f.medium_of.get("main").map(String::as_str),
f.other_medium.as_str()
),
(Some("holger"), "holger")
);
assert_eq!(f.backup_roles, s(&["main"]));
assert_eq!(
(
f.words.name.as_str(),
f.words.title.as_str(),
f.words.account_home.as_str()
),
("holger", "Holger", "")
);
assert_eq!(
f.data_roles
.iter()
.map(|d| (d.role.as_str(), d.mirrors_primary, d.set_suffix.as_str()))
.collect::<Vec<_>>(),
[("main", false, ""), ("twin", true, "-twin")]
);
assert_eq!(f.domain_proof_name, "holger-proof");
assert!(
f.live_slot,
"Holger's boxes carry a live slot (L20), as lifecycle::Holger"
);
// What ops 40, 54 and 60 answered rides in the facts (monetize v2.0 P2 T13).
assert!(!f.tickets && f.guardian);
assert_eq!(
f.test_actions
.iter()
.map(|t| t.id.as_str())
.collect::<Vec<_>>(),
["repository-10gb", "private-small"]
);
}
/// ★ **The six ops, and nothing else** (monetize v2.0 P2 T13): no grants, the meter table in
/// the manifest, and every number that left the contract answers the SDK's unknown-op words.
#[test]
fn the_guest_answers_the_six_ops_only() {
use monetize_product_guest::{dispatch, ops, wire, Answer, Guest as _, Manifest, NoHost};
let m = super::HolgerGuest.manifest();
assert!(
m.grants.is_empty() && !m.identity,
"a product guest opens no door"
);
assert_eq!(
m.meters
.iter()
.filter(|w| w.backing.is_some())
.map(|w| w.name.as_str())
.collect::<Vec<_>>(),
["disk_bytes"]
);
let one: Answer<Manifest> =
wire::decode(&dispatch(&super::HolgerGuest, &NoHost, ops::MANIFEST, &[])).unwrap();
assert_eq!(one.unwrap(), m, "op 1 answers the manifest");
for (op, name) in ops::RETIRED {
let raw = dispatch(&super::HolgerGuest, &NoHost, *op, &[]);
assert!(
String::from_utf8_lossy(&raw).contains(ops::UNKNOWN_OP_PHRASE),
"op {op} ({name}) left the contract"
);
}
}
}
#[cfg(test)]
mod order_kind_tests {
/// ★ MONETIZE-FIX: Holger declares its 8 own order kinds, each one the core takes (a plain
/// word that is no core kind, its params named once, bundle_put's file one of them), all on main.
#[test]
fn holger_declares_its_own_order_kinds_and_the_core_takes_each() {
let kinds = super::order_kinds();
let names: Vec<&str> = kinds.iter().map(|k| k.kind.as_str()).collect();
assert_eq!(
names,
[
"downstream_enrol",
"downstream_invite",
"downstream_revoke",
"downstream_list",
"sync_board",
"sync_key",
"estate_board",
"bundle_put"
]
);
for k in &kinds {
k.check().unwrap_or_else(|e| panic!("{e}"));
assert_eq!(k.role, "main");
}
assert_eq!(super::lifecycle().orders, kinds, "the facts carry them");
}
}