1use serde::{Deserialize, Serialize};
16
17#[derive(Debug, Clone, Default, Serialize, Deserialize)]
21pub struct IntentBinding {
22 pub packs: Vec<PackRequirement>,
24 pub capabilities: Vec<CapabilityRequirement>,
26 pub invariants: Vec<String>,
28 pub resolution: ResolutionTrace,
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct PackRequirement {
35 pub pack_name: String,
36 pub reason: String,
37 pub confidence: f64,
38 pub source: ResolutionLevel,
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct CapabilityRequirement {
44 pub capability: String,
45 pub reason: String,
46 pub confidence: f64,
47 pub source: ResolutionLevel,
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
52#[serde(rename_all = "snake_case")]
53pub enum ResolutionLevel {
54 Declarative,
56 Structural,
58 Semantic,
60 Learned,
62}
63
64#[derive(Debug, Clone, Default, Serialize, Deserialize)]
66pub struct ResolutionTrace {
67 pub levels_attempted: Vec<ResolutionLevel>,
68 pub levels_contributed: Vec<ResolutionLevel>,
69 pub prior_episodes_consulted: usize,
71 pub completeness_confidence: f64,
73}
74
75#[derive(Debug, Clone, Default)]
89pub struct DeclarativeBinding {
90 packs: Vec<PackRequirement>,
91 capabilities: Vec<CapabilityRequirement>,
92 invariants: Vec<String>,
93}
94
95impl DeclarativeBinding {
96 #[must_use]
97 pub fn new() -> Self {
98 Self::default()
99 }
100
101 #[must_use]
102 pub fn pack(mut self, name: impl Into<String>, reason: impl Into<String>) -> Self {
103 self.packs.push(PackRequirement {
104 pack_name: name.into(),
105 reason: reason.into(),
106 confidence: 1.0,
107 source: ResolutionLevel::Declarative,
108 });
109 self
110 }
111
112 #[must_use]
113 pub fn capability(mut self, name: impl Into<String>, reason: impl Into<String>) -> Self {
114 self.capabilities.push(CapabilityRequirement {
115 capability: name.into(),
116 reason: reason.into(),
117 confidence: 1.0,
118 source: ResolutionLevel::Declarative,
119 });
120 self
121 }
122
123 #[must_use]
124 pub fn invariant(mut self, name: impl Into<String>) -> Self {
125 self.invariants.push(name.into());
126 self
127 }
128
129 #[must_use]
130 pub fn build(self) -> IntentBinding {
131 IntentBinding {
132 packs: self.packs,
133 capabilities: self.capabilities,
134 invariants: self.invariants,
135 resolution: ResolutionTrace {
136 levels_attempted: vec![ResolutionLevel::Declarative],
137 levels_contributed: vec![ResolutionLevel::Declarative],
138 prior_episodes_consulted: 0,
139 completeness_confidence: 1.0,
140 },
141 }
142 }
143}
144
145pub trait IntentResolver: Send + Sync {
153 fn level(&self) -> ResolutionLevel;
154 fn resolve(&self, intent: &super::IntentPacket, current: &IntentBinding) -> IntentBinding;
155}
156
157#[cfg(test)]
158mod tests {
159 use super::*;
160
161 #[test]
162 fn declarative_binding_builds_correctly() {
163 let binding = DeclarativeBinding::new()
164 .pack("customers", "lead qualification")
165 .pack("linkedin_research", "enrich leads")
166 .capability("web", "capture company page")
167 .invariant("lead_has_source")
168 .build();
169
170 assert_eq!(binding.packs.len(), 2);
171 assert_eq!(binding.capabilities.len(), 1);
172 assert_eq!(binding.invariants.len(), 1);
173 assert_eq!(binding.packs[0].pack_name, "customers");
174 assert_eq!(binding.packs[0].source, ResolutionLevel::Declarative);
175 assert!((binding.resolution.completeness_confidence - 1.0).abs() < f64::EPSILON);
176 }
177
178 #[test]
179 fn declarative_binding_empty() {
180 let binding = DeclarativeBinding::new().build();
181 assert!(binding.packs.is_empty());
182 assert!(binding.capabilities.is_empty());
183 assert!(binding.invariants.is_empty());
184 assert_eq!(
185 binding.resolution.levels_attempted,
186 vec![ResolutionLevel::Declarative]
187 );
188 assert_eq!(
189 binding.resolution.levels_contributed,
190 vec![ResolutionLevel::Declarative]
191 );
192 assert_eq!(binding.resolution.prior_episodes_consulted, 0);
193 }
194
195 #[test]
196 fn declarative_binding_pack_confidence_is_one() {
197 let binding = DeclarativeBinding::new().pack("test", "reason").build();
198 assert!((binding.packs[0].confidence - 1.0).abs() < f64::EPSILON);
199 }
200
201 #[test]
202 fn declarative_binding_capability_confidence_is_one() {
203 let binding = DeclarativeBinding::new()
204 .capability("ocr", "doc processing")
205 .build();
206 assert!((binding.capabilities[0].confidence - 1.0).abs() < f64::EPSILON);
207 }
208
209 #[test]
210 fn declarative_binding_multiple_invariants() {
211 let binding = DeclarativeBinding::new()
212 .invariant("inv_a")
213 .invariant("inv_b")
214 .invariant("inv_c")
215 .build();
216 assert_eq!(binding.invariants, vec!["inv_a", "inv_b", "inv_c"]);
217 }
218
219 #[test]
220 fn declarative_binding_default() {
221 let binding = DeclarativeBinding::default();
222 assert!(binding.packs.is_empty());
223 assert!(binding.capabilities.is_empty());
224 assert!(binding.invariants.is_empty());
225 }
226
227 #[test]
228 fn intent_binding_default() {
229 let binding = IntentBinding::default();
230 assert!(binding.packs.is_empty());
231 assert!(binding.capabilities.is_empty());
232 assert!(binding.invariants.is_empty());
233 assert!(binding.resolution.levels_attempted.is_empty());
234 assert!(binding.resolution.levels_contributed.is_empty());
235 assert_eq!(binding.resolution.prior_episodes_consulted, 0);
236 assert!((binding.resolution.completeness_confidence - 0.0).abs() < f64::EPSILON);
237 }
238
239 #[test]
240 fn resolution_trace_default() {
241 let trace = ResolutionTrace::default();
242 assert!(trace.levels_attempted.is_empty());
243 assert!(trace.levels_contributed.is_empty());
244 assert_eq!(trace.prior_episodes_consulted, 0);
245 assert!((trace.completeness_confidence - 0.0).abs() < f64::EPSILON);
246 }
247
248 #[test]
249 fn resolution_level_all_variants_distinct() {
250 let variants = [
251 ResolutionLevel::Declarative,
252 ResolutionLevel::Structural,
253 ResolutionLevel::Semantic,
254 ResolutionLevel::Learned,
255 ];
256 for (i, a) in variants.iter().enumerate() {
257 for (j, b) in variants.iter().enumerate() {
258 assert_eq!(i == j, a == b);
259 }
260 }
261 }
262
263 #[test]
264 fn resolution_level_serde_roundtrip() {
265 for level in [
266 ResolutionLevel::Declarative,
267 ResolutionLevel::Structural,
268 ResolutionLevel::Semantic,
269 ResolutionLevel::Learned,
270 ] {
271 let json = serde_json::to_string(&level).unwrap();
272 let back: ResolutionLevel = serde_json::from_str(&json).unwrap();
273 assert_eq!(level, back);
274 }
275 }
276
277 #[test]
278 fn resolution_level_snake_case() {
279 assert_eq!(
280 serde_json::to_string(&ResolutionLevel::Declarative).unwrap(),
281 "\"declarative\""
282 );
283 assert_eq!(
284 serde_json::to_string(&ResolutionLevel::Structural).unwrap(),
285 "\"structural\""
286 );
287 assert_eq!(
288 serde_json::to_string(&ResolutionLevel::Semantic).unwrap(),
289 "\"semantic\""
290 );
291 assert_eq!(
292 serde_json::to_string(&ResolutionLevel::Learned).unwrap(),
293 "\"learned\""
294 );
295 }
296
297 #[test]
298 fn pack_requirement_serde_roundtrip() {
299 let req = PackRequirement {
300 pack_name: "customers".into(),
301 reason: "lead workflow".into(),
302 confidence: 0.85,
303 source: ResolutionLevel::Structural,
304 };
305 let json = serde_json::to_string(&req).unwrap();
306 let back: PackRequirement = serde_json::from_str(&json).unwrap();
307 assert_eq!(back.pack_name, "customers");
308 assert_eq!(back.reason, "lead workflow");
309 assert!((back.confidence - 0.85).abs() < f64::EPSILON);
310 assert_eq!(back.source, ResolutionLevel::Structural);
311 }
312
313 #[test]
314 fn capability_requirement_serde_roundtrip() {
315 let req = CapabilityRequirement {
316 capability: "vision".into(),
317 reason: "document scanning".into(),
318 confidence: 0.7,
319 source: ResolutionLevel::Semantic,
320 };
321 let json = serde_json::to_string(&req).unwrap();
322 let back: CapabilityRequirement = serde_json::from_str(&json).unwrap();
323 assert_eq!(back.capability, "vision");
324 assert_eq!(back.source, ResolutionLevel::Semantic);
325 }
326
327 #[test]
328 fn intent_binding_serde_roundtrip() {
329 let binding = DeclarativeBinding::new()
330 .pack("dd", "due diligence")
331 .capability("web", "scraping")
332 .invariant("hypothesis_has_source")
333 .build();
334
335 let json = serde_json::to_string(&binding).unwrap();
336 let back: IntentBinding = serde_json::from_str(&json).unwrap();
337 assert_eq!(back.packs.len(), 1);
338 assert_eq!(back.capabilities.len(), 1);
339 assert_eq!(back.invariants, vec!["hypothesis_has_source"]);
340 assert_eq!(
341 back.resolution.levels_attempted,
342 vec![ResolutionLevel::Declarative]
343 );
344 }
345
346 #[test]
347 fn resolution_trace_serde_roundtrip() {
348 let trace = ResolutionTrace {
349 levels_attempted: vec![ResolutionLevel::Declarative, ResolutionLevel::Structural],
350 levels_contributed: vec![ResolutionLevel::Declarative],
351 prior_episodes_consulted: 42,
352 completeness_confidence: 0.95,
353 };
354 let json = serde_json::to_string(&trace).unwrap();
355 let back: ResolutionTrace = serde_json::from_str(&json).unwrap();
356 assert_eq!(back.levels_attempted.len(), 2);
357 assert_eq!(back.levels_contributed.len(), 1);
358 assert_eq!(back.prior_episodes_consulted, 42);
359 assert!((back.completeness_confidence - 0.95).abs() < f64::EPSILON);
360 }
361}