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

nordisk/holger

xtask/src/paid_guard.rs

//! **`cargo xtask free-tree`: open Holger links nothing paid** (A5 L-Trust, Gunnar's
//! `xtask/src/paid_guard.rs` ported; lane SC 2026-10-22).
//!
//! Holger Pro lives in Vetra's private `monetize-impl` and reaches a box only as signed
//! wasm (a meter or a product guest built and signed there). Open Holger's tree therefore
//! never names a `monetize-impl` crate — not in the default build, not under `metered`, and
//! not in `Cargo.lock`. The OPEN monetize crates (`monetize-embed`, `monetize-product`,
//! `monetize-proto`, …) are allowed under `metered`: they are AGPL like Holger.
//!
//! Three checks, each naming every offender once:
//!
//! * **the lock** — `Cargo.lock` holds no paid crate by name (a path dependency on
//!   `monetize-impl` would put its crate there, whatever features are on);
//! * **the default server** — `cargo tree -p holger -e normal,build` holds no paid
//!   crate by name and no package from a `monetize-impl` or `private-*` checkout;
//! * **the metered server** — the same under `--features metered,meter-guests`.

use std::collections::BTreeSet;
use std::path::Path;
use std::process::Command;

/// The crates of Vetra's private `monetize-impl` workspace (never published; AGPL +
/// commercial, `publish = false`) — paid wherever they turn up.
pub const PAID_NAMES: &[&str] = &[
    "grow-probe",
    "gunnar-shop-bench",
    "monetize-cloud-impl",
    "monetize-guest-dns-cloudflare",
    "monetize-guest-klarna",
    "monetize-guest-njord",
    "monetize-guest-scaleway",
    "monetize-guest-signup-gunnar",
    "monetize-guest-signup-holger",
    "monetize-guest-stripe",
    "monetize-guest-swish",
    "monetize-guest-upcloud",
    "monetize-impl-build-thing",
    "monetize-pay-impl",
    "monetize-plugin-holger",
    "monetize-product-njord",
    "monetize-server-vetra",
    "monetize-signup-impl",
    "monetize-tax-impl",
    "money-flow",
    "upcloud-storm",
];

/// The server package every check reads, and the feature sets it ships with.
pub const SERVER: &str = "holger";
pub const ROWS: &[(&str, &[&str])] = &[
    ("default", &[]),
    ("metered", &["--features", "metered,meter-guests"]),
];

/// One paid crate in a listing.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Offender {
    pub name: String,
    pub version: String,
}

/// Pure: **the paid crates in a `cargo tree --prefix none --format "{p}"` listing**, each once.
pub fn offenders(tree: &str) -> Vec<Offender> {
    let mut seen = BTreeSet::new();
    let mut out = Vec::new();
    for line in tree.lines() {
        let mut words = line.split_whitespace();
        let (Some(name), Some(version)) =
            (words.next(), words.next().and_then(|v| v.strip_prefix('v')))
        else {
            continue;
        };
        let source = line
            .find(" (")
            .map(|i| &line[i + 2..])
            .and_then(|s| s.split(')').next())
            .unwrap_or_default();
        let private = source.contains("/monetize-impl/") || source.contains("/private-");
        if (PAID_NAMES.contains(&name) || private) && seen.insert(name.to_owned()) {
            out.push(Offender {
                name: name.to_owned(),
                version: version.to_owned(),
            });
        }
    }
    out
}

/// Pure: **the paid crates `Cargo.lock` names**, each once.
pub fn lock_offenders(lock: &str) -> Vec<String> {
    let mut seen = BTreeSet::new();
    lock.lines()
        .filter_map(|l| {
            l.trim()
                .strip_prefix("name = \"")
                .and_then(|r| r.strip_suffix('"'))
        })
        .filter(|n| PAID_NAMES.contains(n) && seen.insert(n.to_string()))
        .map(str::to_owned)
        .collect()
}

fn tree(root: &Path, cargo: &str, extra: &[&str]) -> Result<String, String> {
    let mut args = vec![
        "tree",
        "-p",
        SERVER,
        "-e",
        "normal,build",
        "--prefix",
        "none",
        "--format",
        "{p}",
    ];
    args.extend_from_slice(extra);
    let out = Command::new(cargo)
        .current_dir(root)
        .args(&args)
        .output()
        .map_err(|e| format!("{cargo} tree: {e}"))?;
    if !out.status.success() {
        return Err(format!(
            "{cargo} {}: {}",
            args.join(" "),
            String::from_utf8_lossy(&out.stderr).trim()
        ));
    }
    Ok(String::from_utf8_lossy(&out.stdout).into_owned())
}

