spec_driven_docs/gates/
ki_retire_when.rs1use crate::domain::finding::Finding;
12use crate::domain::rule_id::RuleId;
13use crate::gates::ki_record::{STATES, axis};
14use crate::gates::paths::ki_records_judged;
15use crate::gates::{GateCtx, GateResult, Violation, front_matter_values, read_text};
16
17pub const CITES: &[RuleId] = &[RuleId::RecordCarriesItsRetirementCondition];
19
20const RULE: RuleId = RuleId::RecordCarriesItsRetirementCondition;
21
22pub fn run(ctx: &GateCtx, args: &[String]) -> GateResult {
28 let mut violations = Vec::new();
29 for record in ki_records_judged(ctx, args)? {
30 let text = read_text(ctx, &record)?;
31 let Some(state) = axis(&text, "state", &STATES) else {
35 continue;
36 };
37 let carries = front_matter_values(&text, "retire_when")
38 .iter()
39 .any(|value| !value.is_empty());
40 let detail = match (state.as_str(), carries) {
41 ("masked", false) => "a masked record states no retire_when:",
42 ("mitigated", true) => "a mitigated record states a retire_when:",
43 _ => continue,
44 };
45 violations.push(Violation::Finding(Finding::on_file(RULE, &record, detail)));
46 }
47 Ok(violations)
48}
49
50#[cfg(test)]
51mod tests {
52 use super::*;
53 use crate::gates::tests_support::ki_fixture_state;
54
55 fn run_on(state: &str, retire_line: &str) -> Vec<String> {
56 let dir = ki_fixture_state(state, retire_line);
57 let ctx = GateCtx::new(dir.path().to_str().unwrap());
58 run(&ctx, &[])
59 .unwrap()
60 .iter()
61 .map(ToString::to_string)
62 .collect()
63 }
64
65 #[test]
66 fn accepts_a_masked_record_with_a_condition() {
67 assert!(run_on("masked", "retire_when: release >= 2.0\n").is_empty());
68 }
69
70 #[test]
71 fn accepts_a_mitigated_record_without_one() {
72 assert!(run_on("mitigated", "").is_empty());
73 }
74
75 #[test]
76 fn rejects_a_masked_record_with_a_missing_or_empty_condition() {
77 for retire_line in ["retire_when:\n", ""] {
78 let out = run_on("masked", retire_line);
79 assert_eq!(out.len(), 1, "retire line {retire_line:?}");
80 assert!(
81 out[0].starts_with("FAIL known-issues:a-record-carries-its-retirement-condition ")
82 );
83 assert!(out[0].ends_with(": a masked record states no retire_when:"));
84 }
85 }
86
87 #[test]
88 fn rejects_a_mitigated_record_carrying_a_condition() {
89 let out = run_on("mitigated", "retire_when: release >= 2.0\n");
90 assert_eq!(out.len(), 1);
91 assert!(out[0].ends_with(": a mitigated record states a retire_when:"));
92 }
93
94 #[test]
95 fn leaves_an_unjudgeable_state_to_the_state_gate() {
96 assert!(run_on("closed", "").is_empty());
97 }
98}