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

nordisk/dwarves

dwarves-gui/Cargo.toml

[package]
name = "dwarves-gui"
version = "0.1.4"
edition = "2021"
description = "dwarves's GUI — the native + web desktop client (`dwarves-ui`). Mounts an INTERIM facett render of the CI/CD forge view (a pipeline roster over a run listing) on the reusable `facett-wasm-demo` harness (native window + wasm/WebGPU canvas on Codeberg Pages). Its own crate; NOT wired into the `dwarves` CLI. Upgrades to the shared `facett-cicd` component when built."
license = "MIT OR Apache-2.0"
repository = "https://git.gunnar.rs/nordisk/dwarves"
# publish = true (2026-08-23). The `false` named no condition to lift — its own note
# said only "its own crate; NOT wired into the `dwarves` CLI", which is a statement
# about the dependency graph, not a reason to withhold it from the registry. The owner
# asked for the GPU UI variants on crates.io, and `dwarves-ui-gpu` is a `[[bin]]` of
# THIS package (`required-features = ["gpu"]`), so the package is what has to ship.
publish = true

# The build script transcodes the committed `assets/dwarves.webp` forge logo into a
# PNG in OUT_DIR (the shared `facett-about` widget decodes PNG only). Pure Rust, no
# shell (L9); the artist source stays the `.webp`.
build = "build.rs"

# The desktop UI binary. The launch name is `dwarves-ui` for a uniform `<repo>-ui`
# name across the constellation (matches korp-ui / nornir-ui; `<repo>-server` = the
# service, `<repo>` = the CLI); the PACKAGE around it was `dwarves-demo` until
# 2026-08-21 (renamed on 08-07, landed 08-21) and is now `dwarves-gui`, because "demo"
# had stopped being true — this crate is the shipped GUI, not a sample of one, and the
# old name sent every reader looking for the real UI somewhere else.
[[bin]]
name = "dwarves-ui"
path = "src/main.rs"

# `dwarves-solo` — **the UI with its server compiled in.** One binary, nothing to
# install, no port to configure, no second process to keep alive: `main.rs` starts
# `dwarves::server::serve_all` on a background runtime and then opens the same window
# `dwarves-ui` opens. `role = "solo"` in the fleet build matrix
# (workspace_nordisk/.nornir/build-matrix-2026-08-02.md), and a distinct `[[bin]]` off
# ONE `main.rs` — the shape holger uses and the reason `check_bins` exists: two shapes of
# one source MUST NOT emit one file name.
#
# `required-features = ["solo"]` because the feature is what compiles the server in;
# without it this bin would be a second `dwarves-ui` under a different name. Note the
# cost of that gate: `cargo build --all-targets` SKIPS a `required-features` target in
# silence, which is how `dwarves-mcp` went unbuilt for weeks. It is safe here only
# because a declared variant names it — `cargo xtask build --suite ORIGINAL --variant
# dwarves-solo` — and `crates/dwarves/tests/build_variants.rs` fails if any `[[bin]]`
# in the manifests is not named by a variant.
[[bin]]
name = "dwarves-solo"
path = "src/main.rs"
required-features = ["solo"]

# ── dwarves-ui-gpu: the SAME client, built with the instanced GPU renderer linked ──
#
# The GPU/CPU axis had no build point in dwarves at all. `nornir-warehouse-nav` has
# carried `gpu = ["facett-graphpan/wgpu", "facett-core/wgpu"]` — UNIVERSE mode's
# instanced star-field — since it was written, and NOTHING enabled it, so a box picked
# for its adapter (oden: RTX 4090) could not be given a UI that says so on disk. Added
# 2026-08-16, after korp settled the pattern with `korp-server-gpu`.
#
# A second `[[bin]]` off the SAME `src/main.rs` as `dwarves-ui`, for the reason
# `dwarves-solo` above states: a cargo build point lands at exactly ONE path, so two
# variants both emitting `dwarves-ui` would be one file written twice with nothing on
# disk saying which shape you had — what `BuildVariantSet::check_bins()` exists to catch.
#
# `required-features` is what makes THIS name true, the `dwarves-solo` discipline: there
# is no way to produce a file called `dwarves-ui-gpu` without the wgpu cloud renderer, so
# the GPU half of the pair can never be quietly built without it. The CPU half is the
# plain `dwarves-ui`, whose name cannot carry a "must NOT have".
#
# **What it does and does NOT change, stated because a name must not overclaim.**
# egui/eframe already render on wgpu in BOTH binaries — this is not "the UI moves to the
# GPU". What it turns on is one lane: the Warehouse Navigator's UNIVERSE mode paints its
# node cloud through `facett_core::render::gpu::graphcloud` instead of the CPU dot
# painter. Every other tab is byte-identical. `paint_scene_opts` is fail-safe (no
# installed renderer ⇒ CPU painter, `gpu_cloud == false`), so the honest signal is the
# pane's own `state_json.universe_gpu_cloud`, not the file name — see
# `crates/dwarves/tests/build_variants.rs::the_gpu_and_cpu_ui_atoms_differ_only_in_the_renderer`
# (the citation said `tests/gpu_atom.rs`, a path that has never existed in this repo; the
# guard is real, its address was not — corrected 2026-08-27).
#
# ⚠ AND THAT GUARD GUARDS THE DECLARATION, NOT THE ARTIFACT. MEASURED on t14s
# 2026-08-27: `/usr/local/bin/dwarves-ui` and `/usr/local/bin/dwarves-ui-gpu` were
# BYTE-IDENTICAL — both sha256 `6eb9ec98717b270e…`, both equal to the `dwarves-ui-gpu`
# in that box'"'"'s target tree, which held no `dwarves-ui` at all. The declaration was
# correct the whole time; the INSTALL wrote one file under two names, and nothing either
# file printed could reveal it. That half is closed by `main.rs`'"'"'s `VARIANT`
# (`env!("CARGO_BIN_NAME")`): `--version` and the window title now name the artifact.
[[bin]]
name = "dwarves-ui-gpu"
path = "src/main.rs"
required-features = ["gpu"]

# ── DELETED 2026-08-03: `[[bin]] dwarves-ui-fat` ──────────────────────────────────
#
# It was added on 2026-08-02 (de004d2) because `dwarves-ui` and `dwarves-ui-fat` were two
# variants writing ONE file, and a bin name is a FILE PATH. That fix was right for the
# declaration it had; the declaration is what changed. The fleet matrix has exactly five
# dwarves atoms and `dwarves-ui-fat` is not one of them ("ta bort allt inte stämmer med
# matrisen", the owner 2026-08-03).
#
# Its capability is NOT dropped — that would trade a mis-shaped declaration for a lost
# feature. `dupes-fat` (the local Iceberg read the thin gRPC seam provably cannot carry:
# a 768-float `list<float32>` per row does not survive a stringified `scan_preview`, so
# the SEMANTIC dupe tier is unavailable over the wire) is now part of `solo`, which is
# where it belongs: solo is the FAT UI. The `dupes-fat` feature therefore still has a
# builder and does not rot, which was the whole reason `dwarves-ui-fat` existed.

[build-dependencies]
# webp DECODE (source logo) + png ENCODE (what facett-about consumes). Build-time
# only — no runtime image dep, no wasm impact.
image = { version = "0.25", default-features = false, features = ["webp", "png"] }
# The BUILD RECEIPT writer + the shared sha256 primitive. Build-dependencies because the
# receipt must be inside the artifact, in a linker section, BEFORE the artifact exists —
# nothing at run time can put it there — and because `build.rs` is the only place that
# sees the RESOLVED feature set. Without these, `dwarves-ui` built green carrying no
# `.nordisk.buildthing` section and `--attest` could not read what built it.
nornir-build-thing = { path = "../../edda/crates/nornir-build-thing", version = "0.2.0" }
nornir-hash = { path = "../../edda/crates/nornir-hash", version = "0.1.1" }

