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

nordisk/gunnar-ui

xtask/src/publish.rs

//! `cargo xtask publish` — build the PUBLIC tree and put it on branch `public`.
//!
//! ```text
//!   cargo xtask publish           build the branch, prove it compiles, push nothing
//!   cargo xtask publish --push    …and push it to the public remotes
//! ```
//!
//! # ⚠ A KNOWN TWIN — `monetize/xtask/src/publish.rs`
//!
//! MEASURED 2026-09-10 by `nornir mimir analyze duplicates`: monetize's copy is
//! 616 lines against this file's 662, and `diff` is **188 lines** — nearly all
//! of it the two DATA tables (which globs are the design record, which files are
//! product twice over) and the prose that explains them. The MACHINERY either
//! side of those tables — the throwaway worktree, the cleanse walk, the
//! `cargo metadata` + `cargo check` proof that the public branch still compiles,
//! the orphan-branch commit — is the same procedure written twice. gunnar's own
//! `xtask/src/publish.rs` (856 lines) is the third writing.
//!
//! It is NOT unified, and the reason is the rule in `Cargo.toml` one directory
//! up: **this crate has ZERO dependencies, deliberately**, because a workspace
//! member takes part in resolver-2 feature unification and a dependency here
//! could turn a feature on in a crate the BFF links. The natural home is
//! `edda/crates/nornir-xtask-support` (std-only xtask primitives; it took
//! gunnar's `expand_home` in the same sweep), and the parameterization is
//! already visible in the file: the two `const` tables and the crate names
//! handed to `cargo check` are the whole of what a `PublishConfig` would carry.
//!
//! Naming it is the guard until then: the day this file's cleanse learns
//! something monetize's does not, the twin is where to put it too.
//!
//! # What "public" means here
//!
//! This repository holds two kinds of file. One is the product: the two crates,
//! the vendored protos, the manifests, the tests, the browser matrix, the front
//! page, and the RPC coverage manual a test reads. The other is the record of
//! how the product was arrived at — design notes, plans, resumption points, the
//! state of one afternoon's work. The second kind is not secret in the sense of
//! credentials; it is simply not the thing anyone clones this repository to
//! read, and it names dates and internal ids that mean nothing outside the tree.
//!
//! So the public branch is HEAD's tree **minus a declared list**, and the list
//! is [`EXCLUSIONS`] — a const, with one reason per line, rather than a script
//! somebody keeps in their shell history.
//!
//! # Why an orphan commit and not a filtered history
//!
//! Rewriting history means deciding, for every commit that ever touched an
//! excluded path, what that commit becomes. There is no answer that is both
//! cheap and correct, and a wrong answer is published permanently. One orphan
//! commit per release has no such question in it: the tree is exactly what the
//! predicate says, and nothing behind it can leak because there is nothing
//! behind it.
//!
//! # Why it compiles the result before it is believed
//!
//! Excluding a file that a build reads produces a branch that looks right and
//! does not build, and nothing about writing the exclusion list would have said
//! so. [`run`] therefore checks the tree out into a worktree beside this
//! repository — so a sibling checkout resolves exactly as it does here — runs
//! `cargo metadata`, `cargo check -p gunnar-ui-server` and the README version
//! guard in it, and removes the worktree afterwards.
//!
//! # Git is driven through `Command`
//!
//! `xtask` carries **zero dependencies** by construction, so there is no git
//! library available to it. The repository's pure-Rust, nothing-shelled-out-to
//! rule is about the SERVER, which spawns nothing; this is repository tooling a
//! human types.

use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};

