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

nordisk/edda

crates/nornir-build-thing/src/requires.rs

//! **The requirement CLOSURE — generic over sub build-things.** (the owner,
//! 2026-08-29: *"the kernel is just ONE sub build-thing; any feature of any
//! consumer may declare requirements on any sub build-thing; put the mechanism
//! in the ENGINE, the repos only declare and consume."*)
//!
//! A consumer's tree (`variant → features → settings`) carries EDGES to the sub
//! build-things it consumes (`kernel → tunnr/tunnr-kernel : kernel`; later a
//! toolchain image, a container base, a firmware). A feature of the consumer
//! may place a [`Requirement`] on the thing behind an edge; the requirement is
//! typed by the donor's SURFACE — a `typ = kernel` donor exposes Kconfig
//! symbols ([`Requirement::Kconfig`]), and a future `typ = container` donor
//! would expose its own. The engine:
//!
//! 1. **collects** — [`closure_of_consumer`] asks every [`RequirementSource`]
//!    (the declared option rows, the row-level list, the appliance-boot
//!    contract, the `target.*` facts when that lane plugs in) what the
//!    consumer's ENABLED features require, per edge;
//! 2. **unions recursively** — the planner ([`crate::build_dag::BuildDag`])
//!    walks variant → edges → the donor's own edges and joins every consumer's
//!    closure for one coordinate into that step's requirements;
//! 3. **refuses by name** — two requirers disagreeing, or a donor preset that
//!    vetoes a requirement, is a refusal naming symbol · consumer · feature ·
//!    reason ([`SurfaceClosure`] dispatches to the surface's own checks);
//! 4. **inverts** — the surface emits its why-index beside the artifact
//!    (`kernel.why.toml` for Kconfig: [`crate::kernel_requires::WhyIndex`]).
//!
//! Both thin doors — `dwarves buildthing` and the ⚒ pane — call the same
//! derivation and the same compose path, so there is ONE evaluator and no
//! CLI-only road. Nothing here reads an emitted toml back as authority: the
//! sources read the [`BuildVariantSet`] the impl produced.
//!
//! # Plugging in a source (the `target.*` lane)
//!
//! Implement [`RequirementSource`] over the consumer view and add it to
//! [`default_sources`]. A `target.consoles ∌ serial` fact then yields
//! `Requirement::Kconfig(SERIAL_8250 = n)` on edge `kernel`, joins the same
//! union, is refused by the same rule and appears in the same why-index — no
//! second mechanism.

use std::collections::BTreeMap;

use anyhow::{anyhow, bail, Result};
use serde::{Deserialize, Serialize};

use crate::kernel_requires::{KconfigReq, KernelRequirements};
use crate::options::{OptionSpec, ResolvedOptions};
use crate::toolchain_requires::{ToolRequirements, ToolReq};
use crate::variant::{ArtifactKind, BuildVariantSet};

/// **One requirement, typed by the SURFACE of the sub build-thing it is placed
/// on.** The tag is the surface; a donor whose typ has no surface variant here
/// cannot be required of yet, and saying so is a named refusal.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "surface", rename_all = "kebab-case")]
pub enum Requirement {
    /// A Kconfig symbol on a `typ = kernel` donor.
    Kconfig(KconfigReq),
    /// A TOOL on a `typ = container` donor — the toolchain-image surface
    /// ([`crate::toolchain_requires`]). The second surface, and the one that
    /// made the word "generic" in this module's header true.
    Tool(ToolReq),
}

impl Requirement {
    /// The donor typ this requirement can be placed on.
    pub fn typ(&self) -> ArtifactKind {
        match self {
            Requirement::Kconfig(_) => ArtifactKind::Kernel,
            Requirement::Tool(_) => ArtifactKind::Container,
        }
    }
    /// Declaration-time check, by the surface's own rule.
    pub fn validate(&self, who: &str) -> Result<()> {
        match self {
            Requirement::Kconfig(r) => r.validate(who),
            Requirement::Tool(r) => r.validate(who),
        }
    }
}

/// **A requirement addressed to an edge** — `edge` is the feature path on the
/// consumer that carries the consumption edge (`kernel`, `b.c` for a chained one).
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct EdgeRequirement {
    /// The consumer's edge path.
    pub edge: String,
    /// What is required of the thing behind it.
    #[serde(flatten)]
    pub what: Requirement,
}

/// **The union for ONE edge**, per surface — the data a step carries.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "surface", rename_all = "kebab-case")]
pub enum SurfaceClosure {
    /// Kconfig symbols → requirers ([`KernelRequirements`]).
    Kconfig(KernelRequirements),
    /// Tools → requirers ([`ToolRequirements`]) — the toolchain-image surface.
    Tool(ToolRequirements),
}

impl SurfaceClosure {
    /// The donor typ this closure is about.
    pub fn typ(&self) -> ArtifactKind {
        match self {
            SurfaceClosure::Kconfig(_) => ArtifactKind::Kernel,
            SurfaceClosure::Tool(_) => ArtifactKind::Container,
        }
    }
    /// Nothing required.
    pub fn is_empty(&self) -> bool {
        match self {
            SurfaceClosure::Kconfig(k) => k.is_empty(),
            SurfaceClosure::Tool(t) => t.is_empty(),
        }
    }
    /// Join another consumer's closure over the same edge (same surface, else refused).
    pub fn union(&mut self, other: &SurfaceClosure) -> Result<()> {
        match (self, other) {
            (SurfaceClosure::Kconfig(a), SurfaceClosure::Kconfig(b)) => {
                a.union(b);
                Ok(())
            }
            (SurfaceClosure::Tool(a), SurfaceClosure::Tool(b)) => {
                a.union(b);
                Ok(())
            }
            // One edge, two surfaces: refused by name rather than resolved by a
            // precedence nobody remembers. Reachable only through a hand-built
            // closure — the edge's typ is checked before a requirement lands.
            (a, b) => bail!(
                "one edge cannot carry two surfaces — a {} closure joined with a {} one",
                a.typ().as_str(),
                b.typ().as_str()
            ),
        }
    }
    /// Requirer against requirer, as refusal sentences (empty = consistent).
    pub fn conflicts(&self) -> Vec<String> {
        match self {
            SurfaceClosure::Kconfig(k) => k.conflicts().iter().map(ToString::to_string).collect(),
            SurfaceClosure::Tool(t) => t.conflicts().iter().map(ToString::to_string).collect(),
        }
    }
    /// The step-log sentence.
    pub fn summary(&self) -> String {
        match self {
            SurfaceClosure::Kconfig(k) => k.summary(),
            SurfaceClosure::Tool(t) => t.summary(),
        }
    }
    /// The Kconfig half, when this is one.
    pub fn as_kconfig(&self) -> Option<&KernelRequirements> {
        match self {
            SurfaceClosure::Kconfig(k) => Some(k),
            _ => None,
        }
    }
    /// The toolchain half, when this is one.
    pub fn as_tools(&self) -> Option<&ToolRequirements> {
        match self {
            SurfaceClosure::Tool(t) => Some(t),
            _ => None,
        }
    }
}