# ── the reusable demo host + the panel contract ──────────────────────────────
# The harness (`NativeDemo::run` / `WebDemo::mount` / `publish_state` / `IndexHtml`)
# and the `facett_core::Facet` panel contract. The forge view is rendered with
# `facett_table::Table` — the SAME generic facett component holger-ui's Repos/Browse
# tabs use — fed a dwarves-shaped fixture (an INTERIM render until the shared
# `facett-cicd` forge component is built; same harness API either way).
#
# Path deps to the sibling facett checkout (the facett crates reference each other by
# path internally, so cargo unifies on the same sources — no `[patch.crates-io]`
# needed). `version = "0.1"` lets these flip to published crate versions later.
[dependencies]
facett-wasm-demo = { path = "../../facett/facett-wasm-demo", version = "0.2.0" }
facett-core = { path = "../../facett/facett-core", version = "0.3.0" }
# The 🌊 Codeflow tab's flow graph — the shared host-clock animated flow component
# (`FlowGraph`): `rate` makes each line flow at ITS measured calls/s, `layered`
# lays the call DAG out, `labels` names the nodes.
facett-flow = { path = "../../facett/facett-flow", version = "0.2.0", features = ["host", "labels", "rate", "layered", "compact-state"] }
# The shared brokkr RESULTS component — the real forge view (run roster + tree +
# DAG) that replaces the interim two-table `ForgeView` render. It mirrors dwarves'
# own brokkr Elm shape, so this is a clean one-way edge (no dwarves→facett cycle).
facett-brokkrview = { path = "../../facett/facett-brokkrview", version = "0.2.0" }
# The direct-Claude conversation pane — dwarves' 3rd tab. A pure facett-Elm leaf
# (no nornir/reqwest dep); the Anthropic call rides an injected `ClaudeTransport`.
# dwarves-gui wires the OFFLINE stub transport (below) — the LIVE transport is a
# queued single-source task (needs a SHARED Claude-client crate; nornir's
# `viz::claude::HttpTransport` is nornir-internal and must NOT be duplicated here).
facett-claude = { path = "../../facett/facett-claude", version = "0.2.0" }
# The shared job/task-list widget (task-manager tree — sort/fold/kind-filter, agent-
# aware) — dwarves' Jobs pane. Fleet-consistent: the SAME component nornir/holger/korp
# mount; dwarves feeds it a jera-shaped fixture (a run per parent, a dispatched step
# per child tagged with its agent). Host-agnostic (no nornir/gRPC), so it runs in the
# native tabbed shell and browser-safe for wasm.
facett-jobview = { path = "../../facett/facett-jobview", version = "0.2.0" }
# The SHARED producer registry behind a step bar (edda). dwarves publishes its
# populate phases into the same `Board` korp's collectors publish into, so the two
# bars are one widget over one vocabulary rather than two that drift. The Determinate
# half is ours: `clone_events`/member sync_state give a real (n_ok, N), where korp's
# four steps are all `count: None` by design.
step-board = { path = "../../edda/crates/step-board", version = "0.2.0" }
# 📖 The SHARED embedded Manual pane — one manual-pane core dwarves AND korp mount
# (facett-manual: searchable, sidebar-grouped markdown docview; reuses facett-markdown's
# renderer). The doc set is embedded per-app (see `rust-embed` + the `manual-embed`
# feature + `src/manual.rs`); this crate is just the shared UI + search.
facett-manual = { path = "../../facett/facett-manual", version = "0.2.0" }
# Embeds dwarves' `.nornir/*.md` doc set INTO the binary (compile-time include_bytes via
# the `manual-embed` feature — `debug-embed` so it embeds in debug builds too, not just
# release). Optional: OFF → the Manual tab honestly says "not embedded in this build".
rust-embed = { version = "8", features = ["debug-embed", "include-exclude"], optional = true }
# ── THE PANIC RECORD (edda) ───────────────────────────────────────────────────
# The shared `std::panic` hook that writes a durable, findable report — payload,
# location, thread, FORCED backtrace, build stamp and the artifact's own VARIANT name —
# to a declared path. A GUI is the artifact that needs it most and had it least:
# MEASURED on t14s 2026-08-27, `dwarves-ui` crashed and left no message, no backtrace and
# no file, because a desktop-launched process writes stderr into a closed stream and has
# no RUST_BACKTRACE. Zero dependencies (std only), so it is never the crate a lean build
# leaves out. Reuse, not a twin: nothing here hand-rolls a hook (LAW 5).
nornir-crashlog = { path = "../../edda/crates/nornir-crashlog", version = "0.1" }
egui = "0.36"
serde_json = "1"
# serde derive — the Settings pane's `SettingsConfig` round-trips to/from JSON (the gate:
# `state_json` out == `update_json` in). Derive-only; no runtime cost beyond serde_json.
serde = { version = "1", features = ["derive"] }
# UI-testing coverage ledger — reuse edda's testmatrix (never hand-roll a harness).
# Optional + off by default; the robot pane-drive test emits a functional_status cell
# only under `--features testmatrix`.
nornir-testmatrix = { path = "../../edda/crates/nornir-testmatrix", version = "0.2", optional = true }

