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/feature_axis.rs

//! **The FOURTH axis: features.**
//!
//! build-thing's variant model already spans three axes —
//! [`ArtifactKind`](crate::variant::ArtifactKind) × [`ServiceShape`](crate::variant::ServiceShape)
//! × target. Features were present as *data on a variant* but were never an axis
//! anyone could look at, and that gap is not theoretical. Three times in one day:
//!
//! * **korp** declared `spark41` in its `offers` and no variant named it. build-thing
//!   therefore never built that combination, and **three separate compile breaks
//!   accumulated invisibly** while the default build stayed green.
//! * **nornir**'s `scip` was in no default set, so `nornir-server` compiled no writer
//!   and `scip_occurrences` sat at 0 rows in every repo.
//! * **dwarves-server** is `required-features = ["server"]`, which
//!   `cargo build --all-targets` **silently skips** — so a stale binary stayed
//!   installed.
//!
//! One rule explains all three: **a feature no variant declares is a feature nobody
//! builds, and it rots.** This module makes that visible, in the two directions it
//! can rot in:
//!
//! | direction | question | field |
//! |---|---|---|
//! | offered → built | the repo says it offers `spark41`; does any variant turn it on? | [`FeatureAxis::unbuilt`] |
//! | manifest → offered | `Cargo.toml` declares `scip`; does the build space even know about it? | [`FeatureAxis::unoffered`] |
//! | bin → variant | a `[[bin]]` has `required-features`; does any variant satisfy them? | [`FeatureAxis::unbuildable_bins`] |
//!
//! The report is pure data (a sibling of [`crate::surface::BuildSurface`]) so a UI
//! pane, a CLI `--json` and a gate all read the same thing.

use std::collections::{BTreeMap, BTreeSet};
use std::path::Path;

use serde::{Deserialize, Serialize};

use crate::variant::BuildVariantSet;

/// The feature axis of one repo's build space: which declared features an actual
/// variant builds, and which are declared-but-never-built.
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct FeatureAxis {
    /// The repo this axis belongs to.
    pub repo: String,
    /// The feature universe the repo `offers` (its `[build] offers`).
    pub offers: BTreeSet<String>,
    /// Every feature at least one variant enables → the variants that enable it.
    /// This is the *covered* half of the axis.
    pub built: BTreeMap<String, Vec<String>>,
    /// **The gap.** Features the repo `offers` that NO declared variant enables —
    /// korp's `spark41` before it was noticed. Sorted.
    pub unbuilt: Vec<String>,
    /// Features declared in the repo's `Cargo.toml` `[features]` tables that the
    /// build space does not even `offers` — nornir's `scip`. Empty when no manifest
    /// was supplied, and empty when the space is
    /// [`unconstrained`](Self::unconstrained) (an empty `offers` excludes nothing, so
    /// flagging every manifest feature there would be noise). Sorted.
    pub unoffered: Vec<String>,
    /// **The widest form of the rule, and the one that always applies**: features
    /// declared in `Cargo.toml` that NO variant enables — measurable whether or not
    /// the repo declares an `offers` space, so an unconstrained repo (nornir, dwarves)
    /// is not silently exempt from the axis. Empty when no manifest was read. Sorted.
    pub manifest_unbuilt: Vec<String>,
    /// How many features the manifest declared (the denominator for
    /// [`manifest_unbuilt`](Self::manifest_unbuilt)).
    pub manifest_total: usize,
    /// How many of the repo's `offers` were actually FOUND in the scanned manifest.
    /// Short of `offers.len()` means the scan did not see the whole repo — see
    /// [`manifest_incomplete`](Self::manifest_incomplete).
    pub offers_in_manifest: usize,
    /// `[[bin]] required-features` entries (as mirrored into `[build.required]`)
    /// that no declared variant satisfies — the `dwarves-server` shape, where
    /// `cargo build --all-targets` silently skips the target. `bin → missing`.
    pub unbuildable_bins: BTreeMap<String, Vec<String>>,
    /// Whether a `Cargo.toml` feature list was supplied. `false` ⇒ `unoffered` is
    /// "not measured", NOT "nothing missing" — the pane must say so rather than
    /// render an unmeasured axis as clean.
    pub manifest_read: bool,
    /// The repo's HONEST allowlist (`[build.allow_unbuilt]`): offered-but-unbuilt
    /// features that are accepted debt, each with a reason. The [`gate`](Self::gate)
    /// subtracts these from the rot verdict. Carried onto the axis so a pane / gate
    /// reads the excuse next to the gap.
    pub allow_unbuilt: BTreeMap<String, String>,
}

impl FeatureAxis {
    /// The axis of a build space, from the declaration alone (no manifest read).
    /// `unoffered` is left empty and [`manifest_read`](Self::manifest_read) `false`.
    pub fn of(repo: impl Into<String>, set: &BuildVariantSet) -> Self {
        Self::with_manifest_features(repo, set, None)
    }