/// **Everything one consumer requires of its sub build-things**, by edge path.
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct Closure {
    /// Edge path → the union over that edge.
    pub by_edge: BTreeMap<String, SurfaceClosure>,
}

impl Closure {
    /// Place one requirement from `feature` of `consumer` (`repo/variant`).
    pub fn require(&mut self, consumer: &str, feature: &str, r: &EdgeRequirement) {
        match &r.what {
            Requirement::Kconfig(k) => {
                let slot = self.by_edge.entry(r.edge.clone()).or_insert_with(|| SurfaceClosure::Kconfig(KernelRequirements::default()));
                if let SurfaceClosure::Kconfig(kr) = slot {
                    kr.require(consumer, feature, k);
                }
            }
            Requirement::Tool(t) => {
                let slot = self.by_edge.entry(r.edge.clone()).or_insert_with(|| SurfaceClosure::Tool(ToolRequirements::default()));
                if let SurfaceClosure::Tool(tr) = slot {
                    tr.require(consumer, feature, t);
                }
            }
        }
    }
    /// The closure over one edge, if any feature required anything of it.
    pub fn on_edge(&self, edge: &str) -> Option<&SurfaceClosure> {
        self.by_edge.get(edge).filter(|c| !c.is_empty())
    }
    /// Nothing required of anything.
    pub fn is_empty(&self) -> bool {
        self.by_edge.values().all(SurfaceClosure::is_empty)
    }
}

/// **What a [`RequirementSource`] is shown** — the consumer, resolved.
pub struct ConsumerView<'a> {
    /// `repo/variant`.
    pub consumer: String,
    /// The repo name.
    pub repo: &'a str,
    /// The variant name.
    pub variant: &'a str,
    /// The consumer's whole declaration (the impl's answer, never a toml read back).
    pub set: &'a BuildVariantSet,
    /// The option rows of this variant (installer/iso), empty for other kinds.
    pub options: &'a [OptionSpec],
    /// The row-level requirements of this variant.
    pub row_requires: &'a [EdgeRequirement],
    /// The resolved toggles (`--opt name=on|off` over the declared defaults).
    pub resolved: &'a ResolvedOptions,
    /// The consumer's edges: `(path, donor typ)`.
    pub edges: Vec<(String, ArtifactKind)>,
}

impl ConsumerView<'_> {
    /// The consumer's DECLARED `target` group — `Some` only for an installer
    /// row whose group is not at its default (the effective shape, a preset
    /// resolved). `None` for an undeclared group and for every other kind:
    /// the group is spelled on `[[build.installer]]` alone.
    pub fn declared_target(&self) -> Option<crate::target::Target> {
        let row = self.set.installers.iter().find(|i| i.name == self.variant)?;
        (!row.target.is_default()).then(|| row.target.effective())
    }

    /// The path of the ONE edge of typ `typ`, when exactly one exists — the
    /// implicit address of a `kernel_requires` row (a `kernel_donor` edge is
    /// always at path `kernel`).
    /// The consumer's declared `data` group (`[build.installer.data]`), `None`
    /// when the row is undeclared — an undeclared group must be inert.
    pub fn declared_data(&self) -> Option<crate::data::Data> {
        let row = self.set.installers.iter().find(|i| i.name == self.variant)?;
        (!row.data.is_default()).then(|| row.data.clone())
    }

    pub fn edge_of_typ(&self, typ: ArtifactKind) -> Option<&str> {
        let mut it = self.edges.iter().filter(|(_, t)| *t == typ);
        match (it.next(), it.next()) {
            (Some((p, _)), None) => Some(p.as_str()),
            _ => None,
        }
    }
}

