crates/nornir-fleet/fleet.runa.ron
// fleet.runa.ron — the compose-of-the-CONSTELLATION descriptor (the full 5-app fleet)
//
// The North-Star full-system-autotest harness, grounded in code
// (`.nornir/full-system-autotest-design.md`). This runa pipeline lowers onto engines
// that ALREADY EXIST — jera's boot ledger + draupnir OCI, each app's robotui/web
// scenarios, and nornir-testmatrix's Gap==∅ gate — via the `fleet` crate's
// FleetHarness (the edda-side reference dwarves' runa_exec mirrors).
//
// build ─▶ provision (jera→draupnir OCI) ─▶ drive (egui fat + web thin) ─▶ gate (Gap==∅)
//
// FIVE members — the constellation's UI apps (nornir · dwarves · holger · korp ·
// ordning). Each declares its FULL, CODE-SOURCED viz-tab surface (each tab ×
// {fat, thin}); the CoverageGate aggregates coverage ACROSS all five into ONE
// constellation-wide Gap==∅ verdict — a missing surface on ANY app reds the gate,
// naming `app:tab@leg`.
//
// Surface sourcing (verified from each app's canonical tab enumeration — NOT invented):
// • nornir — 28 tabs · `nornir/src/viz/app.rs` `Tab::ALL` / `Tab::id()`
// • dwarves — 18 tabs · `dwarves/dwarves-gui/src/main.rs` `TabbedView::new(...)`
// • holger — 11 tabs · `holger/server/ui/src/app.rs` `TAB_ORDER` / `slot_id()`
// • korp — 16 tabs · `korp/src/main.rs` `Tab::ALL` / `Tab::key()`
// • ordning — 12 views· `ordning/crates/ordning-gui/src/lib.rs` facet re-exports
//
// HONESTY (no false green): today ONLY dwarves mounts a `CmdJsonServer`
// (dwarves-gui `--robot-remote <socket>`), so a real run installs a
// `MountRouter` that drives dwarves for real and answers `Ok(None)` (probe-only)
// for the other four — their `@fat` nodes then read as not-yet-driven and the gate
// reds, naming each. The four apps carry a per-app mount TODO below; each flips to
// really-driven the moment it grows its own `--robot-remote`-style mount.
//
// Teardown is NOT a step: it is RampGuard-style RAII — the FleetHarness drops and
// every spun member is killed through jera's ledger (`draupnir down`), so a
// crashed run never leaks a container.
Pipeline(
name: "nordisk-fleet-of-five",
triggers: [ Manual ],
stages: [
// 1. BUILD — materialise each app's server/demo Container variant (build-thing).
// A real run lowers these to draupnir `build_image`; the mock declares the
// image ref directly on each SpinFleetMember below.
Stage(
name: "build",
steps: [
Build(variant: "nornir-server-container"),
Build(variant: "dwarves-gui-container"),
Build(variant: "holger-server-container"),
Build(variant: "korp-container"),
Build(variant: "ordning-demo-container"),
],
),
// 2. PROVISION — spin each member as an OCI container through jera → draupnir
// run_to_completion, holding on the healthcheck barrier until Ready. Each
// member's `surface` is its declared viz-tab set the gate requires proven
// (each tab must be driven BOTH fat and thin).
Stage(
name: "provision",
needs: ["build"],
steps: [
// ── nornir — the warehouse/viz app (28 tabs). ─────────────────────
SpinFleetMember(
name: "nornir",
image: "localhost/nornir-server:fleet",
ports: [50051, 8080],
env: [("NORNIR_ZONE", "test")],
surface: [
"Nornir", "Timeline", "DepGraph", "CallGraph", "Architecture",
"Metro", "Helix", "Bloodstream", "Funnel", "TimeTravel",
"LiveRun", "Release", "Knowledge", "Warehouse", "WarehouseDeck",
"Mcp", "Search", "Gates", "Bench", "Test", "Leaderboard",
"Chat", "Security", "Dwarves", "Holger", "Manual", "Claude",
"Models",
],
),
// ── dwarves — the CI/build forge demo (18 tabs). THE MOUNTED APP:
// dwarves-gui `--robot-remote <socket>` serves a CmdJsonServer, so
// the fat leg drives it for real over a MountRouter/UnixEguiWire. ──
SpinFleetMember(
name: "dwarves",
image: "localhost/dwarves-gui:fleet",
ports: [50052, 8081],
env: [("DWARVES_ZONE", "test")],
surface: [
"Warehouse", "Forge", "Pipeline", "CI/CD", "Builds", "Funnel",
"Claude", "Jera", "Tools", "Status", "Release", "Benches",
"Fleet", "Binary", "Coderoom", "Saga", "Settings", "Profile",
],
),
// ── holger — the artifact-registry operator UI (11 tabs). ─────────
// TODO(mount): holger-server must mount a CmdJsonServer over its
// egui UI (mirror dwarves-gui `--robot-remote <socket>`); until
// then MountRouter answers Ok(None) → probe-only (@fat uncovered).
SpinFleetMember(
name: "holger",
image: "localhost/holger-server:fleet",
ports: [50053, 8082],
env: [("HOLGER_ZONE", "test")],
surface: [
"status", "repos", "browse", "archive", "artifact", "graph",
"helix", "lineage", "tools", "upload", "signin",
],
),
// ── korp — the graph/geo analysis app (16 tabs). ──────────────────
// TODO(mount): korp must mount a CmdJsonServer over its egui UI
// (mirror dwarves-gui `--robot-remote <socket>`); until then
// MountRouter answers Ok(None) → probe-only (@fat uncovered).
SpinFleetMember(
name: "korp",
image: "localhost/korp:fleet",
ports: [50054, 8083],
env: [("KORP_ZONE", "test")],
surface: [
"demo", "pipelines", "spark", "graph", "falkordb", "data",
"tables", "ingest", "analysis", "map", "geo", "timeline",
"jobs", "lineage", "investigations", "settings",
],
),
// ── ordning — the planning UI (12 facet views). Serverless (embedded
// event-store, no network port) → declared with the demo image and
// an empty published-port set. ────────────────────────────────────
// TODO(mount): ordning-demo must mount a CmdJsonServer over its egui
// UI (mirror dwarves-gui `--robot-remote <socket>`); until then
// MountRouter answers Ok(None) → probe-only (@fat uncovered).
SpinFleetMember(
name: "ordning",
image: "localhost/ordning-demo:fleet",
ports: [],
env: [("ORDNING_ZONE", "test")],
surface: [
"calendar", "cfd", "file", "gantt", "kanban", "markdown",
"plan", "search", "sprint", "timeline", "toast", "workload",
],
),
],
),
// 3. DRIVE — run each member's egui (fat, wire-remote over its CmdJsonServer
// mount) + web (`nornir robot` WebDriver, thin) scenarios against the spun
// container, streaming a TestResultRow per scenario. Driving BOTH legs of
// every tab is what makes the gate green. dwarves is driven for real; the
// other four are probe-only until they mount (their @fat legs stay
// uncovered — the fleet is HONEST about which apps are really driven).
Stage(
name: "drive",
needs: ["provision"],
steps: [
RobotDriveMember(
member: "nornir",
egui_scenarios: [
"Nornir", "Timeline", "DepGraph", "CallGraph", "Architecture",
"Metro", "Helix", "Bloodstream", "Funnel", "TimeTravel",
"LiveRun", "Release", "Knowledge", "Warehouse", "WarehouseDeck",
"Mcp", "Search", "Gates", "Bench", "Test", "Leaderboard",
"Chat", "Security", "Dwarves", "Holger", "Manual", "Claude",
"Models",
],
web_scenarios: [
"Nornir", "Timeline", "DepGraph", "CallGraph", "Architecture",
"Metro", "Helix", "Bloodstream", "Funnel", "TimeTravel",
"LiveRun", "Release", "Knowledge", "Warehouse", "WarehouseDeck",
"Mcp", "Search", "Gates", "Bench", "Test", "Leaderboard",
"Chat", "Security", "Dwarves", "Holger", "Manual", "Claude",
"Models",
],
),
RobotDriveMember(
member: "dwarves",
egui_scenarios: [
"Warehouse", "Forge", "Pipeline", "CI/CD", "Builds", "Funnel",
"Claude", "Jera", "Tools", "Status", "Release", "Benches",
"Fleet", "Binary", "Coderoom", "Saga", "Settings", "Profile",
],
web_scenarios: [
"Warehouse", "Forge", "Pipeline", "CI/CD", "Builds", "Funnel",
"Claude", "Jera", "Tools", "Status", "Release", "Benches",
"Fleet", "Binary", "Coderoom", "Saga", "Settings", "Profile",
],
),
RobotDriveMember(
member: "holger",
egui_scenarios: [
"status", "repos", "browse", "archive", "artifact", "graph",
"helix", "lineage", "tools", "upload", "signin",
],
web_scenarios: [
"status", "repos", "browse", "archive", "artifact", "graph",
"helix", "lineage", "tools", "upload", "signin",
],
),
RobotDriveMember(
member: "korp",
egui_scenarios: [
"demo", "pipelines", "spark", "graph", "falkordb", "data",
"tables", "ingest", "analysis", "map", "geo", "timeline",
"jobs", "lineage", "investigations", "settings",
],
web_scenarios: [
"demo", "pipelines", "spark", "graph", "falkordb", "data",
"tables", "ingest", "analysis", "map", "geo", "timeline",
"jobs", "lineage", "investigations", "settings",
],
),
RobotDriveMember(
member: "ordning",
egui_scenarios: [
"calendar", "cfd", "file", "gantt", "kanban", "markdown",
"plan", "search", "sprint", "timeline", "toast", "workload",
],
web_scenarios: [
"calendar", "cfd", "file", "gantt", "kanban", "markdown",
"plan", "search", "sprint", "timeline", "toast", "workload",
],
),
],
),
// 4. GATE — nornir-testmatrix Surface − Covered − Allow = Gap; ASSERT Gap==∅
// across ALL FIVE members (one constellation-wide verdict). RED — naming
// the exact `app:tab@leg` — if any declared surface node went undriven.
// Today this is RED by construction: only dwarves mounts, so every other
// app's `@fat` nodes are probe-only (uncovered) until they mount.
Stage(
name: "gate",
needs: ["drive"],
steps: [
CoverageGate(workspace: "nordisk", allow: []),
],
),
],
)