dsfb_debug/policy.rs
1//! DSFB-Debug: policy engine — paper §5.
2//!
3//! Maps the tuple `(matched motif, DSA score, confirmed grammar
4//! state)` to the developer-facing four-level `PolicyState`:
5//!
6//! | State | Meaning |
7//! |-------|---------|
8//! | `Silent` | No structural deviation; suppress dashboard |
9//! | `Watch` | Deviation detected, below escalation threshold |
10//! | `Review` | Sustained drift / low slew; surface to operator |
11//! | `Escalate` | Hard envelope breach / high slew; page on-call |
12//!
13//! The policy state determines whether and how the typed episode
14//! reaches the operator surface. `Silent` and `Watch` episodes are
15//! suppressed from operator alerting (logged for the audit trail
16//! only); `Review` and `Escalate` episodes flow through to dashboards.
17//!
18//! Policy decisions are pure functions of the motif + DSA score +
19//! grammar state inputs; they preserve Theorem 9 deterministic
20//! replay trivially.
21
22use crate::types::*;
23
24/// Apply policy based on grammar state, DSA score, motif match, and persistence.
25///
26/// - Silent: gate failed or no motif
27/// - Watch: motif active, below escalation
28/// - Review: persistence >= K AND motif class == Review
29/// - Escalate: persistence >= K AND Violation-class motif
30#[inline]
31pub fn apply_policy(
32 confirmed_grammar: GrammarState,
33 dsa_score: f64,
34 consistency_gate_passed: bool,
35 semantic: SemanticDisposition,
36 persistence_count: usize,
37 persistence_threshold: usize,
38) -> PolicyState {
39 // No structural evidence → Silent
40 if confirmed_grammar == GrammarState::Admissible && dsa_score < 0.5 {
41 return PolicyState::Silent;
42 }
43
44 // Consistency gate not passed → Silent
45 if !consistency_gate_passed && confirmed_grammar != GrammarState::Violation {
46 return PolicyState::Silent;
47 }
48
49 // Violation always escalates (regardless of persistence gate)
50 if confirmed_grammar == GrammarState::Violation {
51 return PolicyState::Escalate;
52 }
53
54 // Persistence gate: must have >= K consecutive boundary windows
55 if persistence_count < persistence_threshold {
56 // Activity present but not sustained → Watch
57 if confirmed_grammar == GrammarState::Boundary {
58 return PolicyState::Watch;
59 }
60 return PolicyState::Silent;
61 }
62
63 // Persistence gate passed + Boundary state
64 match semantic {
65 SemanticDisposition::Named(motif) => {
66 // Check if the motif warrants Escalate or Review
67 match motif {
68 MotifClass::CascadingTimeoutSlew
69 | MotifClass::DeploymentRegressionSlew
70 | MotifClass::ErrorRateEscalation => PolicyState::Escalate,
71 _ => PolicyState::Review,
72 }
73 }
74 SemanticDisposition::Unknown => {
75 // Endoductive: structure present but unnamed → Review
76 PolicyState::Review
77 }
78 }
79}
80
81#[cfg(test)]
82mod tests {
83 use super::*;
84
85 #[test]
86 fn test_admissible_is_silent() {
87 let p = apply_policy(
88 GrammarState::Admissible, 0.1, false,
89 SemanticDisposition::Unknown, 0, 4,
90 );
91 assert_eq!(p, PolicyState::Silent);
92 }
93
94 #[test]
95 fn test_violation_is_escalate() {
96 let p = apply_policy(
97 GrammarState::Violation, 3.0, true,
98 SemanticDisposition::Unknown, 0, 4,
99 );
100 assert_eq!(p, PolicyState::Escalate);
101 }
102
103 #[test]
104 fn test_boundary_below_persistence_is_watch() {
105 let p = apply_policy(
106 GrammarState::Boundary, 2.5, true,
107 SemanticDisposition::Named(MotifClass::MemoryLeakDrift), 2, 4,
108 );
109 assert_eq!(p, PolicyState::Watch);
110 }
111
112 #[test]
113 fn test_boundary_above_persistence_review() {
114 let p = apply_policy(
115 GrammarState::Boundary, 2.5, true,
116 SemanticDisposition::Named(MotifClass::MemoryLeakDrift), 5, 4,
117 );
118 assert_eq!(p, PolicyState::Review);
119 }
120}