spec_driven_docs/domain/
policy.rs1use crate::domain::rule_id::RuleId;
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub struct Sentinel {
22 pub rule: RuleId,
24 pub source: &'static str,
26 pub destination: &'static str,
29 pub declares: &'static str,
31}
32
33pub const SENTINELS: &[Sentinel] = &[
35 Sentinel {
36 rule: RuleId::RecordedDimensionOnlyShrinks,
37 source: "_docs/specs/SPEC-budget-debt.md",
38 destination: "{docs_root}/specs/SPEC-budget-debt.md",
39 declares: ".spec-driven-docs/debt.yaml records budget debt",
40 },
41 Sentinel {
42 rule: RuleId::ProjectSelectsOneSource,
43 source: "_docs/specs/SPEC-writing-policy.md",
44 destination: "{docs_root}/specs/SPEC-writing-policy.md",
45 declares: ".spec-driven-docs/config.yaml selects a writing style other than builtin",
46 },
47];
48
49#[must_use]
51pub fn sentinel(rule: RuleId) -> Option<&'static Sentinel> {
52 SENTINELS.iter().find(|s| s.rule == rule)
53}
54
55#[cfg(test)]
56mod tests {
57 use super::*;
58
59 #[test]
60 fn every_sentinel_is_owned_by_an_embedded_adopted_specification() {
61 for s in SENTINELS {
62 assert!(
63 crate::embedded::asset(s.source).is_some(),
64 "{} is not embedded",
65 s.source
66 );
67 let adopted = crate::domain::profile::ProfileId::KnowledgeBase
68 .profile()
69 .adopted
70 .iter()
71 .any(|p| p.source == s.source && p.destination == s.destination);
72 assert!(adopted, "{} is not an adopted projection", s.source);
73 let text = crate::embedded::asset(s.source)
74 .and_then(|bytes| std::str::from_utf8(bytes).ok())
75 .unwrap_or_default();
76 assert!(
77 crate::embedded::rule_ids_in(text).any(|id| id == s.rule.as_str()),
78 "{} does not define {}",
79 s.source,
80 s.rule
81 );
82 }
83 }
84
85 #[test]
86 fn no_delivered_gate_cites_a_sentinel() {
87 for row in crate::gates::GATES {
88 for rule in row.cites {
89 assert!(
90 sentinel(*rule).is_none(),
91 "{} cites the sentinel {rule}; an instance upgraded after the binary would fail every commit",
92 row.id
93 );
94 }
95 }
96 }
97}