crates/gunnar-monetize-guest/src/lib.rs
//! **gunnar's own monetize plugin: the gunnar product, as a WASM guest** — the six ops of the
//! product contract (monetize-product-guest 0.2, v2.0 P2 T13): the manifest with Gunnar's meter
//! table, its estate lifecycle (facts and plan), and its estate guardian.
//!
//! # Where it lives, and why here (owner 2026-09-25)
//!
//! It is built and signed from the same commit as gunnar-server: [`BUILD_REF`] is stamped into
//! the module as the custom section `gunnar_build_ref`. It moved here from open monetize
//! (`guests/gunnar`) with its tests.
//!
//! # It opens no door
//!
//! The ONE box never calls into a Gunnar estate: the boxes report their tenants and usage and
//! pull their orders (monetize's pulled product), so the guest has no gRPC half, no credential,
//! no key and no memory of its own (`grants = []`). Gunnar's main mints the actor tickets
//! ([`lifecycle::facts`] says `tickets`), and the ONE box checks them on the host under the
//! appliance key it holds (monetize-server `site_ticket`), never in this guest.
use monetize_product_guest::{Guest, Host, Manifest, ProductError};
// `BUILD_REF`, and on wasm32 the custom section `gunnar_build_ref` carrying it (`build.rs`).
include!(concat!(env!("OUT_DIR"), "/build_ref.rs"));
/// The custom section the build reference is stamped into.
pub const BUILD_REF_SECTION: &str = "gunnar_build_ref";
/// Gunnar's estate guardian (ops 100-102, monetize W3).
pub mod guardian;
/// Gunnar's estate lifecycle (ops 90/91, owner 2026-10-17).
pub mod lifecycle;
pub mod meters;
pub use meters::{
gunnar_honours, meters, GUNNAR_METERS, IRON_CAPS, PROVISIONED_ELSEWHERE, RAM_BYTES_PER_OBJECT,
};
pub const PRODUCT: &str = "gunnar";
/// The gunnar guest. Stateless and pure.
#[derive(Default, Debug)]
pub struct GunnarGuest;
impl Guest for GunnarGuest {
/// 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())
}
/// **What is wrong with the estate now** — Gunnar's linked guardian's rules ([`guardian`]).
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))
}
/// **The steps that carry out `remedy`**, or why Gunnar will not plan it now.
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))
}
/// **Is the `Await` met?**
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))
}
/// **Gunnar's estate lifecycle** ([`lifecycle`]): the shop lays, updates and ends a sold
/// Gunnar by these rules, not by any of its own.
fn lifecycle_facts(
&self,
_: &dyn Host,
) -> Result<monetize_product_guest::wire::LifecycleFacts, ProductError> {
Ok(lifecycle::facts())
}
fn lifecycle_lay_plan(
&self,
_: &dyn Host,
req: &monetize_product_guest::wire::LayPlanReq,
) -> Result<serde_json::Value, ProductError> {
let plan = lifecycle::lay_plan(req).map_err(ProductError::Refused)?;
serde_json::to_value(plan).map_err(|e| ProductError::Refused(e.to_string()))
}
}
monetize_product_guest::export_product!(GunnarGuest, grants = []);
/// **Gunnar's test buttons**: a hosted 10 GB account, and a private Gunnar at
/// the ladder's smallest sizes. The room and the shape are Gunnar's to name;
/// the plan, the price and the vendor are the shop's. They ride in the facts (op 90).
pub fn test_actions() -> Vec<monetize_product_guest::TestAction> {
use monetize_product_guest::{TestAction, TestActionKind};
vec![
TestAction {
id: "hosted-10gb".into(),
label: "Test: 10 GB hosted".into(),
kind: TestActionKind::HostedPlan { cap_gib: 10 },
},
TestAction {
id: "private-small".into(),
label: "Test: private Gunnar small".into(),
kind: TestActionKind::PrivateInstance {
front_plan: String::new(),
main_plan: String::new(),
main_data_gib: 0,
},
},
]
}