Skip to main content

spec_driven_docs/gates/
ki_retire_when.rs

1//! Gate: every known-issue record carries the condition under which it is
2//! removed.
3//!
4//! A record whose workaround has no exit becomes permanent by default, and
5//! the next reader takes it for a design choice. The key must carry a value:
6//! an empty `retire_when:` states no condition. Whether the condition is a
7//! good one is review's business.
8
9use crate::domain::finding::Finding;
10use crate::domain::rule_id::RuleId;
11use crate::gates::paths::ki_records;
12use crate::gates::{GateCtx, GateResult, Violation, read_text};
13
14/// The rules this gate can cite.
15pub const CITES: &[RuleId] = &[RuleId::RecordCarriesItsRetirementCondition];
16
17/// Judge every known-issue record under the resolved roots.
18///
19/// # Errors
20///
21/// [`crate::gates::GateError::Io`] when a record cannot be read.
22pub fn run(ctx: &GateCtx, args: &[String]) -> GateResult {
23    let mut bad = String::new();
24    for record in ki_records(ctx, args) {
25        let carries = read_text(ctx, &record)?.lines().any(|line| {
26            line.strip_prefix("retire_when:")
27                .is_some_and(|value| !value.trim().is_empty())
28        });
29        if !carries {
30            bad.push(' ');
31            bad.push_str(record.as_str());
32        }
33    }
34    if bad.is_empty() {
35        Ok(vec![])
36    } else {
37        Ok(vec![Violation::Finding(Finding::global(
38            RuleId::RecordCarriesItsRetirementCondition,
39            bad.trim_start().to_string(),
40        ))])
41    }
42}
43
44#[cfg(test)]
45mod tests {
46    use super::*;
47    use crate::gates::tests_support::ki_fixture;
48
49    #[test]
50    fn accepts_a_record_with_a_condition() {
51        let dir = ki_fixture("retire_when: release >= 2.0\n");
52        let ctx = GateCtx::new(dir.path().to_str().unwrap());
53        assert!(run(&ctx, &[]).unwrap().is_empty());
54    }
55
56    #[test]
57    fn rejects_a_missing_or_empty_condition() {
58        for frontmatter in ["retire_when:\n", ""] {
59            let dir = ki_fixture(frontmatter);
60            let ctx = GateCtx::new(dir.path().to_str().unwrap());
61            let out: Vec<String> = run(&ctx, &[])
62                .unwrap()
63                .iter()
64                .map(ToString::to_string)
65                .collect();
66            assert_eq!(out.len(), 1, "frontmatter {frontmatter:?}");
67            assert!(
68                out[0].starts_with("FAIL known-issues:a-record-carries-its-retirement-condition: ")
69            );
70            assert!(out[0].contains("KI-vendor.md"));
71        }
72    }
73}