Skip to main content

corpora_rules/
held.rs

1//! Held-detection — makes "forgotten" machine-detectable (schema-v0 §6). An accepted
2//! decision with no implementation and no `realized_by` is unscheduled debt.
3
4use corpora_core::{Diagnostic, Facet, Graph, Impl, Status};
5
6use crate::Rule;
7
8pub struct HeldDecision;
9
10impl Rule for HeldDecision {
11    fn code(&self) -> &'static str {
12        "HELD"
13    }
14
15    fn check(&self, g: &Graph, out: &mut Vec<Diagnostic>) {
16        for r in g.records() {
17            let Facet::Decision(d) = &r.facet else { continue };
18            if d.status == Status::Accepted
19                && d.implementation == Some(Impl::Absent)
20                && d.realized_by.is_empty()
21            {
22                let id = r.id.as_ref().map(|i| i.0.as_str()).unwrap_or("<no-id>");
23                out.push(Diagnostic::warn(
24                    "HELD",
25                    &r.path,
26                    format!("{id} accepted but unrealized and unscheduled"),
27                ));
28            }
29        }
30    }
31}
32
33#[cfg(test)]
34mod tests {
35    use super::*;
36    use corpora_core::*;
37    use std::sync::Arc;
38
39    fn decision(id: &str, status: Status, implementation: Option<Impl>, realized: Vec<Id>) -> Record {
40        Record::minimal(
41            Some(Id(id.into())),
42            DocPath(format!("{id}.md")),
43            Kind::Decision,
44            Lifecycle::Current,
45            Authority::Normative,
46            Facet::Decision(DecisionFacet {
47                status,
48                date: Date("2026-06-21".into()),
49                implementation,
50                fork: None,
51                realized_by: realized,
52            }),
53        )
54    }
55
56    #[test]
57    fn flags_unrealized_unscheduled() {
58        let held = decision("D1", Status::Accepted, Some(Impl::Absent), vec![]);
59        let scheduled = decision("D2", Status::Accepted, Some(Impl::Absent), vec![Id("roadmap.v0".into())]);
60        let done = decision("D3", Status::Accepted, Some(Impl::Implemented), vec![]);
61
62        let (g, _) = Graph::build(vec![Arc::new(held), Arc::new(scheduled), Arc::new(done)]);
63        let mut out = Vec::new();
64        HeldDecision.check(&g, &mut out);
65
66        assert_eq!(out.len(), 1);
67        assert_eq!(out[0].code, "HELD");
68    }
69}