/// **The three checks**; `Err` names every offender (and why), `Ok` what was read.
pub fn check(root: &Path, cargo: &str) -> Result<Vec<String>, Vec<String>> {
    let mut said = Vec::new();
    let mut bad = Vec::new();
    match std::fs::read_to_string(root.join("Cargo.lock")) {
        Ok(lock) => {
            let paid = lock_offenders(&lock);
            if paid.is_empty() {
                said.push("Cargo.lock names no paid crate".to_owned());
            }
            bad.extend(paid.into_iter().map(|n| {
                format!(
                    "Cargo.lock names {n}: Vetra's private monetize-impl, never in Holger's tree"
                )
            }));
        }
        Err(e) => bad.push(format!("Cargo.lock: {e}")),
    }
    for (row, extra) in ROWS {
        match tree(root, cargo, extra) {
            Ok(listing) => {
                let paid = offenders(&listing);
                let open: BTreeSet<&str> = listing
                    .lines()
                    .filter_map(|l| l.split_whitespace().next())
                    .filter(|n| n.starts_with("monetize"))
                    .collect();
                if paid.is_empty() {
                    said.push(format!(
                        "{SERVER} ({row}) links no paid crate; open monetize crates: {}",
                        if open.is_empty() {
                            "none".to_owned()
                        } else {
                            open.into_iter().collect::<Vec<_>>().join(", ")
                        }
                    ));
                }
                bad.extend(paid.into_iter().map(|o| format!("{SERVER} ({row}) links {} v{}: Vetra's private monetize-impl, never in an open build", o.name, o.version)));
            }
            Err(why) => bad.push(why),
        }
    }
    if bad.is_empty() {
        Ok(said)
    } else {
        Err(bad)
    }
}

/// `cargo xtask free-tree`.
pub fn run(root: &Path, cargo: &str) -> anyhow::Result<()> {
    match check(root, cargo) {
        Ok(said) => {
            for l in said {
                println!("free-tree: {l}");
            }
            Ok(())
        }
        Err(bad) => {
            for l in &bad {
                eprintln!("free-tree: ✗ {l}");
            }
            anyhow::bail!("{} paid link(s) in open Holger", bad.len())
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    /// ★ The listing check names each paid crate once — by name, or by the checkout it comes
    /// from — and passes open monetize crates.
    #[test]
    fn the_free_tree_names_every_paid_crate_once_and_passes_open_monetize() {
        let clean = "holger v0.7.0 (/w/holger/server/cli)\nmonetize-embed v0.1.3 (/w/monetize/crates/monetize-embed)\nmonetize-product v0.1.4\nserde v1.0.228\nserde_derive v1.0.228 (proc-macro)\nholger-server-lib v0.6.0 (/w/holger/server/lib) (*)\n";
        assert!(
            offenders(clean).is_empty(),
            "open monetize crates are allowed"
        );
        let dirty = format!("{clean}monetize-pay-impl v0.3.0 (/w/monetize-impl/crates/monetize-pay-impl)\nmonetize-pay-impl v0.3.0 (/w/monetize-impl/crates/monetize-pay-impl) (*)\nrenamed-pro v0.1.0 (/w/monetize-impl/guests/pro)\nops-thing v0.1.0 (/w/private-monetize-ops/x)\nmonetize-plugin-holger v0.1.0\n");
        let names: Vec<String> = offenders(&dirty).into_iter().map(|o| o.name).collect();
        assert_eq!(
            names,
            [
                "monetize-pay-impl",
                "renamed-pro",
                "ops-thing",
                "monetize-plugin-holger"
            ]
        );
    }

    /// The lock check reads `[[package]]` names.
    #[test]
    fn the_lock_names_no_paid_crate_or_is_refused() {
        let clean = "version = 4\n\n[[package]]\nname = \"monetize-embed\"\nversion = \"0.1.3\"\n\n[[package]]\nname = \"serde\"\nversion = \"1.0.228\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\n";
        assert!(lock_offenders(clean).is_empty());
        let dirty = format!("{clean}\n[[package]]\nname = \"monetize-plugin-holger\"\nversion = \"0.1.0\"\n\n[[package]]\nname = \"monetize-pay-impl\"\nversion = \"0.3.0\"\n");
        assert_eq!(
            lock_offenders(&dirty),
            ["monetize-plugin-holger", "monetize-pay-impl"]
        );
    }
}