use crate::receipt::Receipt;
use crate::selected::SelectedList;
use crate::tick::{Check, Ground};
#[derive(Debug, Clone, PartialEq)]
pub enum Verdict {
Green,
Red,
GrayRed,
Unproven,
NotRun { missing_platforms: Vec<String> },
Stale { reason: String },
SilentlyUnbound,
Exempt, NotApplicable, }
impl Verdict {
pub fn label(&self) -> &'static str {
match self {
Verdict::Green => "green",
Verdict::Red => "red",
Verdict::GrayRed => "gray->red",
Verdict::Unproven => "unproven",
Verdict::NotRun { .. } => "not-run",
Verdict::Stale { .. } => "stale",
Verdict::SilentlyUnbound => "silently-unbound",
Verdict::Exempt => "exempt",
Verdict::NotApplicable => "n/a",
}
}
}
use time::{format_description::well_known::Rfc3339, OffsetDateTime};
pub struct Ctx {
pub live_origin_sha: Option<String>, pub selected: Option<SelectedList>, pub now_unix: i64, pub staleness_secs: i64, pub attest: Option<Vec<String>>, }
pub fn verdict_for(
ground: &Ground,
receipts: &[Receipt],
ctx: &Ctx,
triggered_since: bool,
) -> Verdict {
let (reference, verified_at_sha, liveness) = match &ground.check {
Some(Check::Test {
reference,
verified_at_sha,
liveness,
..
}) => (reference.as_str(), verified_at_sha.as_str(), liveness),
_ => return Verdict::NotApplicable,
};
if let Some(origin) = ctx.live_origin_sha.as_deref() {
if origin != verified_at_sha {
return Verdict::Stale {
reason: "verified_at_sha behind live origin".into(),
};
}
}
let attested: Vec<&String> = match &ctx.attest {
Some(set) => liveness
.platforms
.iter()
.filter(|p| set.contains(p))
.collect(),
None => liveness.platforms.iter().collect(),
};
if ctx.attest.is_some() && attested.is_empty() {
return Verdict::Exempt; }
let mut missing = Vec::new();
let mut deciding: Vec<&Receipt> = Vec::new();
for p in attested {
let latest = receipts
.iter()
.filter(|r| r.test == reference && &r.platform == p)
.max_by(|a, b| a.ran_at.cmp(&b.ran_at));
match latest {
None => missing.push(p.clone()),
Some(r) => deciding.push(r),
}
}
if !missing.is_empty() {
return Verdict::NotRun {
missing_platforms: missing,
};
}
if triggered_since {
return Verdict::Stale {
reason: "a triggering change landed after the last run".into(),
};
}
let stale_by_age = deciding.iter().any(|r| {
OffsetDateTime::parse(&r.ran_at, &Rfc3339)
.map(|dt| ctx.now_unix - dt.unix_timestamp() > ctx.staleness_secs)
.unwrap_or(false)
});
if stale_by_age {
return Verdict::Stale {
reason: "deciding receipt older than the staleness window".into(),
};
}
if deciding.iter().any(|r| r.falsifiable == Some(false)) {
return Verdict::Unproven;
}
if deciding.iter().any(|r| r.result == "gray") {
return Verdict::GrayRed;
}
if deciding.iter().any(|r| r.result == "red") {
return Verdict::Red;
}
if let Some(sl) = ctx.selected.as_ref() {
let touched = liveness
.triggered_by
.iter()
.any(|t| sl.changed.iter().any(|c| c == t));
let was_selected = sl.selected.iter().any(|s| s == reference);
if touched && !was_selected {
return Verdict::SilentlyUnbound;
}
}
Verdict::Green
}
#[cfg(test)]
mod tests {
use super::*;
use crate::receipt::Receipt;
use crate::tick::{Check, Ground, Liveness};
fn test_ground(platforms: &[&str]) -> Ground {
Ground {
claim: "no Redis".into(),
supports: "chosen".into(),
check: Some(Check::Test {
reference: "pytest x".into(),
verified_at_sha: "d308afac1b2c3d4e5f60718293a4b5c6d7e8f901".into(),
counter_test: "pytest x::flips".into(),
liveness: Liveness {
platforms: platforms.iter().map(|s| s.to_string()).collect(),
triggered_by: vec!["pyproject.toml".into()],
surfaces: vec!["pyproject-deps".into()],
},
}),
}
}
fn rcpt(platform: &str, ran_at: &str, result: &str) -> Receipt {
Receipt {
test: "pytest x".into(),
platform: platform.into(),
commit: "d308afac1b2c3d4e5f60718293a4b5c6d7e8f901".into(),
ran_at: ran_at.into(),
result: result.into(),
falsifiable: None,
}
}
fn ctx(live_origin_sha: Option<&str>, selected: Option<SelectedList>) -> Ctx {
Ctx {
live_origin_sha: live_origin_sha.map(|s| s.to_string()),
selected,
now_unix: 0,
staleness_secs: i64::MAX,
attest: None,
}
}
fn ctx_attest(attest: Option<&[&str]>) -> Ctx {
Ctx {
live_origin_sha: None,
selected: None,
now_unix: 0,
staleness_secs: i64::MAX,
attest: attest.map(|a| a.iter().map(|s| s.to_string()).collect()),
}
}
#[test]
fn verdict_for_should_be_not_applicable_when_the_ground_has_a_person_check() {
let g = Ground {
claim: "c".into(),
supports: "chosen".into(),
check: Some(Check::Person {
reference: "Q3".into(),
}),
};
let v = verdict_for(&g, &[], &ctx(None, None), false);
assert_eq!(v, Verdict::NotApplicable);
}
#[test]
fn verdict_for_should_be_not_run_when_a_declared_platform_has_no_receipt() {
let g = test_ground(&["linux-ci", "mac"]);
let receipts = vec![rcpt("linux-ci", "2026-01-01T00:00:00Z", "green")];
let v = verdict_for(&g, &receipts, &ctx(None, None), false);
assert_eq!(
v,
Verdict::NotRun {
missing_platforms: vec!["mac".into()]
}
);
}
#[test]
fn verdict_for_should_promote_gray_to_red_when_the_deciding_receipt_is_gray() {
let g = test_ground(&["linux-ci"]);
let receipts = vec![rcpt("linux-ci", "2026-01-01T00:00:00Z", "gray")];
let v = verdict_for(&g, &receipts, &ctx(None, None), false);
assert_eq!(v, Verdict::GrayRed);
}
#[test]
fn verdict_for_should_be_red_when_the_latest_receipt_is_red() {
let g = test_ground(&["linux-ci"]);
let receipts = vec![
rcpt("linux-ci", "2026-01-01T00:00:00Z", "green"),
rcpt("linux-ci", "2026-02-01T00:00:00Z", "red"),
];
let v = verdict_for(&g, &receipts, &ctx(None, None), false);
assert_eq!(v, Verdict::Red);
}
#[test]
fn verdict_for_should_be_green_when_every_platform_has_a_fresh_green_receipt() {
let g = test_ground(&["linux-ci", "mac"]);
let receipts = vec![
rcpt("linux-ci", "2026-01-01T00:00:00Z", "green"),
rcpt("mac", "2026-01-01T00:00:00Z", "green"),
];
let v = verdict_for(&g, &receipts, &ctx(None, None), false);
assert_eq!(v, Verdict::Green);
}
#[test]
fn verdict_for_should_be_stale_when_verified_at_sha_is_behind_the_live_origin() {
let g = test_ground(&["linux-ci"]);
let receipts = vec![rcpt("linux-ci", "2026-01-01T00:00:00Z", "green")];
let origin = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
let v = verdict_for(&g, &receipts, &ctx(Some(origin), None), false);
assert!(matches!(v, Verdict::Stale { .. }));
}
#[test]
fn verdict_for_should_be_silently_unbound_when_a_touched_trigger_was_not_selected() {
let g = test_ground(&["linux-ci"]);
let receipts = vec![rcpt("linux-ci", "2026-01-01T00:00:00Z", "green")];
let sl = crate::selected::SelectedList {
commit: "d308afac1b2c3d4e5f60718293a4b5c6d7e8f901".into(),
changed: vec!["pyproject.toml".into()],
selected: vec![],
};
let v = verdict_for(&g, &receipts, &ctx(None, Some(sl)), false);
assert_eq!(v, Verdict::SilentlyUnbound);
}
#[test]
fn verdict_for_should_be_green_when_the_touched_trigger_was_selected() {
let g = test_ground(&["linux-ci"]);
let receipts = vec![rcpt("linux-ci", "2026-01-01T00:00:00Z", "green")];
let sl = crate::selected::SelectedList {
commit: "d308afac1b2c3d4e5f60718293a4b5c6d7e8f901".into(),
changed: vec!["pyproject.toml".into()],
selected: vec!["pytest x".into()],
};
let v = verdict_for(&g, &receipts, &ctx(None, Some(sl)), false);
assert_eq!(v, Verdict::Green);
}
#[test]
fn verdict_for_should_be_stale_when_the_deciding_receipt_is_older_than_the_window() {
let g = test_ground(&["linux-ci"]);
let receipts = vec![rcpt("linux-ci", "2026-01-01T00:00:00Z", "green")];
let c = Ctx {
live_origin_sha: None,
selected: None,
now_unix: OffsetDateTime::parse("2026-06-01T00:00:00Z", &Rfc3339)
.unwrap()
.unix_timestamp(),
staleness_secs: 7 * 86_400,
attest: None,
};
let v = verdict_for(&g, &receipts, &c, false);
assert!(matches!(v, Verdict::Stale { .. }));
}
#[test]
fn verdict_for_should_be_green_when_the_deciding_receipt_is_within_the_window() {
let g = test_ground(&["linux-ci"]);
let receipts = vec![rcpt("linux-ci", "2026-06-01T00:00:00Z", "green")];
let c = Ctx {
live_origin_sha: None,
selected: None,
now_unix: OffsetDateTime::parse("2026-06-01T01:00:00Z", &Rfc3339)
.unwrap()
.unix_timestamp(),
staleness_secs: 7 * 86_400,
attest: None,
};
let v = verdict_for(&g, &receipts, &c, false);
assert_eq!(v, Verdict::Green);
}
#[test]
fn verdict_for_should_be_stale_when_a_triggering_change_landed_after_the_last_run() {
let g = test_ground(&["linux-ci"]);
let receipts = vec![rcpt("linux-ci", "2026-01-01T00:00:00Z", "green")];
let v = verdict_for(&g, &receipts, &ctx(None, None), true);
assert!(matches!(v, Verdict::Stale { .. }));
}
#[test]
fn verdict_for_should_ignore_triggered_since_when_a_platform_is_already_not_run() {
let g = test_ground(&["linux-ci", "mac"]);
let receipts = vec![rcpt("linux-ci", "2026-01-01T00:00:00Z", "green")];
let v = verdict_for(&g, &receipts, &ctx(None, None), true);
assert_eq!(
v,
Verdict::NotRun {
missing_platforms: vec!["mac".into()]
}
);
}
#[test]
fn verdict_for_should_exempt_a_binding_whose_platforms_this_runner_does_not_attest() {
let g = test_ground(&["mac"]);
assert_eq!(
verdict_for(&g, &[], &ctx_attest(Some(&["linux-ci"])), false),
Verdict::Exempt
);
}
#[test]
fn verdict_for_should_ignore_an_unattested_platform_when_an_attested_one_is_green() {
let g = test_ground(&["linux-ci", "mac"]);
let receipts = vec![rcpt("linux-ci", "2026-01-01T00:00:00Z", "green")];
assert_eq!(
verdict_for(&g, &receipts, &ctx_attest(Some(&["linux-ci"])), false),
Verdict::Green
);
}
#[test]
fn verdict_for_should_be_unproven_when_a_deciding_receipt_is_not_falsifiable() {
let g = test_ground(&["linux-ci"]);
let receipts = vec![Receipt {
test: "pytest x".into(),
platform: "linux-ci".into(),
commit: "d308afac1b2c3d4e5f60718293a4b5c6d7e8f901".into(),
ran_at: "2026-01-01T00:00:00Z".into(),
result: "green".into(),
falsifiable: Some(false),
}];
let v = verdict_for(&g, &receipts, &ctx(None, None), false);
assert_eq!(v, Verdict::Unproven);
}
#[test]
fn verdict_for_should_be_green_when_a_deciding_receipt_is_proven_falsifiable() {
let g = test_ground(&["linux-ci"]);
let receipts = vec![Receipt {
test: "pytest x".into(),
platform: "linux-ci".into(),
commit: "d308afac1b2c3d4e5f60718293a4b5c6d7e8f901".into(),
ran_at: "2026-01-01T00:00:00Z".into(),
result: "green".into(),
falsifiable: Some(true),
}];
let v = verdict_for(&g, &receipts, &ctx(None, None), false);
assert_eq!(v, Verdict::Green);
}
}