/// Paths that stay out of the public tree, each with the reason it is out.
///
/// # The pattern language
///
/// Deliberately tiny, because a glob library is a dependency and this list is
/// read by humans more often than by the matcher:
///
/// * `dir/**` — the path is inside `dir/`.
/// * `dir/*.ext` — a file directly in `dir/` with that extension.
/// * `*text*` — the FILE NAME contains `text` **and** the file is a `.md` or a
///   `.txt`. Narrow on purpose: a whole-path match with no extension test would
///   eat source files. `*session*` would otherwise take
///   `crates/gunnar-ui-server/src/auth/session.rs`-shaped paths; the public
///   branch would look right and not compile, which is what
///   [`prove_it_builds`] exists to catch.
/// * anything else — an exact path.
///
/// [`KEPT`] is consulted first, so an exception never has to be spelled as a
/// negative pattern.
pub const EXCLUSIONS: &[(&str, &str)] = &[
    (
        ".nornir/**",
        "the design record: design notes and work in flight, kept in the private ops repository",
    ),
    (
        "docs/**",
        "the rendered books of the design record (the RPC coverage manual is kept, below)",
    ),
    ("*PLAN*", "working notes: what was going to be done next"),
    ("*HANDOFF*", "working notes passed between pieces of work"),
    ("*session*", "working notes tied to one working session"),
    (
        "*brief*",
        "working notes: the instructions a piece of work started from",
    ),
];

/// The exceptions, checked before [`EXCLUSIONS`].
///
/// `docs/**` is excluded wholesale because rendered books of the design record
/// live there. One file in it is product twice over: `docs/coverage.md` is the
/// manual that says what the console does with each of gunnar's 80 RPCs, and
/// `crates/gunnar-ui-server/tests/contract.rs` READS it — three of its tests
/// parse the table and fail on a row that is missing or stale. Excluding it
/// would publish a tree whose own test suite cannot run.
pub const KEPT: &[(&str, &str)] = &[
    (
        "docs/coverage.md",
        "the RPC coverage manual; `tests/contract.rs` parses it",
    ),
    // `.nornir/**` is excluded wholesale as the design record. This one file in
    // it is not a note: it is the EMITTED description of this repository's build
    // space, the file every tool that discovers a repository by that path reads
    // (the dwarves forge, the build-thing pane, installtest, the bench harness).
    // `crates/gunnar-ui-recipe/tests/toml_parity.rs` reads it too and fails when
    // it is not the code's emission — so excluding it would publish a tree whose
    // own test suite cannot run, which is exactly the `docs/coverage.md` shape.
    (
        ".nornir/build-variants.toml",
        "the emitted build description; `gunnar-ui-recipe`'s `toml_parity` test reads it",
    ),
];

/// Inside a [`KEPT`] directory and still excluded.
///
/// Empty today. It exists because the ordering in [`excluded`] is the whole
/// rule, and the third list is what makes that ordering testable rather than
/// theoretical.
pub const KEPT_BUT_STILL_EXCLUDED: &[(&str, &str)] = &[];

/// Files whose ABSENCE from the public tree would not fail a build, and which
/// something outside the compiler nonetheless reads.
///
/// A missing `README.md` publishes a front page nobody wrote. A missing
/// `.github/wasm-baseline` makes the workflow's bundle ratchet read an empty
/// file and compare against nothing, green, forever — the exact silent
/// non-check that file's own text forbids. A missing
/// `.nornir/build-variants.toml` publishes a repository that is INVISIBLE to
/// every tool that discovers one by that path, while the recipe crate beside it
/// says it is not. None is caught by [`prove_it_builds`], so all are asserted
/// before the branch is written.
pub const MUST_SURVIVE: &[&str] = &[
    "README.md",
    ".github/wasm-baseline",
    ".nornir/build-variants.toml",
];

