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

nordisk/holger

server/lib/src/pull_intake/manifest_job.rs

//! **The manifests of a pull, read in a job** (lane HOL-312-HARDEN, SEC-REVIEW 2026-11-01 D1/D3,
//! row SEC-D3-SANDBOX-JOB): a POM, a `maven-metadata.xml` and a wheel's core metadata are bytes
//! an upstream wrote, so the server never parses them. It hands the bytes to a job of its ONE
//! runner ([`crate::job_runner::Jobs`]) and believes only the job's signed, structured answer:
//!
//! | Program | In | Out ([`RESULT_FILE`]) |
//! |---|---|---|
//! | [`MAVEN_CLOSURE`] | [`INDEX_FILE`] ([`ClosureInput`]) and the POMs and metadata it names | [`ClosureAnswer`]: the files still needed, or the closure |
//! | [`MAVEN_METADATA`] | [`METADATA_XML`] | [`MetadataAnswer`]: the newest release, a group's plugins |
//! | [`WHEEL_REQUIRES`] | [`METADATA_FILE`] (PEP 658) or [`WHEEL_FILE`] | [`WheelAnswer`]: the `Requires-Dist` that hold |
//!
//! A Maven closure is resolved in rounds: the job reads what it was given, names every POM and
//! metadata document its walk reached and was not given, and the server fetches those bytes
//! (nothing more) and runs the next round. The last round, with nothing missing, answers the
//! closure exactly as Maven 3.9 reads it ([`super::pom::closure`]).

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

use holger_job_runner::wire::{Answer, Dirs, WireJob};
use serde::{Deserialize, Serialize};

/// The Maven closure program.
pub const MAVEN_CLOSURE: &str = "maven-closure";
/// The `maven-metadata.xml` program.
pub const MAVEN_METADATA: &str = "maven-metadata";
/// The wheel core-metadata program.
pub const WHEEL_REQUIRES: &str = "wheel-requires";
/// The closure's index, in the input.
pub const INDEX_FILE: &str = "index.json";
/// The answer, in the output.
pub const RESULT_FILE: &str = "result.json";
/// A pulled wheel, in the input.
pub const WHEEL_FILE: &str = "wheel";
/// A wheel's PEP 658 core metadata, in the input.
pub const METADATA_FILE: &str = "metadata";
/// A `maven-metadata.xml`, in the input.
pub const METADATA_XML: &str = "maven-metadata.xml";
/// The most rounds one closure takes; past it the closure is held (fail closed).
pub const MAX_ROUNDS: usize = 64;

/// What a closure round is given.
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ClosureInput {
    pub group: String,
    pub artifact: String,
    pub version: String,
    /// Each POM fetched so far: its path below the repository → its file in the input.
    pub poms: BTreeMap<String, String>,
    /// The POM paths the repository does not publish.
    pub absent_poms: BTreeSet<String>,
    /// Each `maven-metadata.xml` fetched so far, by `<group path>/<artifact>` → its file.
    pub metadata: BTreeMap<String, String>,
    /// The metadata the repository does not publish, by `<group path>/<artifact>`.
    pub absent_metadata: BTreeSet<String>,
}

/// One file of the closure, as the job answers it.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ClosedFile {
    pub path: String,
    /// [`super::pom::Role::name`].
    pub role: String,
    pub via: String,
}

/// A closure round's answer.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
pub enum ClosureAnswer {
    /// The walk reached these and was not given them: fetch them, run again.
    Need {
        poms: Vec<String>,
        /// `<group path>/<artifact>`.
        metadata: Vec<String>,
    },
    /// The closure.
    Done {
        files: Vec<ClosedFile>,
        unresolved: Vec<(String, String)>,
    },
}

/// What a `maven-metadata.xml` says.
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct MetadataAnswer {
    /// `<release>`, else the last non-SNAPSHOT `<version>`.
    pub release: Option<String>,
    /// A group's plugins: `(name, prefix, artifactId)`.
    pub plugins: Vec<(String, String, String)>,
}

