Skip to main content

llm_kernel/dlp/
classifier.rs

1//! L3 — optional local classifier for ambiguous content.
2//!
3//! Trait-only seam: when L1 regex evidence and L2 fingerprint matching leave
4//! a case ambiguous, a local small model (e.g. an Ollama-backed classifier)
5//! can upgrade the sensitivity grade. No implementation ships in this crate.
6
7use crate::error::Result;
8use crate::provider::policy::Sensitivity;
9
10/// Optional local classifier that upgrades the L1 grade when regex evidence
11/// is ambiguous.
12///
13/// Implementations must be `Send + Sync` (called from proxy request paths).
14pub trait ContentClassifier: Send + Sync {
15    /// Classify `content`; `Ok(None)` defers to the L1 [`Sensitivity`] (the
16    /// `scan` result). `Err` falls back to the L1 grade
17    /// at the call site.
18    fn classify(&self, content: &str) -> Result<Option<Sensitivity>>;
19}
20
21#[cfg(test)]
22mod tests {
23    use super::*;
24
25    struct Fixed(Option<Sensitivity>);
26
27    impl ContentClassifier for Fixed {
28        fn classify(&self, _content: &str) -> Result<Option<Sensitivity>> {
29            Ok(self.0)
30        }
31    }
32
33    #[test]
34    fn classifier_returns_verdict_or_none() {
35        let upgrader = Fixed(Some(Sensitivity::Confidential));
36        assert_eq!(
37            upgrader.classify("ambiguous merger notes").unwrap(),
38            Some(Sensitivity::Confidential)
39        );
40
41        let defer = Fixed(None);
42        assert_eq!(defer.classify("anything").unwrap(), None);
43    }
44
45    #[test]
46    fn classifier_is_send_sync() {
47        fn assert_send_sync<T: Send + Sync>() {}
48        assert_send_sync::<Fixed>();
49    }
50}