/// Does `pat` describe `path`? See [`EXCLUSIONS`] for the forms.
fn matches(pat: &str, path: &str) -> bool {
    if let Some(dir) = pat.strip_suffix("/**") {
        return path.starts_with(dir) && path.as_bytes().get(dir.len()) == Some(&b'/');
    }
    if let Some(rest) = pat.strip_prefix('*') {
        if let Some(inner) = rest.strip_suffix('*') {
            // A working note, not a source file: the name carries the word AND
            // the file is prose.
            if !(path.ends_with(".md") || path.ends_with(".txt")) {
                return false;
            }
            let name = path.rsplit('/').next().unwrap_or(path);
            return name.contains(inner);
        }
    }
    if let Some((dir, ext)) = pat.split_once("/*") {
        if !ext.contains('/') {
            let Some(tail) = path.strip_prefix(dir) else {
                return false;
            };
            let Some(tail) = tail.strip_prefix('/') else {
                return false;
            };
            return !tail.contains('/') && tail.ends_with(ext);
        }
    }
    pat == path
}

/// `Some(reason)` when `path` does not belong in the public tree.
///
/// The order is the whole rule: an exception wins over the wholesale exclusion
/// it sits inside, and a narrower exclusion inside an exception wins over that.
pub fn excluded(path: &str) -> Option<&'static str> {
    for (pat, why) in KEPT_BUT_STILL_EXCLUDED {
        if matches(pat, path) {
            return Some(why);
        }
    }
    for (pat, _) in KEPT {
        if matches(pat, path) {
            return None;
        }
    }
    for (pat, why) in EXCLUSIONS {
        if matches(pat, path) {
            return Some(why);
        }
    }
    None
}

/// One entry of HEAD's tree.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Entry {
    pub mode: String,
    pub oid: String,
    pub path: String,
}