/// What a wheel's core metadata requires.
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct WheelAnswer {
    /// Whether core metadata was found at all.
    pub found: bool,
    /// Each `Requires-Dist` whose marker holds: `(normalized name, specifier)`.
    pub requires: Vec<(String, String)>,
}

// ── the programs (in the job) ───────────────────────────────────────────────

/// The most bytes of the closure's index a job reads.
const MAX_INDEX_BYTES: u64 = 16 * 1024 * 1024;
/// The most bytes of a wheel's core metadata a job reads (as the pull always read it).
const MAX_WHEEL_METADATA: usize = 16 * 1024 * 1024;
/// The most bytes of a pulled wheel a job opens.
const MAX_WHEEL_BYTES: u64 = 4 * 1024 * 1024 * 1024;

type Refused = (String, String);

fn refused(why: impl std::fmt::Display) -> Refused {
    (
        holger_errcode::SANDBOX_CONFIG.code.into(),
        holger_errcode::SANDBOX_CONFIG.tag(why.to_string()),
    )
}

/// A file the server put into the job: a plain name, read under `cap`.
fn read_input(dirs: &Dirs, name: &str, cap: u64) -> Result<Vec<u8>, Refused> {
    if name.is_empty()
        || !name
            .bytes()
            .all(|b| b.is_ascii_alphanumeric() || b"-_.".contains(&b))
        || name.starts_with('.')
    {
        return Err(refused(format!("the job's input names the file {name:?}")));
    }
    let p = dirs.input.join(name);
    let len = std::fs::metadata(&p)
        .map_err(|e| refused(format!("the job's input {name}: {e}")))?
        .len();
    if len > cap {
        return Err(refused(format!(
            "the job's input {name} is {len} bytes, past {cap}"
        )));
    }
    std::fs::read(&p).map_err(|e| refused(format!("the job's input {name}: {e}")))
}

fn answer<T: Serialize>(dirs: &Dirs, t: &T) -> Answer {
    match serde_json::to_vec(t)
        .map_err(|e| e.to_string())
        .and_then(|b| std::fs::write(dirs.out.join(RESULT_FILE), b).map_err(|e| e.to_string()))
    {
        Ok(()) => Answer::accepted(vec![RESULT_FILE.into()]),
        Err(e) => Answer::refused(
            holger_errcode::SANDBOX_JOB_ANSWER_INVALID.code,
            holger_errcode::SANDBOX_JOB_ANSWER_INVALID.tag(format!("{RESULT_FILE}: {e}")),
        ),
    }
}

fn finish<T: Serialize>(dirs: &Dirs, r: Result<T, Refused>) -> Answer {
    match r {
        Ok(t) => answer(dirs, &t),
        Err((code, reason)) => Answer::refused(code, reason),
    }
}

/// The POMs and metadata a closure round was given; what it reached and was not given is
/// noted, and the walk goes on as if the repository did not publish it.
struct Given<'a> {
    dirs: &'a Dirs,
    idx: &'a ClosureInput,
    need_poms: std::cell::RefCell<BTreeSet<String>>,
    need_metadata: std::cell::RefCell<BTreeSet<String>>,
}

impl super::pom::PomSource for Given<'_> {
    fn pom(&self, path: &str) -> anyhow::Result<Option<Vec<u8>>> {
        if let Some(f) = self.idx.poms.get(path) {
            return read_input(self.dirs, f, super::pom::MAX_POM_BYTES)
                .map(Some)
                .map_err(|(_, why)| anyhow::anyhow!(why));
        }
        if !self.idx.absent_poms.contains(path) {
            self.need_poms.borrow_mut().insert(path.to_string());
        }
        Ok(None)
    }

    fn versions(&self, group_path: &str, artifact: &str) -> anyhow::Result<Option<Vec<String>>> {
        let key = format!("{group_path}/{artifact}");
        if let Some(f) = self.idx.metadata.get(&key) {
            let b = read_input(self.dirs, f, super::pom::MAX_POM_BYTES)
                .map_err(|(_, why)| anyhow::anyhow!(why))?;
            return Ok(Some(super::pom::metadata_versions(
                &String::from_utf8_lossy(&b),
            )));
        }
        if !self.idx.absent_metadata.contains(&key) {
            self.need_metadata.borrow_mut().insert(key);
        }
        Ok(None)
    }
}

