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

nordisk/dwarves

xtask/src/build_flow_svg.rs

//! `cargo xtask build-flow-svg` — the `Build{variant}` flow drawn from a **data
//! model of audited steps**, so the picture cannot claim a step is
//! declaration-driven when the code decides it.
//!
//! # Why a generator and not a drawing
//!
//! The whole point of this diagram is one distinction — which steps the
//! DECLARATION orders (variant → features → settings/sub-settings → typ) and
//! which are fixed in Rust. A hand-drawn picture of that distinction rots
//! silently: the boxes keep rendering after the code moves. So every node below
//! is a `const` carrying an [`Origin`] and, when it is hard-coded, the
//! `file:line` where that was read on 2026-09-10. There is no way to draw a step
//! as declaration-driven without changing the datum.
//!
//! `xtask/tests/build_flow_svg.rs` asserts the two verdicts actually render
//! differently — a legend that says "coral means hard-coded" while both halves
//! render cyan is exactly the silent green this constellation keeps finding.
//!
//! # Style
//!
//! The `skade` circuit-board house style (`../skade/.nornir/assets/*.svg`):
//! `#0b1c2e`→`#06111d` board gradient, `chip`/`rail` gradients, DejaVu Sans,
//! cyan `#6fd3f5` for the declared signal path, coral `#f0a9a0` for the
//! hard-coded half. Those files are hand-written; this one is not, which is the
//! only intentional departure — the same departure `gunnar/xtask/src/cycle_svg.rs`
//! makes, and this file is that file's idiom applied to the build flow.
//!
//! Rendered to `.nornir/assets/build-flow.svg`; `std` only.

use std::fmt::Write as _;
use std::path::{Path, PathBuf};

/// Where a step's decision comes from.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Origin {
    /// The DECLARATION orders it: the variant's `kind`, its features, its
    /// settings tree or the BUILT-edge DAG decides, and a recipe change changes
    /// the build with no code change.
    Declared,
    /// FIXED IN RUST: the order, the arm or the path is a literal. `at` is where
    /// it was read.
    HardCoded { at: Cite },
    /// Typed, transported over the wasm ABI — and never executed. The plan half
    /// of "the build thing returns a plan and the forge executes it" exists;
    /// the execute half does not.
    TypedNeverRun { at: Cite },
}

/// A repo-relative citation, so the claim can be OPENED rather than merely read.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Cite {
    /// Repo-relative path, `edda:` prefixed when it is the sibling checkout.
    pub file: &'static str,
    /// The line the claim rests on, as read 2026-09-10.
    pub line: u32,
}

impl Cite {
    const fn new(file: &'static str, line: u32) -> Self {
        Cite { file, line }
    }
    fn render(&self) -> String {
        format!("{}:{}", self.file, self.line)
    }
}

/// One box on the flow.
#[derive(Debug, Clone, Copy)]
pub struct Step {
    /// The short name on the box.
    pub name: &'static str,
    /// One line of what it decides.
    pub what: &'static str,
    /// Which lane (column) it sits in: 0 = the doors, 1 = the forge, 2 = the
    /// evaluator, 3 = the isolation layer.
    pub lane: u8,
    /// Row within the lane.
    pub row: u8,
    /// Where the decision comes from.
    pub origin: Origin,
}

const fn declared(name: &'static str, what: &'static str, lane: u8, row: u8) -> Step {
    Step {
        name,
        what,
        lane,
        row,
        origin: Origin::Declared,
    }
}
const fn fixed(
    name: &'static str,
    what: &'static str,
    lane: u8,
    row: u8,
    file: &'static str,
    line: u32,
) -> Step {
    Step {
        name,
        what,
        lane,
        row,
        origin: Origin::HardCoded {
            at: Cite::new(file, line),
        },
    }
}