/// What a run did, so the caller can print it rather than the verb printing
/// from three places.
#[derive(Debug, Default)]
pub struct Report {
    pub tree: String,
    pub commit: String,
    pub kept: usize,
    /// `(reason, count)`, in the order the reasons appear in the const lists.
    pub excluded_by_reason: Vec<(&'static str, usize)>,
    pub checked: bool,
    pub pushed: bool,
}

impl Report {
    pub fn excluded_total(&self) -> usize {
        self.excluded_by_reason.iter().map(|(_, n)| n).sum()
    }
}

fn git(root: &Path, args: &[&str]) -> Result<String, String> {
    let out = Command::new("git")
        .current_dir(root)
        .args(args)
        .output()
        .map_err(|e| format!("git {}: {e}", args.join(" ")))?;
    if !out.status.success() {
        return Err(format!(
            "git {} failed: {}",
            args.join(" "),
            String::from_utf8_lossy(&out.stderr).trim()
        ));
    }
    Ok(String::from_utf8_lossy(&out.stdout).into_owned())
}

/// HEAD's tree, flattened, as `(mode, oid, path)`.
///
/// `-z` and the explicit format because a path may contain anything a
/// filesystem allows, and `git`'s default quoting of such a path would have to
/// be undone before it could be fed back in.
pub fn head_entries(root: &Path) -> Result<Vec<Entry>, String> {
    let raw = git(
        root,
        &[
            "ls-tree",
            "-r",
            "-z",
            "--format=%(objectmode) %(objectname)\t%(path)",
            "HEAD",
        ],
    )?;
    let mut out = Vec::new();
    for rec in raw.split('\0').filter(|s| !s.is_empty()) {
        let (meta, path) = rec
            .split_once('\t')
            .ok_or_else(|| format!("unparsable ls-tree record: {rec:?}"))?;
        let (mode, oid) = meta
            .split_once(' ')
            .ok_or_else(|| format!("unparsable ls-tree meta: {meta:?}"))?;
        out.push(Entry {
            mode: mode.to_string(),
            oid: oid.to_string(),
            path: path.to_string(),
        });
    }
    Ok(out)
}

/// Write `entries` as a tree object, through a temporary index.
///
/// `update-index --index-info` is the one plumbing command that takes a whole
/// index as text, so the public tree is built in one call rather than by
/// removing paths from a copy of HEAD's index one at a time.
fn write_tree(root: &Path, entries: &[Entry]) -> Result<String, String> {
    let index = root.join(".git").join("gunnar-ui-publish-index");
    let _ = std::fs::remove_file(&index);

    let mut child = Command::new("git")
        .current_dir(root)
        .env("GIT_INDEX_FILE", &index)
        .args(["update-index", "-z", "--index-info"])
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .spawn()
        .map_err(|e| format!("git update-index: {e}"))?;

    {
        use std::io::Write as _;
        let mut stdin = child.stdin.take().expect("stdin was piped");
        for e in entries {
            write!(stdin, "{} {} 0\t{}\0", e.mode, e.oid, e.path)
                .map_err(|err| format!("feeding git update-index: {err}"))?;
        }
    }
    let out = child
        .wait_with_output()
        .map_err(|e| format!("git update-index: {e}"))?;
    if !out.status.success() {
        return Err(format!(
            "git update-index failed: {}",
            String::from_utf8_lossy(&out.stderr).trim()
        ));
    }

    let tree = Command::new("git")
        .current_dir(root)
        .env("GIT_INDEX_FILE", &index)
        .args(["write-tree"])
        .output()
        .map_err(|e| format!("git write-tree: {e}"))?;
    let _ = std::fs::remove_file(&index);
    if !tree.status.success() {
        return Err(format!(
            "git write-tree failed: {}",
            String::from_utf8_lossy(&tree.stderr).trim()
        ));
    }
    Ok(String::from_utf8_lossy(&tree.stdout).trim().to_string())
}

/// `[workspace.package] version`, read the same way the README guard reads it.
fn version(root: &Path) -> String {
    crate::release_version::workspace_version(root)
}

/// Today, as `YYYY-MM-DD`, from `git`'s own clock so the verb has no time
/// dependency of its own.
fn today(root: &Path) -> Result<String, String> {
    Ok(git(root, &["log", "-1", "--format=%cd", "--date=short"])?
        .trim()
        .to_string())
}

/// The gates that must hold before a public branch is written at all.
///
/// Both are about *what is being published*, not about tidiness: a dirty tree
/// means the published commit is not the tree anyone can reproduce, and a HEAD
/// that is not `main` means it is not the branch this repository releases from.
pub fn refuse_reasons(status: &str, branch: &str) -> Vec<String> {
    let mut out = Vec::new();
    if !status.trim().is_empty() {
        out.push(format!(
            "the working tree is dirty, so the public tree would not be a tree anyone \
             can reproduce from this repository:\n{}",
            status.trim()
        ));
    }
    if branch.trim() != "main" {
        out.push(format!(
            "HEAD is on `{}`, and this repository releases from `main`",
            branch.trim()
        ));
    }
    out
}

/// Build (and optionally push) the public branch.
pub fn run(root: &Path, push: bool) -> Result<Report, String> {
    let status = git(root, &["status", "--porcelain"])?;
    let branch = git(root, &["rev-parse", "--abbrev-ref", "HEAD"])?;
    let refusals = refuse_reasons(&status, &branch);
    if !refusals.is_empty() {
        return Err(refusals.join("\n"));
    }

    // **The description that is about to be published must be the code's.**
    // This verb writes a tree and pushes it to two public remotes, and
    // `.nornir/build-variants.toml` rides along on [`KEPT`]. A stale one is
    // worse here than anywhere else: it is read by tools in a checkout that has
    // no way back to the person who forgot to re-emit.
    gunnar_ui_recipe::require_committed_is_emitted(root).map_err(|e| format!("{e:#}"))?;

    let all = head_entries(root)?;
    let mut kept = Vec::new();
    let mut counts: Vec<(&'static str, usize)> = Vec::new();
    for e in all {
        match excluded(&e.path) {
            None => kept.push(e),
            Some(why) => match counts.iter_mut().find(|(w, _)| *w == why) {
                Some((_, n)) => *n += 1,
                None => counts.push((why, 1)),
            },
        }
    }

    for must in MUST_SURVIVE {
        if !kept.iter().any(|e| e.path == *must) {
            return Err(format!("the public tree has no `{must}`"));
        }
    }

    let tree = write_tree(root, &kept)?;
    let msg = format!(
        "gunnar-ui {} — public release {}",
        version(root),
        today(root)?
    );
    let commit = git(root, &["commit-tree", &tree, "-m", &msg])?
        .trim()
        .to_string();
    git(root, &["update-ref", "refs/heads/public", &commit])?;

    let mut report = Report {
        tree,
        commit,
        kept: kept.len(),
        excluded_by_reason: counts,
        checked: false,
        pushed: false,
    };

    report.checked = prove_it_builds(root)?;

    if push {
        // Both remotes carry the same branch name and both are pushed with a
        // lease: a force that would discard a commit this repository has not
        // seen is refused rather than performed.
        for remote in ["github-public", "gunnar-rs"] {
            git(
                root,
                &[
                    "push",
                    "--force-with-lease",
                    remote,
                    "refs/heads/public:refs/heads/main",
                ],
            )?;
        }
        report.pushed = true;
    }

    Ok(report)
}

/// Where the throwaway worktree goes: beside the repository, so a sibling
/// checkout resolves from its parent exactly as it does from here.
fn check_dir(root: &Path) -> PathBuf {
    root.parent()
        .unwrap_or(Path::new("."))
        .join("gunnar-ui-public-check")
}

/// **The package the public tree is compiled against**, asked of the recipe.
///
/// The default `server`-role row is what `deploy/deploy.sh` ships and what a
/// reader means by "the console's server"; `check_defaults` proves there is
/// exactly one of it, so this cannot silently pick.
fn default_server_package() -> String {
    let set = gunnar_ui_recipe::declared();
    set.variants
        .iter()
        .find(|v| {
            v.is_default
                && v.role == Some(gunnar_ui_recipe::nornir_build_thing::plugin::Role::Server)
        })
        .and_then(|v| v.package.clone())
        .expect("the recipe declares exactly one default server row (check_defaults)")
}

/// Check the branch out and compile it.
fn prove_it_builds(root: &Path) -> Result<bool, String> {
    let dir = check_dir(root);
    let dir_str = dir.to_string_lossy().into_owned();
    let _ = git(root, &["worktree", "remove", "--force", &dir_str]);
    if dir.exists() {
        std::fs::remove_dir_all(&dir).map_err(|e| format!("clearing {dir_str}: {e}"))?;
    }
    git(root, &["worktree", "add", "--detach", &dir_str, "public"])?;

    let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".to_string());
    // **The package to check is the recipe's answer, not a string typed here.**
    // It used to read `"gunnar-ui-server"`, which was a second place a build
    // point of this repository was written down. It is now the package of the
    // default `server` row — so a rename, or a second BFF package, changes one
    // declaration and this verb follows.
    let server = default_server_package();
    let steps: Vec<Vec<&str>> = vec![
        vec!["metadata", "--format-version", "1"],
        vec!["check", "-p", &server],
        vec!["test", "-p", "xtask", "--test", "readme_version"],
    ];
    let mut failure = None;
    for step in &steps {
        eprintln!("  public tree: cargo {}", step.join(" "));
        let out = Command::new(&cargo)
            .current_dir(&dir)
            // ITS OWN target directory, and that is not tidiness.
            //
            // `CARGO_MANIFEST_DIR` is baked into a test binary at compile time,
            // so a `cargo test -p xtask` run from inside this worktree writes a
            // binary that believes the workspace root is the worktree. Sharing
            // a target directory with the repository hands that binary back to
            // the next run in the repository, where every path it reads is a
            // directory this verb has already deleted. The proof must not be
            // able to break the thing it is proving.
            .env("CARGO_TARGET_DIR", dir.join("target"))
            .args(step)
            .stdout(Stdio::null())
            .stderr(Stdio::piped())
            .output()
            .map_err(|e| format!("cargo {}: {e}", step.join(" ")))?;
        if !out.status.success() {
            let err = String::from_utf8_lossy(&out.stderr);
            failure = Some(format!(
                "the public tree fails `cargo {}`:\n{}",
                step.join(" "),
                diagnosis(&err)
            ));
            break;
        }
    }

    let _ = git(root, &["worktree", "remove", "--force", &dir_str]);
    match failure {
        Some(e) => Err(e),
        None => Ok(true),
    }
}

/// The lines worth printing out of a failed cargo run.
///
/// **Never the tail.** A `cargo check` that fails on one crate prints that
/// crate's error and then keeps printing *warnings* from every dependency that
/// compiled after it, so the last thirty lines of a failed run are routinely
/// three warnings about a sibling repository and no mention of what broke. The
/// error lines are what the caller needs, and the tail is what is left when
/// there are none.
fn diagnosis(stderr: &str) -> String {
    let mut out: Vec<&str> = Vec::new();
    let mut lines = stderr.lines().peekable();
    while let Some(l) = lines.next() {
        if l.starts_with("error") {
            out.push(l);
            // The `--> file:line:col` that follows an error names where.
            while let Some(next) = lines.peek() {
                if next.trim_start().starts_with("-->") {
                    out.push(lines.next().expect("peeked"));
                    break;
                }
                if next.trim().is_empty() || next.starts_with(char::is_whitespace) {
                    lines.next();
                    continue;
                }
                break;
            }
        }
    }
    if out.is_empty() {
        return stderr
            .lines()
            .rev()
            .take(30)
            .collect::<Vec<_>>()
            .into_iter()
            .rev()
            .collect::<Vec<_>>()
            .join("\n");
    }
    out.join("\n")
}

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

    /// The predicate, red first: each of these was chosen because getting it
    /// wrong publishes something, or drops something a build needs.
    #[test]
    fn the_record_is_excluded_and_the_product_is_not() {
        for (path, want) in [
            (".nornir/role-floor-and-auth-model.md", true),
            (".nornir/fancy-graph-design.md", true),
            // Product, not record: the emitted build description. See `KEPT`.
            (".nornir/build-variants.toml", false),
            ("docs/gunnar-ui-manual.pdf", true),
            ("docs/coverage.md", false),
            ("README.md", false),
            ("COMMERCIAL-LICENSING.md", false),
            ("Cargo.toml", false),
            ("Cargo.lock", false),
            (".github/wasm-baseline", false),
            (".github/workflows/ci.yml", false),
            ("crates/gunnar-ui-server/src/serve.rs", false),
            ("crates/gunnar-ui-server/tests/contract.rs", false),
            ("crates/gunnar-ui-web/index.html", false),
            ("crates/gunnar-ui-web/assets/gunnar.webp", false),
            ("crates/gunnar-ui-server/proto/gunnar/v1/mirror.proto", false),
            ("crates/gunnar-ui-server/proto/monetize/v1/purchase.proto", false),
            ("uitest/src/matrix.rs", false),
            ("xtask/src/publish.rs", false),
        ] {
            assert_eq!(
                excluded(path).is_some(),
                want,
                "{path}: expected excluded={want}, got {:?}",
                excluded(path)
            );
        }
    }

    /// A `*text*` rule reaches into directories, and stops at the extension.
    ///
    /// The second half is the one that matters: with no extension test,
    /// `*session*` would exclude a `session.rs` and the public branch would
    /// stop compiling while every other check stayed green.
    #[test]
    fn a_working_note_is_excluded_but_source_named_like_one_is_not() {
        assert!(excluded("WIP-HANDOFF.md").is_some());
        assert!(excluded("deploy/PLAN-2026.md").is_some());
        assert!(excluded("notes/session-3.md").is_some());
        assert!(excluded("docs-src/brief.md").is_some());

        assert!(excluded("crates/gunnar-ui-server/src/auth/session.rs").is_none());
        assert!(excluded("crates/gunnar-ui-server/tests/session_end_to_end.rs").is_none());
        assert!(excluded("crates/gunnar-ui-web/src/pages/plan.rs").is_none());
        assert!(excluded("uitest/src/briefing.rs").is_none());
    }

    /// The exception inside the wholesale exclusion, seen to win.
    ///
    /// `docs/coverage.md` is not decoration: `tests/contract.rs` parses it, so
    /// publishing `docs/**` wholesale would ship a tree whose own test suite
    /// cannot run.
    #[test]
    fn the_coverage_manual_survives_the_records_exclusion() {
        assert!(excluded("docs/anything-else.md").is_some());
        assert!(excluded("docs/coverage.md").is_none());
    }

    /// The two gates, seen to fire.
    #[test]
    fn a_dirty_tree_and_a_wrong_branch_are_refused() {
        let clean = refuse_reasons("", "main\n");
        assert!(clean.is_empty(), "a clean main was refused: {clean:?}");

        let dirty = refuse_reasons(" M crates/gunnar-ui-server/src/serve.rs\n", "main\n");
        assert_eq!(dirty.len(), 1, "{dirty:?}");
        assert!(dirty[0].contains("dirty"), "{dirty:?}");

        let branch = refuse_reasons("", "wip/experiment\n");
        assert_eq!(branch.len(), 1, "{branch:?}");
        assert!(branch[0].contains("wip/experiment"), "{branch:?}");

        assert_eq!(refuse_reasons(" M a\n", "wip\n").len(), 2);
    }

    /// Neither file [`MUST_SURVIVE`] names may be excluded by the list.
    ///
    /// The CI ratchet is the reason the second one is here: an empty
    /// `.github/wasm-baseline` makes the workflow compare the built bundle
    /// against nothing and pass, which is the silent non-check that file
    /// forbids in its own words.
    #[test]
    fn the_front_page_and_the_bundle_ratchet_are_never_excluded() {
        for must in MUST_SURVIVE {
            assert!(
                excluded(must).is_none(),
                "{must} is on MUST_SURVIVE and the exclusion list eats it: {:?}",
                excluded(must)
            );
        }
        assert!(MUST_SURVIVE.contains(&".github/wasm-baseline"));
    }

    /// A failed `cargo check` is reported by its errors, not by whatever
    /// happened to be printed last.
    #[test]
    fn the_diagnosis_is_the_error_and_not_the_trailing_warnings() {
        let stderr = "\
error[E0583]: file not found for module `session`
  --> crates/gunnar-ui-server/src/lib.rs:89:1
   |
89 | mod session;
   | ^^^^^^^^^^^^
warning: value assigned to `waited` is never read
  --> ../edda/src/resolve.rs:1358:25
warning: `tower-http` (lib) generated 3 warnings
";
        let d = diagnosis(stderr);
        assert!(d.contains("E0583"), "the error was dropped: {d:?}");
        assert!(
            d.contains("crates/gunnar-ui-server/src/lib.rs:89:1"),
            "the error's location was dropped: {d:?}"
        );
        assert!(
            !d.contains("never read"),
            "a trailing warning was reported as the failure: {d:?}"
        );
    }

    /// With no error line at all, the tail is better than nothing.
    #[test]
    fn a_run_with_no_error_line_falls_back_to_the_tail() {
        let d = diagnosis("something went wrong\nand said so oddly\n");
        assert!(d.contains("said so oddly"), "{d:?}");
    }

    /// The sentence `xtask/tests/readme_version.rs` scans must survive into the
    /// public tree, because that test runs against it there.
    #[test]
    fn the_public_front_page_names_the_version() {
        let root = Path::new(env!("CARGO_MANIFEST_DIR"))
            .parent()
            .expect("xtask/ has a parent")
            .to_path_buf();
        let src = std::fs::read_to_string(root.join("README.md")).expect("read README.md");
        assert!(excluded("README.md").is_none());
        let version = crate::release_version::workspace_version(&root);
        assert!(
            src.contains(&format!("gunnar-ui {version}")),
            "README.md does not name `gunnar-ui {version}`, so the public tree's front \
             page names no version"
        );
    }
}