/// **[`MAVEN_CLOSURE`]**: one round of a version's closure over what the server fetched.
pub fn run_closure(_job: &WireJob, dirs: &Dirs) -> Answer {
    finish(dirs, closure_round(dirs))
}

fn closure_round(dirs: &Dirs) -> Result<ClosureAnswer, Refused> {
    let idx: ClosureInput = serde_json::from_slice(&read_input(dirs, INDEX_FILE, MAX_INDEX_BYTES)?)
        .map_err(|e| refused(format!("{INDEX_FILE}: {e}")))?;
    let given = Given {
        dirs,
        idx: &idx,
        need_poms: Default::default(),
        need_metadata: Default::default(),
    };
    let root = super::pom::Gav::new(&idx.group, &idx.artifact, &idx.version);
    let c =
        super::pom::closure(&given, &root).map_err(|e| refused(format!("the closure: {e:#}")))?;
    let (poms, metadata) = (
        given.need_poms.into_inner(),
        given.need_metadata.into_inner(),
    );
    Ok(if poms.is_empty() && metadata.is_empty() {
        ClosureAnswer::Done {
            files: c
                .files
                .into_iter()
                .map(|f| ClosedFile {
                    path: f.path,
                    role: f.role.name().into(),
                    via: f.via,
                })
                .collect(),
            unresolved: c.unresolved,
        }
    } else {
        ClosureAnswer::Need {
            poms: poms.into_iter().collect(),
            metadata: metadata.into_iter().collect(),
        }
    })
}

/// **[`MAVEN_METADATA`]**: what a `maven-metadata.xml` says.
pub fn run_metadata(_job: &WireJob, dirs: &Dirs) -> Answer {
    finish(
        dirs,
        read_input(dirs, METADATA_XML, super::pom::MAX_POM_BYTES).map(|b| {
            let t = String::from_utf8_lossy(&b);
            MetadataAnswer {
                release: super::maven::newest_release_of(&t),
                plugins: super::maven::group_plugins(&t),
            }
        }),
    )
}

/// **[`WHEEL_REQUIRES`]**: the `Requires-Dist` of a wheel's core metadata that hold.
pub fn run_wheel(_job: &WireJob, dirs: &Dirs) -> Answer {
    finish(dirs, wheel_requires(dirs))
}

fn wheel_requires(dirs: &Dirs) -> Result<WheelAnswer, Refused> {
    let meta = if dirs.input.join(METADATA_FILE).exists() {
        Some(read_input(dirs, METADATA_FILE, MAX_WHEEL_METADATA as u64)?)
    } else {
        let wheel = read_input(dirs, WHEEL_FILE, MAX_WHEEL_BYTES)?;
        zip_entry::find_member_capped(&wheel, MAX_WHEEL_METADATA, |n| {
            let mut parts = n.split('/');
            matches!((parts.next(), parts.next(), parts.next()), (Some(d), Some("METADATA"), None) if d.ends_with(".dist-info"))
        })
        .ok()
        .flatten()
        .map(|(_, m)| m)
    };
    Ok(match meta {
        None => WheelAnswer::default(),
        Some(m) => WheelAnswer {
            found: true,
            requires: super::closures::requires_dist(&String::from_utf8_lossy(&m))
                .iter()
                .filter_map(|r| super::closures::requirement(r))
                .collect(),
        },
    })
}

// ── the server's half ───────────────────────────────────────────────────────

