Skip to main content

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}