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

nordisk/gunnar

xtask/src/variant_svg.rs

//! `cargo xtask svg` — **one picture per installer variant**: `solo`,
//! `gunnar-twin`, `gunnar-twin-one` (`.nornir/installer-build-thing-variants.md`).
//! There is exactly ONE monetize, the ONE box (owner 2026-10-12): it is drawn OUTSIDE
//! the estate, never on its front.
//!
//! A Build Thing variant carries an SVG of the system it installs. Each picture
//! here is a crop of ONE model, drawn by `setup_svg`'s renderer under its
//! layout guards: the machines (`vm1`, `vm2`, `vm3`), the one medium they boot,
//! the door they join, and the ONE monetize the boxes pull from.
//!
//! Two facts are pinned by test rather than left to the eye: main and twin boot
//! the SAME medium (ruling of 2026-09-21), and both of them JOIN the door (the
//! installer pairs them; the laptop does not).
//!
//! `where = kvm | upcloud` is a setting on every variant, not a picture: the
//! same install on both, so the same drawing.

use crate::setup_svg::{Kind, Model, Role, Route, Scene, Unit, Wire};

/// The installer's plane.
pub const W: f64 = 1040.0;
pub const H: f64 = 560.0;

pub const UNITS: &[Unit] = &[
    Unit {
        id: "front",
        role: Role::Front,
        kicker: Some("vm1"),
        title: "front",
        lines: &[
            "door :8474 \u{b7} mutual TLS \u{b7} elects the twin",
            "relay :51820 \u{b7} clock :50123 \u{b7} gunnar-ui",
        ],
        x: 320.0,
        y: 90.0,
        w: 680.0,
        h: 90.0,
    },
    Unit {
        id: "one",
        role: Role::Money,
        kicker: Some("outside the estate"),
        title: "the ONE monetize",
        lines: &[
            "monetize.ignalina.dk",
            "the products are shown here",
            "it dials no estate",
        ],
        x: 24.0,
        y: 90.0,
        w: 250.0,
        h: 100.0,
    },
    Unit {
        id: "main",
        role: Role::Main,
        kicker: Some("vm2"),
        title: "main",
        lines: &[
            "writable \u{2014} clients push here",
            "git-ssh :2222 \u{b7} control :50051",
            "primary, by the door's vote",
        ],
        x: 320.0,
        y: 290.0,
        w: 280.0,
        h: 108.0,
    },
    // Drawn only in `solo`, where it is the one machine: vm1, in main's place.
    Unit {
        id: "solo",
        role: Role::Main,
        kicker: Some("vm1"),
        title: "gunnar",
        lines: &[
            "writable \u{2014} clients push here",
            "git-ssh :2222 \u{b7} control :50051",
            "no door, no twin, no money",
        ],
        x: 320.0,
        y: 290.0,
        w: 280.0,
        h: 108.0,
    },
    Unit {
        id: "twin",
        role: Role::Twin,
        kicker: Some("vm3"),
        title: "twin",
        lines: &[
            "the SAME medium as main",
            "follows the primary's log",
            "the weaker box, by the door",
        ],
        x: 720.0,
        y: 290.0,
        w: 280.0,
        h: 108.0,
    },
    Unit {
        id: "medium",
        role: Role::Client,
        kicker: Some("the installer"),
        title: "one medium",
        lines: &[
            "the door's address + the installer CA",
            "the SAME bytes on every box",
        ],
        x: 320.0,
        y: 452.0,
        w: 680.0,
        h: 78.0,
    },
    // `solo`'s medium: the same installer with no seed — there is no door to join.
    Unit {
        id: "solo-medium",
        role: Role::Client,
        kicker: Some("the installer"),
        title: "one medium",
        lines: &[
            "gunnar-installer, no pairing seed",
            "nothing to join: the box is the estate",
        ],
        x: 320.0,
        y: 452.0,
        w: 680.0,
        h: 78.0,
    },
];

pub const WIRES: &[Wire] = &[
    Wire {
        from: "solo-medium",
        to: "solo",
        route: Route::Rise { dx: 200.0 },
        kind: Kind::Flow,
        label: "boots",
        note: "",
    },
    Wire {
        from: "medium",
        to: "main",
        route: Route::Rise { dx: 200.0 },
        kind: Kind::Flow,
        label: "boots",
        note: "",
    },
    Wire {
        from: "medium",
        to: "twin",
        route: Route::Rise { dx: 600.0 },
        kind: Kind::Flow,
        label: "boots",
        note: "the same bytes",
    },
    Wire {
        from: "main",
        to: "front",
        route: Route::Rise { dx: 60.0 },
        kind: Kind::Flow,
        label: "join :8474",
        note: "mTLS",
    },
    Wire {
        from: "twin",
        to: "front",
        route: Route::Rise { dx: 60.0 },
        kind: Kind::Flow,
        label: "join :8474",
        note: "mTLS",
    },
    Wire {
        from: "main",
        to: "twin",
        route: Route::Across { dy: 30.0 },
        kind: Kind::Flow,
        label: "follows",
        note: "in the tunnel",
    },
    Wire {
        from: "one",
        to: "main",
        route: Route::Elbow { dx: 110.0 },
        kind: Kind::Flow,
        label: "orders",
        note: "pulled by the boxes",
    },
];