[features]
# 📖 On by default so a stock build ships the manual baked in (like nornir's
# `manual-embed`). A `--no-default-features` build still compiles — the Manual tab
# then renders the honest "manual not embedded in this build" state, never a blank pane.
default = ["manual-embed"]
testmatrix = ["dep:nornir-testmatrix", "nornir-testmatrix/testmatrix"]
# `fixtures` — the deterministic readings the TESTS stand panes on (`FixtureCoverage`,
# `FixtureLens`, `FixtureDupes`, the demo job roster …) and the pane constructors over
# them (`CoveragePane::fixture()` …). OFF in every shipped build: no build variant names
# it, so `dwarves-ui` / `dwarves-ui-gpu` do not contain a single sample row — a pane with
# no answer can only draw its state. The crate's own tests get it through the
# dev-dependency on itself below (the `nornir-warehouse-embed` `testkit` pattern).
fixtures = ["nornir-warehouse-nav/fixtures"]
# `gpu` — the UNIVERSE star-field's instanced GPU lane, and the ONLY thing the
# `dwarves-ui-gpu` atom adds over `dwarves-ui`. It is a passthrough by design: the switch
# itself lives one crate down (`nornir-warehouse-nav/gpu` → `facett-graphpan/wgpu` +
# `facett-core/wgpu`), and this feature exists so a build point can reach it — before it,
# the lane was declared in the library and enableable by nobody.
#
# `facett-core/wgpu` is named DIRECTLY as well as transitively because `main.rs` calls
# `install_cloud_renderer` itself: the feature compiles the renderer IN, the host still
# has to hand it the live `RenderState`, and a feature that links a renderer nobody
# installs is a green that paints nothing.
gpu = ["nornir-warehouse-nav/gpu", "facett-core/wgpu"]
# `dataview-sql` — give the 🏢 Data view tab's SQL prompt a REAL DataFusion engine
# (the same one skade re-exports, v54/arrow-58). The thin client registers the
# thin-fetched warehouse frames as in-memory `MemTable`s and runs `SessionContext::sql`
# over them, returning Arrow rows the tab renders as a result grid + a deck pane. OFF by
# default (like `dupes-fat` / `coverage-fat`, it links the datafusion tree, a real
# build cost, and is native-only): OFF ⇒ the SQL prompt reports the engine is not built
# in and names the feature to rebuild with; ON ⇒ the prompt runs real SQL. The deck
# WALL itself never needs this feature — it renders from thin scans alone.
dataview-sql = ["dep:datafusion", "dep:tokio"]
# `manual-embed` — bake dwarves' `.nornir/*.md` (idiot guides, guides, designs) into the
# binary via rust_embed, so the 📖 Manual tab renders the real manual offline and it
# travels with the installed binary. OFF → the tab says "manual not embedded in this
# build". Mirrors nornir's `manual-embed`. wasm-safe (compile-time embed, no I/O).
manual-embed = ["dep:rust-embed"]
# `vm-boot` — wire the ⚒ Build Thing pane's ▶ Build & Boot on a kind=vm launch cell
# (korp-kvm) to a REAL boot: build the appliance PID-1 and boot it under QEMU/OVMF/KVM
# through the constellation's ONE VM boot path — `jera::vm` (feature `vm-tunnr`) →
# draupnir's `KvmBoot` → tunnr's `boot_test` — streaming the live serial console into
# the pane (the installed/started korp-demo + KORP-APPLIANCE BOOT-OK lines, ending BOOT
# PASS). `tunnr-vm` stays linked for HOST FACTS only (QEMU/OVMF presence, the kernel,
# the appliance recipes), never to boot. OFF by default (native-only, needs QEMU+OVMF
# at RUN time); OFF → the button surfaces the honest "vm-boot not enabled" message,
# never a fake boot. Build: `cargo build -p dwarves-gui --features vm-boot`.
vm-boot = ["dep:tunnr-vm", "dep:fatfs", "dep:jera", "jera/vm-tunnr"]
# `container-run` — wire the ⚒ Build Thing pane's ▶ Run on a kind=container launch cell
# (tunnr-hello-world) to a REAL run: bring the OCI image up THROUGH jera→draupnir's ONE
# bollard/podman engine and FOLLOW its stdout into the pane's live LOG console (the
# `LogConsole` adapter over the same console-sink trait + pump the serial console uses),
# ending on the container's readiness/exit. OFF by default (links `jera` +
# `draupnir/backend-oci`/bollard, native-only, and needs a reachable OCI engine at RUN
# time); OFF → the ▶ Run button surfaces the honest "container-run not enabled" message,
# never fake logs. Build: `cargo build -p dwarves-gui --features container-run`.
container-run = ["dep:jera", "jera/container-oci", "dep:draupnir", "draupnir/backend-oci"]
# `sockets` — the Build Thing bulb→SOCKET deployment surface (brief §SOCKET DEPLOYMENT).
# Skidbladnir OWNS the sockets (Socket/Fixture/WgKeyPanel + the type→socket registry); the
# ⚒ pane RENDERS the lit socket (fixtures + health + the Mode::Test WireGuard keys panel)
# for a tunnr-appliance cell, auto-fetching from the boot console. Optional + lean
# (skidbladnir[sockets] pulls only base64 + app-heartbeat, no pki/reqwest). Reuse, never
# twin: the socket logic is Skidbladnir's; the pane only drives + draws it.
sockets = ["dep:skidbladnir", "skidbladnir/sockets"]
# `dupes-fat` — give the 🧬 Dupes tab the LOCAL warehouse read leg
# (`dwarves::dupes::local`), so the pane opens the Iceberg tree on disk instead of
# reaching a `nornir-server` over the thin gRPC seam.
#
# This is not a convenience. The thin seam CANNOT carry the `embeddings` table — a
# 768-float `list<float32>` per row does not survive a stringified `scan_preview` at
# constellation scale — so over gRPC the SEMANTIC tier reports itself unavailable and
# every leafwatch hit is hash-tiers-only. Embedding cosine is the only tier that sees
# "the same thing, written differently", which is the case the user actually asked
# for, so without this feature the tab is structurally blind to it.
#
# OFF by default because it links arrow + parquet (native-only, and a real build-time
# cost the wasm web demo must not pay). The `dwarves-ui-fat` build variant builds it,
# so it is not a feature nobody compiles.
dupes-fat = ["dwarves/dupes-fat"]
# `solo` — **the UI with its server compiled in** (the `dwarves-solo` atom, `role =
# "solo"`, `linkage = "fat"`). It pulls the dwarves crate's `embed-all` + `read-fat` —
# the SAME pair the `dwarves-server` atom builds — so the solo binary's server half is
# the same capability as the daemon's, not a reduced one, and `main.rs` starts it through
# the SAME `dwarves::server::serve_all`.
#
# It also turns on this crate's own `dupes-fat`, which is where `dwarves-ui-fat` went: a
# fat UI reads the warehouse locally, and that keeps the feature from becoming one nobody
# builds.
#
# `dep:tokio` is the runtime the embedded server runs on (already an optional dep for
# `dataview-sql`, so this adds no new crate). Native-only in effect — the wasm host has
# no `main` to start a server from, and `start_embedded_forge` is
# `cfg(not(target_arch = "wasm32"))`.
solo = ["dupes-fat", "dwarves/embed-all", "dwarves/read-fat", "dep:tokio"]
# `robot-remote` — MOUNT a `nornir_robotui::cmd_json::CmdJsonServer` over the demo's
# egui shell on a Unix socket, so edda's `fleet` can drive dwarves WIRE-REMOTE (the
# fat leg) exactly like a co-located `RobotSession` — the SAME robotui vocabulary
# over a byte stream (REUSE, no second drive path). Pulls `nornir-robotui` with its
# lean serde-only `wire` feature (no eframe/windowing). OFF by default: the SHIPPED
# demo binary never mounts an always-on socket — the mount is reached only via the
# `--robot-remote <socket>` CLI flag under this feature. Native-only (the tabbed
# shell + kittest harness are `cfg(not(wasm32))`).
robot-remote = ["dep:nornir-robotui", "nornir-robotui/wire"]
# `watch` — the VISIBLE robot mode. `dwarves-ui --robot-watch [scenario.toml]` (or
# `--robot-mode watch` / `NORNIR_ROBOT_MODE=watch` / `ROBOTUI_WATCH=1`) opens a REAL,
# paced eframe window and replays the recorded per-tab scenarios so a human WATCHES the
# robot navigate every tab, instead of the headless off-screen kittest drive that
# `--robot-remote <socket>` mounts. Both modes drive the SAME shell + scenarios through
# `nornir_robotui::run_scenario_mode` — this feature just adds the `watch` backend
# (`nornir-robotui/watch` → eframe/winit/glow + accesskit). OFF by default so the shipped
# binary never links the windowing stack. Native-only (the tabbed shell is `cfg(not wasm)`).
watch = ["dep:nornir-robotui", "nornir-robotui/watch"]
# `claude-http` — wire the Claude tab's LIVE transport (`nornir_claude::HttpTransport`).
# Maps straight onto the shared leaf's `http` feature (blocking reqwest + rustls POST
# to /v1/messages). OFF by default so the demo builds credential- and network-free on
# the offline stub. Native-only (nornir-claude is a native-only dep — the tabbed shell
# is `cfg(not(wasm32))`).
claude-http = ["nornir-claude/http"]
# `vault` — wire the Profile tab's credential list AND Settings' vault-unlock to
# dwarves' REAL encrypted vault (`dwarves::vault::{ZnippyVault, Vault, Session}`, landed
# on main). OFF by default: the shipped demo + the robot test drive the honest
# `SampleVault` seam (names only, no values, `available()==false`) so nothing depends on
# a real `~/.dwarves/vault.bin`. With `--features vault` the Profile tab delegates to the
# real vault via `DwarvesVault` (names read through an unlocked `Session` key — never a
# value getter) and Settings' Unlock/Lock drive the real `Session`. Native-only
# (`dwarves` is a native-only dep here).
vault = ["dwarves/vault", "dwarves-embed/vault"]
# `claude-token` — the Settings tab's Claude/Anthropic API-key control writes to the
# REAL vault via `dwarves::claude_token` (the key sealed under `anthropic` through the
# SAME `VaultTokenStore` the OIDC login uses), and the pane reports the resolved
# credential SOURCE (env -> vault). OFF by default: without it the control still renders
# and still reports the env rungs honestly, it just cannot store a key. Implies `vault`
# (the key lives in the vault). Network-free — no reqwest/OIDC stack.
claude-token = ["vault", "dwarves/claude-token"]
# `login` — wire the Profile tab's mounted shared `facett_auth_ui::LoginPane` to dwarves'
# REAL vault-backed OIDC device-code login via the host `DwarvesLoginSeam` (the SHARED
# `nornir-auth-client` RFC 8628 grant, token sealed in the encrypted vault through
# dwarves' `VaultTokenStore`) instead of the network-free `facett_auth_ui::StubLogin`.
# Enables `dwarves/login` (+ `dwarves/vault`) and pulls `nornir-auth-client` (the device
# primitives) + `tokio` (the sync↔async bridge). OFF by default: the shipped demo + the
# robot test drive the honest stub (no IdP, no network); sign-in is gated on the vault
# being unlocked in Settings (the `Session` gate). Native-only.
login = ["dwarves/login", "dwarves/vault", "dep:nornir-auth-client", "nornir-auth-client/online", "dep:tokio"]

