1use 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}