pub const VARIANTS: Model = Model {
    units: UNITS,
    wires: WIRES,
};

const WHERE: &str = "where = kvm | upcloud \u{2014} the same install, so the same picture";

pub const SCENES: &[Scene] = &[
    Scene {
        model: &VARIANTS,
        asset: ".nornir/assets/solo.svg",
        title: "solo \u{b7} one gunnar box",
        subtitle: "one medium, one machine: no door, no twin, no monetize",
        units: &["solo-medium", "solo"],
        omit: &[],
        footer: &[WHERE],
    },
    Scene {
        model: &VARIANTS,
        asset: ".nornir/assets/gunnar-twin.svg",
        title: "gunnar-twin \u{b7} main + twin, paired by the door",
        subtitle: "both boxes boot the same medium and join the front's door; the door elects the twin",
        units: &["front", "main", "twin", "medium"],
        omit: &[],
        footer: &["open: does gunnar-twin keep vm1 for the door, or does the door move onto a box?", WHERE],
    },
    Scene {
        model: &VARIANTS,
        asset: ".nornir/assets/gunnar-twin-one.svg",
        title: "gunnar-twin-one \u{b7} the estate books in the ONE monetize",
        subtitle: "gunnar-twin with metered boxes; the front runs no monetize, the boxes pull from the ONE box",
        units: &["front", "one", "main", "twin", "medium"],
        omit: &[],
        footer: &["enrolled first: `cargo xtask attach enrol` (private-gunnar-ops)", WHERE],
    },
];

#[cfg(test)]
mod tests {
    use super::*;
    use crate::setup_svg::render_scene;
    use crate::setup_svg::tests::{assert_scene_layout, assert_units_fit};

    #[test]
    fn the_model_closes() {
        let mut seen = std::collections::BTreeSet::new();
        for u in UNITS {
            assert!(
                seen.insert(u.id),
                "variant_svg: two units called {:?}",
                u.id
            );
            assert!(
                u.x >= 0.0 && u.right() <= W && u.y >= 0.0 && u.bottom() <= H,
                "variant_svg: {} is off the plane",
                u.id
            );
        }
        for w in WIRES {
            assert!(
                seen.contains(w.from) && seen.contains(w.to),
                "variant_svg: a wire names no unit: {} → {}",
                w.from,
                w.to
            );
        }
        assert_units_fit(UNITS);
    }

    /// One picture per variant the owner named, no more, no fewer.
    #[test]
    fn one_picture_per_variant() {
        let names: Vec<&str> = SCENES
            .iter()
            .map(|s| {
                s.asset
                    .trim_start_matches(".nornir/assets/")
                    .trim_end_matches(".svg")
            })
            .collect();
        // Owner 2026-10-12: there is exactly ONE monetize, the ONE box — no variant puts a
        // monetize on the estate's front; the ONE variant draws it OUTSIDE the estate.
        assert_eq!(names, ["solo", "gunnar-twin", "gunnar-twin-one"]);
        assert!(
            !SCENES
                .iter()
                .any(|s| s.has("monetize") || s.has("provided")),
            "a monetize drawn on the estate"
        );
        for u in UNITS {
            assert!(
                SCENES.iter().any(|s| s.has(u.id)),
                "variant_svg: unit {:?} is in no picture",
                u.id
            );
        }
    }

    /// Main and twin boot the SAME medium, and both JOIN the door — in every
    /// picture that draws a twin. A picture where the laptop pairs them, or the
    /// twin has a medium of its own, is the design this module exists to refuse.
    #[test]
    fn the_installer_pairs_both_boxes_from_one_medium() {
        for sc in SCENES.iter().filter(|s| s.has("twin")) {
            let wires = sc.wires();
            for b in ["main", "twin"] {
                assert!(
                    wires.iter().any(|w| w.from == "medium" && w.to == b),
                    "{}: {b} does not boot the medium",
                    sc.asset
                );
                assert!(
                    wires.iter().any(|w| w.from == b && w.to == "front"),
                    "{}: {b} does not join the door",
                    sc.asset
                );
            }
        }
    }

    #[test]
    fn nothing_is_drawn_on_top_of_anything() {
        for sc in SCENES {
            assert_scene_layout(sc);
        }
    }

    #[test]
    fn every_box_is_drawn() {
        for sc in SCENES {
            let svg = render_scene(sc);
            for u in sc.units() {
                assert!(
                    svg.contains(&crate::cycle_svg::esc(u.title)),
                    "{}: {} is not drawn",
                    sc.asset,
                    u.id
                );
            }
        }
    }

    #[test]
    fn the_committed_asset_is_fresh() {
        for sc in SCENES {
            crate::cycle_svg::assert_asset_fresh(
                &crate::guard::workspace_root(),
                sc.asset,
                &render_scene(sc),
                "cargo xtask svg",
            );
        }
    }
}