Skip to main content

quant_governor/
decision.rs

1//! Codec decision types and profile definitions.
2
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5
6use crate::degradation::DegradationReceipt;
7use crate::receipt::ExactFallbackReceipt;
8
9/// Codec profiles available for governance selection.
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
11#[serde(rename_all = "lowercase")]
12pub enum CodecProfile {
13    /// Uncompressed representation
14    Raw,
15    /// 8-bit quantization
16    Q8,
17    /// 4-bit quantization
18    Q4,
19    /// Turbo-quant accelerated codec (symmetric; reconstructs original vector)
20    Turbo,
21    /// Fibonacci-weighted precision codec (symmetric; reconstructs original vector)
22    Fib,
23    /// Polar-only quantization (asymmetric; score_inner_product / score_l2 only)
24    Polar,
25    /// QJL random-projection sketch (asymmetric; fixed-size inner-product estimator)
26    Qjl,
27}
28
29impl CodecProfile {
30    /// Returns the default degradation threshold for this profile.
31    pub fn default_degradation_threshold(&self) -> f64 {
32        match self {
33            CodecProfile::Raw => 0.0,
34            CodecProfile::Q8 => 0.05,
35            CodecProfile::Q4 => 0.10,
36            CodecProfile::Turbo => 0.08,
37            CodecProfile::Fib => 0.03,
38            CodecProfile::Polar => 0.06,
39            CodecProfile::Qjl => 0.04,
40        }
41    }
42
43    /// Returns true if this is a high-fidelity profile.
44    pub fn is_high_fidelity(&self) -> bool {
45        matches!(self, CodecProfile::Raw | CodecProfile::Fib)
46    }
47
48    /// Returns true if this codec is asymmetric (cannot reconstruct the
49    /// original vector from compressed bytes alone). For asymmetric
50    /// codecs the wire format is a sketch/code, and the caller must
51    /// use score_inner_product / score_l2 against a known query.
52    pub fn is_asymmetric(&self) -> bool {
53        matches!(self, CodecProfile::Polar | CodecProfile::Qjl)
54    }
55
56    /// Returns estimated compression ratio for this profile.
57    pub fn estimated_compression_ratio(&self) -> f64 {
58        match self {
59            CodecProfile::Raw => 1.0,
60            CodecProfile::Q8 => 2.0,
61            CodecProfile::Q4 => 4.0,
62            CodecProfile::Turbo => 3.0,
63            CodecProfile::Fib => 2.5,
64            // Polar: dim * 2 bytes for radii + angle_indices, plus JSON
65            // envelope overhead; rough estimate is dim * 2 / (dim * 4)
66            // = 0.5. Use 1.0 because it's not a storage win.
67            CodecProfile::Polar => 1.0,
68            // QJL: 32 projections * ~3 bytes each ≈ 120 bytes regardless
69            // of dim. So compression ratio scales linearly with dim.
70            // For 768-dim raw = 3072, qjl = 120, ratio ≈ 25.6.
71            // We report a conservative 8.0 to avoid overpromising.
72            CodecProfile::Qjl => 8.0,
73        }
74    }
75}
76
77impl std::fmt::Display for CodecProfile {
78    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79        match self {
80            CodecProfile::Raw => write!(f, "raw"),
81            CodecProfile::Q8 => write!(f, "q8"),
82            CodecProfile::Q4 => write!(f, "q4"),
83            CodecProfile::Turbo => write!(f, "turbo"),
84            CodecProfile::Fib => write!(f, "fib"),
85            CodecProfile::Polar => write!(f, "polar"),
86            CodecProfile::Qjl => write!(f, "qjl"),
87        }
88    }
89}
90
91/// Outcome of policy evaluation containing selected codec and metadata.
92#[derive(Debug, Clone, Serialize, Deserialize)]
93pub struct CodecDecision {
94    /// Selected codec profile
95    pub codec: CodecProfile,
96
97    /// True if exact fallback was triggered (compressed to raw)
98    pub exact_fallback: bool,
99
100    /// Allowed degradation budget for this decision
101    pub degradation_budget: f64,
102
103    /// Receipt containing detailed tracking information
104    pub receipt: CodecReceipt,
105}
106
107/// Receipt variants for codec decisions.
108#[derive(Debug, Clone, Serialize, Deserialize)]
109#[serde(untagged)]
110pub enum CodecReceipt {
111    /// Exact fallback receipt when degrading from compressed to raw
112    ExactFallback(ExactFallbackReceipt),
113
114    /// Degradation receipt when moving between non-raw profiles
115    Degradation(DegradationReceipt),
116
117    /// Direct encoding without fallback
118    Direct {
119        /// Timestamp of decision
120        timestamp: DateTime<Utc>,
121        /// Profile selected
122        profile: CodecProfile,
123    },
124}
125
126impl CodecDecision {
127    /// Create a direct codec decision without fallback.
128    pub fn direct(codec: CodecProfile, degradation_budget: f64) -> Self {
129        Self {
130            codec,
131            exact_fallback: false,
132            degradation_budget,
133            receipt: CodecReceipt::Direct {
134                timestamp: Utc::now(),
135                profile: codec,
136            },
137        }
138    }
139
140    /// Create a codec decision with exact fallback.
141    pub fn with_exact_fallback(
142        codec: CodecProfile,
143        degradation_budget: f64,
144        receipt: ExactFallbackReceipt,
145    ) -> Self {
146        Self {
147            codec,
148            exact_fallback: true,
149            degradation_budget,
150            receipt: CodecReceipt::ExactFallback(receipt),
151        }
152    }
153
154    /// Create a codec decision with degradation between profiles.
155    pub fn with_degradation(
156        codec: CodecProfile,
157        degradation_budget: f64,
158        receipt: DegradationReceipt,
159    ) -> Self {
160        Self {
161            codec,
162            exact_fallback: false,
163            degradation_budget,
164            receipt: CodecReceipt::Degradation(receipt),
165        }
166    }
167
168    /// Returns true if this decision involved any form of fallback.
169    pub fn had_fallback(&self) -> bool {
170        self.exact_fallback || matches!(self.receipt, CodecReceipt::Degradation(_))
171    }
172
173    /// Returns the effective profile after any fallback.
174    pub fn effective_profile(&self) -> CodecProfile {
175        self.codec
176    }
177}
178
179impl Default for CodecDecision {
180    fn default() -> Self {
181        Self::direct(CodecProfile::Raw, 0.0)
182    }
183}
184
185#[cfg(test)]
186mod tests {
187    use super::*;
188
189    #[test]
190    fn codec_profile_ordering() {
191        assert!(CodecProfile::Raw.is_high_fidelity());
192        assert!(CodecProfile::Fib.is_high_fidelity());
193        assert!(!CodecProfile::Q4.is_high_fidelity());
194    }
195
196    #[test]
197    fn codec_decision_direct() {
198        let decision = CodecDecision::direct(CodecProfile::Q8, 0.05);
199        assert_eq!(decision.codec, CodecProfile::Q8);
200        assert!(!decision.exact_fallback);
201        assert!(!decision.had_fallback());
202    }
203
204    #[test]
205    fn compression_ratios() {
206        assert_eq!(CodecProfile::Raw.estimated_compression_ratio(), 1.0);
207        assert_eq!(CodecProfile::Q4.estimated_compression_ratio(), 4.0);
208        assert_eq!(CodecProfile::Turbo.estimated_compression_ratio(), 3.0);
209    }
210}