/// **The audited flow**, 2026-09-10. Three doors, one funnel, one dispatch
/// chain, one isolation layer.
pub const STEPS: &[Step] = &[
    // ── lane 0: the three doors ─────────────────────────────────────────────
    declared("CLI", "dwarves buildthing <p> --variant V", 0, 0),
    declared("rpc", "dwarf.v1.BuildThing (the --server door)", 0, 1),
    declared("⚒ pane", "Act::Build{row,target,settings}", 0, 2),
    // ── lane 1: the forge ───────────────────────────────────────────────────
    fixed(
        "prologue",
        "resolve → stale-reader → refuse-build-flags → recipe line",
        1,
        0,
        "crates/dwarves/src/buildthing.rs",
        988,
    ),
    fixed(
        "phase order",
        "code road → opts → kernel axis → plan → resolve → loop → GATE",
        1,
        1,
        "crates/dwarves/src/buildthing.rs",
        1306,
    ),
    declared(
        "selection",
        "suite_cli::resolve — WHICH variants, from the set",
        1,
        2,
    ),
    fixed(
        "the funnel",
        "BuildRun::spawn_recording_with_selection — every door lands here",
        1,
        3,
        "crates/dwarves/src/build_thing.rs",
        1813,
    ),
    fixed(
        "typ default",
        "kind_of(v).unwrap_or(Exe) — an undeclared row silently becomes cargo",
        1,
        4,
        "crates/dwarves/src/build_thing.rs",
        1841,
    ),
    // ── lane 2: the evaluator ───────────────────────────────────────────────
    declared(
        "is_cargo(typ)",
        "Exe|Lib|Wasm|Service → cargo; else assemble",
        2,
        0,
    ),
    fixed(
        "dispatch(kind)",
        "the 7-arm builder match — one arm per typ, in Rust",
        2,
        1,
        "edda:nornir-build-thing/src/assemble.rs",
        955,
    ),
    fixed(
        "assemble_cell",
        "dwarves' SECOND full kind chain over the same typs",
        2,
        2,
        "crates/dwarves/src/assemble_cell.rs",
        395,
    ),
    declared(
        "settings tree",
        "--opt / --param / presets resolve against the declaration",
        2,
        3,
    ),
    declared(
        "BUILT edges",
        "BuildDag::derive — the chain's order IS the DAG",
        2,
        4,
    ),
    Step {
        name: "BuildPlan",
        what: "typed, ABI-transported, NEVER executed — dwarves never calls plan()",
        lane: 2,
        row: 5,
        origin: Origin::TypedNeverRun {
            at: Cite::new("edda:nornir-build-thing/src/plugin.rs", 990),
        },
    },
    // ── lane 3: isolation + output ──────────────────────────────────────────
    declared(
        "container?",
        "[build.toolchain] present → container; absent → HOST",
        3,
        0,
    ),
    fixed(
        "host fallback",
        "no toolchain ⇒ build on the host — undeclarable, unrefusable",
        3,
        1,
        "crates/dwarves/src/build_thing.rs",
        1917,
    ),
    fixed(
        "kernel runner",
        "DraupnirKernelRunner + ContainerKernelBuilder, behind a cfg!",
        3,
        2,
        "crates/dwarves/src/assemble_cell.rs",
        553,
    ),
    fixed(
        "kernel script",
        "the in-container step list is a String builder: make → merge → bzImage",
        3,
        3,
        "edda:nornir-build-thing/src/kernel.rs",
        1677,
    ),
    fixed(
        "output dirs",
        ".iso-out · .kernel-out/<name> · target/ · /usr/local/bin",
        3,
        4,
        "edda:nornir-build-thing/src/image_build.rs",
        760,
    ),
];

/// The palette, named once.
mod paint {
    /// The declared signal path.
    pub const CYAN: &str = "#6fd3f5";
    /// The hard-coded half.
    pub const CORAL: &str = "#f0a9a0";
    /// Typed but never executed.
    pub const AMBER: &str = "#e8c46a";
    pub const INK: &str = "#eaf6ff";
    pub const DIM: &str = "#9fc9e0";
    pub const RULE: &str = "#143a52";
}

const W: u32 = 1180;
const LANE_X: [u32; 4] = [40, 320, 620, 920];
const BOX_W: u32 = 250;
const BOX_H: u32 = 52;
const ROW_H: u32 = 68;
const TOP: u32 = 118;

