pushkin-core 0.2.1

Core envelope, manifest, pipeline, and waiver types for the pushkin write-gate
Documentation
//! The ignored-test accounting for `pushkin floor` (spec §8.2 stage 5) — pure
//! functions over captured command output, so `pushkin-core` stays free of I/O
//! and the arithmetic is testable without running cargo inside cargo.
//!
//! **Why this exists.** `scripts/floor.sh` was written because a cited floor
//! number silently omitted the two `#[ignore]`d latency benchmarks, so *"every
//! floor figure in this program's commit history counted less than it claimed,
//! and one of the omitted gates was red at the time"* (F62). The repair was not
//! "remember the bench" — it was that **completeness must not depend on anyone
//! remembering.** This module is that repair expressed as a function the verb
//! calls on every run.
//!
//! The rule: a command declaring `reconcile_ignored` reports some number of
//! ignored tests, and those tests must be executed by a later command declaring
//! `covers_ignored_of = <that command's name>`. The coverer's tests-RUN count
//! must EQUAL the ignored count — not merely be nonzero, and not merely exceed
//! it. Under-coverage means something is `#[ignore]`d and never run;
//! over-coverage means the declared link no longer describes what runs. Both
//! stop the accounting from being evidence, so both are red.

/// Cargo-test-shaped counts summed across every `test result:` line in one
/// command's output.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Tally {
    pub passed: u64,
    pub failed: u64,
    pub ignored: u64,
}

/// Sums every `test result:` line in `output`.
///
/// Output carrying no such line tallies to all-zero rather than to anything
/// resembling a clean run — a command that is not cargo-test-shaped has not
/// accounted for a single test, and must not look like it has.
///
/// Fields are read by the NAME that follows them (`12 passed`, `2 ignored`),
/// not by position, so the `measured` and `filtered out` columns cannot be
/// mis-attributed by a parser that drifts one field.
#[must_use]
pub fn tally(output: &str) -> Tally {
    let mut total = Tally::default();
    for line in output.lines() {
        let Some(rest) = line.trim_start().strip_prefix("test result:") else {
            continue;
        };
        let fields: Vec<&str> = rest.split_whitespace().collect();
        for pair in fields.windows(2) {
            let (Ok(count), label) = (pair[0].parse::<u64>(), pair[1].trim_end_matches(';')) else {
                continue;
            };
            match label {
                "passed" => total.passed += count,
                "failed" => total.failed += count,
                "ignored" => total.ignored += count,
                _ => {}
            }
        }
    }
    total
}

/// The verdict on one `reconcile_ignored` command's ignored tests.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IgnoredVerdict {
    /// Every ignored test was executed by the declared coverer (or there were
    /// none to account for).
    Accounted { ignored: u64 },
    /// Tests were ignored and no command declared itself their coverer. This is
    /// the F62 shape exactly.
    Uncovered { ignored: u64 },
    /// A coverer ran, but not the same number of tests that were ignored.
    Mismatch { ignored: u64, covered: u64 },
}

impl IgnoredVerdict {
    #[must_use]
    pub fn is_red(self) -> bool {
        !matches!(self, IgnoredVerdict::Accounted { .. })
    }

    /// How many tests the graded command reported as ignored.
    #[must_use]
    pub fn ignored_count(self) -> u64 {
        match self {
            IgnoredVerdict::Accounted { ignored }
            | IgnoredVerdict::Uncovered { ignored }
            | IgnoredVerdict::Mismatch { ignored, .. } => ignored,
        }
    }

    /// Operator-facing prose. The uncovered arm deliberately reuses
    /// `scripts/floor.sh`'s own wording — someone who has met that message
    /// before should recognise it here.
    #[must_use]
    pub fn message(self) -> String {
        match self {
            IgnoredVerdict::Accounted { ignored } => {
                format!(
                    "{ignored} ignored, all of them run by the declared coverer. Accounted for."
                )
            }
            IgnoredVerdict::Uncovered { ignored } => format!(
                "!! {ignored} ignored tests, and no command declares \
                 covers_ignored_of for them. Something is #[ignore]d and NEVER \
                 RUN. This is the exact defect the accounting exists to \
                 prevent — find it before citing this floor."
            ),
            IgnoredVerdict::Mismatch { ignored, covered } => format!(
                "!! {ignored} ignored tests but the declared coverer ran \
                 {covered}. The declared link no longer describes what runs, so \
                 this floor is not evidence — reconcile it before citing it."
            ),
        }
    }
}

/// Grades one command's ignored count against what its declared coverer ran.
///
/// `covered` is `None` when no command declared `covers_ignored_of` for it.
/// Zero ignored needs no coverer; anything else does, and the counts must match
/// exactly.
#[must_use]
pub fn reconcile(ignored: u64, covered: Option<u64>) -> IgnoredVerdict {
    match covered {
        _ if ignored == 0 => IgnoredVerdict::Accounted { ignored: 0 },
        None => IgnoredVerdict::Uncovered { ignored },
        Some(covered) if covered == ignored => IgnoredVerdict::Accounted { ignored },
        Some(covered) => IgnoredVerdict::Mismatch { ignored, covered },
    }
}