/// **Run `program` over `inputs` in a job of the chain's runner** and believe only its signed
/// answer ([`crate::job_runner::Jobs::run`]). No runner, or a refusal, holds the pull by name
/// ([`crate::jobs::JobPermanent`]); a runner that did not run it is retried.
pub(super) fn run_job<T: serde::de::DeserializeOwned>(
    chain: &crate::promote::intake::IntakeChain,
    base: &std::path::Path,
    program: &str,
    inputs: &[(String, &[u8])],
) -> anyhow::Result<T> {
    use crate::job_runner::heimdall_core::{Job, JobId, Limits, Nonce, Policy, Verdict};
    use crate::job_runner::{holger_job_runner as runner, JobDirs, JOBS_DIR};
    let Some(jobs) = chain.jobs().cloned() else {
        anyhow::bail!(crate::jobs::JobPermanent(format!(
            "this server has no job runner, and a {program} manifest is read only in a job (SEC-D3)"
        )));
    };
    let id = JobId::random();
    let dirs = JobDirs::new(&base.join(JOBS_DIR), &id)?;
    for (name, bytes) in inputs {
        std::fs::write(dirs.input.join(name), bytes)?;
    }
    let job = Job {
        id,
        nonce: Nonce::random(),
        kind: runner::worker(&[program]),
        input_dir: dirs.input.clone(),
        output_dir: dirs.output.clone(),
        params: serde_json::json!({}),
        policy: Policy {
            limits: Limits {
                wall: std::time::Duration::from_secs(120),
                memory_bytes: 1 << 30,
                cpus: 1.0,
                pids: 16,
                ..Default::default()
            },
            ..Default::default()
        },
    };
    let r = jobs
        .run(job)
        .map_err(|f| anyhow::anyhow!("the {program} job: {f}"))?;
    if let Verdict::Refused { code, reason } = &r.verdict {
        anyhow::bail!(crate::jobs::JobPermanent(format!(
            "the {program} job refused it: [{code}] {reason}"
        )));
    }
    let bytes = std::fs::read(dirs.output.join(RESULT_FILE))?;
    serde_json::from_slice(&bytes).map_err(|e| {
        anyhow::anyhow!(crate::jobs::JobPermanent(format!(
            "the {program} job's {RESULT_FILE}: {e}"
        )))
    })
}

