Skip to main content

spec_driven_docs/gates/
ki_checked_date.rs

1//! Gate: a record whose handling depends on an upstream state carries the
2//! date that state was last confirmed, and no other record carries one.
3//!
4//! A masked record states the condition that removes its workaround, and a
5//! monitoring record states the upstream it watches. Neither says when
6//! anyone last looked, so a mask outlives its bug and the next reader takes
7//! the workaround for a design choice. The other states describe handling
8//! this project owns, where there is no upstream observation to date.
9//!
10//! The gate judges presence, shape, and a date that is not in the future.
11//! It never judges age: an old date over an upstream that has not moved is
12//! an accurate record, and failing it teaches people to touch the date
13//! rather than to check the condition. Whether the observation is recent
14//! enough is review's business.
15
16use jiff::civil::Date;
17
18use crate::domain::finding::Finding;
19use crate::domain::rule_id::RuleId;
20use crate::gates::ki_record::{STATES, axis};
21use crate::gates::paths::ki_records_judged;
22use crate::gates::{GateCtx, GateResult, Violation, front_matter_values, read_text};
23use crate::services::tracking::today_utc;
24
25/// The rules this gate can cite.
26pub const CITES: &[RuleId] = &[RuleId::RecordRecordsItsLastCheck];
27
28const RULE: RuleId = RuleId::RecordRecordsItsLastCheck;
29
30/// The states whose handling waits on something outside this project.
31const DATED_STATES: [&str; 2] = ["masked", "monitoring"];
32
33/// The ten-character ISO calendar date, and nothing else.
34///
35/// `jiff` accepts shapes this key does not want, so the accepted set is
36/// stated here rather than delegated.
37fn iso_date(value: &str) -> Option<Date> {
38    let bytes = value.as_bytes();
39    if bytes.len() != 10 || bytes[4] != b'-' || bytes[7] != b'-' {
40        return None;
41    }
42    if !bytes
43        .iter()
44        .enumerate()
45        .all(|(index, byte)| index == 4 || index == 7 || byte.is_ascii_digit())
46    {
47        return None;
48    }
49    value.parse::<Date>().ok()
50}
51
52fn detail(state: &str, values: &[String], as_of: Date) -> Option<String> {
53    let dated = DATED_STATES.contains(&state);
54    match (dated, values) {
55        (false, []) => None,
56        (false, _) => Some(format!("a {state} record states a checked:")),
57        (true, []) => Some(format!("a {state} record states no checked:")),
58        (true, [value]) => match iso_date(value) {
59            None if value.is_empty() => Some(format!("a {state} record states an empty checked:")),
60            None => Some(format!("checked: {value} is not an ISO YYYY-MM-DD date")),
61            Some(date) if date > as_of => Some(format!("checked: {value} is in the future")),
62            Some(_) => None,
63        },
64        (true, many) => Some(format!("{} checked: lines, expected one", many.len())),
65    }
66}
67
68fn judge(ctx: &GateCtx, args: &[String], as_of: Date) -> GateResult {
69    let mut violations = Vec::new();
70    for record in ki_records_judged(ctx, args)? {
71        let text = read_text(ctx, &record)?;
72        // A record whose state is missing or invalid is `ki-state`'s to
73        // report; judging its date here would name the same defect twice
74        // under a rule that does not own it.
75        let Some(state) = axis(&text, "state", &STATES) else {
76            continue;
77        };
78        let values = front_matter_values(&text, "checked");
79        if let Some(detail) = detail(&state, &values, as_of) {
80            violations.push(Violation::Finding(Finding::on_file(RULE, &record, detail)));
81        }
82    }
83    Ok(violations)
84}
85
86/// Judge every known-issue record under the resolved roots.
87///
88/// # Errors
89///
90/// [`crate::gates::GateError::Io`] when a record cannot be read.
91pub fn run(ctx: &GateCtx, args: &[String]) -> GateResult {
92    judge(ctx, args, today_utc())
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98    use crate::gates::tests_support::ki_fixture_checked;
99
100    const AS_OF: &str = "2026-06-30";
101
102    fn run_on(state: &str, checked_line: &str) -> Vec<String> {
103        let dir = ki_fixture_checked(state, checked_line);
104        let ctx = GateCtx::new(dir.path().to_str().unwrap());
105        judge(&ctx, &[], AS_OF.parse().unwrap())
106            .unwrap()
107            .iter()
108            .map(ToString::to_string)
109            .collect()
110    }
111
112    #[test]
113    fn accepts_a_masked_record_with_a_past_date() {
114        assert!(run_on("masked", "checked: 2026-06-18\n").is_empty());
115    }
116
117    #[test]
118    fn accepts_a_monitoring_record_dated_today() {
119        assert!(run_on("monitoring", "checked: 2026-06-30\n").is_empty());
120    }
121
122    #[test]
123    fn accepts_an_undated_state_carrying_none() {
124        for state in ["investigating", "mitigated"] {
125            assert!(run_on(state, "").is_empty(), "state {state}");
126        }
127    }
128
129    #[test]
130    fn rejects_a_dated_state_carrying_none() {
131        for state in DATED_STATES {
132            let out = run_on(state, "");
133            assert_eq!(out.len(), 1, "state {state}");
134            assert!(out[0].starts_with("FAIL known-issues:a-record-records-its-last-check "));
135            assert!(out[0].ends_with(&format!(": a {state} record states no checked:")));
136        }
137    }
138
139    #[test]
140    fn rejects_an_empty_date() {
141        let out = run_on("masked", "checked:\n");
142        assert_eq!(out.len(), 1);
143        assert!(out[0].ends_with(": a masked record states an empty checked:"));
144    }
145
146    #[test]
147    fn rejects_a_date_that_is_not_iso() {
148        for value in ["2026-6-9", "June 1", "2026-06-18T00:00:00Z", "2026-02-30"] {
149            let out = run_on("masked", &format!("checked: {value}\n"));
150            assert_eq!(out.len(), 1, "value {value}");
151            assert!(out[0].ends_with(&format!(": checked: {value} is not an ISO YYYY-MM-DD date")));
152        }
153    }
154
155    #[test]
156    fn rejects_a_date_in_the_future() {
157        let out = run_on("masked", "checked: 2026-07-01\n");
158        assert_eq!(out.len(), 1);
159        assert!(out[0].ends_with(": checked: 2026-07-01 is in the future"));
160    }
161
162    #[test]
163    fn rejects_an_undated_state_carrying_one() {
164        let out = run_on("mitigated", "checked: 2026-06-18\n");
165        assert_eq!(out.len(), 1);
166        assert!(out[0].ends_with(": a mitigated record states a checked:"));
167    }
168
169    #[test]
170    fn rejects_two_dates() {
171        let out = run_on("masked", "checked: 2026-06-18\nchecked: 2026-06-19\n");
172        assert_eq!(out.len(), 1);
173        assert!(out[0].ends_with(": 2 checked: lines, expected one"));
174    }
175
176    #[test]
177    fn leaves_an_unjudgeable_state_to_the_state_gate() {
178        assert!(run_on("closed", "").is_empty());
179    }
180
181    #[test]
182    fn the_real_clock_accepts_a_past_date() {
183        let dir = ki_fixture_checked("masked", "checked: 2020-01-01\n");
184        let ctx = GateCtx::new(dir.path().to_str().unwrap());
185        assert!(run(&ctx, &[]).unwrap().is_empty());
186    }
187}