# ── native-only (the LIVE Funnel tab, backend (2)) ───────────────────────────
# The funnel tab talks to a running `nornir-server` over thin gRPC (tonic/tokio),
# which is native-only — the wasm web demo keeps the single Forge panel. This is
# the reusable `<guest>-embed` adapter (proto client + reconstruct + facett-plandag).
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
# The Pipeline tab's runs leg draws the runs and a run's steps on the shared table
# (`pipeline_runs.rs`) — the same component the Forge tab's unit summary uses.
facett-table = { path = "../../facett/facett-table", version = "0.2.0" }
# The ▶ Build & Boot leg (feature `vm-boot`, native-only): boot a kind=vm launch
# cell (korp-kvm) under QEMU/OVMF/KVM THROUGH tunnr's boot primitive
# (`tunnr_vm::boot_test` → `BootHandle`), streaming the live serial into the pane.
# Optional + native-only (it links QEMU-driving code); a wasm/no-feature build ships
# the honest "vm-boot not enabled" stub. Reuse, never twin: tunnr owns the boot engine.
tunnr-vm = { path = "../../tunnr", version = "0.2.0", optional = true }
# The INSTALLER boot leg (feature `vm-boot`): the ⚒ Build Thing pane's ▶ Build & Boot
# on the `korp-installer` ISO cell masters the pure-Rust installer ISO, stages a FAT
# EFI System Partition carrying the korp-installer UEFI stub, and boots it under
# QEMU/OVMF with a BLANK target disk — gating on `INSTALLER: INSTALL-OK`. Formatting
# the ESP is pure-Rust `fatfs` (zero shell, no mkfs.vfat/mtools) — the SAME crate
# korp-installer's `kvm_install` e2e formats its ESP with. Optional + native-only.
fatfs = { version = "0.3.6", optional = true }
# The ▶ Run leg (feature `container-run`, native-only): run a kind=container launch
# cell (tunnr-hello-world) THROUGH jera → draupnir's ONE bollard/podman OCI engine and
# follow its logs into the pane's live LOG console. Optional + native-only (it links
# bollard + a tokio runtime via draupnir's `backend-oci`); the no-feature build ships
# the honest "container-run not enabled" stub. Reuse, never twin: jera/draupnir own the
# ONE OCI engine — dwarves only drives it and drains its logs into the shared pump.
jera = { path = "../../edda/crates/jera", version = "0.2.0", optional = true, default-features = false }
# The SAME OCI engine jera routes through, named directly for ONE reason: the Exec pane's
# sandbox needs a BIND MOUNT, and jera's detached `BootRunner::spawn` cannot carry one —
# `draupnir::BootSpec` has no bind field, so `ContainerSpec::mounts` is dropped on that
# leg (measured 2026-08-06: the container came up and `/artefakt/<x>` was not there).
# `ContainerBoot::boot_with_binds` is the mount-aware twin, and it is on draupnir, not on
# jera. This is not a second engine — it is the same one, reached at the layer that has
# the mount.
draupnir = { path = "../../draupnir", version = "0.2.0", optional = true, default-features = false }
# The SOCKET deployment surface (feature `sockets`): Skidbladnir owns the Socket/Fixture/
# WgKeyPanel model + the type→socket registry; the pane lights a TunnrTestSocket for a
# tunnr-appliance cell and renders its json() (fixtures + WG keys panel). default-features
# = false so only the lean `sockets` feature is pulled (base64 + app-heartbeat, no pki).
skidbladnir = { path = "../../Skidbladnir", version = "0.2.0", optional = true, default-features = false }
nornir-funnel-embed = { path = "../crates/nornir-funnel-embed", version = "0.2.0" }
# The warehouse CONTROL embed: "Add warehouse (SSH) → Populate" drives nornir's
# `Workspaces.Register` (add + eager-populate) + `Workspaces.Fetch` (re-sync) over
# thin gRPC — the UI lives here, the warehouse-WRITE happens in nornir (no cycle).
nornir-warehouse-embed = { path = "../crates/nornir-warehouse-embed", version = "0.2.0" }
# 🏢 The "Data view" tab: the SHARED multi-pane warehouse DASHBOARD
# (`facett_warehousedeck::WarehouseDeck`) — a FacetDeck WALL of N panes, each binding
# one Mímir/warehouse table to a view-kind (3D graph / grid / chart). dwarves feeds it
# Arrow frames built from the SAME thin `Warehouse.Scan` gRPC read seam the Binary /
# Coverage / Lenses tabs use (`nornir_warehouse_embed::scan_warehouse_table`) — the deck
# is engine-agnostic (RecordBatch in), so this is a clean one-way edge (no `nornir`
# dep, no cycle). Native-only (warehouse-fed over gRPC); the wasm web demo keeps Forge.
facett-warehousedeck = { path = "../../facett/facett-warehousedeck", version = "0.2.0" }
# The snapshot-picker + virtualized row grid over Arrow — the Data view's SQL RESULT
# grid (the DataFusion query's `RecordBatch`es rendered as a browsable table). Same
# arrow-59 stack as the deck.
facett-icebergview = { path = "../../facett/facett-icebergview", version = "0.2.0" }
# Build the deck/result Arrow `RecordBatch`es from the stringified `Warehouse.Scan`
# reply (all-Utf8 columns) — the SAME arrow-59 the deck + icebergview + datafusion pin.
#
# ⚠ "the SAME arrow" was NOT true until 2026-08-24, and it was not a comment slip —
# it was a SHIPPED BUILD BREAK. These two said 59 while `datafusion` below said 54,
# which resolves arrow-array 58.x, so `--features dataview-sql` could not compile at
# all. Measured before the fix, three of them:
#     error[E0308]: expected `datafusion::arrow::array::RecordBatch`,
#                   found `arrow_array::RecordBatch`
#     error[E0308]: expected `datafusion::arrow::datatypes::Schema`,
#                   found `arrow_schema::Schema`
# The feature is optional and NOT in `default` (`default = ["manual-embed"]`), which
# is the only reason nobody hit it — it is the same two-arrow-majors failure knut
# documented at `backend/native.rs:788` and `:887`.
arrow-array = "59"
arrow-schema = "59"
# The DataFusion SQL engine for the Data view's SQL prompt — the SAME engine skade
# standardizes on and re-exports (`skade::datafusion`, v55 / arrow-59) behind its `sql`
# feature. We depend on it DIRECTLY, `default-features = false` (no parquet / no C
# `liblzma` → stays pure-Rust and lean), because the thin client registers the
# thin-fetched frames as in-memory `MemTable`s and runs `SessionContext::sql` over them
# — it does NOT open nornir's Iceberg catalog (the CATALOG DOCTRINE: nornir owns the one
# live catalog; clients read over gRPC), so skade's server-side Iceberg table provider
# is not reachable here. Optional + native-only, behind the `dataview-sql` feature (like
# `dupes-fat` / `coverage-fat`): OFF ⇒ the SQL prompt says so honestly; ON ⇒ real SQL.
datafusion = { version = "55", default-features = false, features = ["sql", "unicode_expressions"], optional = true }
# The shared BINARY-ANALYSIS pane (task #93): ONE presentational `facett_binview::BinaryPane`
# (bloat treemap · checksec badges · size sparkline · before→after diff waterfall) the
# fleet mounts, fed by the host's `BinaryDataSeam`. dwarves' `binview::DwarvesBinaryData`
# reads the four Mimir tables (binary_facts/section_sizes/symbol_sizes/artifact_diff) over
# the SAME `dyn Warehouse` gRPC read seam above (no `nornir` edge). Native-only (the tab is
# warehouse-fed over gRPC); the wasm web demo keeps the single Forge panel.
facett-binview = { path = "../../facett/facett-binview", version = "0.2.0" }
# The shared WORLD-CODEROOM pane (#92): ONE presentational `facett_coderoom_view::CoderoomPane`
# (a crate@version dependency node-graph · green/red compat edges · a what-breaks impact
# highlight) the suite mounts, fed by the host's `CoderoomDataSeam`. dwarves'
# `coderoom::DwarvesCoderoomData` reads the coderoom warehouse tables (symbol_facts crates@
# versions / semver_diffs compat edges / call_edges references) over the SAME `dyn Warehouse`
# gRPC read seam above (no `nornir` edge) — TRUE OF EVERY QUERY as of ca40d41, and it was
# not before: the scans went through the seam while What-changed and the transitive
# closure SPAWNED the `nornir` binary and parsed its JSON. The claim held for the part a
# reader would check and hid the two that mattered, so a pane documented as having no
# `nornir` edge shipped one for months. Both are ports now, reading the same rows.
# Pointed at the DEDICATED coderoom warehouse (a
# configurable served-workspace name), NOT the selected workspace's Mimir. Native-only (the
# tab is warehouse-fed over gRPC); the wasm web demo keeps the single Forge panel.
facett-coderoom-view = { path = "../../facett/facett-coderoom-view", version = "0.2.0" }
# The SHARED FLEET COVERAGE BOARD (autotest North Star): ONE presentational
# `facett_fleet_view::FleetView` — a full-system-autotest run as a member×surface grid
# under a `Gap == ∅` verdict banner — the fleet analogue of `facett-matrix`. dwarves'
# `fleet::FleetTab` MOUNTS it (no second coverage model), fed a `FleetRunView` built from
# `nornir-testmatrix`'s own `GateReport` + `TestResultRow`s + members: offline from a
# `$DWARVES_FLEET_RUN` file (the file seam Release/Benches/Saga use), else the never-blank
# `FleetView::local()` demo. Native-only (mounted only in the native tabbed shell); the
# wasm web demo keeps the single Forge panel. Its `testmatrix` feature maps onto ours.
facett-fleet-view = { path = "../../facett/facett-fleet-view", version = "0.2.0" }
# The Warehouse Navigator — dwarves' LANDING view (the first tab). A 2D
# constellation→repo→crate dependency navigator over the SHARED `nornir-depgraph`
# suite model + the reused `facett-graphpan` renderer, with click-to-drill-down,
# per-repo grouping hulls, categorical colour-by-repo, and recency=glow. Native-only
# (nornir-depgraph parses manifests / the live model rides gRPC); the wasm web demo
# keeps the single Forge panel. Renders from a faithful nordisk fixture, no server.
nornir-warehouse-nav = { path = "../crates/nornir-warehouse-nav", version = "0.1.0" }
# The SHARED depgraph + function-tier model (edda leaf, egui-free / warehouse-free) —
# already in the tree via nornir-warehouse-nav. dwarves-gui pulls it DIRECTLY only to
# GROUP the thin-fetched `symbol_facts` + `call_edges` rows into per-crate
# `CrateFuncGraph`s (`funcgraph::build_function_graph`) — the exact shape the navigator's
# `RealFnSource` seam feeds `WarehouseNav::set_real_functions`, so the world graph's
# function tier is REAL, not synthetic. Zero-cost edge (same sources cargo already builds).
nornir-depgraph = { path = "../../edda/crates/nornir-depgraph", version = "0.1.3" }
# The declarative UI-SCENE layer (facett-app `scene`): the whole tabbed shell is now
# described as data (a menubar + a foldable pane tree + routes) and rendered through
# facett-app's OWN primitives (Router / TabView / facett-menu) by `scene::render`. The
# `TabbedView` host is a thin `SceneHost` that fills each Slot with the SAME existing
# `Box<dyn Panel>` (reuse, not rewrite). Native-only (the tabbed shell is `cfg(not wasm)`).
facett-app = { path = "../../facett/facett-app", version = "0.2.0" }
# The SHARED build-thing DECK: three real panes (Variants · Graph · Result) with
# draggable dividers, plus the graph pane that maps nornir-build-thing's serde
# `BuildGraph` onto the reused `facett-graphpan` renderer. The ⚒ tab MOUNTS it — it
# owns no render of its own, and a clicked graph node comes back as a variant to
# select. Native-only, like the tabbed shell.
facett-buildthing = { path = "../../facett/facett-buildthing", version = "0.2.0" }
# The build-thing TYPES + the host-side feature-GATING scanner (`gating::scan`), which
# reads `#[cfg(feature = …)]` and Cargo `required-features` off the checkout so the
# graph can show which source files a variant compiles and which it leaves OUT. Host
# side by necessity: the wasm plugin has no filesystem. Already in the tree via
# `dwarves` and `facett-buildthing`, so this is a zero-cost edge. It is also what the
# pane's acceptance test parses holger's own declaration with.
# 0.1.25 (merged onto main's 0.2.0 line, 2026-10-05): the kernel leaf reads the kconfig SOLVER's chains off a built kernel's
# `kernel.why.toml` (`kernel_requires::WhyIndex::from_toml`, `WhyClass::KconfigDerived`'s
# fields) — `kernel_scene::KconfigWhy`.
nornir-build-thing = { path = "../../edda/crates/nornir-build-thing", version = "0.2.0" }
# The app-agnostic APP-ALIVE liveness+capability contract (edda/crates/app-heartbeat) —
# the Build Thing socket side of brief §3c: the pane reads whatever APP-ALIVE manifest a
# booted app self-reported on its console and surfaces deploy_ok + per-function verdicts,
# app-agnostic (never hardcodes korp). Same logic Skidbladnir's socket AppAliveProbe wraps.
app-heartbeat = { path = "../../edda/crates/app-heartbeat", version = "0.1.0" }
# The 🔑 DEPLOY-KEY facet: the public key with a copy action + where-to-paste guidance,
# the resolution ladder (which rung answered), and the real per-remote fetch state. Its
# five states are five — "never probed" is not "fine". The MODEL lives in the shared
# `nornir-git` leaf (`deploykey`), which is also what `gitio::nornir_ssh_key_path`
# delegates to, so the pane shows the key the fetch actually authenticates with.
facett-deploykey = { path = "../../facett/facett-deploykey", version = "0.2.0" }
# The deploy-key HOST half: resolve the key, discover remotes, probe them. Same leaf the
# forge's git transport uses — one ladder, never a second one.
nornir-git = { path = "../../edda/crates/nornir-git", version = "0.2.0" }
# The build-pipeline DAG tab: the REUSED engine-agnostic pipeline viewer +
# dwarves' `ForgePipelineAdapter` (brokkr build DAG → `PipelineModel`). `dwarves`
# is pulled lean (`default-features = false` → no CLI/server/tonic) with only the
# `facett-adapter` feature. Native-only — the wasm web demo keeps the Forge panel.
# The `runa-adapter` feature adds `RunaPipelineAdapter` — the SECOND adapter onto the
# SAME viewer, so the Pipeline tab renders the forge's authored `*.runa.ron` CI/CD
# pipeline (stages → nodes, `needs` → edges, typed steps → sublabels) without a viewer
# twin. cargo unions it with dwarves-embed's identical request.
facett-pipeline = { path = "../../facett/facett-pipeline", version = "0.2.0", features = ["runa-adapter"] }
# The typed pipeline AUTHORING layer (edda leaf) — the Pipeline tab loads a REAL committed
# `*.runa.ron` through `dwarves_runa::Pipeline::from_ron_str`, which parses AND runs runa's own
# validator (duplicate stage / unknown `needs` / DAG cycle), so an unsound pipeline can
# never reach the viewer pretending to be a DAG. A pure leaf (ron + serde, no facett, no
# egui); already in the tree via dwarves-embed, so this is a zero-cost edge.
dwarves-runa = { path = "../../edda/crates/dwarves-runa", version = "0.3.0" }
# `dwarves` stays LEAN (`default-features = false` → no CLI/server/tonic/release stack).
# Beyond `facett-adapter` (the brokkr→PipelineModel adapter) the demo consumes two
# always-compiled, read-only modules: `dwarves::store` (the run-history reader that lights
# the runa DAG per stage) and `dwarves::release_state` (the ramp/bump-plan/gate reader the
# Release tab renders). Neither can build, publish or mutate anything.
# `dupes` adds the DUPLICATE BOX engine (`dwarves::dupes`) the 🧬 Dupes tab renders.
# Dep-light: the feature pulls only the shared `nornir-hash` leaf + `gatling`, no arrow /
# parquet (the tab reads the warehouse over the SAME thin gRPC seam the Binary tab uses).
# `coverage` adds the COVERAGE INDEX engine (`dwarves::coverage`) the 📐 Coverage tab
# renders. Dep-light for the same reason: the feature pulls only the shared `autonom`
# gate, and the tab reads the warehouse over that same thin gRPC seam. The FAT leg
# (`coverage-fat`, a local Iceberg tree) is deliberately NOT enabled here — it links
# arrow + parquet, which belongs in the `dwarves coverage` CLI, not the demo binary.
# `warehouse` is what makes ⚒ Build Thing's ▶ Build FILE A RECORD: it links the thin
# `nornir-warehouse-embed` client so `BuildRun` can submit the finished build's
# `build_provenance` + `build_info` rows (through the dwarves HOST's Telemetry proxy —
# the UI still holds no nornir address). Without it a build compiles a binary and files
# nothing, which is exactly how those two tables stayed at zero rows while every other
# table in the same warehouse filled. Native-only (tonic); the wasm demo is unaffected.
#
# `build-image` + `appliance-iso` are what make ▶ Build WORK ON A NON-CARGO CELL, and
# without them the button is a dead end on exactly the cells that need it most.
# `BuildRun::spawn_recording` routes on `ArtifactKind`: cargo kinds go to
# `build_variant_observed`, and `container` / `iso` / `vm` go to `dwarves::assemble_cell`.
# That module is feature-gated by construction — `#[cfg(not(feature = "build-image"))]
# fn assemble_container` bails with "needs this forge built with `--features
# build-image`", and the `appliance-iso` twin bails the same way for an ISO that chains
# a cargo variant. Honest, but still a red: the UI could LIST gunnar's `gunnar-image`
# and `gunnar-appliance-iso` cells and could not build either one. The pane and the
# `dwarves buildthing` CLI share that one code path, so the CLI could build a cell the
# GUI could not — the same product answering differently through two doors.
# Neither feature is part of the "no CLI/server/tonic/release stack" leanness this dep
# is keeping: `build-image` is `nornir-build-thing/image-build` (bollard, over the
# rootless podman socket) and `appliance-iso` is `korp-installer` (the pure-Rust
# ISO-9660 + El Torito payload/ESP composer). No clap, no tonic server, no release stack.
# ⚠ `kernel-build` and `container-oci` are here because WITHOUT THEM DOOR 1 CANNOT
# BUILD. Measured 2026-08-27 by pressing the pane's own `BYGG kärnan` button on the
# real declaration: the chain refused with
#   `dwarves-buildthing-7 — tunnr-kernel — could not start: assemble: no KernelBuilder
#    injected into Builders`
# — `assemble_cell`'s `ContainerKernelBuilder` injection is `#[cfg(feature =
# "kernel-build")]`, which is DEFAULT-ON for the `dwarves` crate and was therefore
# invisible to everything that builds dwarves normally; this dep is
# `default-features = false`, so the ⚒ pane got the empty `Builders` and every
# kernel-consuming cell in the UI was dead by declaration. Exactly the shape of the
# 2026-08-25 finding where `dwarves-server` was never given `appliance-iso`.
# `container-oci` is the same class one step on: it is what makes the containerised
# cargo build reachable for a cell that declares a `[build.toolchain]`.
dwarves = { path = "../crates/dwarves", version = "0.2.0", default-features = false, features = ["facett-adapter", "dupes", "coverage", "mimir", "warehouse", "galois-axis", "build-image", "appliance-iso", "kernel-build", "container-oci", "tune-jobs"] }
# (`nornir-warehouse-embed` is already a direct dep above — the ⚒ pane's build-record
# store status reads through that SAME thin client, with its honest typed
# `WarehouseReadError`, so "no build records" stays distinguishable from "could not
# read the store".)
# The LIVE forge-run tab (backend (2)): a thin gRPC client to a running
# `dwarves-server` that fetches a run's full RunReport (parent RunRow + per-step
# BuildRows + `needs` edges) over `GetRunReport` and renders it on the SAME reused
# `facett-pipeline` viewer as the in-process Pipeline tab. The reusable `<guest>-embed`
# shape (nornir-funnel-embed is the template). Native-only.
dwarves-embed = { path = "../crates/dwarves-embed", version = "0.1.0" }
# The reused release-convergence MODEL (`ReleaseConvergence`) — same crate dwarves-embed's
# Release pane wraps. dwarves-gui pulls it only so the Release-tab robot test can seed a
# distinct per-workspace convergence file (proving the tab loads THAT run, not the
# fixture); the tab itself goes through `dwarves_embed::release_panel_at` (no viewer twin).
# Already in the tree via dwarves-embed, so this is a zero-cost edge. Native-only.
facett-releaseview = { path = "../../facett/facett-releaseview", version = "0.2.0" }
# The SHARED 2D graph ENGINE (vello-backed, runtime GPU/CPU select) — the ONE renderer
# for dwarves' DAG surfaces: the release convergence graph and the funnel DAG. Its own
# header calls it "a future drop-in for nornir's `draw_graph`", and its model mirrors
# nornir's shared graph API exactly, so this is reuse of the engine the fleet is
# converging on rather than a per-tab graph painter. `dag_graph.rs` is the ONE lowering
# both tabs go through. Native-only, like the tabbed shell.
facett-graphview = { path = "../../facett/facett-graphview", version = "0.2.0" }
# The dependency picture's model (`GaloisPane::model`) is graphpan's; the build graph
# lowers it into the one graphview pane, so both model types are named here.
facett-graphpan = { path = "../../facett/facett-graphpan", version = "0.2.0" }
# The SHARED 3D graph — the canonical node cloud (`Graph3D`: `Layout3D::Force` placement
# built ONCE, `nodes[].pos` cached) plus the `codevault` node-state vocabulary
# (`NodeState{Ok, Changed, Glitch}`) the kernel feature scene paints its
# kept/cascade-dropped/refused states with. `kernel_scene.rs` is the THIRD consumer of
# that camera+state pair (the funnel's 3D Plan-DAG and the warehouse world graph are the
# other two) — reuse, not a fourth 3D painter. Already in the tree via
# `facett-warehousedeck`, so this is a zero-cost edge. Native-only, like the tabbed shell.
facett-graph3d = { path = "../../facett/facett-graph3d", version = "0.2.0", features = ["rate"] }
# The `nornir_bench` read/write API — same crate dwarves-embed's Testmatrix+Benches pane
# CONSUMES for the mimir bench trend. dwarves-gui pulls it only so the Benches-tab robot
# test can seed a distinct per-workspace `bench_history.jsonl` (proving the tab loads THAT
# run, not the fixture); the tab itself goes through `dwarves_embed::benches_panel` (no
# harness twin). Already in the tree via dwarves-embed, so this is a zero-cost edge. Native-only.
nornir-bench = { path = "../../edda/crates/nornir-bench", version = "0.2.0" }
# The lean saga engine (edda) — same crate the Saga tab's `dwarves_embed::SagaView`
# pane CONSUMES (Player transport + saga_to_ui + summarize_sessions). dwarves-gui
# pulls it only so the Saga-tab robot test can seed a distinct per-workspace `.jsonl`
# tape (proving the tab loads THAT tape, not the sample); the tab itself goes through
# `dwarves_embed::saga_panel` (no transport twin). Already in the tree via
# dwarves-embed, so this is a zero-cost edge. Native-only.
nornir-saga = { path = "../../edda/crates/nornir-saga", version = "0.2.0" }
# The SHARED zone/jera framework widgets (edda-of-facett, consumed everywhere per L5):
# `JeraCentral` (the ONE jobs/zones central panel over facett-jobview + a per-job zone
# column) UNIFIES dwarves' old Jobs / Jobs (live) duality into one central mount; and
# `ZonePane` (Tools → pick a zone, see its default infra, Generate) is dwarves' Tools
# tab. Both consume the ONE `nornir_zone::Zone` edda leaf — no dwarves-local twin. Its
# `testmatrix` feature maps onto dwarves-gui's so the pane emits functional-status rows.
facett-zoneui = { path = "../../facett/facett-zoneui", version = "0.2.0" }
# The shared Anthropic Claude client leaf (edda) — the ONE Messages-API transport the
# `ClaudeTab` now CONSUMES (L5), retiring dwarves' hand-rolled `OfflineClaudeTransport`
# twin. `nornir_claude::OfflineTransport` drives the offline demo (no key/no network);
# the live `nornir_claude::HttpTransport` rides the `claude-http` feature above. The
# `EddaBackend` newtype in `tabbed.rs` is the ~3-line adapter (guide §10). Native-only,
# like the tabbed shell that hosts the Claude pane.
nornir-claude = { path = "../../edda/crates/nornir-claude", version = "0.1.1" }
# The SHARED About-card widget (facett, consumed per L5): ONE egui `Window` that
# renders a `nornir_appshell::Identity` (logo · wordmark · tagline · engines ·
# framework) — the SAME widget korp/holger use, not a dwarves twin. dwarves' About
# popup embeds the forge logo (assets/dwarves.webp → PNG at build). Re-exports
# `Identity`, so no direct nornir-appshell edge needed. Native-only (egui).
facett-about = { path = "../../facett/facett-about", version = "0.2.0" }
# The SHARED in-GUI device-code SIGN-IN pane (facett, consumed per L5): the ONE
# `facett_auth_ui::LoginPane` korp/holger also mount — the Profile tab renders it,
# never a per-app device-code twin. Presentational + auth-library-agnostic: dwarves
# supplies the host `login_seam::DwarvesLoginSeam` (the real backend, behind `login`)
# or the honest `facett_auth_ui::StubLogin` (the default build). Native-only (the
# Profile pane is native-only, like About).
facett-auth-ui = { path = "../../facett/facett-auth-ui", version = "0.2.0" }
# The SHARED device-code client (edda) the `DwarvesLoginSeam` drives — the SAME RFC 8628
# grant korp/holger reuse (`device::request_device_code` + `poll_token_full`), never a
# hand-rolled loop (L5). OPTIONAL, behind the `login` feature (with `online` for the
# reqwest device primitives); OFF ⇒ the Profile pane runs the network-free stub.
nornir-auth-client = { path = "../../edda/crates/nornir-auth-client", version = "0.1.1", optional = true, default-features = false }
# The sync↔async bridge for the `DwarvesLoginSeam` (the device grant is async, the
# `LoginSeam` is sync — a short-lived current-thread runtime per begin/poll call, IO not
# CPU fan-out — ROOT LAW #0). OPTIONAL, behind `login`. Native-only.
tokio = { version = "1", optional = true, default-features = false, features = ["rt", "rt-multi-thread", "net", "io-util", "time", "macros"] }
# The SHARED robot-UI framework (edda) as a NORMAL optional dep — behind `robot-remote`
# it mounts a `cmd_json::CmdJsonServer` over the shell so the fleet can drive dwarves
# over a real Unix socket. Pulled with the lean serde-only `wire` feature (no eframe/
# windowing); OFF by default so the shipped binary never links the kittest/wire stack.
# (The wizard robot TEST keeps its own no-`wire` dev-dependency below — cargo unifies.)
nornir-robotui = { path = "../../edda/crates/nornir-robotui", version = "0.4.0", optional = true, default-features = false }

