Skip to main content

quant_governor/
policy.rs

1//! Governance policy definition and evaluation.
2
3use serde::{Deserialize, Serialize};
4
5use crate::decision::{CodecDecision, CodecProfile};
6use crate::error::GovernorError;
7
8/// Content type for routing decisions.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
10pub enum ContentType {
11    /// Text content
12    Text,
13    /// Image data
14    Image,
15    /// Audio data
16    Audio,
17    /// Video data
18    Video,
19    /// Structured data
20    Structured,
21    /// Model weights
22    Model,
23    /// Other/unknown
24    #[default]
25    Other,
26}
27
28impl std::fmt::Display for ContentType {
29    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30        match self {
31            ContentType::Text => write!(f, "text"),
32            ContentType::Image => write!(f, "image"),
33            ContentType::Audio => write!(f, "audio"),
34            ContentType::Video => write!(f, "video"),
35            ContentType::Structured => write!(f, "structured"),
36            ContentType::Model => write!(f, "model"),
37            ContentType::Other => write!(f, "other"),
38        }
39    }
40}
41
42/// Admissibility class for content routing.
43#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
44pub enum AdmissibilityClass {
45    /// Critical content requiring highest fidelity
46    Critical,
47    /// High priority content
48    HighPriority,
49    /// Standard content
50    #[default]
51    Standard,
52    /// Compressible content
53    Compressible,
54    /// Best effort content
55    BestEffort,
56}
57
58/// Request for codec decision.
59#[derive(Debug, Clone, Serialize, Deserialize)]
60pub struct GovernanceRequest {
61    /// Content type for routing
62    pub content_type: ContentType,
63
64    /// Size of content in bytes
65    pub size_bytes: u64,
66
67    /// Required accuracy (0.0 to 1.0)
68    pub accuracy_requirement: f64,
69
70    /// Maximum latency tolerance in milliseconds
71    pub latency_tolerance_ms: u64,
72
73    /// Admissibility class
74    pub admissibility: AdmissibilityClass,
75}
76
77impl Default for GovernanceRequest {
78    fn default() -> Self {
79        Self {
80            content_type: ContentType::Other,
81            size_bytes: 0,
82            accuracy_requirement: 0.95,
83            latency_tolerance_ms: 1000,
84            admissibility: AdmissibilityClass::Standard,
85        }
86    }
87}
88
89/// Governance policy for codec selection.
90#[derive(Debug, Clone, Serialize, Deserialize)]
91pub struct GovernancePolicy {
92    /// Maximum degradation allowed (0.0 to 1.0)
93    max_degradation: f64,
94
95    /// Size threshold for small content bypass (bytes)
96    small_content_threshold: u64,
97
98    /// Minimum accuracy for raw codec
99    raw_min_accuracy: f64,
100
101    /// Policy name for debugging
102    name: String,
103}
104
105impl Default for GovernancePolicy {
106    fn default() -> Self {
107        Self {
108            max_degradation: 0.1,
109            small_content_threshold: 256,
110            raw_min_accuracy: 0.99,
111            name: "default".to_string(),
112        }
113    }
114}
115
116impl GovernancePolicy {
117    /// Create a new governance policy with custom settings.
118    pub fn new(max_degradation: f64, small_content_threshold: u64, raw_min_accuracy: f64) -> Self {
119        Self {
120            max_degradation,
121            small_content_threshold,
122            raw_min_accuracy,
123            name: "custom".to_string(),
124        }
125    }
126
127    /// Create a policy optimized for storage efficiency.
128    pub fn storage_efficient() -> Self {
129        Self {
130            max_degradation: 0.15,
131            small_content_threshold: 512,
132            raw_min_accuracy: 0.90,
133            name: "storage_efficient".to_string(),
134        }
135    }
136
137    /// Create a policy optimized for low latency.
138    pub fn low_latency() -> Self {
139        Self {
140            max_degradation: 0.12,
141            small_content_threshold: 1024,
142            raw_min_accuracy: 0.92,
143            name: "low_latency".to_string(),
144        }
145    }
146
147    /// Create a policy optimized for accuracy.
148    pub fn accuracy_oriented() -> Self {
149        Self {
150            max_degradation: 0.05,
151            small_content_threshold: 128,
152            raw_min_accuracy: 0.999,
153            name: "accuracy_oriented".to_string(),
154        }
155    }
156
157    /// Evaluate a governance request and produce a codec decision.
158    pub fn evaluate(&self, request: GovernanceRequest) -> Result<CodecDecision, GovernorError> {
159        // Small content bypass
160        if request.size_bytes <= self.small_content_threshold
161            && request.admissibility != AdmissibilityClass::Critical
162        {
163            return Ok(CodecDecision::direct(
164                CodecProfile::Raw,
165                self.max_degradation,
166            ));
167        }
168
169        // Critical content always gets raw
170        if request.accuracy_requirement >= self.raw_min_accuracy
171            || request.admissibility == AdmissibilityClass::Critical
172        {
173            return Ok(CodecDecision::direct(CodecProfile::Raw, 0.0));
174        }
175
176        // Select codec based on content type and requirements
177        let codec = self.select_codec(&request)?;
178        let degradation = codec.default_degradation_threshold();
179
180        Ok(CodecDecision::direct(codec, degradation))
181    }
182
183    /// Select appropriate codec based on request.
184    fn select_codec(&self, request: &GovernanceRequest) -> Result<CodecProfile, GovernorError> {
185        match request.content_type {
186            ContentType::Text => self.select_for_text(request),
187            ContentType::Image => self.select_for_image(request),
188            ContentType::Audio => self.select_for_audio(request),
189            ContentType::Video => self.select_for_video(request),
190            ContentType::Structured => self.select_for_structured(request),
191            ContentType::Model => self.select_for_model(request),
192            ContentType::Other => Ok(CodecProfile::Q8),
193        }
194    }
195
196    fn select_for_text(&self, request: &GovernanceRequest) -> Result<CodecProfile, GovernorError> {
197        if request.accuracy_requirement >= 0.98 {
198            Ok(CodecProfile::Raw)
199        } else if request.size_bytes > 1_000_000 {
200            Ok(CodecProfile::Turbo)
201        } else if request.latency_tolerance_ms < 50 {
202            // Low-latency text workloads (search, retrieval, RAG re-rank)
203            // benefit from QJL's constant-size sketches and Polar's
204            // asymmetric inner-product scoring. Pick the right one based
205            // on the size budget: tiny vectors use Polar for finer
206            // granularity, large vectors use QJL for fixed-cost sketches.
207            if request.size_bytes > 50_000 {
208                Ok(CodecProfile::Qjl)
209            } else {
210                Ok(CodecProfile::Polar)
211            }
212        } else {
213            Ok(CodecProfile::Q8)
214        }
215    }
216
217    fn select_for_image(&self, request: &GovernanceRequest) -> Result<CodecProfile, GovernorError> {
218        if request.accuracy_requirement >= 0.95 {
219            Ok(CodecProfile::Q8)
220        } else if request.size_bytes > 5_000_000 {
221            Ok(CodecProfile::Q4)
222        } else {
223            Ok(CodecProfile::Q8)
224        }
225    }
226
227    fn select_for_audio(&self, request: &GovernanceRequest) -> Result<CodecProfile, GovernorError> {
228        if request.latency_tolerance_ms < 100 {
229            Ok(CodecProfile::Turbo)
230        } else if request.accuracy_requirement >= 0.97 {
231            Ok(CodecProfile::Fib)
232        } else {
233            Ok(CodecProfile::Q8)
234        }
235    }
236
237    fn select_for_video(&self, request: &GovernanceRequest) -> Result<CodecProfile, GovernorError> {
238        if request.latency_tolerance_ms < 50 {
239            Ok(CodecProfile::Turbo)
240        } else {
241            Ok(CodecProfile::Q4)
242        }
243    }
244
245    fn select_for_structured(
246        &self,
247        request: &GovernanceRequest,
248    ) -> Result<CodecProfile, GovernorError> {
249        if request.accuracy_requirement >= 0.99 {
250            Ok(CodecProfile::Raw)
251        } else {
252            Ok(CodecProfile::Q8)
253        }
254    }
255
256    fn select_for_model(&self, request: &GovernanceRequest) -> Result<CodecProfile, GovernorError> {
257        if request.admissibility == AdmissibilityClass::Critical {
258            Ok(CodecProfile::Raw)
259        } else if request.accuracy_requirement >= 0.98 {
260            Ok(CodecProfile::Fib)
261        } else if request.size_bytes > 100_000_000 {
262            Ok(CodecProfile::Q4)
263        } else {
264            Ok(CodecProfile::Q8)
265        }
266    }
267
268    /// Returns the policy name.
269    pub fn name(&self) -> &str {
270        &self.name
271    }
272
273    /// Returns max degradation setting.
274    pub fn max_degradation(&self) -> f64 {
275        self.max_degradation
276    }
277}
278
279#[cfg(test)]
280mod tests {
281    use super::*;
282
283    #[test]
284    fn default_policy_evaluation() {
285        let policy = GovernancePolicy::default();
286        let request = GovernanceRequest::default();
287
288        let result = policy.evaluate(request);
289        assert!(result.is_ok());
290    }
291
292    #[test]
293    fn small_content_bypass() {
294        let policy = GovernancePolicy::default();
295        let request = GovernanceRequest {
296            size_bytes: 100,
297            admissibility: AdmissibilityClass::Standard,
298            ..Default::default()
299        };
300
301        let decision = policy.evaluate(request).unwrap();
302        assert_eq!(decision.codec, CodecProfile::Raw);
303    }
304
305    #[test]
306    fn critical_content_gets_raw() {
307        let policy = GovernancePolicy::default();
308        let request = GovernanceRequest {
309            admissibility: AdmissibilityClass::Critical,
310            accuracy_requirement: 0.5,
311            ..Default::default()
312        };
313
314        let decision = policy.evaluate(request).unwrap();
315        assert_eq!(decision.codec, CodecProfile::Raw);
316    }
317
318    #[test]
319    fn image_content_routing() {
320        let policy = GovernancePolicy::default();
321
322        // Large image with lower accuracy gets Q4
323        let request = GovernanceRequest {
324            content_type: ContentType::Image,
325            size_bytes: 10_000_000,
326            accuracy_requirement: 0.8,
327            ..Default::default()
328        };
329
330        let decision = policy.evaluate(request).unwrap();
331        assert_eq!(decision.codec, CodecProfile::Q4);
332    }
333
334    #[test]
335    fn model_content_routing() {
336        let policy = GovernancePolicy::default();
337
338        // Critical model gets raw
339        let request = GovernanceRequest {
340            content_type: ContentType::Model,
341            admissibility: AdmissibilityClass::Critical,
342            ..Default::default()
343        };
344
345        let decision = policy.evaluate(request).unwrap();
346        assert_eq!(decision.codec, CodecProfile::Raw);
347    }
348
349    #[test]
350    fn low_latency_audio_gets_turbo() {
351        // Use low_latency policy so small_content_threshold doesn't block
352        // the latency-sensitive path before the audio routing decision fires
353        let policy = GovernancePolicy::low_latency();
354        let request = GovernanceRequest {
355            content_type: ContentType::Audio,
356            size_bytes: 2000, // exceeds small_content_threshold of 1024
357            latency_tolerance_ms: 50,
358            accuracy_requirement: 0.8,
359            ..Default::default()
360        };
361
362        let decision = policy.evaluate(request).unwrap();
363        assert_eq!(decision.codec, CodecProfile::Turbo);
364    }
365
366    #[test]
367    fn policy_presets() {
368        let storage = GovernancePolicy::storage_efficient();
369        assert_eq!(storage.name(), "storage_efficient");
370
371        let latency = GovernancePolicy::low_latency();
372        assert_eq!(latency.name(), "low_latency");
373
374        let accuracy = GovernancePolicy::accuracy_oriented();
375        assert_eq!(accuracy.name(), "accuracy_oriented");
376    }
377}