    /// The axis including the manifest direction: `manifest` is every feature name
    /// declared in the repo's `Cargo.toml` `[features]` tables (see
    /// [`manifest_features`]).
    pub fn with_manifest_features(
        repo: impl Into<String>,
        set: &BuildVariantSet,
        manifest: Option<&BTreeSet<String>>,
    ) -> Self {
        let mut built: BTreeMap<String, Vec<String>> = BTreeMap::new();
        for v in &set.variants {
            for f in &v.features {
                built.entry(f.clone()).or_default().push(v.name.clone());
            }
        }
        // offered but never enabled by any variant.
        let unbuilt: Vec<String> =
            set.offers.iter().filter(|f| !built.contains_key(*f)).cloned().collect();

        // declared in Cargo.toml but outside the build space's `offers`. Only
        // meaningful when the repo actually constrains its space: an empty `offers`
        // means "unconstrained", and flagging every manifest feature there would be
        // noise, not a finding.
        let (unoffered, manifest_read) = match manifest {
            Some(m) if !set.offers.is_empty() => (
                m.iter().filter(|f| !set.offers.contains(*f)).cloned().collect::<Vec<_>>(),
                true,
            ),
            Some(_) => (Vec::new(), true),
            None => (Vec::new(), false),
        };
        // The widest form: declared in Cargo.toml, built by nobody. This is the one
        // measure an UNCONSTRAINED repo cannot escape — without it, declaring no
        // `offers` would exempt a repo from the axis entirely, which is precisely how
        // nornir's `scip` went unbuilt in every repo.
        let (manifest_unbuilt, manifest_total) = match manifest {
            Some(m) => (
                m.iter().filter(|f| !built.contains_key(*f)).cloned().collect::<Vec<_>>(),
                m.len(),
            ),
            None => (Vec::new(), 0),
        };
        // How much of the repo the scan actually saw. The repo's own `offers` is the
        // control: every offered feature is by definition declared SOMEWHERE in the
        // repo, so an offered feature the scan never encountered means the scan missed
        // a manifest (a detached sub-workspace, an unlisted member). Without this the
        // Cargo column would silently understate — skade offers 8 and its root manifest
        // is a shell around three vendored forks, so it would have read `0/2`, which is
        // not a small number, it is the wrong denominator.
        let offers_in_manifest = match manifest {
            Some(m) => set.offers.iter().filter(|f| m.contains(*f)).count(),
            None => 0,
        };

        // A `required-features` bin no variant can build. `[build.required]` mirrors
        // Cargo's `[[bin]] required-features`, so this is the same silent-skip that
        // `cargo build --all-targets` performs.
        let mut unbuildable_bins: BTreeMap<String, Vec<String>> = BTreeMap::new();
        for (bin, req) in &set.required {
            let satisfied = set
                .variants
                .iter()
                .any(|v| req.iter().all(|r| v.features.contains(r)));
            if !satisfied {
                let missing: Vec<String> = req
                    .iter()
                    .filter(|r| !built.contains_key(*r))
                    .cloned()
                    .collect();
                unbuildable_bins.insert(bin.clone(), if missing.is_empty() { req.clone() } else { missing });
            }
        }

        FeatureAxis {
            repo: repo.into(),
            offers: set.offers.clone(),
            built,
            unbuilt,
            unoffered,
            manifest_unbuilt,
            manifest_total,
            offers_in_manifest,
            unbuildable_bins,
            manifest_read,
            allow_unbuilt: set.allow_unbuilt.clone(),
        }
    }

    /// The manifest scan did not see the whole repo: at least one feature the repo
    /// `offers` was never found in any scanned `Cargo.toml`. The Cargo column is then
    /// a floor, not a measurement, and must be reported as such rather than as a
    /// (small, wrong) number.
    pub fn manifest_incomplete(&self) -> bool {
        self.manifest_read && !self.offers.is_empty() && self.offers_in_manifest < self.offers.len()
    }

    /// True when nothing on this axis rots: every offered feature is built by some
    /// variant, every `required-features` bin is buildable, and (when measured) the
    /// manifest declares nothing the space does not offer.
    ///
    /// Note what this deliberately does NOT fold in:
    /// [`manifest_unbuilt`](Self::manifest_unbuilt). A repo can legitimately declare
    /// a Cargo feature it does not ship a variant for; the offered space is the
    /// verdict, and the manifest count is the wider *number* reported beside it. What
    /// this DOES refuse to do is call an [`unconstrained`](Self::unconstrained) repo
    /// clean without saying so — see [`headline`](Self::headline).
    pub fn is_clean(&self) -> bool {
        self.unbuilt.is_empty() && self.unoffered.is_empty() && self.unbuildable_bins.is_empty()
    }

    /// **The rotting features**: the offered-but-unbuilt ([`unbuilt`](Self::unbuilt))
    /// and declared-but-unoffered ([`unoffered`](Self::unoffered)) features that are
    /// NOT excused by the [`allow_unbuilt`](Self::allow_unbuilt) allowlist. Sorted,
    /// de-duplicated. This is the set the gate fails on — the live rot, after the
    /// honest debt has been subtracted.
    pub fn rotting(&self) -> Vec<String> {
        let mut r: BTreeSet<String> = BTreeSet::new();
        for f in self.unbuilt.iter().chain(self.unoffered.iter()) {
            if !self.allow_unbuilt.contains_key(f) {
                r.insert(f.clone());
            }
        }
        r.into_iter().collect()
    }

    /// **Stale allowlist entries**: features excused as unbuilt in `allow_unbuilt`
    /// that some variant NOW builds. A dead excuse is itself rot — it lets the next
    /// real gap hide behind a line nobody re-reads — so the gate fails on these too
    /// and asks for the stale line to be removed. `feature → the variants that build it`.
    pub fn stale_allowlist(&self) -> BTreeMap<String, Vec<String>> {
        self.allow_unbuilt
            .keys()
            .filter_map(|f| self.built.get(f).map(|v| (f.clone(), v.clone())))
            .collect()
    }

    /// **The gate verdict, allowlist-aware.** `Ok(())` when nothing rots that has
    /// not been honestly excused; otherwise a [`FeatureRot`] naming every rotting
    /// feature, every `required-features` bin no variant can build, and every stale
    /// excuse. Unlike [`is_clean`](Self::is_clean) this consults
    /// [`allow_unbuilt`](Self::allow_unbuilt), so "known, documented debt" passes and
    /// only silent rot fails.
    ///
    /// This is the reusable check the task asks for: call it from a `cargo test`, a
    /// CI step, or a release gate, and it fails LOUD, naming the feature, the moment
    /// an offered capability stops being built by any variant.
    pub fn gate(&self) -> Result<(), FeatureRot> {
        let rotting = self.rotting();
        let stale = self.stale_allowlist();
        if rotting.is_empty() && self.unbuildable_bins.is_empty() && stale.is_empty() {
            return Ok(());
        }
        Err(FeatureRot {
            repo: self.repo.clone(),
            rotting,
            unbuildable_bins: self.unbuildable_bins.clone(),
            stale_allowlist: stale,
            unknown_allowlist: Vec::new(),
            unmeasured: Vec::new(),
        })
    }

