spec_driven_docs/gates/
ki_retire_when.rs1use 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
14pub const CITES: &[RuleId] = &[RuleId::RecordCarriesItsRetirementCondition];
16
17pub 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}