crates/nornir-build-thing/Cargo.toml
[package]
name = "nornir-build-thing"
# 0.1.9: the `laws` module — the bijection conformance gate (`assert_bijective`)
# every BuildThing impl's suite calls — plus `kernel_features::parse_decision_fragment`
# (the decision ↔ fragment inverse). gunnar's feature-gated conformance test waits
# on this version reaching the registry.
# 0.1.10: the settings TREE (`tree`): feature ▸ setting ▸ val nodes with paths as ids,
# derived from the flat declaration; the kernel donor projected as the first edge.
# `BuildThing::{options,try_toggle,actions}` over the wasm ABI (ops 11–13), the
# `nordisk::fetch` host import, the kind → verb table (`verbs`), path ↔ flag
# (`flags`), and laws B/C/D extended over the tree.
# 0.1.11: DSSE/in-toto SLSA seal beside CMS. `BuildProvenance::to_slsa_statement`
# (+ `slsa_statement_bytes`), `Seal` grows a variant (`SealBody::{Cms,Dsse}` — CMS
# JSON byte-unchanged), `DsseSeal`, the `DsseSealer` seam, `Attestation::
# seal_dsse_with`. The bump is REQUIRED, not cosmetic: local 0.1.10 already drifted
# from the PUBLISHED 0.1.10 on crates.io (same-version drift is invisible to a
# consumer resolving via `[patch.crates-io]`), so modgunn's DSSE-verify code — which
# needs these methods — only reaches the published-version graph once this ships.
# 0.1.12: the recipe format's COMPATIBILITY RULE (`schema`) — a root-level
# `[nornir-build-thing] schema = N` header a reader too old refuses BY NAME
# ("upgrade to >= X") instead of by a serde field error, plus R3: the loader now
# refuses to drop a `build.*` key it cannot parse rather than discarding it in
# silence (`serde_ignored`, one pass). The bump is REQUIRED, not cosmetic and not
# only for this feature: the PUBLISHED 0.1.11 has neither `InstallerBuild::
# {target,data}` nor `OptionSpec::kernel_requires` — lanes AE/AF added them to
# this tree WITHOUT a bump, so two different crates were on crates.io and in
# edda's tree under one version number, and `nornir`'s lockfile holds BOTH (one
# path, one registry) with nothing able to tell them apart. `schema::FIRST_READER`
# must name a version that really contains the vocabulary, or R2's refusal is a lie.
# 0.1.13: the PACKAGE vocabulary — `ArtifactKind::Package`, `[[build.package]]`
# (`package::PackageBuild`), the `PackageBuilder` seam, `image_build::build_package`
# and the ONE `SHA256SUMS` writer (moved here from facett's xtask so the forge door
# and the repo's own publish verb leave the same release set behind). Recipe schema
# 3, and the bump is REQUIRED rather than cosmetic for the reason 0.1.12's note just
# above already records: `BuildVariantSet` is `deny_unknown_fields`, so a 0.1.12
# reader handed a recipe with a `[[build.package]]` table refuses the WHOLE file
# with a serde error naming a word the author spelled correctly.
# `schema::FIRST_READER` must name a version that really contains the vocabulary,
# or R2's refusal is a lie — so the crate version and the schema row move together,
# in this commit.
# 0.1.14: the WASM CELL — `[[build.wasm]]` (`wasm::WasmBuild`) and its TYPED
# post-build stages: `BindgenStage` (an ENUM target, outputs DERIVED from the crate
# name so the `_bg` split cannot be misspelled, version taken from `Cargo.lock` and
# never declared) and `ComponentManifestStage` (a cargo EXAMPLE name, not a command).
# Recipe schema 4. The bump is REQUIRED for the reason 0.1.13's note records:
# `BuildVariantSet` is `deny_unknown_fields`, so a 0.1.13 reader handed a
# `[[build.wasm]]` table refuses the WHOLE file, and `schema::FIRST_READER` must name
# a version that really contains the vocabulary or R2's refusal is a lie.
# 0.1.17: **the false green a cargo build could hand back.** `provenance::
# ensure_exe_was_produced` — the ONE judge both cargo roads (host and container)
# now pass their result through: a run that exited 0 while reporting no
# `executable` for a `kind = "exe"` variant is a named refusal AT THE BUILDER,
# not a `BuildOutcome { success: true, artifacts: [] }` that the stager trips
# over three frames later. The bump is REQUIRED rather than cosmetic for the
# reason every note above records: a behaviour change under a version number
# already on crates.io is two different crates nothing can tell apart.
# MEASURED on t14s 2026-09-12 — the gunnar appliance compose died at step 3 of 3
# with `chained variant 'gunnar-server' has kind 'Exe', which produced no single
# artifact to stage` while the build above it reported success, because the
# container's log stream had delivered a truncated stdout (draupnir 0.1.13 fixes
# that half, and the floor below now names it).
# 0.1.24: SIGNED variants and BROWSER WEB APPS — `sign = { purpose = "plugin" }`
# on a variant (`sign::SignSpec`; the forge writes `<artifact>.p7s` + `.blake3`)
# and `[[build.web_app]]` (`web_app::WebAppBuild`: the cargo wasm build in the
# toolchain container, wasm-bindgen as a LIBRARY behind feature `web-app`, the
# page's `<link data-trunk>` tags assembled in Rust). Recipe schema 5; both keys
# land in `deny_unknown_fields` structs, so the bump is REQUIRED for the reason
# every note above records.
# 0.1.25 (release-3 line; merged onto main's 0.2.0 on 2026-10-05 — the published
# 0.2.0 does NOT contain it, so the next publish needs its own number): the KCONFIG SOLVER (lane AP 2026-08-30, ported 2026-10-03 onto edda's release-3 and main —
# it had lived only on the unmerged `lane/ap-kconfig-solver`, tag
# `saved/kconfig-solver-2026-08-30`). `kconfig_graph` — the Kconfig GRAPH and the
# solver that explains the third class of `kernel.why.toml`. `WhyClass::KconfigDerived`
# grows `head`/`head_class`/`chain`/`heads`/`unexplained` (a STRUCT variant now, so this
# is a source break for anyone matching it; no consumer in the constellation does, and
# the fields are `serde(default)` so an OLD why.toml still reads). Also
# `KconfigGraph::dangling_declarations`, refused before the build container starts, and
# `kernel::KERNEL_BASE_CONFIG` beside the built `.config`. The bump is REQUIRED for the
# reason every note above records: 0.1.24 is on crates.io without any of it.
# 0.2.2 (0.2.1 is on crates.io from the side branch fix/r5-3-3-alloc-peak, so the next
# number): `params::Landing` (lane B1) — a staged parameter may declare `lands = "release"`:
# the composer folds it into the signed release bundle (korp-installer `release`), laid by
# every install (keep included) and every live update, instead of a first-boot /data file.
# `ParamSpec` gains the `lands` field (default `data`, not serialized), so a struct literal
# that names every field needs it; `IsoBuild::staged_param_files_landing`.
# 0.2.5 (2026-10-21, lane G10; 0.2.4 is lane B4's kernel build number): `kernel_requires::hotplug_requires` (moved AS IS from
# gunnar-recipe), `kernel_requires::{CD_BLOCK, missing_cd_block, refuse_unless_reads_its_cd}`
# (Gunnar 09fe2c48 on the BUILT .config), and `lfs::{lfs_pointers, refuse_lfs_pointers}`
# (Holger D13, out of holger xtask). Additive.
version = "0.2.6"
edition = "2024"
rust-version = "1.85"
description = "Declarative cargo build variants + SLSA-style build-provenance attestation: a repo declares its buildable variants (embedded/minimal/full …) as data; build one, capture a content-addressed provenance record (variant, toolchain, host cores+memory, inputs, artifact hashes) with a znippy-seal seam. Dependency-light so a leaf repo's xtask can pull it. The build-thing core of nornir."
license = "MIT OR Apache-2.0"
repository = "https://git.gunnar.rs/nordisk/edda"
keywords = ["cargo", "build", "provenance", "slsa", "attestation"]
categories = ["development-tools::cargo-plugins", "development-tools::build-utils"]
[features]
# VCS state on a captured provenance record (HEAD sha + branch, worktree
# freshness), read in-process through nornir-git/gix. The ONE default: the forge
# (dwarves) and every caller that keeps the defaults record HEAD as before. A
# crate that takes this one for its TYPES (korp-boot-bios -> `bios_patch`, on
# korp-installer's install path) turns the defaults off and links no gix and no
# russh; a record it captured would read `unknown`, as on wasm32.
default = ["provenance"]
provenance = ["dep:nornir-git"]
# HOST-side build-thing PLUGIN loading: the wasmtime runtime that loads an app's
# `.wasm` build thing and CALLS it (`plugin_host`). This is what lets the forge
# enumerate and validate an app's build space while depending on NO app and on no
# buildspec crate — a new app needs zero forge rebuild. OFF by default: a leaf's
# xtask consuming the types must not link a wasm runtime, and the GUEST side (the
# `plugin` module + `export_build_thing!`) needs none of this.
plugin-host = ["dep:wasmtime"]
# Read a build thing's wasm module out of an artifact's `.nordisk.buildthing` linker
# section with an OBJECT PARSER — never by executing the artifact (which would fail
# on any cross-built binary and would be remote code execution from a registry on the
# host that holds the deploy keys). Split from `plugin-host` because reading a
# section and running a sandbox are independent capabilities.
# `object` is now an unconditional dependency (the linkage audit's symbol read
# needs it), so this feature only gates the section-reading CODE, not the dep.
artifact-section = []
# Functional-status emit MODE: pull `nornir-testmatrix` and light this crate's
# `selftest::emit` bridge so its real build surfaces (variant validate / cargo
# argv / surface projection) record honest matrix rows. OFF by default — release
# strips the emit to an `#[inline]` no-op and does NOT link `nornir-testmatrix`.
testmatrix = ["dep:nornir-testmatrix", "nornir-testmatrix/testmatrix"]
# HEAVY container-image build path: the real `BollardImageBuilder` that BUILDS a
# `ContainerBuild` over the podman/Docker REST API (bollard, zero-shell) and seals
# provenance over the produced image digest. OFF by default so the dep-light
# default crate stays free of the bollard/hyper/tokio stack — a leaf xtask pulls
# only the seam + provenance types. Mirrors nornir's `test-boot-container-bollard`.
# The dep-light VM provenance path (`PrebuiltVmBuilder`) needs none of this.
image-build = ["dep:bollard", "dep:tokio", "dep:futures", "dep:bytes"]
# Columnar build-info: light up the Arrow RecordBatch binding for the build-info
# stream (schema §f3 + emitter, `build_info::{build_info_schema,build_info_batch}`).
# OFF by default so the dep-light core stays free of the arrow stack — the flat
# `BuildInfoRow` + `BuildInfoSink` seam work WITHOUT this feature; only callers
# that want Arrow batches (the Flight/at-rest halves, a later slice) pull it.
# Reuses znippy's arrow crate pins (59 — the deps below say so; this said 58 until
# 2026-08-24) rather than re-rolling arrow plumbing.
build-info-arrow = ["dep:arrow-array", "dep:arrow-schema"]
# SERVICE-SPEC bridge: pull the real `skidbladnir` crate (by REGISTRY version —
# see the dep below for why it must never be a path dep) and light up
# `ServiceSpec::to_skidbladnir`, which reconstructs the authoritative
# `skidbladnir::service::ServiceSpec` through skidbladnir's OWN builder (no twinned
# installer). OFF by default so the dep-light core stays free of the airgap
# (tar/zstd) stack — the default build ships only the lean hand-off POD + the
# `service_spec` seam; a consumer that actually installs pulls this feature.
skidbladnir = ["dep:skidbladnir"]
# HEAVY kernel-compile path: the LIVE legs of the `kernel` kind — draupnir's
# `backend-oci` container engine (the SAME bollard leg dwarves already drives,
# reused rather than re-rolled: `DraupnirKernelRunner`) + the pure-Rust ureq
# tarball fetcher (`UreqFetcher`). OFF by default so the dep-light core stays
# free of the bollard/tokio stack — the declaration, validation, kconfig merge,
# script authoring and the whole orchestration (`build_kernel_with`) compile and
# test WITHOUT this feature over the fake-able `TarballFetcher` /
# `KernelContainerRunner` seams; only a consumer that actually compiles a kernel
# pulls it. The real 30-min build itself lives behind the `#[ignore]`d
# `tests/kernel_build_live.rs`.
kernel-build = ["dep:draupnir", "draupnir/backend-oci", "dep:ureq", "dep:libc"]
# HEAVY package-assembly path: the LIVE leg of the `package` kind —
# `DraupnirPackageRunner`, the SAME draupnir `backend-oci` engine the kernel leg and
# dwarves already drive, reused rather than re-rolled. OFF by default for the same
# reason `kernel-build` is: the declaration, the member set, the SHA256SUMS writer,
# the produced-set verification and the whole orchestration
# (`ContainerPackageBuilder`) compile and test WITHOUT it, over the fake-able
# `PackageContainerRunner` / `PackageStager` seams — no daemon, no network, every red
# arm reachable. Only a consumer that actually assembles a wheel pulls it.
package-build = ["dep:draupnir", "draupnir/backend-oci"]
# The LIVE leg of the wasm cell's TYPED STAGES (`wasm::XtaskWasmStageRunner`):
# wasm-bindgen at the version `Cargo.lock` pins, plus the cargo example whose stdout
# is the component manifest. Pulls `nornir-xtask-support` for
# `ensure_wasm_target` / `ensure_wasm_bindgen` / `locked_wasm_bindgen_version` —
# REUSED rather than re-derived; that last one exists because a bindgen CLI that does
# not match the lock produces the classic silent blank canvas. OFF by default like
# every other heavy leg: the declaration, the derived output names, the validation
# and the whole orchestration (`run_stages`) compile and test WITHOUT it over the
# fake-able `WasmStageRunner` seam, with no wasm target installed and no network.
wasm-stage = ["dep:nornir-xtask-support"]
# The LIVE leg of the CARGO kind's container build (`cargo_container`) — the SAME
# draupnir `backend-oci` engine `kernel-build` uses, so the constellation has one
# container engine and not two. OFF by default for the same reason: the
# declaration, validation, script authoring, stdout parse, provenance capture and
# the byte-identity gate all compile and test WITHOUT it, over the fake-able
# `CargoContainerRunner` seam. Only a consumer that actually runs a build
# container pulls the bollard/tokio stack.
cargo-container = ["dep:draupnir", "draupnir/backend-oci"]
# The LIVE bindgen of the web-app step (`web_app::LibraryBindgen`):
# wasm-bindgen-cli-support LINKED at one pinned version, so no bindgen CLI is
# downloaded or spawned. OFF by default: the declaration, the page assembly and
# the orchestration compile and test WITHOUT it over the `WebAppBindgen` seam.
web-app = ["dep:wasm-bindgen-cli-support"]
[dependencies]
# Deliberately dep-LIGHT (no iceberg / arrow / eframe): a leaf repo's xtask must
# pull this cheaply. Host facts come from std + /proc.
#
# It is NOT gix-free, and saying so here was wrong twice over: VCS state is read
# IN-PROCESS via gix (`nornir-git`, host-only, below), never via the `git` CLI —
# that is the LAW nornir-git states, and this crate shells out to no git at all.
# The claim mattered because gix flips `maybe-async` to is_sync for the whole
# graph, which breaks any consumer that also links `rust-s3`; skade's manifest
# warns about exactly that, and this line read as reassurance that it did not
# apply here.
serde = { version = "1", features = ["derive"] }
# The recipe format's R3 (`schema::dropped_keys`): report every key path serde
# IGNORED, in the same pass that builds the set. Reused rather than twinned — the
# alternative is a second `toml::Value` walk beside the real deserialize, which is
# exactly the second door that let `allow_unbuilt_bins` live five months without a
# schema (see `variant::BuildVariantSet::allow_unbuilt_bins`).
serde_ignored = "0.1"
serde_json = "1"
anyhow = "1"
sha2 = "0.11"
# The web app's `integrity="sha384-…"` attributes (standard base64, the SRI
# spelling). The same major edda's other crates use.
base64 = "0.23"
# wasm-bindgen as a LIBRARY (feature `web-app`). `=` because the app's own
# lock must pin the SAME wasm-bindgen, and `web_app::LibraryBindgen::VERSION`
# names it; a caret here would let the linked library move under that name.
wasm-bindgen-cli-support = { version = "=0.2.128", optional = true }
# CRC-32/ISO-HDLC for the GPT-hybrid medium `iso_writer` lays (protective MBR +
# GPT header/array CRCs, UEFI 2.x §5.3) — the zlib CRC the spec mandates. The
# same crate korp-installer's disk-side `gpt.rs` uses; already in the lock.
crc32fast = "1"
# Shared lowercase-hex encode (edda leaf) — the provenance record's digest→hex
# rendering, consumed instead of a private `write!("{b:02x}")` loop.
nornir-hash = { version = "0.1.1", path = "../nornir-hash" }
# The xtask toolchain helpers the `wasm-stage` leg drives. OPTIONAL: the dep-light
# default crate links none of it. No cycle — nornir-xtask-support does not depend on
# this crate.
nornir-xtask-support = { version = "0.1.0", path = "../nornir-xtask-support", optional = true }
# Timestamps for build-info rows. `default-features = false` is REQUIRED, not tidiness:
# chrono's default `wasmbind` feature pulls wasm-bindgen on `wasm32-unknown-unknown`,
# which puts `__wbindgen_placeholder__::__wbindgen_describe` in a build thing's import
# section — and the plugin host refuses any import outside the `nordisk` permission
# list, so the whole plugin path would fail to load. `clock` keeps `Utc::now()`; on
# wasm it is never called (a plugin has no clock, by design).
chrono = { version = "0.4", default-features = false, features = ["std", "clock"] }
cargo_metadata = "0.23"
toml = "1"
# Functional-status emit sink — optional, linked ONLY under `--features
# testmatrix` (mirrors nornir-git / nornir-jobs).
nornir-testmatrix = { version = "0.2.4", path = "../nornir-testmatrix", optional = true }
# ── HEAVY container-image build path (feature `image-build`, all optional) ──
# The pure-Rust podman/Docker REST API client + its async stack, pulled ONLY by
# `image-build` so the default crate stays dep-light. 0.20 line (matches knut /
# nornir's test-boot bollard). tar assembles the build context, bytes carries it.
# ── Columnar build-info Arrow binding (feature `build-info-arrow`, optional) ──
# The §f3 RecordBatch schema + emitter reuse the arrow builder plumbing at the
# SAME versions znippy pins (58), so the two halves stay wire-compatible. Only
# arrow-array + arrow-schema (no ipc/compute/flight) — the light core of arrow.
arrow-array = { version = "59", optional = true }
arrow-schema = { version = "59", optional = true }
# ── Service-spec bridge (feature `skidbladnir`, optional) ──
# The real airgap install framework — consumed ONLY to reconstruct its own
# `ServiceSpec` via its own builder (the build→install seam). Optional so the
# dep-light default never pulls its tar/zstd stack.
#
# REGISTRY, NOT PATH (fixed 2026-07-31). This used to carry
# `path = "../../../Skidbladnir"`. Skidbladnir is a CONSUMER of edda — its
# manifest path-deps `../edda/crates/nornir-escape` (and nornir-hash,
# app-heartbeat, jera). That makes the edge MUTUAL, and a mutual path edge is
# not a crate cycle (the crates it reaches are leaves) but a PATH IDENTITY
# collision: `../../../Skidbladnir` resolves relative to the manifest, so from a
# git WORKTREE of edda it reaches the *sibling* Skidbladnir, whose own
# `../edda/...` lines then reach the MAIN edda checkout. cargo therefore sees
# `nornir-escape v0.1.0` at two paths and refuses to resolve at all:
#
# error: package collision in the lockfile: packages nornir-escape v0.1.0
# (…/edda-wt-<x>/crates/nornir-escape) and nornir-escape v0.1.0
# (…/edda/crates/nornir-escape) are different, but only one can be written
# to lockfile unambiguously
#
# This ONE line made edda un-buildable from ANY git worktree — every agent in a
# worktree-by-design fleet was funnelled into the single shared checkout, and the
# branch collisions that cost hours came out of that. `cargo metadata --no-deps`
# still succeeds (it never resolves), so a worktree looked healthy right until it
# built. Guarded by `nornir-release-core/tests/no_path_dep_on_our_consumer.rs`,
# which now derives the consumer set from the siblings' manifests instead of a
# hand-kept list.
skidbladnir = { version = "0.1.12", optional = true }
# ── Kernel-compile path (feature `kernel-build`, all optional) ──
# draupnir is the constellation's ONE boot/provisioning library; its
# `backend-oci` bollard leg runs the kernel-build toolchain container
# (bind-mounted workdir, bounded run). PATH + version, exactly the shape
# `jera`'s draupnir dep uses: draupnir's only edge back onto edda is a
# DEV-dependency (nornir-testmatrix), which cargo does not resolve for
# consumers — so this is NOT the Skidbladnir mutual-path-identity trap the
# comment above `skidbladnir` documents (that edge is a NORMAL dep). From a
# worktree, siblings resolve because edda worktrees are created as siblings
# under git/ (`edda-wt-<x>`), same as jera relies on.
# 0.1.13 is a FLOOR, not a formality: below it `run_to_completion` takes its
# final log drain the instant the container reports Exited, so a chatty build's
# stdout can come back truncated or EMPTY with exit 0 — and this crate reads
# cargo's `--message-format=json` off exactly that stream (`cargo_container::
# parse_container_stdout`), where the one line naming the produced executable is
# the second-to-last of ~900. MEASURED on t14s 2026-09-12: 4 of 20 runs lost it.
draupnir = { version = "0.2", path = "../../../draupnir", default-features = false, optional = true }
# Pure-Rust HTTP for the kernel.org tarball fetch: ureq over rustls (ring) — no
# openssl, no curl/wget subprocess (the host-side pure-Rust law; the container
# only compiles). Same crate/features draupnir's redfish leg resolves to.
ureq = { version = "3", default-features = false, features = ["rustls", "gzip"], optional = true }
# statvfs for the kernel build's DISK PREFLIGHT — a build that dies at 95%
# because the filesystem filled spent the whole compile to say what statvfs
# knew before it started. Optional, and only under `kernel-build`: the
# dep-light default answers "not checked" rather than "fine".
libc = { version = "0.2", optional = true }
bollard = { version = "0.20", optional = true }
tokio = { version = "1", default-features = false, features = ["rt-multi-thread"], optional = true }
futures = { version = "0.3", optional = true }
bytes = { version = "1", optional = true }
# The wasm runtime the FORGE uses to load an app's build thing (feature `plugin-host`).
# Pinned to the version znippy already carries so the constellation resolves one
# wasmtime. `default-features = false` + only `cranelift`/`runtime`: no WASI, no
# component model, no debug/profiling — the sandbox is granted nothing, so there is
# nothing to configure and nothing to get wrong.
wasmtime = { version = "49", optional = true, default-features = false, features = ["cranelift", "runtime", "std"] }
# The pure-Rust object-file parser. Two uses: reading `.nordisk.buildthing` out of
# an artifact (feature `artifact-section`) AND the linkage audit's symbol read
# (`linkage`, always compiled) — the in-process replacement for `nm -C` (LAW:
# symbol listing goes through `object`/`gimli`, never the `nm` binary). Read-only,
# format-agnostic (ELF/Mach-O/PE), and — the whole point — it PARSES the binary
# instead of running it, so it works on an artifact this host cannot execute.
object = { version = "0.40", default-features = false, features = ["read", "std"] }
# Demangles Rust v0/legacy symbols so the linkage audit matches the readable names
# `nm -C` prints (families like `nornir::server` only appear demangled). Tiny,
# pure-Rust, no deps.
rustc-demangle = "0.1"
# ── host-only deps ───────────────────────────────────────────────────────────
# Record ids for a CAPTURED build (`BuildProvenance::capture` / `build_container`).
# Deliberately NOT a wasm dependency: `uuid/v4` needs an OS randomness source, and on
# `wasm32-unknown-unknown` there is none — it is a hard compile ERROR, not a runtime
# one. That target is exactly where this crate ships as a build-thing PLUGIN (`abi`):
# a plugin DECLARES and VALIDATES, it captures no provenance and mints no record id.
# Keeping uuid unconditional would make the whole plugin path unbuildable for the sake
# of an id no plugin ever asks for. The wasm build substitutes a deterministic
# counter (`provenance::record_id`).
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
# ── cloud MEDIUM writers (`image_writer`) ───────────────────────────────────
# The USTAR writer for GCE's `disk.raw` tarball and the gzip stream around it.
# LIBRARIES, in-process — the constellation LAW forbids shelling to `tar(1)`,
# `gzip(1)` or `qemu-img` on an artifact path, exactly as `iso_writer` masters
# ISO-9660 with no `xorriso`. flate2's default backend is miniz_oxide, so this
# is pure Rust with no zlib C.
#
# `tar` moved here from the optional `image-build` line (it was `dep:tar` in
# that feature) because the GCE medium is not an opt-in: a preset row declaring
# `raw disk.raw in a tar.gz` must reach a writer in the DEFAULT build, or it is
# the same unreachable row this module exists to fix. `image-build`'s bollard
# context assembly still uses the same crate.
#
# HOST-ONLY, for the same reason `uuid` below is: a build-thing PLUGIN compiled
# to `wasm32-unknown-unknown` declares and validates — it converts no disk
# images — and `tar`'s filetime path has no business in that target's import
# section. `lib.rs` gates the module with the same cfg.
tar = "0.4"
flate2 = "1"
uuid = { version = "1", features = ["v4"] }
# VCS state for a captured provenance record, read in-process via gix (the leaf's
# `gitio::head_sha_and_branch` / `worktree_freshness`) — NEVER the `git` CLI (LAW).
# Host-only: nornir-git pulls gix + the russh SSH bridge, which do not build for
# wasm32 (where this crate is a build-thing PLUGIN that captures no provenance).
nornir-git = { version = "0.2.0", path = "../nornir-git", default-features = false, features = ["porcelain"], optional = true }
[dev-dependencies]
# ── the cloud-medium proofs (`tests/image_writer*.rs`) ───────────────────────
# `tar` + `flate2` are LIBRARY deps of the crate already; they are listed again
# here because an integration test links only dev-dependencies, and the GCE red
# probe has to BUILD a wrong tarball (`image.raw`, a second member) to prove the
# refusal fires. A probe that cannot construct the failure it claims to catch is
# not a probe — that is the whole lesson of the hollow-guard sweep.
# `sha2` likewise: the tests compute the expected content id independently of
# the writer, so an assertion cannot be satisfied by the writer agreeing with
# itself.
tar = "0.4"
flate2 = "1"
sha2 = "0.11"
tempfile = "3"
# The role/linkage CONTRACT guard (`tests/role_linkage_contract.rs`) sweeps every
# checked-in `.nornir/build-variants.toml` in the constellation, so it needs the sibling
# walk. That already exists as `nornir_testmatrix::citations::sibling_checkouts` — consumed
# here rather than re-rolled (L5: reuse, do not twin). Dev-only: the shipped crate links
# nothing extra.
nornir-testmatrix = { path = "../nornir-testmatrix" }
# Assemble tiny wasm modules from `.wat` text in the plugin-host tests: a module that
# imports a capability, one that reports the wrong ABI, one that spins forever. Hand-
# writing those as byte arrays would be unreadable, and building real crates for them
# would make the tests slow and circular. Test-only.
wat = "1"
# Pure-Rust FAT filesystem — used ONLY by the ignored `iso_boot` heavy test to
# format the EFI System Partition (`efiboot.img`) the mastered UEFI ISO boots.
# Test-only, and itself zero-shell (no `mkfs`/`mtools`): the whole ISO+ESP build
# stays pure Rust; QEMU is the only external, and only to prove the boot.
fatfs = "0.3.6"
# ── the cloud-medium BOOT proof lives in `crates/cloud-media-bootproof` ───────
# (moved 2026-09-29). It needs korp-installer's `gpt` / `esp` / `kvmproof`, and
# korp-installer lives in TUNNR since 2026-09-26 and path-deps BACK into edda
# (`tunnr/crates/korp-installer/Cargo.toml:81` nornir-tpm, `:87`
# wg-appliance-core, and korp-boot-bios -> edda/crates/nornir-build-thing). A
# `path = "../../../tunnr/crates/korp-installer"` dev-dep HERE closed a path cycle
# edda -> tunnr -> edda that made edda un-resolvable from any non-canonical
# checkout (`no_path_dep_on_our_consumer.rs`, both layers red). The registry
# copy cannot live here either: registry korp-installer -> korp-boot-bios 0.1.2 ->
# registry nornir-build-thing 0.1.24, i.e. THIS crate's own name twice in its own
# test graph, and `cargo test -p nornir-build-thing` then dies with
# "specification `nornir-build-thing` is ambiguous". A separate test-only crate
# carries both edges without either failure.
# ── examples that need the container runner ───────────────────────────────────
# `DraupnirCargoRunner` is `#[cfg(feature = "cargo-container")]` — it links
# draupnir's OCI backend, which a leaf's xtask consuming these types must not be
# made to link. These three examples call it, and cargo auto-discovers every file
# under `examples/` and builds it for `cargo test`, so without a declaration they
# were compiled WITHOUT the feature and failed:
#
# error[E0432]: unresolved import `nornir_build_thing::cargo_container::DraupnirCargoRunner`
#
# MEASURED 2026-09-20: `cargo test -p nornir-build-thing` could not even reach the
# test binaries. The runner is the door the LAW names for building in a container
# rather than on the host ("use `build_variant_in_container`, not `build_variant`"),
# so its own examples not compiling is the worst place for this to hide.
[[example]]
name = "probe_container_stdout"
required-features = ["cargo-container"]
[[example]]
name = "mint_cargo_toolchain"
required-features = ["cargo-container"]
[[example]]
name = "name_collision_recovery"
required-features = ["cargo-container"]
[[example]]
name = "probe_log_tail"
required-features = ["cargo-container"]