    /// **The MANIFEST direction of the same rule** — the half [`gate`](Self::gate)
    /// deliberately leaves out, and the ONLY half a repo without a `[build] offers`
    /// space has.
    ///
    /// [`gate`](Self::gate) is a verdict on the `offers` space: `unbuilt` ∪
    /// `unoffered`, both of which are EMPTY BY CONSTRUCTION when `offers` is absent.
    /// So a repo that declares no offers passes that gate no matter what its
    /// `Cargo.toml` says — it is green because there was nothing to ask, which is
    /// exactly the shape of a check that can never report red. Measured on
    /// znippy-zoomies 2026-08-22: nine declared features, **zero** built by any
    /// declared variant, `gate()` `Ok(())`.
    ///
    /// This gate asks the question that still has an answer there: **every feature
    /// the repo's `Cargo.toml` declares is either enabled by some declared
    /// `[[build.variant]]`, or carries a line in `[build.allow_unbuilt]` saying in
    /// words why nothing compiles it.** Same allowlist, same stale-excuse rule.
    ///
    /// It refuses to be vacuous, and that is not decoration: without the refusal a
    /// manifest scan that found nothing (a moved root, a member glob that stopped
    /// expanding) reads exactly like a repo whose every feature is built. So a gate
    /// that never read a manifest, or read one declaring no features at all, is an
    /// `Err` naming that fact — never an `Ok`.
    ///
    /// [`manifest_unbuilt`](Self::manifest_unbuilt) is unchanged and so is
    /// [`gate`](Self::gate): this is a second question over the same data, not a
    /// change to the first one.
    pub fn manifest_gate(&self) -> Result<(), FeatureRot> {
        let mut unmeasured = Vec::new();
        if !self.manifest_read {
            unmeasured.push(
                "no Cargo.toml feature set was supplied — build the axis with \
                 `FeatureAxis::with_manifest_features(.., Some(&manifest_features(root)))`"
                    .to_string(),
            );
        } else if self.manifest_total == 0 {
            unmeasured.push(
                "the manifest scan found ZERO declared features — either the repo really \
                 declares none (then this gate has nothing to say and should not be called) \
                 or the scan missed the manifests (a moved root, a member glob that no \
                 longer expands). An empty oracle is not a pass."
                    .to_string(),
            );
        }
        if self.manifest_incomplete() {
            unmeasured.push(format!(
                "the scan saw only {} of the {} features this repo `offers` — the Cargo \
                 column is a floor, not a measurement",
                self.offers_in_manifest,
                self.offers.len()
            ));
        }
        let rotting = self.manifest_rotting();
        let stale = self.stale_allowlist();
        let unknown = self.unknown_allowlist();
        if rotting.is_empty() && stale.is_empty() && unknown.is_empty() && unmeasured.is_empty() {
            return Ok(());
        }
        Err(FeatureRot {
            repo: self.repo.clone(),
            rotting,
            unbuildable_bins: self.unbuildable_bins.clone(),
            stale_allowlist: stale,
            unknown_allowlist: unknown,
            unmeasured,
        })
    }

    /// The features [`manifest_unbuilt`](Self::manifest_unbuilt) names that no
    /// `[build.allow_unbuilt]` line excuses — what [`manifest_gate`](Self::manifest_gate)
    /// fails on. Sorted.
    pub fn manifest_rotting(&self) -> Vec<String> {
        self.manifest_unbuilt
            .iter()
            .filter(|f| !self.allow_unbuilt.contains_key(*f))
            .cloned()
            .collect()
    }

    /// **Excuses for features that do not exist.** An `[build.allow_unbuilt]` entry
    /// naming a feature the manifest scan never saw and no variant builds: a renamed
    /// or deleted feature whose excuse outlived it. Same disease as
    /// [`stale_allowlist`](Self::stale_allowlist) — a line nobody re-reads, standing
    /// between the next real gap and the reader — so
    /// [`manifest_gate`](Self::manifest_gate) fails on it too. Empty when no manifest
    /// was read (not measured is not the same as nothing wrong). Sorted.
    pub fn unknown_allowlist(&self) -> Vec<String> {
        if !self.manifest_read {
            return Vec::new();
        }
        let unbuilt: BTreeSet<&String> = self.manifest_unbuilt.iter().collect();
        self.allow_unbuilt
            .keys()
            .filter(|f| !unbuilt.contains(*f) && !self.built.contains_key(*f))
            .cloned()
            .collect()
    }

    /// The allowlist-aware sibling of [`is_clean`](Self::is_clean): nothing rots that
    /// has not been documented. `true` ⇔ [`gate`](Self::gate) is `Ok`.
    pub fn gate_clean(&self) -> bool {
        self.gate().is_ok()
    }
}

/// The feature-rot gate's failure: the ways a repo's feature space is rotting after
/// the honest [`FeatureAxis::allow_unbuilt`] allowlist has been subtracted. Carries
/// enough to be actioned without re-reading the manifest — the feature name, the bin,
/// and the fix.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FeatureRot {
    pub repo: String,
    /// Offered / declared features no variant builds and no allowlist excuses.
    pub rotting: Vec<String>,
    /// `required-features` bins no variant can build (the silent `--all-targets` skip).
    pub unbuildable_bins: BTreeMap<String, Vec<String>>,
    /// Allowlist entries a variant now builds — dead excuses to delete.
    pub stale_allowlist: BTreeMap<String, Vec<String>>,
    /// Allowlist entries naming a feature that is neither declared in the manifest
    /// nor built by anything — an excuse that outlived the feature it excused. Only
    /// [`FeatureAxis::manifest_gate`] fills this; [`FeatureAxis::gate`] leaves it empty.
    pub unknown_allowlist: Vec<String>,
    /// **Why the verdict could not be trusted as a measurement.** Non-empty ⇒ the
    /// gate failed because it could not SEE, not because it saw rot: no manifest was
    /// read, the scan found no features at all, or it saw less of the repo than the
    /// repo's own `offers` proves is there. An unmeasured axis must never be reported
    /// as a clean one. Only [`FeatureAxis::manifest_gate`] fills this.
    pub unmeasured: Vec<String>,
}

impl std::fmt::Display for FeatureRot {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        writeln!(f, "feature-rot gate FAILED for `{}`:", self.repo)?;
        if !self.rotting.is_empty() {
            writeln!(
                f,
                "  · OFFERED BUT NEVER BUILT (a feature no variant declares is a feature nobody \
                 builds, and it rots): {}",
                self.rotting.join(", ")
            )?;
            writeln!(
                f,
                "    fix: add a `[[build.variant]]` that enables it, OR document it in \
                 `[build.allow_unbuilt]` with a reason."
            )?;
        }
        for (bin, missing) in &self.unbuildable_bins {
            writeln!(
                f,
                "  · BIN `{bin}` has required-features {:?} that NO variant satisfies — \
                 `cargo build --all-targets` silently skips it. fix: declare a variant that \
                 builds it.",
                missing
            )?;
        }
        for (feat, by) in &self.stale_allowlist {
            writeln!(
                f,
                "  · STALE EXCUSE: `{feat}` is allowlisted as unbuilt but variant(s) {:?} now \
                 build it — delete the `[build.allow_unbuilt]` line.",
                by
            )?;
        }
        for feat in &self.unknown_allowlist {
            writeln!(
                f,
                "  · EXCUSE FOR A FEATURE THAT IS NOT THERE: `{feat}` is allowlisted as \
                 unbuilt, but no `Cargo.toml` in this repo declares it and nothing builds \
                 it — delete the `[build.allow_unbuilt]` line, or restore the feature."
            )?;
        }
        for why in &self.unmeasured {
            writeln!(f, "  · NOT MEASURED (so NOT a pass): {why}")?;
        }
        Ok(())
    }
}

