Skip to main content

rto_graph/
compile_claim.rs

1//! When a green check refutes "this will not compile" — and when it does not
2//! (Stage 35).
3//!
4//! # The measurement this exists to spend
5//!
6//! On the adjudicated corpus ([`crate::review_corpus`]) *every* false positive was
7//! a claim that the code would not build, and *every* claim that the code would not
8//! build was a false positive — with no real defect anywhere in the class. That is
9//! the filter's whole licence, so it is **asserted against the data** rather than
10//! recorded here as a number that could go stale: see
11//! `the_compile_claim_class_is_still_the_only_false_one_and_wholly_false` in
12//! `tests/review_corpus.rs`, which fails the build if a real defect ever joins the
13//! class.
14//!
15//! CI already computed the refutation each time: the `msrv` job had gone **green at
16//! the very commit the comment was left on**, by 65 seconds on one and 83 on
17//! another. So withholding such a claim while the relevant check is green costs no
18//! extra compute and, on this evidence, discards nothing true. Every investigation
19//! those comments triggered was avoidable by reading a status that already existed.
20//!
21//! # Why "the build is green" is the wrong rule
22//!
23//! `docs/REVIEW_CHECKLIST.md` records the trap, and it is not hypothetical: this
24//! repository has already shipped a defect that a green build was structurally
25//! blind to. The `GGML_ASSERT` engine-teardown abort of #291 was **macOS-only**,
26//! and every compiling job here runs on `ubuntu-latest`. A filter keyed on "was
27//! the build green" would have suppressed a report of it.
28//!
29//! So a check refutes a claim only when it **ran at that commit** and **covered
30//! the configuration the claim is about**. Three axes decide coverage, each one a
31//! way this project's CI is narrower than "the build":
32//!
33//! - **Platform** — every job is `ubuntu-latest`, so nothing here compiles
34//!   `cfg(target_os = "macos")` code, of which this repo has a good deal (Metal,
35//!   the engine teardown path, the sandbox backend).
36//! - **Features** — `msrv` and `checks` are `--all-features`; `default-features`
37//!   is the default set. Neither covers the other: turning features *on* cannot
38//!   find a defect in code being cfg'd *out*, which is exactly why the
39//!   `default-features` job exists.
40//! - **Targets** — `msrv` is `cargo check --workspace --all-features`, with **no
41//!   `--all-targets`**. It therefore never compiles `#[cfg(test)]` modules or
42//!   `tests/` integration targets. A claim that *test* code will not build on the
43//!   MSRV toolchain is refuted by no job in this repository: the jobs that compile
44//!   test targets do so on `stable`. That gap falls out of the model here rather
45//!   than being asserted, and [`Suppression::Unrefuted`] reports it.
46//!
47//! Deliberately conservative on every axis: an unknown site is never refuted, and
48//! coverage is exact match rather than subsumption, because the cost of the two
49//! errors is not symmetric. A claim wrongly suppressed is a defect shipped
50//! silently — the #291 shape. A claim wrongly kept costs a human one look at a CI
51//! page.
52//!
53//! # Deciding, not fetching
54//!
55//! Everything here is a pure function of evidence a caller supplies. This crate
56//! cannot reach the network (its `gix` is pinned without transports), and a
57//! suppression rule is precisely the code that would otherwise acquire a "just ask
58//! the API" call. Whoever holds a GitHub token turns check runs into
59//! [`CheckRun`]s; the policy lives here where it can be tested exhaustively and
60//! offline.
61
62use std::collections::BTreeSet;
63
64use serde::{Deserialize, Serialize};
65
66/// The platform a compilation covers, or that a code site requires.
67#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
68#[serde(rename_all = "kebab-case")]
69pub enum TargetOs {
70    /// Linux — every compiling job in this repository's CI.
71    Linux,
72    /// macOS. Nothing in CI compiles it; see the module docs.
73    MacOs,
74    /// Windows.
75    Windows,
76}
77
78/// Which Cargo feature set a compilation used, or that a code site needs to be
79/// compiled at all.
80///
81/// Not ordered by "more features": `--all-features` does not subsume the default
82/// set, because code behind `cfg(not(feature = …))` is compiled by exactly one of
83/// them. The `default-features` CI job exists because three `-D warnings` errors
84/// had rotted in code that `--all-features` structurally cannot see.
85#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
86#[serde(rename_all = "kebab-case")]
87pub enum Features {
88    /// The crate's default feature set.
89    Default,
90    /// `--all-features`.
91    All,
92    /// `--no-default-features`.
93    None,
94}
95
96/// Which targets a compilation built.
97#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
98#[serde(rename_all = "kebab-case")]
99pub enum Targets {
100    /// Libraries and binaries only — `cargo check` with no `--all-targets`. Does
101    /// **not** compile `#[cfg(test)]` modules or `tests/` integration targets.
102    LibsAndBins,
103    /// `--all-targets`: tests, benches and examples too.
104    AllTargets,
105}
106
107impl Targets {
108    /// Whether this scope compiles test code.
109    #[must_use]
110    pub fn compiles_tests(self) -> bool {
111        self == Self::AllTargets
112    }
113}
114
115/// How a check run finished. Only [`Conclusion::Success`] can refute anything; the
116/// rest are spelled out so that "no check run at all" and "a check run that
117/// failed" cannot be confused for each other by a caller mapping an API response.
118#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
119#[serde(rename_all = "kebab-case")]
120pub enum Conclusion {
121    /// Green.
122    Success,
123    /// Red.
124    Failure,
125    /// Cancelled, timed out, skipped, or still running — anything that is not a
126    /// statement about whether the code compiles.
127    Inconclusive,
128}
129
130/// One compiling CI job, as it ran.
131#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
132pub struct CheckRun {
133    /// Job name, for the message a suppression prints (`msrv`, `checks`, …). A
134    /// human told *which* job refutes the claim can go and look; a human told
135    /// "CI is green" cannot.
136    pub job: String,
137    /// The commit this job ran on. Compared for equality with the claim's
138    /// `reviewed_sha`: a green run on a *later* commit says nothing about the tree
139    /// the reviewer saw.
140    pub sha: String,
141    /// How it finished.
142    pub conclusion: Conclusion,
143    /// The toolchain it used (`1.94`, `stable`), verbatim.
144    pub toolchain: String,
145    /// The platform it ran on.
146    pub platform: TargetOs,
147    /// The feature set it compiled.
148    pub features: Features,
149    /// The targets it compiled.
150    pub targets: Targets,
151}
152
153/// The code a compile claim is about, in the terms that decide whether a job
154/// compiled it.
155///
156/// Every field is a *requirement*, and `None` means "not established". An
157/// unestablished requirement is never satisfied, so a claim whose site is unknown
158/// is never suppressed — the default is to let the human look.
159#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
160pub struct ClaimSite {
161    /// The commit the claim was made against — the corpus's `reviewed_sha`.
162    pub sha: String,
163    /// Path the claim is anchored to, for the suppression message.
164    pub path: String,
165    /// The platform whose `cfg` gates this code, or `None` if it is compiled on
166    /// every platform.
167    ///
168    /// `Some(MacOs)` is the #291 shape: no CI job compiles it, so no CI job can
169    /// refute a claim about it.
170    pub platform: Option<TargetOs>,
171    /// The feature set that compiles this code. `None` means unconditional — any
172    /// feature set compiles it.
173    ///
174    /// `Some(Features::All)` covers a module behind a non-default feature, like
175    /// `rto-exec`'s `#[cfg(feature = "exec-boxlite")] pub mod boxlite`.
176    pub features: Option<Features>,
177    /// Whether the site is test code (`#[cfg(test)]` or a `tests/` target). Test
178    /// code needs a job that passed `--all-targets`.
179    pub is_test_code: bool,
180    /// The toolchain the claim is about, if it names one — a claim of the form
181    /// "this is not on MSRV 1.94" is only refuted by a job that used that
182    /// toolchain, not by a green `stable` build.
183    pub toolchain: Option<String>,
184}
185
186impl ClaimSite {
187    /// A site at `sha`/`path` with nothing else established — the conservative
188    /// default, which no check run refutes.
189    #[must_use]
190    pub fn unknown(sha: impl Into<String>, path: impl Into<String>) -> Self {
191        Self {
192            sha: sha.into(),
193            path: path.into(),
194            ..Self::default()
195        }
196    }
197
198    /// Whether `run` compiled this site's code, ignoring the run's conclusion and
199    /// commit — the coverage half of the decision.
200    #[must_use]
201    pub fn covered_by(&self, run: &CheckRun) -> bool {
202        // Platform: an unconditional site is compiled by every platform; a gated
203        // site only by its own.
204        if self.platform.is_some_and(|p| p != run.platform) {
205            return false;
206        }
207        // Features: exact match, not subsumption. See `Features`.
208        if self.features.is_some_and(|f| f != run.features) {
209            return false;
210        }
211        // Targets: test code needs `--all-targets`.
212        if self.is_test_code && !run.targets.compiles_tests() {
213            return false;
214        }
215        // Toolchain: only a claim that names one constrains this.
216        if self
217            .toolchain
218            .as_deref()
219            .is_some_and(|t| t != run.toolchain)
220        {
221            return false;
222        }
223        true
224    }
225}
226
227/// Whether a compile claim may be withheld, and why — or why not.
228///
229/// The negative variants carry their reason because that is the actionable half:
230/// "unrefuted, because no green job compiled `cfg(target_os = "macos")` code at
231/// that commit" tells a reviewer it owes the claim a real look, which is exactly
232/// what the #291 teardown abort needed and did not get.
233#[derive(Debug, Clone, PartialEq, Eq)]
234pub enum Suppression {
235    /// A green job compiled this code at this commit. Withhold the claim.
236    Refuted {
237        /// The job that refutes it.
238        job: String,
239        /// One sentence naming the job, the commit and the configuration.
240        reason: String,
241    },
242    /// No green job covered this configuration at this commit. Keep the claim.
243    Unrefuted {
244        /// Why the evidence falls short.
245        reason: String,
246    },
247}
248
249impl Suppression {
250    /// Whether the claim should be withheld.
251    #[must_use]
252    pub fn is_refuted(&self) -> bool {
253        matches!(self, Self::Refuted { .. })
254    }
255
256    /// The explanation, in either case.
257    #[must_use]
258    pub fn reason(&self) -> &str {
259        match self {
260            Self::Refuted { reason, .. } | Self::Unrefuted { reason } => reason,
261        }
262    }
263}
264
265/// Decide whether `checks` refute a compile claim about `site`.
266///
267/// Refuted only by a run that is [`Conclusion::Success`], ran at **exactly**
268/// `site.sha`, and covered the site's configuration ([`ClaimSite::covered_by`]).
269/// Among several qualifying runs the first in job-name order is reported, so the
270/// answer does not depend on the order a caller happened to collect them in.
271#[must_use]
272pub fn suppression(site: &ClaimSite, checks: &[CheckRun]) -> Suppression {
273    let at_sha: Vec<&CheckRun> = checks.iter().filter(|c| c.sha == site.sha).collect();
274    if at_sha.is_empty() {
275        return Suppression::Unrefuted {
276            reason: format!(
277                "no check run recorded at {} — a green run on any other commit says \
278                 nothing about the tree the claim was made against",
279                short(&site.sha)
280            ),
281        };
282    }
283
284    let mut green_covering: Vec<&CheckRun> = at_sha
285        .iter()
286        .copied()
287        .filter(|c| c.conclusion == Conclusion::Success && site.covered_by(c))
288        .collect();
289    green_covering.sort_by(|a, b| a.job.cmp(&b.job));
290    if let Some(run) = green_covering.first() {
291        return Suppression::Refuted {
292            job: run.job.clone(),
293            reason: format!(
294                "`{}` was green at {} and compiled {} ({}), so the claim that it \
295                 does not build is already refuted",
296                run.job,
297                short(&site.sha),
298                site.path,
299                configuration(run),
300            ),
301        };
302    }
303
304    // Something ran at this commit, so say which axis fell short — a reviewer
305    // reading "unrefuted" needs to know whether to look at the code or at CI.
306    let covering: Vec<&CheckRun> = at_sha
307        .iter()
308        .copied()
309        .filter(|c| site.covered_by(c))
310        .collect();
311    if covering.is_empty() {
312        return Suppression::Unrefuted {
313            reason: format!(
314                "no check run at {} compiled {} ({}) — {}",
315                short(&site.sha),
316                site.path,
317                requirement(site),
318                "turning features on cannot find a defect in code cfg'd out, and \
319                 no job here compiles another platform's code, so this claim is \
320                 unrefuted and owes a real look",
321            ),
322        };
323    }
324    Suppression::Unrefuted {
325        reason: format!(
326            "the check run(s) covering {} at {} did not conclude green ({}), so \
327             nothing refutes the claim",
328            site.path,
329            short(&site.sha),
330            covering
331                .iter()
332                .map(|c| format!("{}: {:?}", c.job, c.conclusion))
333                .collect::<Vec<_>>()
334                .join(", "),
335        ),
336    }
337}
338
339/// The distinct job names that could ever refute a claim about `site`, given
340/// `checks` — what to tell an operator whose CI does not cover a configuration.
341#[must_use]
342pub fn jobs_covering(site: &ClaimSite, checks: &[CheckRun]) -> BTreeSet<String> {
343    checks
344        .iter()
345        .filter(|c| site.covered_by(c))
346        .map(|c| c.job.clone())
347        .collect()
348}
349
350/// A run's configuration, as one readable phrase.
351fn configuration(run: &CheckRun) -> String {
352    let features = match run.features {
353        Features::Default => "default features",
354        Features::All => "--all-features",
355        Features::None => "--no-default-features",
356    };
357    let targets = match run.targets {
358        Targets::LibsAndBins => "libs and bins",
359        Targets::AllTargets => "--all-targets",
360    };
361    format!(
362        "{:?}, {}, {}, toolchain {}",
363        run.platform, features, targets, run.toolchain
364    )
365}
366
367/// What a site needs compiled, as one readable phrase.
368fn requirement(site: &ClaimSite) -> String {
369    let mut parts = Vec::new();
370    if let Some(p) = site.platform {
371        parts.push(format!("needs {p:?}"));
372    }
373    if let Some(f) = site.features {
374        parts.push(format!("needs {f:?} features"));
375    }
376    if site.is_test_code {
377        parts.push("is test code, so needs --all-targets".to_owned());
378    }
379    if let Some(t) = &site.toolchain {
380        parts.push(format!("the claim names toolchain {t}"));
381    }
382    if parts.is_empty() {
383        "unconditional code".to_owned()
384    } else {
385        parts.join("; ")
386    }
387}
388
389/// Short form of a sha for a message, without assuming it is 40 characters.
390fn short(sha: &str) -> &str {
391    sha.get(..8).unwrap_or(sha)
392}
393
394#[cfg(test)]
395mod tests {
396    use super::{
397        CheckRun, ClaimSite, Conclusion, Features, Suppression, TargetOs, Targets, jobs_covering,
398        suppression,
399    };
400
401    /// This repository's compiling jobs at a commit, as `.github/workflows/ci.yml`
402    /// defines them. Written out because the whole filter turns on their exact
403    /// narrowness: all three are `ubuntu-latest`, only `msrv` is on the MSRV
404    /// toolchain, and only `msrv` omits `--all-targets`.
405    fn ci_at(sha: &str) -> Vec<CheckRun> {
406        vec![
407            CheckRun {
408                job: "msrv".to_owned(),
409                sha: sha.to_owned(),
410                conclusion: Conclusion::Success,
411                toolchain: "1.94".to_owned(),
412                platform: TargetOs::Linux,
413                features: Features::All,
414                targets: Targets::LibsAndBins,
415            },
416            CheckRun {
417                job: "checks".to_owned(),
418                sha: sha.to_owned(),
419                conclusion: Conclusion::Success,
420                toolchain: "stable".to_owned(),
421                platform: TargetOs::Linux,
422                features: Features::All,
423                targets: Targets::AllTargets,
424            },
425            CheckRun {
426                job: "default-features".to_owned(),
427                sha: sha.to_owned(),
428                conclusion: Conclusion::Success,
429                toolchain: "stable".to_owned(),
430                platform: TargetOs::Linux,
431                features: Features::Default,
432                targets: Targets::AllTargets,
433            },
434        ]
435    }
436
437    /// **The four corpus rows this filter is licensed by.** Each is unconditional
438    /// library code — verified at its `reviewed_sha` — except `boxlite.rs`, whose
439    /// module is `#[cfg(feature = "exec-boxlite")]`, so it needs an
440    /// `--all-features` job. None is test code. Every one is refuted, which is the
441    /// measured claim: the filter discards nothing true on this corpus.
442    #[test]
443    fn every_known_false_compile_claim_is_refuted() {
444        let rows = [
445            ("2b761ce7", "crates/rto-llama/src/slot.rs", None),
446            ("5e25f921", "crates/rto-graph/src/media.rs", None),
447            ("add397f2", "crates/roteiro/src/main.rs", None),
448            (
449                "c1481836",
450                "crates/rto-exec/src/boxlite.rs",
451                Some(Features::All),
452            ),
453        ];
454        for (sha, path, features) in rows {
455            let site = ClaimSite {
456                features,
457                ..ClaimSite::unknown(sha, path)
458            };
459            let verdict = suppression(&site, &ci_at(sha));
460            assert!(
461                verdict.is_refuted(),
462                "{path} at {sha} should be refuted: {}",
463                verdict.reason()
464            );
465            assert!(
466                verdict.reason().contains(sha) && verdict.reason().contains(path),
467                "the reason names the commit and the file: {}",
468                verdict.reason()
469            );
470        }
471    }
472
473    /// The #352 claim named a toolchain — "not on MSRV 1.94". Only the `msrv` job
474    /// can refute that; a green `stable` build cannot. The filter must pick the
475    /// right job rather than any green one.
476    #[test]
477    fn a_claim_naming_the_msrv_toolchain_is_refuted_only_by_the_msrv_job() {
478        let sha = "c1481836";
479        let site = ClaimSite {
480            features: Some(Features::All),
481            toolchain: Some("1.94".to_owned()),
482            ..ClaimSite::unknown(sha, "crates/rto-exec/src/boxlite.rs")
483        };
484        let Suppression::Refuted { ref job, .. } = suppression(&site, &ci_at(sha)) else {
485            panic!("the msrv job compiled it");
486        };
487        assert_eq!(job, "msrv");
488
489        // Strip the MSRV job and the same claim stands: the remaining green jobs
490        // are `stable`, which says nothing about 1.94.
491        let stable_only: Vec<CheckRun> =
492            ci_at(sha).into_iter().filter(|c| c.job != "msrv").collect();
493        let verdict = suppression(&site, &stable_only);
494        assert!(!verdict.is_refuted(), "{}", verdict.reason());
495        assert!(
496            verdict.reason().contains("1.94"),
497            "says which toolchain went uncovered: {}",
498            verdict.reason()
499        );
500    }
501
502    /// **The #291 case, and the reason this is not a "green build" check.** The
503    /// `GGML_ASSERT` teardown abort was `cfg(target_os = "macos")`. Every CI job
504    /// here is `ubuntu-latest`, so a wholly green CI must leave a claim about that
505    /// code standing.
506    #[test]
507    fn a_macos_only_site_is_never_refuted_by_ci_here() {
508        let sha = "0123456789abcdef0123456789abcdef01234567";
509        let site = ClaimSite {
510            platform: Some(TargetOs::MacOs),
511            ..ClaimSite::unknown(sha, "crates/rto-llama/src/backend.rs")
512        };
513        let verdict = suppression(&site, &ci_at(sha));
514        assert!(
515            !verdict.is_refuted(),
516            "a green ubuntu CI must not refute macOS-only code: {}",
517            verdict.reason()
518        );
519        assert!(
520            verdict.reason().contains("MacOs"),
521            "names the uncovered platform: {}",
522            verdict.reason()
523        );
524        assert!(
525            jobs_covering(&site, &ci_at(sha)).is_empty(),
526            "no job in this repository compiles macOS code"
527        );
528    }
529
530    /// A `--no-default-features` claim is unrefuted: no job here builds that set,
531    /// and `--all-features` cannot cover it because the two compile different code.
532    #[test]
533    fn a_no_default_features_site_is_unrefuted() {
534        let sha = "abcdefabcdefabcdefabcdefabcdefabcdefabcd";
535        let site = ClaimSite {
536            features: Some(Features::None),
537            ..ClaimSite::unknown(sha, "crates/rto-graph/src/lib.rs")
538        };
539        assert!(!suppression(&site, &ci_at(sha)).is_refuted());
540    }
541
542    /// Turning features *on* cannot find a defect in code being cfg'd *out*: a
543    /// default-set site is not covered by the `--all-features` jobs, only by
544    /// `default-features`.
545    #[test]
546    fn a_default_features_site_is_covered_only_by_the_default_features_job() {
547        let sha = "1111111111111111111111111111111111111111";
548        let site = ClaimSite {
549            features: Some(Features::Default),
550            ..ClaimSite::unknown(sha, "crates/rto-llama/src/lib.rs")
551        };
552        assert_eq!(
553            jobs_covering(&site, &ci_at(sha)),
554            ["default-features".to_owned()].into_iter().collect()
555        );
556    }
557
558    /// **The gap the model exposes.** `msrv` is `cargo check --workspace
559    /// --all-features` with no `--all-targets`, so it never compiles test code;
560    /// the jobs that do compile test code run on `stable`. A claim that test code
561    /// will not build on the MSRV toolchain is therefore refuted by no job in this
562    /// repository, and the filter must say so rather than suppress it.
563    #[test]
564    fn an_msrv_claim_about_test_code_is_refuted_by_no_job_here() {
565        let sha = "2222222222222222222222222222222222222222";
566        let site = ClaimSite {
567            is_test_code: true,
568            toolchain: Some("1.94".to_owned()),
569            ..ClaimSite::unknown(sha, "crates/rto-graph/tests/review_corpus.rs")
570        };
571        let verdict = suppression(&site, &ci_at(sha));
572        assert!(
573            !verdict.is_refuted(),
574            "no job compiles test code on the MSRV toolchain: {}",
575            verdict.reason()
576        );
577        assert!(
578            jobs_covering(&site, &ci_at(sha)).is_empty(),
579            "msrv omits --all-targets; the --all-targets jobs are stable"
580        );
581        // The same site without a toolchain claim *is* covered — by the stable
582        // jobs that pass `--all-targets`. So the gap is specifically MSRV-and-test,
583        // not test code in general.
584        let stable_claim = ClaimSite {
585            toolchain: None,
586            ..site
587        };
588        assert!(suppression(&stable_claim, &ci_at(sha)).is_refuted());
589    }
590
591    /// A green run on a different commit refutes nothing. This is the sibling of
592    /// the corpus's `reviewed_sha` rule: the tree that matters is the one the
593    /// reviewer saw.
594    #[test]
595    fn a_green_run_on_another_commit_refutes_nothing() {
596        let site = ClaimSite::unknown(
597            "3333333333333333333333333333333333333333",
598            "crates/rto-graph/src/lib.rs",
599        );
600        let elsewhere = ci_at("4444444444444444444444444444444444444444");
601        let verdict = suppression(&site, &elsewhere);
602        assert!(!verdict.is_refuted());
603        assert!(
604            verdict.reason().contains("no check run recorded"),
605            "{}",
606            verdict.reason()
607        );
608    }
609
610    /// A failing or still-running job is not a refutation, and the message says
611    /// which it was — "unrefuted" alone would leave a reviewer unsure whether to
612    /// look at the code or wait for CI.
613    #[test]
614    fn a_non_green_conclusion_is_not_a_refutation() {
615        let sha = "5555555555555555555555555555555555555555";
616        for conclusion in [Conclusion::Failure, Conclusion::Inconclusive] {
617            let runs: Vec<CheckRun> = ci_at(sha)
618                .into_iter()
619                .map(|c| CheckRun { conclusion, ..c })
620                .collect();
621            let site = ClaimSite::unknown(sha, "crates/roteiro/src/main.rs");
622            let verdict = suppression(&site, &runs);
623            assert!(!verdict.is_refuted(), "{conclusion:?}");
624            assert!(
625                verdict.reason().contains(&format!("{conclusion:?}")),
626                "names the conclusion: {}",
627                verdict.reason()
628            );
629        }
630    }
631
632    /// With no evidence at all, nothing is suppressed. The filter is opt-in on
633    /// evidence, so a caller that cannot reach CI loses the filter rather than
634    /// gaining a blanket suppression.
635    #[test]
636    fn no_evidence_suppresses_nothing() {
637        let site = ClaimSite::unknown(
638            "6666666666666666666666666666666666666666",
639            "crates/rto-graph/src/lib.rs",
640        );
641        assert!(!suppression(&site, &[]).is_refuted());
642    }
643
644    /// The reported job does not depend on the order the caller collected runs in.
645    #[test]
646    fn the_reported_job_is_order_independent() {
647        let sha = "7777777777777777777777777777777777777777";
648        let site = ClaimSite::unknown(sha, "crates/roteiro/src/main.rs");
649        let mut reversed = ci_at(sha);
650        reversed.reverse();
651        let forward = suppression(&site, &ci_at(sha));
652        let backward = suppression(&site, &reversed);
653        assert_eq!(forward, backward);
654        let Suppression::Refuted { ref job, .. } = forward else {
655            panic!("unconditional code is refuted by a green all-features job");
656        };
657        assert_eq!(job, "checks", "job-name order, not collection order");
658    }
659}