1use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
12#[serde(rename_all = "snake_case")]
13#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
14pub enum AutonomyLevel {
15 #[default]
17 Operator,
18 Collaborator,
20 Consultant,
22 Approver,
24 Observer,
26}
27
28#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
30#[serde(rename_all = "snake_case")]
31#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
32pub enum SignalTarget {
33 Above(f64),
35 Below(f64),
37 Between { min: f64, max: f64 },
39}
40
41#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
43#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
44pub struct AlignmentSignal {
45 pub name: String,
47 pub source: String,
49 pub metric: String,
51 pub target: SignalTarget,
53}
54
55#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
60#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
61pub struct ResolutionRule {
62 pub condition: String,
64 pub priority: Vec<String>,
66 #[serde(default, skip_serializing_if = "Option::is_none")]
68 pub escalate_to: Option<String>,
69 #[serde(default)]
71 pub max_authority: f64,
72}
73
74#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
76#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
77pub struct DelegationBoundary {
78 #[serde(default)]
80 pub authorized_actions: Vec<String>,
81 #[serde(default)]
83 pub requires_approval: Vec<String>,
84 #[serde(default)]
86 pub forbidden_actions: Vec<String>,
87}
88
89#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
94#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
95pub struct IntentDef {
96 pub name: String,
98 #[serde(default)]
100 pub goals: Vec<String>,
101 #[serde(default)]
103 pub resolution_rules: Vec<ResolutionRule>,
104 #[serde(default)]
106 pub autonomy_level: AutonomyLevel,
107 #[serde(default)]
109 pub delegation_boundaries: DelegationBoundary,
110 #[serde(default)]
112 pub alignment_signals: Vec<AlignmentSignal>,
113 #[serde(default = "default_drift_threshold")]
115 pub drift_threshold: f64,
116}
117
118fn default_drift_threshold() -> f64 {
119 0.85
120}
121
122#[derive(Debug, Clone, Serialize, Deserialize)]
124#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
125pub struct DriftReport {
126 pub alignment_score: f64,
128 pub is_drifting: bool,
130 pub signal_scores: Vec<SignalScore>,
132 #[serde(default, skip_serializing_if = "Option::is_none")]
136 pub multi_agent_drift: Option<crate::drift::DriftMeter>,
137}
138
139#[derive(Debug, Clone, Serialize, Deserialize)]
141#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
142pub struct SignalScore {
143 pub signal_name: String,
145 pub current_value: f64,
147 pub within_target: bool,
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154
155 #[test]
156 fn autonomy_level_default_is_operator() {
157 assert_eq!(AutonomyLevel::default(), AutonomyLevel::Operator);
158 }
159
160 #[test]
161 fn autonomy_level_serde_roundtrip() {
162 let levels = vec![
163 AutonomyLevel::Operator,
164 AutonomyLevel::Collaborator,
165 AutonomyLevel::Consultant,
166 AutonomyLevel::Approver,
167 AutonomyLevel::Observer,
168 ];
169 for level in levels {
170 let json = serde_json::to_string(&level).unwrap();
171 let d: AutonomyLevel = serde_json::from_str(&json).unwrap();
172 assert_eq!(level, d);
173 }
174 }
175
176 #[test]
177 fn signal_target_serde_roundtrip() {
178 let targets = vec![
179 SignalTarget::Above(0.8),
180 SignalTarget::Below(0.2),
181 SignalTarget::Between { min: 0.3, max: 0.7 },
182 ];
183 for t in targets {
184 let json = serde_json::to_string(&t).unwrap();
185 let d: SignalTarget = serde_json::from_str(&json).unwrap();
186 assert_eq!(t, d);
187 }
188 }
189
190 #[test]
191 fn delegation_boundary_default_is_empty() {
192 let d = DelegationBoundary::default();
193 assert!(d.authorized_actions.is_empty());
194 assert!(d.requires_approval.is_empty());
195 assert!(d.forbidden_actions.is_empty());
196 }
197
198 #[test]
199 fn intent_def_serde_roundtrip() {
200 let intent = IntentDef {
201 name: "customer_success".into(),
202 goals: vec!["retention".into(), "satisfaction".into()],
203 resolution_rules: vec![ResolutionRule {
204 condition: "sentiment < 0.3".into(),
205 priority: vec!["satisfaction".into()],
206 escalate_to: Some("manager".into()),
207 max_authority: 1000.0,
208 }],
209 autonomy_level: AutonomyLevel::Collaborator,
210 delegation_boundaries: DelegationBoundary {
211 authorized_actions: vec!["send_email".into()],
212 requires_approval: vec!["refund".into()],
213 forbidden_actions: vec!["delete_account".into()],
214 },
215 alignment_signals: vec![AlignmentSignal {
216 name: "retention_rate".into(),
217 source: "customer_events".into(),
218 metric: "30d_retention".into(),
219 target: SignalTarget::Above(0.85),
220 }],
221 drift_threshold: 0.85,
222 };
223 let json = serde_json::to_string(&intent).unwrap();
224 let d: IntentDef = serde_json::from_str(&json).unwrap();
225 assert_eq!(intent, d);
226 }
227
228 #[test]
229 fn default_drift_threshold_is_0_85() {
230 let intent: IntentDef = serde_json::from_str(
231 r#"{
232 "name": "test",
233 "goals": [],
234 "resolution_rules": [],
235 "delegation_boundaries": {}
236 }"#,
237 )
238 .unwrap();
239 assert!((intent.drift_threshold - 0.85).abs() < f64::EPSILON);
240 }
241
242 #[test]
243 fn drift_report_serde_roundtrip() {
244 let report = DriftReport {
245 alignment_score: 0.92,
246 is_drifting: false,
247 signal_scores: vec![SignalScore {
248 signal_name: "retention".into(),
249 current_value: 0.95,
250 within_target: true,
251 }],
252 multi_agent_drift: None,
253 };
254 let json = serde_json::to_string(&report).unwrap();
255 let d: DriftReport = serde_json::from_str(&json).unwrap();
256 assert!((d.alignment_score - 0.92).abs() < f64::EPSILON);
257 assert!(!d.is_drifting);
258 assert_eq!(d.signal_scores.len(), 1);
259 }
260}