# ── wasm (the LIVE WEB DEMO host) ────────────────────────────────────────────
[target.'cfg(target_arch = "wasm32")'.dependencies]
wasm-bindgen = "0.2"
# The grpc-web forge client: the SAME generated `Dwarf` client + proto types the native
# embed client uses, over a browser (`fetch`) transport. The wasm Forge view calls its
# `fetch_history` to replace the fixture roster with LIVE runs from a `dwarves-server`
# `:7900` (which serves grpc-web). Wasm-only — the native shell keeps `dwarves-embed`.
dwarves-web-client = { path = "../crates/dwarves-web-client", version = "0.1.4" }
# Drive the grpc-web fetch on the browser event loop (single-threaded IO — NOT a CPU
# thread; ROOT LAW #0 is about parallelism, and wasm is single-threaded regardless).
wasm-bindgen-futures = "0.4"
# Read the page's `?server=…&token=…` query params so the forge endpoint + bearer are
# configured at load time, never hardcoded into the bundle.
web-sys = { version = "0.3", features = ["Window", "Location"] }

# The Binary-tab (task #93) robot test seeds a workspace warehouse with binary rows by
# standing up the SHARED mock `Warehouse` gRPC server the read seam ships
# (`nornir_warehouse_embed::testkit::spawn_mock_warehouse`) — REUSE, do NOT twin a tonic
# mock (L5). The `testkit` feature is unioned into the test build here (native-only, like
# the tab). Test-only; no runtime/wasm impact.
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
nornir-warehouse-embed = { path = "../crates/nornir-warehouse-embed", features = ["testkit"] }
# The funnel submit→visible robot test (`tests/robot_funnel_live_wire.rs`) stands up the
# SHARED mock `Funnel` gRPC server (`nornir_funnel_embed::testkit::spawn_mock_funnel` —
# mounted through the warehouse testkit's ONE spawn body, L5), submits over the SAME thin
# write leg the MCP tool uses, and asserts the idea's title text renders in the SHIPPED
# embedded+live pane. Test-only (native); unions `testkit` into the test build.
nornir-funnel-embed = { path = "../crates/nornir-funnel-embed", features = ["testkit"] }
# `tests/robot_funnel_off_thread.rs` implements `LiveFunnelSource` itself (a stand-in
# consult with a deliberate latency), and that trait's `fetch` is spelled in terms of
# `funnel_core::state::Funnel` — so the test needs the type by name. Test-only; the
# shipped binary reaches funnel-core through nornir-funnel-embed as it always has.
# NO `version =` — see the VersionedPathDevDep note in crates/dwarves/Cargo.toml.
funnel-core = { path = "../../funnel-core" }
# …and the error half of that same signature. Test-only.
anyhow = "1"
# The host-only LIVE-DATA-VIA-HOST robot test (`tests/robot_warehouse_proxy_live.rs`)
# stands up the REAL dwarves warehouse/workspaces PROXY (`dwarves::warehouse_proxy::
# spawn_proxy`, behind `server`) in front of the mock nornir above, then drives the SHIPPED
# shell given ONLY that dwarves host — proving the panes render live warehouse data THROUGH
# the host. Test-only (native); the shipped `dwarves-ui` binary never links `server`.
dwarves = { path = "../crates/dwarves", default-features = false, features = ["server", "release-build"] }

