Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/gunnar

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,
            },
        },
    ]
}