aios_core/
trace_engine.rs1use aios_spec::traits::{PrivacySanitizer, TraceValidator};
19use aios_spec::{
20 ExecutedAction, GoldenTrace, Intent, IntentBatch, ReplayResult, SanitizedEvent, SuggestedAction,
21};
22
23pub struct DefaultTraceEngine {
25 sanitizer: Box<dyn PrivacySanitizer + Send + Sync>,
26}
27
28impl DefaultTraceEngine {
29 pub fn new(sanitizer: impl PrivacySanitizer + Send + Sync + 'static) -> Self {
30 Self {
31 sanitizer: Box::new(sanitizer),
32 }
33 }
34
35 pub fn validate_sanitization(&self, golden: &GoldenTrace) -> ReplayResult {
45 let sanitization_divergences = self.sanitization_divergences(golden);
46 ReplayResult {
47 trace_id: golden.trace_id.clone(),
48 sanitization_match: sanitization_divergences.is_empty(),
49 sanitization_divergences,
50 policy_match: false,
51 policy_divergences: vec![],
52 execution_match: false,
53 execution_divergences: vec![],
54 }
55 }
56
57 fn sanitization_divergences(&self, golden: &GoldenTrace) -> Vec<usize> {
58 let actual_sanitized: Vec<SanitizedEvent> = golden
59 .raw_events
60 .iter()
61 .map(|raw| self.sanitizer.sanitize(raw.clone()))
62 .collect();
63
64 let mut divergences = Vec::new();
65 for (i, (actual, expected)) in actual_sanitized
66 .iter()
67 .zip(golden.expected_sanitized.iter())
68 .enumerate()
69 {
70 if !sanitized_eq(actual, expected) {
71 divergences.push(i);
72 }
73 }
74 let actual_len = actual_sanitized.len();
76 let expected_len = golden.expected_sanitized.len();
77 for i in actual_len.min(expected_len)..actual_len.max(expected_len) {
78 divergences.push(i);
79 }
80 divergences
81 }
82}
83
84impl TraceValidator for DefaultTraceEngine {
85 fn validate(
86 &self,
87 golden: &GoldenTrace,
88 actual_intents: &IntentBatch,
89 actual_executed: &[ExecutedAction],
90 ) -> ReplayResult {
91 let sanitization_divergences = self.sanitization_divergences(golden);
92 let policy_divergences = intent_divergences(&golden.expected_intents, actual_intents);
93 let execution_divergences =
94 execution_divergences(&golden.expected_actions, actual_executed);
95
96 ReplayResult {
97 trace_id: golden.trace_id.clone(),
98 sanitization_match: sanitization_divergences.is_empty(),
99 sanitization_divergences,
100 policy_match: policy_divergences.is_empty(),
101 policy_divergences,
102 execution_match: execution_divergences.is_empty(),
103 execution_divergences,
104 }
105 }
106}
107
108fn sanitized_eq(a: &SanitizedEvent, b: &SanitizedEvent) -> bool {
116 a.event_type == b.event_type
117 && a.source_tier == b.source_tier
118 && a.app_package == b.app_package
119 && a.uid == b.uid
120}
121
122fn intent_divergences(expected: &IntentBatch, actual: &IntentBatch) -> Vec<String> {
126 let mut divergences = Vec::new();
127
128 if expected.model != actual.model {
129 divergences.push(format!(
130 "model mismatch: expected={:?} actual={:?}",
131 expected.model, actual.model
132 ));
133 }
134 if expected.intents.len() != actual.intents.len() {
135 divergences.push(format!(
136 "intent count mismatch: expected={} actual={}",
137 expected.intents.len(),
138 actual.intents.len()
139 ));
140 }
142 let pairs = expected
143 .intents
144 .iter()
145 .zip(actual.intents.iter())
146 .enumerate();
147 for (i, (e, a)) in pairs {
148 if let Some(reason) = intent_diff(e, a) {
149 divergences.push(format!("intent[{i}]: {reason}"));
150 }
151 }
152 divergences
153}
154
155fn intent_diff(expected: &Intent, actual: &Intent) -> Option<String> {
156 if expected.intent_type != actual.intent_type {
157 return Some(format!(
158 "intent_type: expected={:?} actual={:?}",
159 expected.intent_type, actual.intent_type
160 ));
161 }
162 if expected.risk_level != actual.risk_level {
163 return Some(format!(
164 "risk_level: expected={:?} actual={:?}",
165 expected.risk_level, actual.risk_level
166 ));
167 }
168 if (expected.confidence - actual.confidence).abs() > f32::EPSILON {
169 return Some(format!(
170 "confidence: expected={} actual={}",
171 expected.confidence, actual.confidence
172 ));
173 }
174 if expected.rationale_tags != actual.rationale_tags {
175 return Some(format!(
176 "rationale_tags: expected={:?} actual={:?}",
177 expected.rationale_tags, actual.rationale_tags
178 ));
179 }
180 if expected.suggested_actions.len() != actual.suggested_actions.len() {
181 return Some(format!(
182 "suggested_actions count: expected={} actual={}",
183 expected.suggested_actions.len(),
184 actual.suggested_actions.len()
185 ));
186 }
187 for (j, (e_act, a_act)) in expected
188 .suggested_actions
189 .iter()
190 .zip(actual.suggested_actions.iter())
191 .enumerate()
192 {
193 if !suggested_eq(e_act, a_act) {
194 return Some(format!(
195 "suggested_actions[{j}]: expected={:?} actual={:?}",
196 e_act, a_act
197 ));
198 }
199 }
200 None
201}
202
203fn suggested_eq(a: &SuggestedAction, b: &SuggestedAction) -> bool {
204 a.action_type == b.action_type && a.target == b.target && a.urgency == b.urgency
205}
206
207fn execution_divergences(expected: &[ExecutedAction], actual: &[ExecutedAction]) -> Vec<usize> {
208 let mut divergences = Vec::new();
209 for (i, (e, a)) in expected.iter().zip(actual.iter()).enumerate() {
210 if !executed_eq(e, a) {
211 divergences.push(i);
212 }
213 }
214 let actual_len = actual.len();
216 let expected_len = expected.len();
217 for i in actual_len.min(expected_len)..actual_len.max(expected_len) {
218 divergences.push(i);
219 }
220 divergences
221}
222
223fn executed_eq(a: &ExecutedAction, b: &ExecutedAction) -> bool {
225 a.action_type == b.action_type
226 && a.target == b.target
227 && a.success == b.success
228 && a.error_reason == b.error_reason
229}