impl std::error::Error for FeatureRot {}

/// **The constellation-wide gate.** Run [`FeatureAxis::gate`] over every repo's axis
/// and fold the failures into one report, so a single `cargo test` / CI step covers
/// the whole suite and names every rotting repo at once. `Ok(())` when the whole
/// constellation is rot-free (allowlist-aware).
pub fn gate_all(axes: &[FeatureAxis]) -> Result<(), ConstellationRot> {
    let failures: Vec<FeatureRot> = axes.iter().filter_map(|a| a.gate().err()).collect();
    if failures.is_empty() {
        Ok(())
    } else {
        Err(ConstellationRot { failures })
    }
}

/// Every repo whose feature-rot gate failed, folded into one error.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ConstellationRot {
    pub failures: Vec<FeatureRot>,
}

impl std::fmt::Display for ConstellationRot {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        writeln!(
            f,
            "feature-rot gate FAILED across {} repo(s) — offered capabilities that no build \
             variant compiles:",
            self.failures.len()
        )?;
        for r in &self.failures {
            write!(f, "{r}")?;
        }
        Ok(())
    }
}

impl std::error::Error for ConstellationRot {}

impl FeatureAxis {

    /// The repo declares no `offers`, so its feature SPACE is undeclared and nothing
    /// can be found "missing" from it. Not a clean bill of health — an unstated space.
    pub fn unconstrained(&self) -> bool {
        self.offers.is_empty()
    }

    /// How many Cargo-manifest features at least one variant builds, over how many
    /// the manifest declares. `(built, declared)`; `(0, 0)` when no manifest was read.
    /// This is the measure an unconstrained repo cannot escape.
    pub fn manifest_ratio(&self) -> (usize, usize) {
        (self.manifest_total.saturating_sub(self.manifest_unbuilt.len()), self.manifest_total)
    }

    /// How many offered features an actual variant builds, over how many are offered.
    /// `(built, offered)`. A repo with an empty `offers` reports `(n, n)` over the
    /// features its variants use — unconstrained, so nothing can be *missing*.
    pub fn ratio(&self) -> (usize, usize) {
        if self.offers.is_empty() {
            let n = self.built.len();
            (n, n)
        } else {
            (self.offers.len() - self.unbuilt.len(), self.offers.len())
        }
    }

    /// The one honest line a report leads with — it names the unmeasured direction
    /// rather than letting an unread manifest look like a clean bill of health.
    pub fn headline(&self) -> String {
        let (built, offered) = self.ratio();
        let mut s = if self.unconstrained() {
            format!(
                "{}: feature space UNDECLARED (no `offers`) — nothing can be found missing from it, \
                 which is not the same as nothing missing",
                self.repo
            )
        } else {
            format!("{}: {built}/{offered} offered features are built by a variant", self.repo)
        };
        if self.manifest_incomplete() {
            s.push_str(&format!(
                " · manifest scan INCOMPLETE ({} of {} offered features found on disk) — the Cargo \
                 count below is a floor, not a measurement",
                self.offers_in_manifest,
                self.offers.len()
            ));
        } else if self.manifest_read && self.manifest_total > 0 {
            let (mb, mt) = self.manifest_ratio();
            s.push_str(&format!(" · {mb}/{mt} Cargo features built by some variant"));
        }
        if !self.unbuilt.is_empty() {
            s.push_str(&format!(" · NEVER BUILT: {}", self.unbuilt.join(", ")));
        }
        if !self.unbuildable_bins.is_empty() {
            let bins: Vec<&str> = self.unbuildable_bins.keys().map(|s| s.as_str()).collect();
            s.push_str(&format!(" · bin(s) no variant can build: {}", bins.join(", ")));
        }
        if !self.manifest_read {
            s.push_str(" · manifest features NOT MEASURED");
        } else if !self.unoffered.is_empty() {
            s.push_str(&format!(" · in Cargo.toml but not offered: {}", self.unoffered.join(", ")));
        }
        s
    }

    /// The `state_json` shape a viz pane / CLI `--json` reads.
    pub fn state_json(&self) -> serde_json::Value {
        let (built, offered) = self.ratio();
        let (mbuilt, mtotal) = self.manifest_ratio();
        serde_json::json!({
            "repo": self.repo,
            "offered": offered,
            "built": built,
            "unbuilt": self.unbuilt,
            "unoffered": self.unoffered,
            "unbuildable_bins": self.unbuildable_bins,
            "manifest_read": self.manifest_read,
            "manifest_incomplete": self.manifest_incomplete(),
            "manifest_total": mtotal,
            "manifest_built": mbuilt,
            "manifest_unbuilt": self.manifest_unbuilt,
            "unconstrained": self.unconstrained(),
            "clean": self.is_clean(),
            "gate_clean": self.gate_clean(),
            "rotting": self.rotting(),
            "allow_unbuilt": self.allow_unbuilt,
            "stale_allowlist": self.stale_allowlist(),
            "headline": self.headline(),
        })
    }
}

/// Every feature name declared in a repo's `Cargo.toml` `[features]` tables.
///
/// Walks the manifest at `repo_root/Cargo.toml` and, when it is a workspace root,
/// each `[workspace] members` manifest one level down (globs are expanded by a
/// single-level directory scan — enough for the constellation's `crates/*` layout
/// and dep-light: this crate parses TOML, it does not shell out to `cargo metadata`).
///
/// `default` is excluded: it is not a capability, it is a selection of them.
pub fn manifest_features(repo_root: &Path) -> BTreeSet<String> {
    let mut out = BTreeSet::new();
    let root = repo_root.join("Cargo.toml");
    let Some(doc) = read_toml(&root) else { return out };
    collect_features(&doc, &mut out);

    // Workspace members, one level of glob expansion.
    if let Some(members) = doc
        .get("workspace")
        .and_then(|w| w.get("members"))
        .and_then(|m| m.as_array())
    {
        for m in members.iter().filter_map(|m| m.as_str()) {
            for dir in expand_member(repo_root, m) {
                if let Some(d) = read_toml(&dir.join("Cargo.toml")) {
                    collect_features(&d, &mut out);
                }
            }
        }
    }
    out.remove("default");
    out
}

