sqz_engine/
confidence_router.rs1use crate::entropy_analyzer::EntropyAnalyzer;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum CompressionMode {
15 Safe,
18 Default,
20 Aggressive,
23}
24
25impl CompressionMode {
26 pub fn as_str(&self) -> &'static str {
27 match self {
28 Self::Safe => "safe",
29 Self::Default => "default",
30 Self::Aggressive => "aggressive",
31 }
32 }
33}
34
35pub struct ConfidenceRouter {
37 entropy_analyzer: EntropyAnalyzer,
38}
39
40impl Default for ConfidenceRouter {
41 fn default() -> Self {
42 Self::new()
43 }
44}
45
46impl ConfidenceRouter {
47 pub fn new() -> Self {
48 Self {
49 entropy_analyzer: EntropyAnalyzer::new(),
50 }
51 }
52
53 pub fn route(&self, content: &str) -> CompressionMode {
55 if content.len() < 100 {
56 return CompressionMode::Default;
57 }
58
59 if self.is_high_risk(content) {
61 return CompressionMode::Safe;
62 }
63
64 let blocks = self.entropy_analyzer.analyze(content);
66 if blocks.is_empty() {
67 return CompressionMode::Default;
68 }
69
70 let avg_entropy: f64 = blocks.iter().map(|b| b.entropy).sum::<f64>() / blocks.len() as f64;
71
72 if avg_entropy < 2.5 {
74 return CompressionMode::Aggressive;
75 }
76
77 CompressionMode::Default
81 }
82
83 fn is_high_risk(&self, content: &str) -> bool {
90 let lower = content.to_lowercase();
91
92 if (lower.contains("panicked at") || lower.contains("traceback"))
96 && (lower.contains("stack backtrace") || lower.contains(" 0:") || lower.contains(" at "))
97 {
98 return true;
99 }
100 if lower.contains("stack trace") && lower.contains(" at ") {
101 return true;
102 }
103
104 if (lower.contains("alter table") || lower.contains("create table") || lower.contains("drop table"))
107 && (lower.contains("column") || lower.contains("index") || lower.contains("constraint")
108 || lower.contains("primary key") || lower.contains("references"))
109 {
110 return true;
111 }
112
113 if lower.contains("-----begin") {
116 return true;
118 }
119 if (lower.contains("private_key") || lower.contains("secret_key") || lower.contains("api_key"))
120 && (lower.contains('=') || lower.contains(':'))
121 && !self.looks_like_commit_log(&lower)
122 {
123 return true;
124 }
125 if lower.contains("password")
127 && (lower.contains("password:") || lower.contains("password=") || lower.contains("password \""))
128 && !self.looks_like_commit_log(&lower)
129 {
130 return true;
131 }
132
133 if lower.contains("terms of service") || lower.contains("privacy policy")
135 || lower.contains("license agreement") || lower.contains("gdpr")
136 {
137 return true;
138 }
139
140 if lower.contains("apiversion:") && lower.contains("kind:")
142 && (lower.contains("secret") || lower.contains("configmap"))
143 {
144 return true;
145 }
146
147 false
148 }
149
150 fn looks_like_commit_log(&self, lower: &str) -> bool {
154 let lines: Vec<&str> = lower.lines().take(10).collect();
155 let mut commit_lines = 0;
156 for line in &lines {
157 let trimmed = line.trim();
158 if trimmed.len() > 8
162 && trimmed.chars().take(7).all(|c| c.is_ascii_hexdigit())
163 && trimmed.chars().count() > 7
164 {
165 commit_lines += 1;
166 }
167 if trimmed.starts_with("feat:") || trimmed.starts_with("fix:")
169 || trimmed.starts_with("chore:") || trimmed.starts_with("docs:")
170 || trimmed.starts_with("refactor:") || trimmed.starts_with("test:")
171 || trimmed.starts_with("ci:") || trimmed.starts_with("style:")
172 || trimmed.starts_with("perf:")
173 {
174 commit_lines += 1;
175 }
176 }
177 commit_lines > 0 && commit_lines >= lines.len() / 3
178 }
179}
180
181#[cfg(test)]
186mod tests {
187 use super::*;
188
189 #[test]
190 fn routes_stack_trace_to_safe() {
191 let router = ConfidenceRouter::new();
192 let trace = "thread 'main' panicked at 'index out of bounds', src/main.rs:42\nstack backtrace:\n 0: std::panicking::begin_panic\n 1: myapp::process\n 2: main";
193 assert_eq!(router.route(trace), CompressionMode::Safe);
194 }
195
196 #[test]
197 fn routes_migration_to_safe() {
198 let router = ConfidenceRouter::new();
199 let migration = "ALTER TABLE users ADD COLUMN email VARCHAR(255) NOT NULL;\nCREATE TABLE sessions (id UUID PRIMARY KEY, user_id INT REFERENCES users(id));";
200 assert_eq!(router.route(migration), CompressionMode::Safe);
201 }
202
203 #[test]
209 fn multibyte_input_with_password_marker_does_not_panic() {
210 let router = ConfidenceRouter::new();
211 let content = "Привет мир строка номер 0\n\
214 Привет мир строка номер 1\n\
215 Привет мир строка номер 2\n\
216 пароль тут\n\
217 password: тайна";
218 assert!(content.len() >= 100, "fixture must reach is_high_risk");
219 assert_eq!(router.route(content), CompressionMode::Safe);
222 }
223
224 #[test]
227 fn cjk_and_emoji_input_does_not_panic() {
228 let router = ConfidenceRouter::new();
229 let cjk = "私はガラスを食べられます。それは私を傷つけません。\n\
230 api_key = 秘密🦀\n\
231 日本語のテキストの行がもう一つあります";
232 assert!(cjk.len() >= 100);
233 let _ = router.route(cjk);
234 }
235
236 #[test]
237 fn git_log_with_fix_is_not_high_risk() {
238 let router = ConfidenceRouter::new();
239 let git_log = "abc1234 fix: password reset flow\ndef5678 feat: migrate user auth to OAuth\n\
240 1234567 chore: update dependencies\n8901234 fix: migration path handling\n\
241 abcdef0 refactor: clean up error handling\n1111111 docs: update README\n\
242 2222222 fix: handle edge case in parser\n3333333 feat: add new API endpoint";
243 let mode = router.route(git_log);
244 assert_ne!(mode, CompressionMode::Safe,
245 "git log with 'fix:' and 'migration' should NOT trigger safe mode");
246 }
247
248 #[test]
249 fn commit_message_with_password_is_not_high_risk() {
250 let router = ConfidenceRouter::new();
251 let git_log = "abc1234 fix: password reset flow broken on mobile\n\
252 def5678 feat: add password strength indicator\n\
253 1234567 chore: update password hashing library\n\
254 8901234 fix: password validation regex\n\
255 abcdef0 test: add password reset integration tests";
256 let mode = router.route(git_log);
257 assert_ne!(mode, CompressionMode::Safe,
258 "git log mentioning 'password' should NOT trigger safe mode");
259 }
260
261 #[test]
262 fn actual_password_config_is_high_risk() {
263 let router = ConfidenceRouter::new();
264 let config = "database:\n host: localhost\n port: 5432\n username: admin\n password: super_secret_123\n database: myapp_prod\n ssl: true\n pool_size: 10";
265 assert_eq!(router.route(config), CompressionMode::Safe,
266 "config file with password: value should trigger safe mode");
267 }
268
269 #[test]
270 fn routes_repetitive_logs_to_aggressive() {
271 let router = ConfidenceRouter::new();
272 let logs = "// comment\n// comment\n// comment\n// comment\n// comment\n// comment\n// comment\n// comment\n// comment\n// comment\n// comment\n// comment\n// comment\n// comment\n// comment\n// comment\n// comment\n// comment\n// comment\n// comment\n";
274 let mode = router.route(logs);
275 assert!(mode == CompressionMode::Aggressive || mode == CompressionMode::Default);
277 }
278
279 #[test]
280 fn routes_normal_code_to_default() {
281 let router = ConfidenceRouter::new();
282 let code = "fn process(items: &[Item]) -> Result<Vec<Output>, Error> {\n let mut results = Vec::new();\n for item in items {\n let output = transform(item)?;\n results.push(output);\n }\n Ok(results)\n}";
283 let mode = router.route(code);
284 assert_eq!(mode, CompressionMode::Default,
285 "normal code should route to Default, not Safe");
286 }
287
288 #[test]
289 fn short_content_is_default() {
290 let router = ConfidenceRouter::new();
291 assert_eq!(router.route("hello"), CompressionMode::Default);
292 }
293
294 #[test]
295 fn pem_key_routes_to_safe() {
296 let router = ConfidenceRouter::new();
297 let key = "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEA0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOP\nQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz==\n-----END RSA PRIVATE KEY-----\n";
299 assert_eq!(router.route(key), CompressionMode::Safe);
300 }
301}