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

nordisk/holger

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");
    }
}