/// **A version's closure, resolved in jobs** ([`MAVEN_CLOSURE`], round by round): the server
/// fetches only the bytes each round names (`pom`, `metadata` by `<group path>/<artifact>`) and
/// parses none of them. Past [`MAX_ROUNDS`] the closure is held.
pub(super) fn closure_in_jobs(
    chain: &crate::promote::intake::IntakeChain,
    base: &std::path::Path,
    root: &super::pom::Gav,
    pom: &dyn Fn(&str) -> anyhow::Result<Option<Vec<u8>>>,
    metadata: &dyn Fn(&str) -> anyhow::Result<Option<Vec<u8>>>,
) -> anyhow::Result<super::pom::Closure> {
    let mut idx = ClosureInput {
        group: root.group.clone(),
        artifact: root.artifact.clone(),
        version: root.version.clone(),
        ..ClosureInput::default()
    };
    let mut files: Vec<(String, Vec<u8>)> = Vec::new();
    for _ in 0..MAX_ROUNDS {
        let index = serde_json::to_vec(&idx)?;
        let mut inputs: Vec<(String, &[u8])> = files
            .iter()
            .map(|(n, b)| (n.clone(), b.as_slice()))
            .collect();
        inputs.push((INDEX_FILE.into(), index.as_slice()));
        match run_job::<ClosureAnswer>(chain, base, MAVEN_CLOSURE, &inputs)? {
            ClosureAnswer::Done { files, unresolved } => {
                return Ok(super::pom::Closure {
                    files: files
                        .into_iter()
                        .filter_map(|f| {
                            Some(super::pom::ClosureFile {
                                role: super::pom::Role::from_name(&f.role)?,
                                path: f.path,
                                via: f.via,
                            })
                        })
                        .collect(),
                    unresolved,
                })
            }
            ClosureAnswer::Need {
                poms,
                metadata: wanted,
            } => {
                if poms.is_empty() && wanted.is_empty() {
                    anyhow::bail!(crate::jobs::JobPermanent(
                        "the maven-closure job needs nothing and answers no closure".into()
                    ));
                }
                for p in poms {
                    if idx.poms.contains_key(&p) || idx.absent_poms.contains(&p) {
                        anyhow::bail!(crate::jobs::JobPermanent(format!(
                            "the maven-closure job asks again for {p}"
                        )));
                    }
                    match pom(&p)? {
                        Some(b) => {
                            let f = format!("p-{:06}", files.len());
                            files.push((f.clone(), b));
                            idx.poms.insert(p, f);
                        }
                        None => {
                            idx.absent_poms.insert(p);
                        }
                    }
                }
                for k in wanted {
                    if idx.metadata.contains_key(&k) || idx.absent_metadata.contains(&k) {
                        anyhow::bail!(crate::jobs::JobPermanent(format!(
                            "the maven-closure job asks again for {k}/maven-metadata.xml"
                        )));
                    }
                    match metadata(&k)? {
                        Some(b) => {
                            let f = format!("x-{:06}", files.len());
                            files.push((f.clone(), b));
                            idx.metadata.insert(k, f);
                        }
                        None => {
                            idx.absent_metadata.insert(k);
                        }
                    }
                }
            }
        }
    }
    Ok(super::pom::Closure {
        files: Vec::new(),
        unresolved: vec![(
            root.to_string(),
            format!("the closure took more than {MAX_ROUNDS} rounds; held"),
        )],
    })
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::job_runner::heimdall_core::Verdict;
    use holger_job_runner::wire::{Answer, Dirs, WireJob, JOB_SCHEMA};

    /// Run `program` in-process over `inputs`, as the worker would: the answer and the output.
    fn run(program: &str, inputs: &[(String, Vec<u8>)]) -> (Answer, Option<Vec<u8>>) {
        let dir = tempfile::tempdir().unwrap();
        let (input, out) = (dir.path().join("in"), dir.path().join("out"));
        std::fs::create_dir_all(&input).unwrap();
        std::fs::create_dir_all(&out).unwrap();
        for (name, bytes) in inputs {
            std::fs::write(input.join(name), bytes).unwrap();
        }
        let job = WireJob {
            schema: JOB_SCHEMA.into(),
            job_id: "manifest-1".into(),
            argv: vec![program.into()],
            params: serde_json::json!({}),
            reads: 0,
            answer: None,
        };
        let dirs = Dirs {
            input,
            read: vec![],
            out: out.clone(),
        };
        let a = crate::job_runner::programs::run(&job, &dirs);
        let r = std::fs::read(out.join(RESULT_FILE)).ok();
        (a, r)
    }

    fn pom(g: &str, a: &str, v: &str, body: &str) -> String {
        format!(
            "<project><modelVersion>4.0.0</modelVersion><groupId>{g}</groupId>\
             <artifactId>{a}</artifactId><version>{v}</version>{body}</project>"
        )
    }

    /// The fake repository: `com.example:app:1.0` (parent `com.example:parent:1`, which names
    /// `lib.version`), depending on `com.example:lib:${lib.version}` and on
    /// `com.example:ranged:[1,2)` (a range: the metadata is read).
    fn repo() -> BTreeMap<String, Vec<u8>> {
        let mut m = BTreeMap::new();
        m.insert(
            "com/example/parent/1/parent-1.pom".into(),
            pom(
                "com.example",
                "parent",
                "1",
                "<packaging>pom</packaging><properties><lib.version>2.0</lib.version></properties>",
            )
            .into_bytes(),
        );
        m.insert(
            "com/example/app/1.0/app-1.0.pom".into(),
                            "<project><modelVersion>4.0.0</modelVersion>\
                 <parent><groupId>com.example</groupId><artifactId>parent</artifactId><version>1</version></parent>\
                 <artifactId>app</artifactId><version>1.0</version><dependencies>\
                 <dependency><groupId>com.example</groupId><artifactId>lib</artifactId><version>${lib.version}</version></dependency>\
                 <dependency><groupId>com.example</groupId><artifactId>ranged</artifactId><version>[1,2)</version></dependency>\
                 </dependencies></project>"
            .to_string()
            .into_bytes(),
        );
        m.insert(
            "com/example/lib/2.0/lib-2.0.pom".into(),
            pom("com.example", "lib", "2.0", "").into_bytes(),
        );
        m.insert(
            "com/example/ranged/1.5/ranged-1.5.pom".into(),
            pom("com.example", "ranged", "1.5", "").into_bytes(),
        );
        m.insert(
            "com/example/ranged/maven-metadata.xml".into(),
            b"<metadata><versioning><versions><version>1.0</version><version>1.5</version>\
              <version>2.0</version></versions></versioning></metadata>"
                .to_vec(),
        );
        m
    }

    /// The server's half of the rounds over `repo`: give the job what it named, until it answers.
    fn resolve(repo: &BTreeMap<String, Vec<u8>>) -> (ClosureAnswer, usize) {
        let mut idx = ClosureInput {
            group: "com.example".into(),
            artifact: "app".into(),
            version: "1.0".into(),
            ..ClosureInput::default()
        };
        let mut files: Vec<(String, Vec<u8>)> = Vec::new();
        for round in 1..=MAX_ROUNDS {
            let mut inputs = files.clone();
            inputs.push((INDEX_FILE.into(), serde_json::to_vec(&idx).unwrap()));
            let (a, out) = run(MAVEN_CLOSURE, &inputs);
            assert_eq!(a.verdict, Verdict::Accepted, "round {round}: {a:?}");
            assert_eq!(a.outputs, vec![RESULT_FILE.to_string()]);
            let ans: ClosureAnswer = serde_json::from_slice(&out.unwrap()).unwrap();
            match ans {
                ClosureAnswer::Need { poms, metadata } => {
                    assert!(
                        !poms.is_empty() || !metadata.is_empty(),
                        "a need names something"
                    );
                    for p in poms {
                        match repo.get(&p) {
                            Some(b) => {
                                let f = format!("p-{:06}", files.len());
                                files.push((f.clone(), b.clone()));
                                idx.poms.insert(p, f);
                            }
                            None => {
                                idx.absent_poms.insert(p);
                            }
                        }
                    }
                    for k in metadata {
                        match repo.get(&format!("{k}/maven-metadata.xml")) {
                            Some(b) => {
                                let f = format!("x-{:06}", files.len());
                                files.push((f.clone(), b.clone()));
                                idx.metadata.insert(k, f);
                            }
                            None => {
                                idx.absent_metadata.insert(k);
                            }
                        }
                    }
                }
                done => return (done, round),
            }
        }
        panic!("no closure in {MAX_ROUNDS} rounds");
    }

    /// ★ **A Maven closure is resolved in jobs, round by round**: the job names what it needs,
    /// the server fetches only bytes, and the last round answers the closure (the parent's
    /// property, the range read from the metadata in the job).
    #[test]
    fn a_maven_closure_is_resolved_in_jobs_round_by_round() {
        let (ans, rounds) = resolve(&repo());
        let ClosureAnswer::Done { files, unresolved } = ans else {
            unreachable!()
        };
        assert!(unresolved.is_empty(), "{unresolved:?}");
        let paths: BTreeSet<&str> = files.iter().map(|f| f.path.as_str()).collect();
        for want in [
            "com/example/app/1.0/app-1.0.pom",
            "com/example/parent/1/parent-1.pom",
            "com/example/lib/2.0/lib-2.0.pom",
            "com/example/lib/2.0/lib-2.0.jar",
            "com/example/ranged/1.5/ranged-1.5.pom",
            "com/example/ranged/1.5/ranged-1.5.jar",
        ] {
            assert!(paths.contains(want), "{want} is in the closure: {paths:?}");
        }
        assert!(
            files
                .iter()
                .any(|f| f.path.ends_with("parent-1.pom") && f.role == "parent"),
            "{files:?}"
        );
        assert!(rounds <= 6, "{rounds} rounds");
    }

    /// ★ **A property bomb is held in the job** (D1): the server never expands it, and the job's
    /// answer names the version unresolved.
    #[test]
    fn a_property_bomb_is_held_in_the_job() {
        let mut repo = repo();
        let a = "${a}".repeat(100);
        repo.insert(
            "com/example/app/1.0/app-1.0.pom".into(),
            pom(
                "com.example",
                "app",
                "1.0",
                &format!(
                    "<properties><a>{a}</a></properties><dependencies><dependency>\
                     <groupId>com.example</groupId><artifactId>lib</artifactId>\
                     <version>${{a}}</version></dependency></dependencies>"
                ),
            )
            .into_bytes(),
        );
        let (ans, _) = resolve(&repo);
        let ClosureAnswer::Done { unresolved, .. } = ans else {
            unreachable!()
        };
        assert!(!unresolved.is_empty(), "the bomb holds the set");
    }

    /// ★ **A `maven-metadata.xml` is read in a job**: the newest release and a group's plugins.
    #[test]
    fn maven_metadata_is_read_in_a_job() {
        let xml = b"<metadata><versioning><release>3.1</release><versions><version>3.0</version>\
                    <version>3.1</version></versions></versioning><plugins><plugin><name>Dep</name>\
                    <prefix>dependency</prefix><artifactId>maven-dependency-plugin</artifactId>\
                    </plugin></plugins></metadata>";
        let (a, out) = run(MAVEN_METADATA, &[(METADATA_XML.into(), xml.to_vec())]);
        assert_eq!(a.verdict, Verdict::Accepted, "{a:?}");
        let m: MetadataAnswer = serde_json::from_slice(&out.unwrap()).unwrap();
        assert_eq!(m.release.as_deref(), Some("3.1"));
        assert_eq!(
            m.plugins,
            vec![(
                "Dep".to_string(),
                "dependency".to_string(),
                "maven-dependency-plugin".to_string()
            )]
        );
    }

    /// ★ **A wheel's core metadata is read in a job**: from PEP 658's document, or from the
    /// wheel itself; only the requirements whose marker holds come back.
    #[test]
    fn wheel_requires_are_read_in_a_job() {
        let meta = "Metadata-Version: 2.1\nName: app\nVersion: 1.0\nRequires-Dist: dep<2,>=1.0\n\
                    Requires-Dist: winonly; sys_platform == \"win32\"\n\
                    Requires-Dist: extra-dep; extra == \"x\"\n\nRequires-Dist: body\n";
        let want = vec![("dep".to_string(), "<2,>=1.0".to_string())];
        let (a, out) = run(WHEEL_REQUIRES, &[(METADATA_FILE.into(), meta.into())]);
        assert_eq!(a.verdict, Verdict::Accepted, "{a:?}");
        let w: WheelAnswer = serde_json::from_slice(&out.unwrap()).unwrap();
        assert!(w.found);
        assert_eq!(w.requires, want);
        let wheel = zip_entry::fixture::zip(&[
            ("app-1.0.dist-info/METADATA", meta.as_bytes()),
            ("app/__init__.py", b"\n"),
        ]);
        let (a, out) = run(WHEEL_REQUIRES, &[(WHEEL_FILE.into(), wheel)]);
        assert_eq!(a.verdict, Verdict::Accepted, "{a:?}");
        let w: WheelAnswer = serde_json::from_slice(&out.unwrap()).unwrap();
        assert_eq!((w.found, w.requires), (true, want));
        let none = zip_entry::fixture::zip(&[("app/__init__.py", b"\n")]);
        let (_, out) = run(WHEEL_REQUIRES, &[(WHEEL_FILE.into(), none)]);
        let w: WheelAnswer = serde_json::from_slice(&out.unwrap()).unwrap();
        assert!(!w.found);
    }
}