[dev-dependencies]
# The crate's own integration tests see the `fixtures` feature (see `[features]`); a
# normal build of the bins never does.
dwarves-gui = { path = ".", features = ["fixtures"] }
# The robot proof for the ⚒ Feature-axis tab reads `Reachability` straight off the
# derived analyses, so it asserts the resolver's own verdict rather than a string.
nornir-feature-graph = { path = "../../edda/crates/nornir-feature-graph" }
# The Fleet robot proof builds its `GateReport` the way a real gate does —
# `GateReport::compute(surface, covered, allowlist)` over a `Surface` of `viz_tabs`, with
# the hole knocked out of `covered` — instead of hand-forging a `Gap`. Those three
# (`Surface`, `viz_tabs`, `Allowlist`) live in `nornir-testmatrix` and are NOT among the
# three symbols `facett-fleet-view` re-exports, so the test needs its own edge.
#
# RENAMED, and the rename is load-bearing. `nornir-testmatrix` is in this graph TWICE
# and has been since before this change: `facett-fleet-view` depends on the REGISTRY
# crate (`= "0.2"` -> 0.2.10) while this crate's own optional `testmatrix` dependency is
# the local PATH copy (0.2.12). They are different crates to rustc. The `GateReport` that
# `facett_fleet_view::FleetRunView` holds is the registry one, so that is the one this
# helper must construct — a path-dep edge compiles and then fails with "expected
# `nornir_testmatrix::discover::Surface`, found `nornir_testmatrix::Surface`". A rename
# lets one package name both copies unambiguously.
#
# Adds no package to the lock: 0.2.10 is already there, pulled by facett-fleet-view.
# Dev-only — no runtime, no wasm impact.
fleet-testmatrix = { package = "nornir-testmatrix", version = "0.2" }
# The Profile/Settings real-vault robot tests (`--features vault`) write a throwaway
# vault.bin + an isolated session under a tempdir. Test-only; no runtime/wasm impact.
tempfile = "3"
# The korp-installer GPU e2e reads the installed target disk back and verifies the
# app.znippy's blake3 root-of-trust pin — the SAME blake3 the real korp-start verifies.
# Test-only (native, --features vm-boot); already in the workspace via korp-installer.
blake3 = "1"
# The installer drive reads the INSTALLED target disk back — the GPT entry array and a
# file out of the ESP's FAT — to prove `korp.entry=<product>` landed on the cmdline the
# installed machine boots with. Applied output on disk, not a serial claim. The range
# readers are `korp_installer::kvmproof`, the SAME ones holger's and gunnar's own crown
# proofs use (LAW 5: one reader, not a third copy of 70 lines of span plumbing).
# `default-features = false` keeps znippy/OpenZL and the image codecs out of a test dep.
# `medium` (tunnr, lane B4): the drive runs the PRODUCTION RAID install, which lays the
# legacy-BIOS path, so it keeps the boot-code emitter a box build leaves out.
# Test-only (native, --features vm-boot).
korp-installer = { path = "../../tunnr/crates/korp-installer", default-features = false, features = ["medium"] }
# JWT minting for the sign-in robot tests (`--features login`): the local stub IdP mints
# an unsigned display-only JWT so the pane can decode a real subject, and the SAME string
# is the secret sentinel we prove never reaches the pane state. Test-only.
base64 = "0.23"
# The SHARED robot-UI framework (edda) — REUSE, do NOT twin a harness. The CI/CD wizard
# robot scenario drives the pane's REAL egui widgets through `egui_kittest` (AccessKit
# click/type) and emits `nornir-testmatrix` rows via `RobotSession::run_scenario`, the
# "kittest where a real widget needs it" leg of the user-journey coverage. Test-only
# (native); pulls no GPU (headless, `default = []`).
nornir-robotui = { path = "../../edda/crates/nornir-robotui" }
# The headless render harness for the scene tests: egui_kittest drives a real egui
# frame so a test can assert that folding OMITS content from the AccessKit tree and
# that a draggable split re-apportions the live ratio.
egui_kittest = { version = "0.36", features = ["snapshot"] }
# The SCAN guard filters the AccessKit tree by ROLE, so a plain label that happens to
# carry `⟳` is not mistaken for a control a human can press. Already in the lock via
# egui/kittest — this adds no package, only a direct name to say `Role::Button` with.
accesskit = "0.24"
# The Jera tab's workspace-lifecycle test writes a REAL jera ledger, so the "gone" leg
# is asserted against a file that really existed and really was unlinked. Already in the
# lock via dwarves-embed.
# NO `version =` — see the VersionedPathDevDep note in crates/dwarves/Cargo.toml.
jera = { path = "../../edda/crates/jera", default-features = false }