fn read_toml(path: &Path) -> Option<toml::Value> {
    let text = std::fs::read_to_string(path).ok()?;
    toml::from_str::<toml::Value>(&text).ok()
}

fn collect_features(doc: &toml::Value, out: &mut BTreeSet<String>) {
    if let Some(t) = doc.get("features").and_then(|f| f.as_table()) {
        for k in t.keys() {
            out.insert(k.clone());
        }
    }
}

/// Expand ONE workspace-member entry into concrete directories. Handles the plain
/// path (`crates/dwarves`) and a single trailing `*` (`crates/*`); anything more
/// exotic resolves to nothing rather than to a wrong answer.
fn expand_member(repo_root: &Path, member: &str) -> Vec<std::path::PathBuf> {
    if let Some(prefix) = member.strip_suffix("/*") {
        let base = repo_root.join(prefix);
        let Ok(rd) = std::fs::read_dir(&base) else { return Vec::new() };
        rd.filter_map(|e| e.ok())
            .map(|e| e.path())
            .filter(|p| p.is_dir())
            .collect()
    } else if member.contains('*') {
        Vec::new()
    } else {
        vec![repo_root.join(member)]
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    /// korp's real shape at the moment the breaks accumulated: `spark41` offered,
    /// no variant naming it.
    const KORP_BEFORE: &str = r#"
[build]
offers = ["iceberg", "sweden-clip", "server", "client", "spark41", "testmatrix"]

[build.required]
"korp-server" = ["server"]

[[build.variant]]
name = "korp"
package = "korp"
bin = "korp"
features = ["iceberg", "sweden-clip"]

[[build.variant]]
name = "korp-server"
package = "korp"
bin = "korp-server"
features = ["server", "iceberg"]

[[build.variant]]
name = "korp-thin"
package = "korp"
bin = "korp"
features = ["client"]
"#;

    #[test]
    fn an_offered_feature_no_variant_names_is_the_gap() {
        let set = BuildVariantSet::from_toml_str(KORP_BEFORE).unwrap();
        let axis = FeatureAxis::of("korp", &set);
        assert!(
            axis.unbuilt.contains(&"spark41".to_string()),
            "spark41 is offered and built by nothing — the korp break, made visible: {:?}",
            axis.unbuilt
        );
        assert!(axis.unbuilt.contains(&"testmatrix".to_string()));
        assert!(!axis.is_clean());
        assert_eq!(axis.ratio(), (4, 6), "iceberg/sweden-clip/server/client are built");
        assert!(axis.headline().contains("NEVER BUILT"), "{}", axis.headline());
        // And the covered half names WHICH variant covers it.
        assert_eq!(axis.built["iceberg"], vec!["korp", "korp-server"]);
    }

    #[test]
    fn declaring_the_variant_closes_the_gap() {
        let mut set = BuildVariantSet::from_toml_str(KORP_BEFORE).unwrap();
        set.variants.push(crate::variant::BuildVariant {
            name: "korp-spark41".into(),
            package: Some("korp".into()),
            bin: Some("korp".into()),
            features: vec!["iceberg".into(), "sweden-clip".into(), "spark41".into()],
            ..Default::default()
        });
        let axis = FeatureAxis::of("korp", &set);
        assert!(!axis.unbuilt.contains(&"spark41".to_string()), "the fix is a DECLARED variant");
        assert_eq!(axis.built["spark41"], vec!["korp-spark41"]);
    }

    /// The `dwarves-server` shape: a bin gated behind a feature that no variant
    /// enables. `cargo build --all-targets` skips it SILENTLY; this does not.
    #[test]
    fn a_required_features_bin_no_variant_satisfies_is_named() {
        let toml = r#"
[build]
offers = ["cli", "server"]

[build.required]
"dwarves-server" = ["server"]

[[build.variant]]
name = "dwarves-default"
package = "dwarves"
bin = "dwarves"
features = ["cli"]
"#;
        let set = BuildVariantSet::from_toml_str(toml).unwrap();
        let axis = FeatureAxis::of("dwarves", &set);
        assert_eq!(
            axis.unbuildable_bins.get("dwarves-server").map(|v| v.as_slice()),
            Some(["server".to_string()].as_slice()),
            "the silently-skipped target is named, with the feature it needs"
        );
        assert!(!axis.is_clean());
        assert!(axis.headline().contains("dwarves-server"), "{}", axis.headline());
    }

    #[test]
    fn an_unread_manifest_is_reported_as_unmeasured_not_as_clean() {
        let set = BuildVariantSet::from_toml_str(
            r#"
[build]
offers = ["a"]
[[build.variant]]
name = "v"
features = ["a"]
"#,
        )
        .unwrap();
        let axis = FeatureAxis::of("x", &set);
        assert!(axis.is_clean(), "nothing measured is missing");
        assert!(!axis.manifest_read);
        assert!(
            axis.headline().contains("NOT MEASURED"),
            "an unmeasured direction must never read as clean: {}",
            axis.headline()
        );

        // With the manifest, nornir's `scip` shape surfaces.
        let manifest: BTreeSet<String> = ["a", "scip"].iter().map(|s| s.to_string()).collect();
        let axis = FeatureAxis::with_manifest_features("x", &set, Some(&manifest));
        assert_eq!(axis.unoffered, vec!["scip".to_string()]);
        assert_eq!(axis.manifest_unbuilt, vec!["scip".to_string()]);
        assert_eq!(axis.manifest_ratio(), (1, 2));
        assert!(!axis.is_clean());
        assert!(axis.manifest_read);
    }

    /// **An undeclared feature space is not a clean one.** A repo with no `offers`
    /// (nornir, dwarves) has nothing that can be "missing" from its space, so the
    /// offered ratio is vacuously full — and that is exactly how a repo could exempt
    /// itself from the axis. The manifest measure is the one it cannot escape, and
    /// the headline refuses to read as green.
    #[test]
    fn an_unconstrained_repo_is_still_measured_against_its_manifest() {
        let set = BuildVariantSet::from_toml_str(
            r#"
[build]
[[build.variant]]
name = "nornir"
package = "nornir"
bin = "nornir"
profile = "release"
"#,
        )
        .unwrap();
        let manifest: BTreeSet<String> =
            ["scip", "server", "warehouse"].iter().map(|s| s.to_string()).collect();
        let axis = FeatureAxis::with_manifest_features("nornir", &set, Some(&manifest));

        assert!(axis.unconstrained(), "no `offers` ⇒ the space is undeclared");
        assert!(axis.is_clean(), "vacuously — nothing is offered, so nothing is missing");
        assert!(
            axis.headline().contains("UNDECLARED"),
            "and the headline says so instead of reading green: {}",
            axis.headline()
        );
        // The measure it cannot escape: the variant enables NO feature, so every
        // Cargo feature — `scip` included — is built by nobody.
        assert_eq!(axis.manifest_ratio(), (0, 3));
        assert!(
            axis.manifest_unbuilt.contains(&"scip".to_string()),
            "nornir's `scip`: declared, in no variant, so no writer was ever compiled"
        );
        assert!(axis.headline().contains("0/3 Cargo features"), "{}", axis.headline());
    }

    /// **A scan that missed half the repo must not report a small number as if it
    /// were the measurement.** skade's root `Cargo.toml` is a shell around three
    /// vendored forks; its own crates live in a detached sub-workspace, so a
    /// root-anchored scan sees 2 features while the repo offers 8. The repo's own
    /// `offers` is the control that catches it — an offered feature is declared
    /// SOMEWHERE, so one the scan never met means the scan missed a manifest.
    #[test]
    fn a_manifest_scan_that_missed_a_sub_workspace_says_so() {
        let set = BuildVariantSet::from_toml_str(
            r#"
[build]
offers = ["sql", "s3", "aws-s3", "rustfs", "lineage", "stream", "lineage-http", "testmatrix"]
[[build.variant]]
name = "skade"
package = "skade"
features = ["sql", "s3"]
"#,
        )
        .unwrap();
        // What a root-anchored scan of skade actually finds: the root shell only.
        let seen: BTreeSet<String> = ["bench", "testmatrix"].iter().map(|s| s.to_string()).collect();
        let axis = FeatureAxis::with_manifest_features("skade", &set, Some(&seen));

        assert!(axis.manifest_incomplete(), "6 of 8 offered features were never seen on disk");
        assert_eq!(axis.offers_in_manifest, 1, "only `testmatrix` was found in the scanned manifest");
        assert!(
            axis.headline().contains("INCOMPLETE"),
            "the headline reports a floor, not a measurement: {}",
            axis.headline()
        );
        assert!(
            !axis.headline().contains("0/2 Cargo features"),
            "and it does NOT print the wrong denominator as if it were the answer: {}",
            axis.headline()
        );

        // A repo the scan DID see whole reports the real number, with no caveat.
        let whole: BTreeSet<String> = set.offers.iter().cloned().collect();
        let axis = FeatureAxis::with_manifest_features("skade", &set, Some(&whole));
        assert!(!axis.manifest_incomplete());
        assert_eq!(axis.manifest_ratio(), (2, 8));
        assert!(axis.headline().contains("2/8 Cargo features"), "{}", axis.headline());
    }

    #[test]
    fn manifest_features_reads_a_workspace_and_drops_default() {
        let dir = tempfile::tempdir().unwrap();
        let root = dir.path();
        std::fs::write(
            root.join("Cargo.toml"),
            "[workspace]\nmembers = [\"crates/*\"]\n[features]\ndefault = [\"cli\"]\ncli = []\n",
        )
        .unwrap();
        std::fs::create_dir_all(root.join("crates/leaf")).unwrap();
        std::fs::write(
            root.join("crates/leaf/Cargo.toml"),
            "[package]\nname=\"leaf\"\n[features]\nscip = []\nserver = []\n",
        )
        .unwrap();

        let f = manifest_features(root);
        assert!(f.contains("cli") && f.contains("scip") && f.contains("server"), "{f:?}");
        assert!(!f.contains("default"), "`default` is a selection, not a capability");
    }

    #[test]
    fn state_json_carries_the_gap_for_a_pane() {
        let set = BuildVariantSet::from_toml_str(KORP_BEFORE).unwrap();
        let j = FeatureAxis::of("korp", &set).state_json();
        assert_eq!(j["repo"], "korp");
        assert_eq!(j["offered"], 6);
        assert_eq!(j["built"], 4);
        assert_eq!(j["clean"], false);
        assert!(j["unbuilt"].as_array().unwrap().iter().any(|v| v == "spark41"));
    }

    // ── the ROT GATE (allowlist-aware) ───────────────────────────────────────
    //
    // korp's REAL shape today: `spark41` is now built (the `korp-spark41` variant),
    // but SEVEN offered features are enabled by no variant — the debt korp's own
    // build-variants.toml documents in a comment. The gate makes that comment an
    // enforced check.
    const KORP_TODAY: &str = r#"
[build]
offers = [
    "iceberg", "sweden-clip", "planet-download", "server", "client",
    "collectors", "collectors-osm", "live-pipelines", "spark41",
    "containers", "testmatrix", "demo-watch",
]

[build.required]
"korp-server" = ["server"]

[[build.variant]]
name = "korp"
features = ["iceberg", "sweden-clip"]

[[build.variant]]
name = "korp-server"
bin = "korp-server"
features = ["server", "iceberg"]

[[build.variant]]
name = "korp-thin"
features = ["client"]

[[build.variant]]
name = "korp-spark41"
features = ["iceberg", "sweden-clip", "spark41"]
"#;

    /// RED: run against korp's real declared shape and the gate FAILS, naming the
    /// seven offered-but-never-built features — the live rot, spark41 no longer among
    /// them because a variant now builds it.
    #[test]
    fn the_gate_catches_korps_real_orphaned_features() {
        let set = BuildVariantSet::from_toml_str(KORP_TODAY).unwrap();
        let axis = FeatureAxis::of("korp", &set);
        let err = axis.gate().expect_err("korp's offered-but-unbuilt features are live rot");
        let msg = err.to_string();
        // spark41 is BUILT now → not in the rot; the seven documented-debt features are.
        assert!(!err.rotting.contains(&"spark41".to_string()), "spark41 is built: {msg}");
        for feat in
            ["collectors", "collectors-osm", "containers", "demo-watch", "live-pipelines", "planet-download", "testmatrix"]
        {
            assert!(err.rotting.contains(&feat.to_string()), "`{feat}` must be named rot: {msg}");
        }
        assert!(msg.contains("a feature no variant declares is a feature nobody builds"), "{msg}");
    }

    /// GREEN: the SAME shape, with the seven documented HONESTLY in
    /// `[build.allow_unbuilt]` (each with a reason), passes the gate — known,
    /// accepted debt is not rot.
    #[test]
    fn documenting_the_debt_in_allow_unbuilt_passes_the_gate() {
        let with_allowlist = format!(
            "{KORP_TODAY}\n[build.allow_unbuilt]\n\
             collectors = \"open-data collectors: no shipped variant yet, tracked debt\"\n\
             \"collectors-osm\" = \"OSM collector: needs the osmium leg, not yet a variant\"\n\
             containers = \"container bring-up: exercised by korp-image, not a cargo variant\"\n\
             \"demo-watch\" = \"demo file-watch: dev convenience, no deployed variant\"\n\
             \"live-pipelines\" = \"live Spark path: folded into spark41 work, no own variant\"\n\
             \"planet-download\" = \"planet-scale OSM download: opt-in, never a default build\"\n\
             testmatrix = \"testmatrix emitter: turned on by the test harness, not a build variant\"\n"
        );
        let set = BuildVariantSet::from_toml_str(&with_allowlist).unwrap();
        let axis = FeatureAxis::of("korp", &set);
        assert!(axis.gate().is_ok(), "documented debt is not rot: {:?}", axis.gate().err());
        assert!(axis.rotting().is_empty());
        // is_clean() is unchanged — it still sees the raw (un-excused) gap.
        assert!(!axis.is_clean(), "the raw axis still reports the gap; the GATE is what forgives it");
    }

    /// An UNDOCUMENTED orphan next to documented ones still fails — the allowlist
    /// forgives exactly what it names and nothing more.
    #[test]
    fn an_undocumented_orphan_beside_documented_debt_still_fails() {
        let toml = r#"
[build]
offers = ["a", "b", "c"]
[build.allow_unbuilt]
b = "known debt, tracked"
[[build.variant]]
name = "v"
features = ["a"]
"#;
        let set = BuildVariantSet::from_toml_str(toml).unwrap();
        let axis = FeatureAxis::of("x", &set);
        let err = axis.gate().expect_err("`c` is undocumented rot");
        assert_eq!(err.rotting, vec!["c".to_string()], "only the undocumented one: {err}");
    }

    /// A STALE excuse — a feature allowlisted as unbuilt that a variant now builds —
    /// fails the gate so the allowlist itself cannot rot.
    #[test]
    fn a_stale_allowlist_entry_fails_the_gate() {
        let toml = r#"
[build]
offers = ["a"]
[build.allow_unbuilt]
a = "was debt — but a variant below now builds it, so this line is stale"
[[build.variant]]
name = "v"
features = ["a"]
"#;
        let set = BuildVariantSet::from_toml_str(toml).unwrap();
        let axis = FeatureAxis::of("x", &set);
        let err = axis.gate().expect_err("a dead excuse is itself rot");
        assert!(err.stale_allowlist.contains_key("a"), "{err}");
        assert!(err.to_string().contains("STALE EXCUSE"), "{err}");
    }

    /// **The real-constellation gate**, run against every sibling checkout's ACTUAL
    /// `.nornir/build-variants.toml`. Ignored by default (siblings are a dev
    /// convenience, not a dependency); run it on a full checkout to see the live rot:
    ///
    /// ```text
    /// cargo test -p nornir-build-thing --lib \
    ///     feature_axis::tests::the_real_constellation_feature_rot_gate -- --ignored --nocapture
    /// ```
    ///
    /// This is the reusable gate the release/CI path calls: point it at the checkouts
    /// and it fails LOUD, naming every offered capability no variant builds.
    #[test]
    #[ignore = "reads sibling checkouts; run on a full constellation tree"]
    fn the_real_constellation_feature_rot_gate() {
        let git_root = Path::new(env!("CARGO_MANIFEST_DIR"))
            .ancestors()
            .nth(3)
            .expect("edda is nested at least 3 deep");
        let members = [
            "dwarves", "nornir", "korp", "holger", "knut", "modgunn", "tunnr", "skade",
        ];
        let mut axes = Vec::new();
        for m in members {
            let root = git_root.join(m);
            let vf = BuildVariantSet::conventional_path(&root);
            if !vf.is_file() {
                eprintln!("SKIP {m}: no .nornir/build-variants.toml");
                continue;
            }
            let set = match BuildVariantSet::load_file(&vf) {
                Ok(s) => s,
                Err(e) => {
                    eprintln!("SKIP {m}: parse error {e:#}");
                    continue;
                }
            };
            let manifest = root
                .join("Cargo.toml")
                .is_file()
                .then(|| manifest_features(&root));
            let axis = FeatureAxis::with_manifest_features(m, &set, manifest.as_ref());
            eprintln!("── {m} ──\n{}", axis.headline());
            match axis.gate() {
                Ok(()) => eprintln!("   GATE: OK (no un-excused rot)\n"),
                Err(e) => eprintln!("   GATE: {e}"),
            }
            axes.push(axis);
        }
        if axes.is_empty() {
            eprintln!("no sibling checkouts found — nothing to gate");
            return;
        }
        // Print the folded verdict. This assertion is what a release gate would trip
        // on; left as an eprintln + soft check so the ignored run is a REPORT, and the
        // per-repo `gate()` above is the hard check a CI wires per repo once its own
        // allowlist is honest.
        match gate_all(&axes) {
            Ok(()) => eprintln!("CONSTELLATION FEATURE-ROT GATE: CLEAN"),
            Err(e) => eprintln!("CONSTELLATION FEATURE-ROT GATE:\n{e}"),
        }
    }

    /// The constellation gate folds every repo's failure into one report.
    #[test]
    fn gate_all_names_every_rotting_repo_at_once() {
        let korp = FeatureAxis::of("korp", &BuildVariantSet::from_toml_str(KORP_TODAY).unwrap());
        let clean = FeatureAxis::of(
            "clean-repo",
            &BuildVariantSet::from_toml_str(
                "[build]\noffers=[\"a\"]\n[[build.variant]]\nname=\"v\"\nfeatures=[\"a\"]\n",
            )
            .unwrap(),
        );
        assert!(clean.gate().is_ok());
        let err = gate_all(&[korp, clean]).expect_err("one repo rots");
        assert_eq!(err.failures.len(), 1, "only korp fails");
        assert_eq!(err.failures[0].repo, "korp");
        assert!(err.to_string().contains("across 1 repo"), "{err}");
    }

    // ── manifest_gate: the half a repo without `offers` cannot escape ──────────

    /// znippy-zoomies' real shape: five atoms, every one of them the lean default
    /// build (`features = []`), and NO `[build] offers` at all.
    const NO_OFFERS_SPACE: &str = r#"
[build]
base_suite = "ORIGINAL"

[[build.variant]]
name = "osm-katana"
package = "osm-katana"
bin = "osm-katana"
features = []

[[build.variant]]
name = "ljar"
package = "ljar"
bin = "ljar"
features = []
"#;

    fn manifest(names: &[&str]) -> BTreeSet<String> {
        names.iter().map(|s| s.to_string()).collect()
    }

    /// **RED FIRST, and it is the whole reason this method exists.** `gate()` is
    /// `Ok(())` on a space with no `offers` no matter how many features rot, because
    /// both of its inputs are empty by construction there. `manifest_gate()` is the
    /// one that can still say red.
    #[test]
    fn the_offers_gate_is_structurally_blind_without_an_offers_space() {
        let set = BuildVariantSet::from_toml_str(NO_OFFERS_SPACE).unwrap();
        let m = manifest(&["timing", "zlib-ng", "gen", "search-index", "testmatrix"]);
        let axis = FeatureAxis::with_manifest_features("znippy-zoomies", &set, Some(&m));

        assert!(axis.offers.is_empty(), "the precondition: this repo offers nothing");
        assert!(
            axis.gate().is_ok(),
            "THE BLIND SPOT: the offers gate passes a repo where nothing at all is built"
        );
        assert_eq!(axis.manifest_total, 5);
        assert_eq!(axis.manifest_unbuilt.len(), 5, "not one of the five is built");

        let rot = axis.manifest_gate().expect_err("the manifest gate must see it");
        assert_eq!(rot.rotting, vec!["gen", "search-index", "testmatrix", "timing", "zlib-ng"]);
        assert!(rot.unmeasured.is_empty(), "it MEASURED — this is real rot, not blindness");
        assert!(rot.to_string().contains("NEVER BUILT"), "{rot}");
    }

    /// A written reason passes; the same feature without one does not. Both
    /// directions, so the allowlist cannot be what makes the gate green everywhere.
    #[test]
    fn a_written_reason_excuses_and_an_unwritten_one_does_not() {
        let with_reasons = format!(
            "{NO_OFFERS_SPACE}\n[build.allow_unbuilt]\n\
             timing = \"stderr timing prints, dev-only\"\n\
             \"zlib-ng\" = \"links the C zlib-ng; the shipped decoder is pure Rust\"\n\
             gen = \"pulls the third-party rivals this repo exists to beat\"\n\
             \"search-index\" = \"the katana search index, not in any shipped artifact\"\n\
             testmatrix = \"nornir test-matrix emit; on only under the matrix run\"\n"
        );
        let set = BuildVariantSet::from_toml_str(&with_reasons).unwrap();
        let m = manifest(&["timing", "zlib-ng", "gen", "search-index", "testmatrix"]);
        let axis = FeatureAxis::with_manifest_features("znippy-zoomies", &set, Some(&m));
        assert_eq!(axis.allow_unbuilt.len(), 5, "the five reasons parsed");
        axis.manifest_gate().expect("every gap has a written reason");

        // RED: one more feature appears in the manifest and nobody wrote it down.
        let m2 = manifest(&["timing", "zlib-ng", "gen", "search-index", "testmatrix", "pmtiles"]);
        let axis2 = FeatureAxis::with_manifest_features("znippy-zoomies", &set, Some(&m2));
        let rot = axis2.manifest_gate().expect_err("the new one is not written down");
        assert_eq!(rot.rotting, vec!["pmtiles"], "and it is named");
    }

    /// A dead excuse is rot too, in both of its shapes: the feature is built now, or
    /// the feature does not exist any more.
    #[test]
    fn an_excuse_that_outlived_its_feature_fails_too() {
        let toml = format!(
            "{NO_OFFERS_SPACE}\n[build.allow_unbuilt]\n\
             timing = \"dev-only\"\n\
             \"vann-mmap\" = \"deleted in the sweep, but the excuse stayed\"\n"
        );
        let set = BuildVariantSet::from_toml_str(&toml).unwrap();
        let m = manifest(&["timing"]);
        let axis = FeatureAxis::with_manifest_features("zz", &set, Some(&m));
        let rot = axis.manifest_gate().expect_err("the excuse names nothing");
        assert_eq!(rot.unknown_allowlist, vec!["vann-mmap"]);
        assert!(rot.rotting.is_empty(), "timing itself is honestly excused");
        assert!(rot.to_string().contains("NOT THERE"), "{rot}");

        // The other shape: a variant now builds it, so the excuse is stale.
        let mut set2 = BuildVariantSet::from_toml_str(&toml).unwrap();
        set2.variants[0].features.push("timing".into());
        let axis2 = FeatureAxis::with_manifest_features("zz", &set2, Some(&m));
        let rot2 = axis2.manifest_gate().expect_err("built AND excused");
        assert_eq!(rot2.stale_allowlist["timing"], vec!["osm-katana"]);
    }

    /// **An empty oracle is not a pass.** The failure mode that would make this whole
    /// gate ornamental: a manifest scan that found nothing looks exactly like a repo
    /// whose every feature is built.
    #[test]
    fn a_gate_that_measured_nothing_is_red_not_green() {
        let set = BuildVariantSet::from_toml_str(NO_OFFERS_SPACE).unwrap();

        let never_read = FeatureAxis::of("zz", &set);
        let rot = never_read.manifest_gate().expect_err("no manifest was supplied");
        assert!(rot.rotting.is_empty(), "it found no rot — because it could not look");
        assert_eq!(rot.unmeasured.len(), 1);
        assert!(rot.to_string().contains("NOT MEASURED"), "{rot}");

        let empty = FeatureAxis::with_manifest_features("zz", &set, Some(&BTreeSet::new()));
        let rot = empty.manifest_gate().expect_err("the scan found zero features");
        assert!(rot.unmeasured[0].contains("ZERO declared features"), "{rot}");

        // And the control: the SAME construction with one real feature and one real
        // reason is green, so "always red" is not how this passes its own tests.
        let with_reason =
            format!("{NO_OFFERS_SPACE}\n[build.allow_unbuilt]\ntiming = \"dev-only\"\n");
        let set2 = BuildVariantSet::from_toml_str(&with_reason).unwrap();
        FeatureAxis::with_manifest_features("zz", &set2, Some(&manifest(&["timing"])))
            .manifest_gate()
            .expect("measured, and every gap written down");
    }
}