/// **A plug-in that says what a consumer requires** — the declared rows are
/// one, the appliance-boot contract is another, the `target.*` facts are the
/// next. Every source answers `(feature, requirement)` rows; the engine unions.
pub trait RequirementSource {
    /// The source's name (for a refusal).
    fn name(&self) -> &str;
    /// The rows this source places for `view` — ENABLED features only.
    fn requirements(&self, view: &ConsumerView<'_>) -> Result<Vec<(String, EdgeRequirement)>>;
}

/// The declared `kernel_requires` / `requires` rows of the consumer's ENABLED
/// options (an option that is off is not compiled in and asks nothing), plus
/// the row-level list.
pub struct DeclaredRows;

impl RequirementSource for DeclaredRows {
    fn name(&self) -> &str {
        "declared rows"
    }
    fn requirements(&self, view: &ConsumerView<'_>) -> Result<Vec<(String, EdgeRequirement)>> {
        let mut out = Vec::new();
        let kernel_edge = view.edge_of_typ(ArtifactKind::Kernel).map(str::to_string);
        for r in view.row_requires {
            out.push(("variant".to_string(), r.clone()));
        }
        for o in view.options {
            if view.resolved.is_on(&o.name) != Some(true) {
                continue;
            }
            for r in &o.requires {
                out.push((o.name.clone(), r.clone()));
            }
            if !o.kernel_requires.is_empty() {
                let Some(edge) = &kernel_edge else {
                    bail!(
                        "{}: option '{}' declares kernel_requires but the variant consumes no kernel (no kernel_donor / no edge of typ kernel) — a requirement on nothing",
                        view.consumer,
                        o.name
                    );
                };
                for k in &o.kernel_requires {
                    out.push((o.name.clone(), EdgeRequirement { edge: edge.clone(), what: Requirement::Kconfig(k.clone()) }));
                }
            }
        }
        Ok(out)
    }
}

/// The PID-1 boot contract this crate owns ([`crate::kernel_requires::appliance_boot_requirements`])
/// — placed on the kernel edge of every consumer that has one, under the
/// feature [`crate::kernel_requires::APPLIANCE_BOOT_FEATURE`].
pub struct ApplianceBootContract;

impl RequirementSource for ApplianceBootContract {
    fn name(&self) -> &str {
        crate::kernel_requires::APPLIANCE_BOOT_FEATURE
    }
    fn requirements(&self, view: &ConsumerView<'_>) -> Result<Vec<(String, EdgeRequirement)>> {
        let Some(edge) = view.edge_of_typ(ArtifactKind::Kernel) else { return Ok(Vec::new()) };
        // Once the consumer DECLARES its `target` group, that group is the one
        // writer of every symbol it CAN name (uarts, consoles, buses, nic,
        // cpu): the contract's measured defaults for those step aside, so a
        // target with no UART can say `SERIAL_8250=n` without contradicting the
        // contract's `=y` — and the contract's `SERIAL_8250_CONSOLE=y` goes
        // with it, a console on a driver the group switched off. "Can name" is
        // derived from the writer itself: the symbols of the default shape's
        // rows (everything present) plus the declared shape's (the absences).
        // No hand list here; `target.rs` stays the only place a fact's symbols
        // are spelled. Undeclared: the contract stands whole — byte-identical
        // to before the group existed.
        //
        // ⚠ THE VOCABULARY IS WHAT STEPS THE CONTRACT ASIDE, so a group that
        // declines to speak inside it leaves the symbol to `kconfig/base.config`
        // rather than to the contract. That is why `Target::kconfig_needs` says
        // `SERIAL_8250=y` for a machine with real ports and no serial console
        // (UpCloud) instead of going quiet: a fact this group owns must be
        // ANSWERED by it, and the honest answer for a machine with a ttyS1 is
        // that the driver belongs in the kernel. Silence is only ever right for
        // a symbol NOT in the vocabulary — `SERIAL_8250_CONSOLE` on a machine
        // no `console=` points at a UART on, where base.config's `=y` is inert.
        let owned: Vec<String> = view
            .declared_target()
            .map(|t| {
                crate::target::Target::default()
                    .kconfig_needs()
                    .into_iter()
                    .chain(t.kconfig_needs())
                    .map(|(s, _, _)| s.to_string())
                    .collect()
            })
            .unwrap_or_default();
        // ONE user is the contract's DEFAULT, not a law (lane HOLGER-MEDIA,
        // 2026-10-23): a consumer whose own rows require `CONFIG_MULTIUSER=y`
        // (Holger's main and twin: every scan in a container, which needs user
        // namespaces and cgroups, both `depends on MULTIUSER`) lifts the
        // contract's `=n` by saying so; every other consumer keeps it.
        //
        // IPv4 only is lifted the same way and only by a ROW (owner
        // 2026-10-29): a Kubernetes node requires `CONFIG_IPV6=y` because
        // Cilium's socket-LB programs read `bpf_sock->src_ip6`, and boots with
        // `ipv6.disable=1`. A feature asking for IPv6 is still refused by name.
        //
        // The framebuffer console likewise (owner 2026-10-30, option A): the
        // k8s node's row requires FB, FB_EFI and FRAMEBUFFER_CONSOLE so its
        // console dashboard shows under UEFI; DRM and LOGO are never lifted.
        let lifted: Vec<&str> = crate::kernel_requires::ROW_LIFTABLE_SYMBOLS
            .iter()
            .copied()
            .filter(|sym| {
                view.row_requires.iter().any(|r| {
                    matches!(&r.what, Requirement::Kconfig(k) if k.symbol == *sym && k.value == crate::kernel_requires::KconfigValue::Y)
                })
            })
            .collect();
        Ok(crate::kernel_requires::appliance_boot_requirements()
            .into_iter()
            .filter(|k| !owned.contains(&k.symbol))
            .filter(|k| !lifted.contains(&k.symbol.as_str()))
            .map(|k| (self.name().to_string(), EdgeRequirement { edge: edge.to_string(), what: Requirement::Kconfig(k) }))
            .collect())
    }
}

/// **The `target` facts as kernel requirements** — the consumer's declared
/// machine shape ([`crate::target::Target`], `[build.installer.target]`) placed
/// on its kernel edge through the SAME closure every other feature uses. The
/// rows are [`crate::target::Target::kconfig_needs`]'s — `SERIAL_8250`
/// + `SERIAL_EARLYCON` iff the machine has a COM PORT (`target.uarts`, not the
/// console: the ports decide the driver), `SERIAL_8250_CONSOLE` iff a serial
/// console, `VT VT_CONSOLE VGA_CONSOLE PRINTK` iff vga, `ATA ATA_PIIX
/// BLK_DEV_SR ISO9660_FS` iff `ide-cdrom` is a bus the KERNEL drives (which the
/// way a platform hands a medium over never decides — see
/// [`crate::target::presets::MediumBring::kernel_medium_bus`]),
/// `VIRTIO_BLK VIRTIO_PCI(_LEGACY)`
/// iff virtio-blk, `SCSI BLK_DEV_SD SCSI_VIRTIO` iff virtio-scsi, `VIRTIO_NET`
/// iff nic virtio, `E1000` iff e1000, `CPU_SUP_AMD` iff epyc — consumed here,
/// never restated: `target.rs` is the one writer of what a fact needs, this
/// source only carries it. The feature each row lands under is the group the
/// row's own reason names (`target.uarts`, `target.consoles`, `target.buses`,
/// `target.nic`, `target.cpu`), so the why-index says which fact asked.
///
/// Only a DECLARED group speaks (`!Target::is_default()`): an undeclared row
/// changes nothing anywhere in the group, and here that means the boot
/// contract's measured defaults stand as they did. A declared fact the donor's
/// preset vetoes — `ide-cdrom` on a `nuked` kernel, which says `# CONFIG_ATA is
/// not set` — is refused BY NAME by [`crate::kernel::KernelBuild::apply_requirements`],
/// not merged over the veto: pick a preset that grants it, or drop the bus.
/// That refusal is the mechanism working: it is the UpCloud IDE-cdrom lane
/// (2026-08-28/29) stated as a build fact instead of a dark boot.
pub struct TargetFacts;

/// The feature a `kconfig_needs` row lands under: the leading `target.<group>`
/// word of its reason (`"target.consoles names serial: …"` → `target.consoles`).
fn target_feature_of(why: &str) -> String {
    why.split_whitespace()
        .next()
        .unwrap_or("target")
        .trim_end_matches(':')
        .to_string()
}

impl RequirementSource for TargetFacts {
    fn name(&self) -> &str {
        "target facts"
    }
    fn requirements(&self, view: &ConsumerView<'_>) -> Result<Vec<(String, EdgeRequirement)>> {
        let Some(edge) = view.edge_of_typ(ArtifactKind::Kernel) else { return Ok(Vec::new()) };
        let Some(target) = view.declared_target() else { return Ok(Vec::new()) };
        Ok(target
            .kconfig_needs()
            .into_iter()
            .map(|(symbol, on, why)| {
                let req = if on { KconfigReq::y(symbol, &why) } else { KconfigReq::n(symbol, &why) };
                (target_feature_of(&why), EdgeRequirement { edge: edge.to_string(), what: Requirement::Kconfig(req) })
            })
            .collect())
    }
}

/// **The sources the engine asks by default** — the one list both doors and
/// the planner use. A new fact family joins HERE.
/// **The `data` facts as kernel requirements** — the consumer's declared
/// `/data` filesystem ([`crate::data::Data`], `[build.installer.data]`) on
/// its kernel edge, every row under the feature `data.fs` (the leading word
/// of its reason, the same rule as [`TargetFacts`]). `xfs` lands
/// `CONFIG_XFS_FS=y` + CRC32C and the one-user `=n` narrowings; an undeclared
/// group lands nothing, so every pre-group closure is byte-identical.
pub struct DataFacts;

impl RequirementSource for DataFacts {
    fn name(&self) -> &str {
        "data facts"
    }
    fn requirements(&self, view: &ConsumerView<'_>) -> Result<Vec<(String, EdgeRequirement)>> {
        let Some(edge) = view.edge_of_typ(ArtifactKind::Kernel) else { return Ok(Vec::new()) };
        let Some(data) = view.declared_data() else { return Ok(Vec::new()) };
        Ok(data
            .kconfig_needs()
            .into_iter()
            .map(|(symbol, on, why)| {
                let req = if on { KconfigReq::y(symbol, &why) } else { KconfigReq::n(symbol, &why) };
                (target_feature_of(&why), EdgeRequirement { edge: edge.to_string(), what: Requirement::Kconfig(req) })
            })
            .collect())
    }
}

/// **What a KERNEL row needs of the toolchain image behind its `toolchain`
/// edge** — the SECOND surface, and the first source that speaks to a donor
/// that is not a kernel ([`crate::toolchain_requires`]).
///
/// It is the sentence the generic engine exists to make sayable: the kernel is a
/// CONSUMER as well as a donor. `tunnr-kernel` consumes
/// `tunnr/nordisk-kernel-toolchain : container`, and its own axes decide which
/// tools the image owes it — `toolchain = "clang"` or `lto` pulls `clang`/`lld`
/// under the feature `kernel.lto`, a named `solver` pulls `kmax` under
/// `kernel.minify`, a probe pulls `qemu-system-x86` under `kernel.probe-boot`.
///
/// A kernel row that declares no container edge asks nothing, so every existing
/// declaration is byte-identical — exactly the shape [`DataFacts`] and
/// [`TargetFacts`] take for an undeclared group.
///
/// The consumer here is a `[[build.kernel]]` row, which carries no option rows
/// at all: the closure's `options` are empty for it and the toggles are empty
/// with them. That is not a gap — a kernel's axes are FIELDS (`toolchain`,
/// `lto`, `solver`, `probe_timeout_secs`), and this source reads them off the
/// row the way `DataFacts` reads the `data` group off an installer's.
pub struct ToolchainTools;

impl RequirementSource for ToolchainTools {
    fn name(&self) -> &str {
        "toolchain tools"
    }
    fn requirements(&self, view: &ConsumerView<'_>) -> Result<Vec<(String, EdgeRequirement)>> {
        let Some(k) = view.set.kernels.iter().find(|k| k.name == view.variant) else { return Ok(Vec::new()) };
        let Some(edge) = view.edge_of_typ(ArtifactKind::Container) else { return Ok(Vec::new()) };
        Ok(crate::toolchain_requires::kernel_tool_requirements(k)
            .into_iter()
            .map(|(feature, req)| (feature, EdgeRequirement { edge: edge.to_string(), what: Requirement::Tool(req) }))
            .collect())
    }
}

pub fn default_sources() -> Vec<Box<dyn RequirementSource>> {
    vec![Box::new(DeclaredRows), Box::new(ApplianceBootContract), Box::new(TargetFacts), Box::new(DataFacts), Box::new(ToolchainTools)]
}

/// **The closure of one consumer variant** through [`default_sources`].
/// `toggles` are the consumer's root-scope `--opt name=on|off`. `Ok(None)`
/// when the variant declares no edges (nothing to require of nobody).
pub fn closure_of_consumer(set: &BuildVariantSet, repo: &str, variant: &str, toggles: &BTreeMap<String, bool>) -> Result<Option<Closure>> {
    closure_of_consumer_with(set, repo, variant, toggles, &default_sources())
}

/// [`closure_of_consumer`] over explicit sources (a test's, or a door that adds one).
pub fn closure_of_consumer_with(
    set: &BuildVariantSet,
    repo: &str,
    variant: &str,
    toggles: &BTreeMap<String, bool>,
    sources: &[Box<dyn RequirementSource>],
) -> Result<Option<Closure>> {
    let Some(tree) = crate::tree::SettingsTree::of_set(set, variant) else {
        return Ok(None);
    };
    let edges: Vec<(String, ArtifactKind)> = tree.walk().into_iter().filter_map(|n| n.edge.as_ref().map(|e| (n.path.clone(), e.typ))).collect();
    if edges.is_empty() {
        return Ok(None);
    }
    let empty_opts: Vec<OptionSpec> = Vec::new();
    let empty_reqs: Vec<EdgeRequirement> = Vec::new();
    let (options, params, row_requires): (&[OptionSpec], &[crate::params::ParamSpec], &[EdgeRequirement]) =
        if let Some(i) = set.installers.iter().find(|i| i.name == variant) {
            (&i.options, &i.params, &i.requires)
        } else if let Some(i) = set.isos.iter().find(|i| i.name == variant) {
            (&i.options, &i.params, &i.requires)
        } else {
            (&empty_opts, &[], &empty_reqs)
        };
    // Only the toggles that name a DECLARED option are the closure's business;
    // a root toggle on a cargo variant's feature (`--opt fast=on`) is judged by
    // the variant's own door, not here.
    let toggles: BTreeMap<String, bool> = toggles.iter().filter(|(n, _)| options.iter().any(|o| o.name == **n)).map(|(n, v)| (n.clone(), *v)).collect();
    let resolved = crate::options::resolve_options(options, params, &toggles)?;
    let view = ConsumerView { consumer: format!("{repo}/{variant}"), repo, variant, set, options, row_requires, resolved: &resolved, edges };
    let mut out = Closure::default();
    for s in sources {
        for (feature, r) in s.requirements(&view).map_err(|e| anyhow!("{}: source '{}': {e:#}", view.consumer, s.name()))? {
            // The edge must exist and the surface must be the donor's typ.
            match view.edges.iter().find(|(p, _)| *p == r.edge) {
                None => bail!(
                    "{}: feature '{feature}' requires something of edge '{}', which the variant does not declare (edges: {})",
                    view.consumer,
                    r.edge,
                    view.edges.iter().map(|(p, t)| format!("{p} : {}", t.as_str())).collect::<Vec<_>>().join(", ")
                ),
                Some((_, t)) if *t != r.what.typ() => bail!(
                    "{}: feature '{feature}' places a {} requirement on edge '{}', whose donor is typ {} — the surface does not match",
                    view.consumer,
                    r.what.typ().as_str(),
                    r.edge,
                    t.as_str()
                ),
                Some(_) => out.require(&view.consumer, &feature, &r),
            }
        }
    }
    Ok(Some(out))
}

/// The consumer's root-scope toggles out of a flag list: `--opt name=on|off`
/// with no `.` in the path (a dotted path is scoped into an edge). `prefix` is
/// the consumer's own scope for a chained consumer (`""` for the target).
pub fn toggles_in_scope(flags: &[crate::flags::Flag], prefix: &str) -> Result<BTreeMap<String, bool>> {
    let mut pairs: Vec<String> = Vec::new();
    for f in flags {
        if let crate::flags::Flag::Opt { path, value } = f {
            let Some(inner) = path.strip_prefix(prefix) else { continue };
            if inner.contains(crate::tree::PATH_SEP) || inner == "preset" || inner == "profile" || inner == crate::kernel_trim::coverage::COVERAGE_OPT {
                continue;
            }
            pairs.push(format!("{inner}={value}"));
        }
    }
    crate::options::parse_cli_options(&pairs)
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::installer::{InstallerBuild, KernelDonor};
    use crate::kernel_requires::KconfigValue;

    fn set() -> BuildVariantSet {
        let mut set = BuildVariantSet::default();
        set.installers.push(InstallerBuild {
            name: "inst".into(),
            options: vec![
                OptionSpec {
                    name: "dhcp".into(),
                    feature: "dhcp-client".into(),
                    default: true,
                    kernel_requires: vec![KconfigReq::y("CONFIG_INET", "UDP 0.0.0.0:68")],
                    ..Default::default()
                },
                OptionSpec {
                    name: "wireguard".into(),
                    feature: "wg-client".into(),
                    default: true,
                    kernel_requires: vec![KconfigReq::y("CONFIG_TUN", "boringtun")],
                    ..Default::default()
                },
            ],
            kernel_donor: Some(KernelDonor { repo: "tunnr".into(), kernel: "tunnr-kernel".into() }),
            ..Default::default()
        });
        set
    }

    #[test]
    fn the_closure_follows_the_toggles_and_lands_on_the_kernel_edge() {
        let c = closure_of_consumer(&set(), "gunnar", "inst", &BTreeMap::new()).unwrap().unwrap();
        let k = c.on_edge("kernel").unwrap().as_kconfig().unwrap();
        assert!(k.rows.contains_key("CONFIG_TUN") && k.rows.contains_key("CONFIG_INET") && k.rows.contains_key("CONFIG_PROC_FS"));
        assert_eq!(k.rows["CONFIG_IPV6"][0].0, KconfigValue::N, "the negative law rides the boot contract");
        let mut t = BTreeMap::new();
        t.insert("wireguard".to_string(), false);
        let c = closure_of_consumer(&set(), "gunnar", "inst", &t).unwrap().unwrap();
        assert!(!c.on_edge("kernel").unwrap().as_kconfig().unwrap().rows.contains_key("CONFIG_TUN"));
        // A variant with no edges has no closure.
        let mut plain = set();
        plain.installers[0].kernel_donor = None;
        assert!(closure_of_consumer(&plain, "gunnar", "inst", &BTreeMap::new()).unwrap().is_none());
    }

    /// **An option's own `kernel_requires` follows the option's toggle** — the
    /// OPTION is the requirer (not the row, not the variant), ON places the
    /// rows and OFF places none, and a second reason for one symbol is refused
    /// at declaration time.
    ///
    /// Lane AE (2026-08-30) had to park gunnar's rows on a temporary
    /// `[[build.installer.option_requires]]` table, because gunnar's own reader
    /// was a published `nornir-build-thing 0.1.10` whose `OptionSpec` is
    /// `deny_unknown_fields` and refused the whole recipe on the key. Lane AF
    /// moved gunnar onto a path dep and the rows onto the option rows, and this
    /// test is AE's, restated over the key that survives: same feature name,
    /// same ON/OFF rule, same union. Two of AE's three refusals are gone
    /// because they became IMPOSSIBLE rather than unchecked — a row addressed
    /// to an option nobody declares cannot be written inside that option, and
    /// neither can a symbol spelled in two places for one option. The third
    /// (one option, one writer per symbol) is the assertion below.
    #[test]
    fn an_options_kernel_requires_follows_the_same_toggle_and_a_second_reason_for_one_symbol_is_refused() {
        let mut s = set();
        s.installers[0].options[1].kernel_requires.push(KconfigReq::y("CONFIG_NETDEVICES", "TUN is a netdevice"));
        s.installers[0].validate().unwrap();
        let on = closure_of_consumer(&s, "gunnar", "inst", &BTreeMap::new()).unwrap().unwrap();
        let k = on.on_edge("kernel").unwrap().as_kconfig().unwrap();
        let who: Vec<&str> = k.rows["CONFIG_NETDEVICES"].iter().map(|(_, q)| q.feature.as_str()).collect();
        assert!(who.contains(&"wireguard"), "the OPTION is the requirer, not the variant: {who:?}");
        // OFF ⇒ the requirer is gone. (The symbol itself stays, required by the
        // boot contract — that is the union, not a declaration that cannot fail.)
        let mut t = BTreeMap::new();
        t.insert("wireguard".to_string(), false);
        let off = closure_of_consumer(&s, "gunnar", "inst", &t).unwrap().unwrap();
        let k = off.on_edge("kernel").unwrap().as_kconfig().unwrap();
        assert!(
            !k.rows["CONFIG_NETDEVICES"].iter().any(|(_, q)| q.feature == "wireguard"),
            "wireguard=off asks nothing: {:?}",
            k.rows["CONFIG_NETDEVICES"]
        );

        // RED: one option, two reasons for one symbol.
        let mut twice = set();
        twice.installers[0].options[1].kernel_requires.push(KconfigReq::y("CONFIG_TUN", "again"));
        let e = format!("{:#}", twice.installers[0].validate().unwrap_err());
        assert!(e.contains("CONFIG_TUN") && e.contains("one writer per symbol"), "{e}");
        // RED: a symbol that is not a CONFIG_* symbol at all.
        let mut typo = set();
        typo.installers[0].options[0].kernel_requires.push(KconfigReq::y("TUN", "no prefix"));
        let e = format!("{:#}", typo.installers[0].validate().unwrap_err());
        assert!(e.contains("not a CONFIG_* symbol"), "{e}");
    }

    #[test]
    fn a_requirement_on_an_undeclared_edge_or_the_wrong_surface_is_refused_by_name() {
        let mut s = set();
        s.installers[0].requires.push(EdgeRequirement { edge: "firmware".into(), what: Requirement::Kconfig(KconfigReq::y("CONFIG_X", "x")) });
        let e = format!("{:#}", closure_of_consumer(&s, "gunnar", "inst", &BTreeMap::new()).unwrap_err());
        assert!(e.contains("edge 'firmware'") && e.contains("does not declare"), "{e}");
        // kernel_requires on a variant with no kernel edge.
        let mut s = set();
        s.installers[0].kernel_donor = None;
        s.installers[0].edges.insert("base".into(), crate::tree::EdgeDecl { repo: "x".into(), variant: "y".into(), typ: ArtifactKind::Container, lazy: false });
        let e = format!("{:#}", closure_of_consumer(&s, "gunnar", "inst", &BTreeMap::new()).unwrap_err());
        assert!(e.contains("consumes no kernel"), "{e}");
    }

    #[test]
    fn a_plugged_in_source_joins_the_same_union() {
        struct Target;
        impl RequirementSource for Target {
            fn name(&self) -> &str {
                "target.consoles"
            }
            fn requirements(&self, view: &ConsumerView<'_>) -> Result<Vec<(String, EdgeRequirement)>> {
                let edge = view.edge_of_typ(ArtifactKind::Kernel).unwrap().to_string();
                Ok(vec![("target.consoles".into(), EdgeRequirement { edge, what: Requirement::Kconfig(KconfigReq::n("CONFIG_SERIAL_8250", "no UART on this host")) })])
            }
        }
        let mut sources = default_sources();
        sources.push(Box::new(Target));
        let c = closure_of_consumer_with(&set(), "gunnar", "inst", &BTreeMap::new(), &sources).unwrap().unwrap();
        let k = c.on_edge("kernel").unwrap();
        // The contract says SERIAL_8250=y, the target fact says n: ONE refusal, naming both.
        let conflicts = k.conflicts();
        assert_eq!(conflicts.len(), 1, "{conflicts:?}");
        assert!(conflicts[0].contains("CONFIG_SERIAL_8250") && conflicts[0].contains("target.consoles") && conflicts[0].contains("appliance-boot"), "{}", conflicts[0]);
    }

    /// The `data` group the same way: undeclared = byte-identical closure;
    /// declared xfs = `CONFIG_XFS_FS=y` under feature `data.fs` on the
    /// kernel edge and `CONFIG_XFS_QUOTA=n` beside it, named by the requirer.
    #[test]
    fn declared_data_facts_land_on_the_kernel_edge_and_an_undeclared_group_is_inert() {
        use crate::data::{Data, DataFs, DataIo};
        let with = closure_of_consumer(&set(), "gunnar", "inst", &BTreeMap::new()).unwrap().unwrap();
        let without_src: Vec<Box<dyn RequirementSource>> = vec![Box::new(DeclaredRows), Box::new(ApplianceBootContract), Box::new(TargetFacts)];
        let without = closure_of_consumer_with(&set(), "gunnar", "inst", &BTreeMap::new(), &without_src).unwrap().unwrap();
        assert_eq!(with, without, "an undeclared data group changes nothing");
        assert!(!with.on_edge("kernel").unwrap().as_kconfig().unwrap().rows.contains_key("CONFIG_XFS_FS"));

        let mut s = set();
        s.installers[0].data = Data { fs: DataFs::Xfs, io: DataIo::Safe };
        let c = closure_of_consumer(&s, "gunnar", "inst", &BTreeMap::new()).unwrap().unwrap();
        let rows = &c.on_edge("kernel").unwrap().as_kconfig().unwrap().rows;
        assert_eq!(rows["CONFIG_XFS_FS"][0].0, KconfigValue::Y, "{rows:?}");
        assert_eq!(rows["CONFIG_LIBCRC32C"][0].0, KconfigValue::Y);
        assert_eq!(rows["CONFIG_XFS_QUOTA"][0].0, KconfigValue::N);
        assert_eq!(rows["CONFIG_FS_POSIX_ACL"][0].0, KconfigValue::N);
        let feature = &rows["CONFIG_XFS_FS"][0].1;
        assert_eq!(feature.feature, "data.fs", "the requirer is named: {feature:?}");
        assert!(!rows.contains_key("CONFIG_IO_URING"), "safe asks for no ring");

        // The writer arm: `io = uring` lands CONFIG_IO_URING=y under ITS OWN
        // feature `data.io`, so kernel.why.toml names the arm as the requirer.
        s.installers[0].data.io = DataIo::Uring;
        let c = closure_of_consumer(&s, "gunnar", "inst", &BTreeMap::new()).unwrap().unwrap();
        let k = c.on_edge("kernel").unwrap();
        assert!(k.conflicts().is_empty(), "{:?}", k.conflicts());
        let rows = &k.as_kconfig().unwrap().rows;
        assert_eq!(rows["CONFIG_IO_URING"][0].0, KconfigValue::Y, "{rows:?}");
        assert_eq!(rows["CONFIG_IO_URING"][0].1.feature, "data.io");
        assert_eq!(rows["CONFIG_XFS_FS"][0].1.feature, "data.fs", "the fs rows keep their own feature");
    }

    /// **The `target` group's Kconfig rows ride the closure, one writer.** A
    /// declared VGA-only machine (UpCloud's console shape) lands
    /// `SERIAL_8250=n` and `VT=y` on the kernel edge with NO contradiction —
    /// the boot contract yields the symbols the group names; e1000/epyc land
    /// their rows; an undeclared group leaves the closure byte-identical to a
    /// closure computed without the source at all.
    ///
    /// ⚠ This function LOST its `#[test]` and ran nowhere between lane L/S and
    /// lane AF (2026-08-30): the `data` test was inserted between this one's doc
    /// comment + attribute and its own body, so the attribute landed on the new
    /// function (twice — `warning: duplicated attribute` was the only trace) and
    /// this one compiled as dead code. A test that does not run is the purest
    /// form of the green nobody has seen red. Restored, and green as written.
    /// ONE user is the contract's default, lifted by name (lane HOLGER-MEDIA,
    /// 2026-10-23): a consumer whose own row requires `CONFIG_MULTIUSER=y` (it runs
    /// containers) gets no `=n` from the contract — one row, the consumer's, no
    /// contradiction; every other consumer keeps the contract's `=n` under the
    /// contract's feature, byte for byte as before.
    #[test]
    fn a_consumer_that_requires_multiuser_lifts_the_contracts_single_user_row() {
        let kept = closure_of_consumer(&set(), "gunnar", "inst", &BTreeMap::new()).unwrap().unwrap();
        let k = kept.on_edge("kernel").unwrap().as_kconfig().unwrap();
        let row = &k.rows[crate::kernel_requires::SINGLE_USER_SYMBOL];
        assert_eq!(row.len(), 1);
        assert_eq!(row[0].0, KconfigValue::N, "one user by default");
        assert_eq!(row[0].1.feature, crate::kernel_requires::APPLIANCE_BOOT_FEATURE);

        let mut s = set();
        s.installers[0].requires.push(EdgeRequirement {
            edge: "kernel".into(),
            what: Requirement::Kconfig(KconfigReq::y(crate::kernel_requires::SINGLE_USER_SYMBOL, "every scan runs in a container: user namespaces and cgroups depend on it")),
        });
        let lifted = closure_of_consumer(&s, "gunnar", "inst", &BTreeMap::new()).unwrap().unwrap();
        let edge = lifted.on_edge("kernel").unwrap();
        assert!(edge.conflicts().is_empty(), "the lift leaves no contradiction: {:?}", edge.conflicts());
        let k = edge.as_kconfig().unwrap();
        let row = &k.rows[crate::kernel_requires::SINGLE_USER_SYMBOL];
        assert_eq!(row.len(), 1, "the contract's =n is gone, the row's =y stands alone: {row:?}");
        assert_eq!(row[0].0, KconfigValue::Y);
        assert_ne!(row[0].1.feature, crate::kernel_requires::APPLIANCE_BOOT_FEATURE);
        assert_eq!(k.rows["CONFIG_PROC_FS"][0].1.feature, crate::kernel_requires::APPLIANCE_BOOT_FEATURE, "the rest of the contract stands");
    }

    /// IPv4 only is the contract's law for the appliance, lifted the same way
    /// as the one-user default and ONLY by a row that requires `CONFIG_IPV6=y`
    /// itself (owner 2026-10-29: a Kubernetes node, whose Cilium socket-LB
    /// programs read `bpf_sock->src_ip6`; the node boots `ipv6.disable=1`).
    /// A FEATURE asking for IPv6 is still refused by name; every other
    /// consumer keeps the contract's `=n`.
    #[test]
    fn a_consumer_row_that_requires_ipv6_lifts_the_contracts_ipv4_only_row() {
        let kept = closure_of_consumer(&set(), "gunnar", "inst", &BTreeMap::new()).unwrap().unwrap();
        let k = kept.on_edge("kernel").unwrap().as_kconfig().unwrap();
        assert_eq!(k.rows["CONFIG_IPV6"][0].0, KconfigValue::N, "IPv4 only by default");

        let mut s = set();
        s.installers[0].requires.push(EdgeRequirement {
            edge: "kernel".into(),
            what: Requirement::Kconfig(KconfigReq::y("CONFIG_IPV6", "a k8s node: Cilium's socket-LB programs read bpf_sock->src_ip6")),
        });
        let lifted = closure_of_consumer(&s, "gunnar", "inst", &BTreeMap::new()).unwrap().unwrap();
        let edge = lifted.on_edge("kernel").unwrap();
        assert!(edge.conflicts().is_empty(), "the lift leaves no contradiction: {:?}", edge.conflicts());
        let row = &edge.as_kconfig().unwrap().rows["CONFIG_IPV6"];
        assert_eq!(row.len(), 1, "the contract's =n is gone, the row's =y stands alone: {row:?}");
        assert_eq!(row[0].0, KconfigValue::Y);
        let k = edge.as_kconfig().unwrap();
        assert_eq!(k.rows["CONFIG_WIREGUARD"][0].0, KconfigValue::N, "the rest of the negative law stands");
        assert_eq!(k.rows[crate::kernel_requires::SINGLE_USER_SYMBOL][0].0, KconfigValue::N, "lifting IPv6 lifts nothing else");
    }

    /// **The framebuffer console is the contract's `=n`, lifted only by a ROW**
    /// (owner 2026-10-30, option A: the k8s node's console dashboard must show
    /// under UEFI, where there is no VGA text mode). Every consumer gets
    /// `FB`, `FB_EFI` and `FRAMEBUFFER_CONSOLE` `=n`; a row that requires the
    /// three `=y` lifts exactly those three. `DRM` and `LOGO` stay vetoed for
    /// everyone: a row asking for either is a contradiction, not a lift.
    #[test]
    fn a_consumer_row_that_requires_the_framebuffer_console_lifts_fb_and_drm_logo_stay_vetoed() {
        const FB: [&str; 3] = ["CONFIG_FB", "CONFIG_FB_EFI", "CONFIG_FRAMEBUFFER_CONSOLE"];
        const NEVER: [&str; 2] = ["CONFIG_DRM", "CONFIG_LOGO"];
        let kept = closure_of_consumer(&set(), "gunnar", "inst", &BTreeMap::new()).unwrap().unwrap();
        let k = kept.on_edge("kernel").unwrap().as_kconfig().unwrap();
        // FB_EFI and FRAMEBUFFER_CONSOLE depend on FB: the contract's FB=n is their veto.
        for sym in ["CONFIG_FB"].iter().chain(NEVER.iter()) {
            let row = k.rows.get(*sym).unwrap_or_else(|| panic!("{sym}: the contract vetoes it for every consumer"));
            assert_eq!(row.len(), 1, "{sym}: {row:?}");
            assert_eq!(row[0].0, KconfigValue::N, "{sym}: vetoed by default");
            assert_eq!(row[0].1.feature, crate::kernel_requires::APPLIANCE_BOOT_FEATURE, "{sym}");
        }

        let mut s = set();
        for sym in FB {
            s.installers[0].requires.push(EdgeRequirement {
                edge: "kernel".into(),
                what: Requirement::Kconfig(KconfigReq::y(sym, "a k8s node: the console dashboard on the UEFI framebuffer")),
            });
        }
        let lifted = closure_of_consumer(&s, "gunnar", "inst", &BTreeMap::new()).unwrap().unwrap();
        let edge = lifted.on_edge("kernel").unwrap();
        assert!(edge.conflicts().is_empty(), "the lift leaves no contradiction: {:?}", edge.conflicts());
        let k = edge.as_kconfig().unwrap();
        for sym in FB {
            let row = &k.rows[sym];
            assert_eq!(row.len(), 1, "{sym}: the contract's =n is gone, the row's =y stands alone: {row:?}");
            assert_eq!(row[0].0, KconfigValue::Y, "{sym}");
        }
        for sym in NEVER {
            assert_eq!(k.rows[sym][0].0, KconfigValue::N, "{sym}: lifting the framebuffer console lifts no DRM and no logo");
        }
        assert_eq!(k.rows["CONFIG_IPV6"][0].0, KconfigValue::N, "lifting the framebuffer lifts nothing else");

        // DRM and LOGO are not liftable: a row asking for one meets the contract.
        for sym in NEVER {
            let mut s = set();
            s.installers[0].requires.push(EdgeRequirement { edge: "kernel".into(), what: Requirement::Kconfig(KconfigReq::y(sym, "wants it")) });
            let c = closure_of_consumer(&s, "gunnar", "inst", &BTreeMap::new()).unwrap().unwrap();
            let conflicts = c.on_edge("kernel").unwrap().conflicts();
            assert_eq!(conflicts.len(), 1, "{sym}: {conflicts:?}");
            assert!(conflicts[0].to_string().contains(sym), "{}", conflicts[0]);
        }
    }

    #[test]
    fn declared_target_facts_land_on_the_kernel_edge_and_an_undeclared_group_is_inert() {
        use crate::target::{Console, Cpu, Nic, UartPort};
        // Undeclared: the default sources and the sources WITHOUT TargetFacts agree byte for byte.
        let with = closure_of_consumer(&set(), "gunnar", "inst", &BTreeMap::new()).unwrap().unwrap();
        let without_src: Vec<Box<dyn RequirementSource>> = vec![Box::new(DeclaredRows), Box::new(ApplianceBootContract)];
        let without = closure_of_consumer_with(&set(), "gunnar", "inst", &BTreeMap::new(), &without_src).unwrap().unwrap();
        assert_eq!(with, without, "an undeclared target changes nothing");
        assert_eq!(with.on_edge("kernel").unwrap().as_kconfig().unwrap().rows["CONFIG_SERIAL_8250"][0].0, KconfigValue::Y);

        // Declared VGA only, no UART: serial OUT, vga IN, no contradiction.
        let mut s = set();
        s.installers[0].target.consoles = vec![Console::Vga];
        s.installers[0].target.uarts = Vec::new();
        let c = closure_of_consumer(&s, "gunnar", "inst", &BTreeMap::new()).unwrap().unwrap();
        let k = c.on_edge("kernel").unwrap();
        assert!(k.conflicts().is_empty(), "{:?}", k.conflicts());
        let k = k.as_kconfig().unwrap();
        assert_eq!(k.rows["CONFIG_SERIAL_8250"][0].0, KconfigValue::N);
        assert_eq!(k.rows["CONFIG_SERIAL_8250"][0].1.feature, "target.uarts", "the PORTS own the driver, not the console (2026-09-07)");
        assert!(!k.rows.contains_key("CONFIG_SERIAL_8250_CONSOLE"), "nothing asks for a console on a UART that is not there");
        assert_eq!(k.rows["CONFIG_VT"][0].0, KconfigValue::Y);
        assert_eq!(k.rows["CONFIG_VGA_CONSOLE"][0].0, KconfigValue::Y);
        assert_eq!(k.rows["CONFIG_PROC_FS"][0].1.feature, crate::kernel_requires::APPLIANCE_BOOT_FEATURE, "the rest of the contract stands");
        assert!(k.rows["CONFIG_VT"].iter().all(|(_, q)| q.feature == "target.consoles"), "the group is the ONE writer of a symbol it names: {:?}", k.rows["CONFIG_VT"]);

        // Declared serial on COM2 (UpCloud's real UART), e1000, EPYC.
        let mut s = set();
        s.installers[0].target.consoles = vec![Console::Serial];
        s.installers[0].target.uarts = vec![UartPort::Com2];
        s.installers[0].target.buses = vec![crate::target::Bus::VirtioBlk];
        s.installers[0].target.nic = Nic::E1000;
        s.installers[0].target.cpu = Cpu::Epyc;
        let c = closure_of_consumer(&s, "gunnar", "inst", &BTreeMap::new()).unwrap().unwrap();
        let k = c.on_edge("kernel").unwrap().as_kconfig().unwrap().clone();
        assert_eq!(k.rows["CONFIG_SERIAL_8250_CONSOLE"][0].0, KconfigValue::Y);
        assert!(k.rows["CONFIG_SERIAL_8250"][0].1.reason.contains("ttyS1"), "{:?}", k.rows["CONFIG_SERIAL_8250"]);
        assert_eq!(k.rows["CONFIG_VT"][0].0, KconfigValue::N);
        assert_eq!((k.rows["CONFIG_E1000"][0].0.clone(), k.rows["CONFIG_E1000"][0].1.feature.as_str()), (KconfigValue::Y, "target.nic"));
        assert!(!k.rows.contains_key("CONFIG_VIRTIO_NET"), "nic = e1000 asks nothing of virtio-net: {:?}", k.rows.keys());
        assert_eq!((k.rows["CONFIG_CPU_SUP_AMD"][0].0.clone(), k.rows["CONFIG_CPU_SUP_AMD"][0].1.feature.as_str()), (KconfigValue::Y, "target.cpu"));
        assert_eq!(target_feature_of("target.buses has ide-cdrom: the medium is an IDE cdrom"), "target.buses");
        // The absences NARROW a nuked kernel (its base fragment has VT=y): a
        // `=n` row over a preset `=y` lands and renders as the veto directive;
        // only a POSITIVE row over a preset veto is refused.
        let mut nuked = crate::kernel::KernelBuild { name: "k".into(), version: "6.12.104".into(), tarball_sha256: "0".repeat(64), preset: "nuked".into(), ..Default::default() };
        nuked.apply_requirements(&k).expect("a narrowing is not a veto met");
        let frag = nuked.requirements.as_ref().unwrap().fragment();
        assert!(frag.contains("# CONFIG_VT is not set") && frag.contains("CONFIG_E1000=y"), "{frag}");
    }

    /// **The closure half of the same ruling.** A declared machine with REAL
    /// ports and no serial console keeps `CONFIG_SERIAL_8250=y` on its kernel
    /// edge with no conflict — written by `target.uarts`, the fact that
    /// actually decides a driver, while `target.consoles` keeps only
    /// `SERIAL_8250_CONSOLE` and the VGA rows.
    ///
    /// RED until 2026-09-07: the group vetoed the DRIVER whenever no `console=`
    /// named a UART, which is UpCloud exactly — `Uarts::Known([Com2, Com3])`,
    /// VNC the only console — and the refusal that produced named a symbol the
    /// shipped `tunnr-kernel` has had `=y` all month while serving off that very
    /// machine.
    #[test]
    fn a_declared_machine_with_ports_but_no_serial_console_leaves_the_contract_standing() {
        use crate::target::{Console, UartPort};
        let mut s = set();
        s.installers[0].target.consoles = vec![Console::Vga];
        s.installers[0].target.uarts = vec![UartPort::Com2, UartPort::Com3];
        let c = closure_of_consumer(&s, "gunnar", "inst", &BTreeMap::new()).unwrap().unwrap();
        let k = c.on_edge("kernel").unwrap();
        assert!(k.conflicts().is_empty(), "a machine with ports vetoes nothing: {:?}", k.conflicts());
        let k = k.as_kconfig().unwrap();
        assert_eq!(k.rows["CONFIG_SERIAL_8250"][0].0, KconfigValue::Y);
        assert_eq!(
            k.rows["CONFIG_SERIAL_8250"][0].1.feature,
            "target.uarts",
            "the PORTS own the driver — the contract steps aside for a symbol the group names, and here it names it `y`: {:?}",
            k.rows["CONFIG_SERIAL_8250"]
        );
        assert_eq!(k.rows["CONFIG_SERIAL_EARLYCON"][0].0, KconfigValue::Y, "and the earlycon door stays open");
        assert_eq!(k.rows["CONFIG_VGA_CONSOLE"][0].1.feature, "target.consoles", "what the operator DOES read is still the group's");
    }

    /// **A declared bus the donor's preset vetoes is refused BY NAME, never
    /// merged over the veto.** UpCloud attaches the ISO as an IDE cdrom; the
    /// `nuked` preset says `# CONFIG_ATA is not set`; the refusal names the
    /// symbol, the consumer, the feature and the preset — the dark UpCloud boot
    /// as a build-time sentence.
    #[test]
    fn an_ide_cdrom_target_on_a_nuked_kernel_is_refused_by_name() {
        use crate::target::{Bus, Console, UartPort};
        let mut s = set();
        // UpCloud's buses on a COM1 machine: the ide-cdrom is the fact under
        // test, spelled as fields so the assertion does not ride the preset
        // roster (which another lane owns).
        s.installers[0].target.buses = vec![Bus::IdeCdrom, Bus::VirtioBlk];
        s.installers[0].target.consoles = vec![Console::Vga, Console::Serial];
        s.installers[0].target.uarts = vec![UartPort::Com1];
        s.installers[0].target.nics = 2;
        let c = closure_of_consumer(&s, "gunnar", "inst", &BTreeMap::new()).unwrap().unwrap();
        let k = c.on_edge("kernel").unwrap().as_kconfig().unwrap().clone();
        assert_eq!(k.rows["CONFIG_ATA"][0].0, KconfigValue::Y);
        let mut nuked = crate::kernel::KernelBuild { name: "tunnr-kernel".into(), version: "6.12.104".into(), tarball_sha256: "0".repeat(64), preset: "nuked".into(), ..Default::default() };
        let err = format!("{:#}", nuked.apply_requirements(&k).unwrap_err());
        assert!(err.contains("CONFIG_ATA") && err.contains("gunnar/inst:target.buses") && err.contains("nuked"), "{err}");
        // A preset that grants it lands the same rows.
        let mut normal = crate::kernel::KernelBuild { preset: "normal".into(), ..nuked.clone() };
        normal.apply_requirements(&k).unwrap();
        assert!(normal.requirements.as_ref().unwrap().rows.contains_key("CONFIG_ATA_PIIX"));
    }

    #[test]
    fn toggles_in_scope_take_root_features_only() {
        use crate::flags::Flag;
        let flags = vec![Flag::opt("dhcp", true), Flag::choice("kernel.preset", "nuked"), Flag::opt("wireguard", false), Flag::opt("a.b", true)];
        let t = toggles_in_scope(&flags, "").unwrap();
        assert_eq!(t.len(), 2);
        assert_eq!(t["dhcp"], true);
        assert_eq!(t["wireguard"], false);
        let t = toggles_in_scope(&flags, "a.").unwrap();
        assert_eq!(t.len(), 1);
        assert_eq!(t["b"], true);
    }
}