fn box_xy(s: &Step) -> (u32, u32) {
    (LANE_X[s.lane as usize], TOP + u32::from(s.row) * ROW_H)
}

/// The stroke a step is drawn with — THE distinction the whole diagram exists
/// for, and the only place the two verdicts may differ.
fn stroke_of(o: &Origin) -> &'static str {
    match o {
        Origin::Declared => paint::CYAN,
        Origin::HardCoded { .. } => paint::CORAL,
        Origin::TypedNeverRun { .. } => paint::AMBER,
    }
}

/// Hard-coded steps are drawn DASHED as well as coral, so the distinction
/// survives a greyscale print and a colour-blind reader.
fn dash_of(o: &Origin) -> &'static str {
    match o {
        Origin::Declared => "",
        Origin::HardCoded { .. } => " stroke-dasharray=\"6 4\"",
        Origin::TypedNeverRun { .. } => " stroke-dasharray=\"2 5\"",
    }
}

fn esc(s: &str) -> String {
    s.replace('&', "&amp;")
        .replace('<', "&lt;")
        .replace('>', "&gt;")
}

/// Render the whole diagram.
#[must_use]
pub fn svg() -> String {
    let rows = STEPS.iter().map(|s| u32::from(s.row)).max().unwrap_or(0);
    let height = TOP + (rows + 1) * ROW_H + 96;
    let mut o = String::with_capacity(16_000);

    let _ = write!(
        o,
        r##"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {W} {height}" font-family="'DejaVu Sans', 'Segoe UI', sans-serif">
  <defs>
    <linearGradient id="board" x1="0" y1="0" x2="0" y2="1">
      <stop offset="0" stop-color="#0b1c2e"/><stop offset="1" stop-color="#06111d"/>
    </linearGradient>
    <linearGradient id="chip" x1="0" y1="0" x2="0" y2="1">
      <stop offset="0" stop-color="#14384f"/><stop offset="1" stop-color="#0e2a3e"/>
    </linearGradient>
    <linearGradient id="rail" x1="0" y1="0" x2="0" y2="1">
      <stop offset="0" stop-color="#173d57"/><stop offset="1" stop-color="#0c2438"/>
    </linearGradient>
    <marker id="arrow" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
      <path d="M0 0 L10 5 L0 10 z" fill="{cyan}"/>
    </marker>
  </defs>
  <rect x="0" y="0" width="{W}" height="{height}" fill="url(#board)"/>
  <g stroke="{rule}" stroke-width="1"><line x1="0" y1="96" x2="{W}" y2="96"/></g>
  <text x="40" y="40" fill="{ink}" font-size="22" font-weight="700">build thing &#8212; where the flow is DECLARED and where it is CODE</text>
  <text x="40" y="62" fill="{dim}" font-size="14">every box carries the file:line it was audited at, 2026-09-10 &#8212; generated by <tspan fill="#bfe6ff">cargo xtask build-flow-svg</tspan>, never drawn</text>
"##,
        cyan = paint::CYAN,
        rule = paint::RULE,
        ink = paint::INK,
        dim = paint::DIM,
    );

    // Lane headings.
    for (i, label) in [
        "the DOORS",
        "the FORGE",
        "the EVALUATOR",
        "ISOLATION + OUTPUT",
    ]
    .iter()
    .enumerate()
    {
        let _ = writeln!(
            o,
            "  <text x=\"{}\" y=\"{}\" fill=\"{}\" font-size=\"13\" font-weight=\"700\" letter-spacing=\"1\">{}</text>",
            LANE_X[i],
            TOP - 20,
            paint::DIM,
            label
        );
    }

    // The lane-to-lane rails, drawn first so boxes sit on top.
    for i in 0..3 {
        let x1 = LANE_X[i] + BOX_W;
        let x2 = LANE_X[i + 1];
        let _ = writeln!(
            o,
            "  <line x1=\"{x1}\" y1=\"{y}\" x2=\"{x2}\" y2=\"{y}\" stroke=\"{c}\" stroke-width=\"2\" marker-end=\"url(#arrow)\" opacity=\"0.55\"/>",
            y = TOP + BOX_H / 2,
            c = paint::CYAN,
        );
    }

    // The boxes.
    for s in STEPS {
        let (x, y) = box_xy(s);
        let stroke = stroke_of(&s.origin);
        let _ = writeln!(
            o,
            "  <rect x=\"{x}\" y=\"{y}\" width=\"{BOX_W}\" height=\"{BOX_H}\" rx=\"6\" fill=\"url(#chip)\" stroke=\"{stroke}\" stroke-width=\"1.6\"{dash}/>",
            dash = dash_of(&s.origin)
        );
        let _ = writeln!(
            o,
            "  <text x=\"{tx}\" y=\"{ty}\" fill=\"{ink}\" font-size=\"13\" font-weight=\"700\">{n}</text>",
            tx = x + 10,
            ty = y + 19,
            ink = paint::INK,
            n = esc(s.name)
        );
        let _ = writeln!(
            o,
            "  <text x=\"{tx}\" y=\"{ty}\" fill=\"{dim}\" font-size=\"10.5\">{w}</text>",
            tx = x + 10,
            ty = y + 33,
            dim = paint::DIM,
            w = esc(s.what)
        );
        // The citation IS the claim: a hard-coded box that could not name where
        // it was read would be a drawing again.
        let cite = match &s.origin {
            Origin::Declared => "from the declaration".to_string(),
            Origin::HardCoded { at } | Origin::TypedNeverRun { at } => at.render(),
        };
        let _ = writeln!(
            o,
            "  <text x=\"{tx}\" y=\"{ty}\" fill=\"{c}\" font-size=\"9.5\" font-style=\"italic\">{cite}</text>",
            tx = x + 10,
            ty = y + 46,
            c = stroke,
            cite = esc(&cite)
        );
    }

    // The legend + the count, which is the diagram's verdict in two numbers.
    let hard = STEPS
        .iter()
        .filter(|s| matches!(s.origin, Origin::HardCoded { .. }))
        .count();
    let decl = STEPS
        .iter()
        .filter(|s| matches!(s.origin, Origin::Declared))
        .count();
    let ly = height - 56;
    let _ = write!(
        o,
        r##"  <rect x="40" y="{ly}" width="{lw}" height="40" rx="6" fill="url(#rail)" stroke="{rule}"/>
  <line x1="56" y1="{lty}" x2="88" y2="{lty}" stroke="{cyan}" stroke-width="2.4"/>
  <text x="96" y="{lt}" fill="{ink}" font-size="12">DECLARED &#183; {decl} steps the recipe orders</text>
  <line x1="356" y1="{lty}" x2="388" y2="{lty}" stroke="{coral}" stroke-width="2.4" stroke-dasharray="6 4"/>
  <text x="396" y="{lt}" fill="{ink}" font-size="12">HARD-CODED &#183; {hard} steps fixed in Rust</text>
  <line x1="686" y1="{lty}" x2="718" y2="{lty}" stroke="{amber}" stroke-width="2.4" stroke-dasharray="2 5"/>
  <text x="726" y="{lt}" fill="{ink}" font-size="12">TYPED, NEVER EXECUTED &#183; BuildPlan/PlanStep</text>
</svg>
"##,
        lw = W - 80,
        lty = ly + 20,
        lt = ly + 24,
        cyan = paint::CYAN,
        coral = paint::CORAL,
        amber = paint::AMBER,
        ink = paint::INK,
        rule = paint::RULE,
    );
    o
}

/// Write the diagram to `<repo>/.nornir/assets/build-flow.svg`.
///
/// # Errors
/// Any I/O failure creating the directory or writing the file.
pub fn run(workspace_root: &Path) -> std::io::Result<PathBuf> {
    let dir = workspace_root.join(".nornir").join("assets");
    std::fs::create_dir_all(&dir)?;
    let dst = dir.join("build-flow.svg");
    std::fs::write(&dst, svg())?;
    Ok(dst)
}