1use crate::{CapabilityExecutionResult, CapabilityRef, DistributedCapabilityAdvertisement};
9use serde::{Deserialize, Serialize};
10use std::collections::HashMap;
11
12#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
14pub enum RouteDecision {
15 Local,
17 Remote(String), Unavailable,
21}
22
23#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct PeerStateVersion {
26 pub peer_id: String,
28 pub namespace: String,
30 pub version: u64,
32 pub updated_ms: u128,
34}
35
36impl PeerStateVersion {
37 pub fn new(peer_id: String, namespace: String, version: u64) -> Self {
38 Self {
39 peer_id,
40 namespace,
41 version,
42 updated_ms: crate::now_ms(),
43 }
44 }
45}
46
47pub struct CapabilityRouter {
49 local_capabilities: HashMap<String, DistributedCapabilityAdvertisement>,
51 remote_capabilities: HashMap<String, Vec<DistributedCapabilityAdvertisement>>,
53 peer_state_versions: HashMap<String, PeerStateVersion>,
55}
56
57impl CapabilityRouter {
58 pub fn new() -> Self {
59 Self {
60 local_capabilities: HashMap::new(),
61 remote_capabilities: HashMap::new(),
62 peer_state_versions: HashMap::new(),
63 }
64 }
65
66 pub fn register_local(&mut self, ad: DistributedCapabilityAdvertisement) {
68 self.local_capabilities
69 .insert(ad.capability_ref.canonical(), ad);
70 }
71
72 pub fn register_remote(&mut self, ad: DistributedCapabilityAdvertisement) {
74 let peer_id = ad.peer_id.clone();
75 self.remote_capabilities
76 .entry(peer_id)
77 .or_insert_with(Vec::new)
78 .push(ad);
79 }
80
81 pub fn update_peer_state(&mut self, peer_id: String, namespace: String, version: u64) {
83 let key = format!("{}:{}", peer_id, namespace);
84 self.peer_state_versions
85 .insert(key, PeerStateVersion::new(peer_id, namespace, version));
86 }
87
88 pub fn get_peer_state_version(&self, peer_id: &str, namespace: &str) -> u64 {
90 let key = format!("{}:{}", peer_id, namespace);
91 self.peer_state_versions
92 .get(&key)
93 .map(|v| v.version)
94 .unwrap_or(0)
95 }
96
97 pub fn route(
99 &self,
100 cap_ref: &CapabilityRef,
101 required_state_version: u64,
102 local_peer_id: &str,
103 ) -> RouteDecision {
104 let canonical = cap_ref.canonical();
105
106 if self.local_capabilities.contains_key(&canonical) {
108 if self.check_state_compatibility(local_peer_id, cap_ref, required_state_version) {
110 return RouteDecision::Local;
111 }
112 }
113
114 let providers = self.find_compatible_providers(cap_ref, required_state_version);
116
117 if let Some(provider) = providers.first() {
118 RouteDecision::Remote(provider.peer_id.clone())
119 } else {
120 RouteDecision::Unavailable
121 }
122 }
123
124 fn check_state_compatibility(
126 &self,
127 peer_id: &str,
128 cap_ref: &CapabilityRef,
129 required_version: u64,
130 ) -> bool {
131 let peer_version = self.get_peer_state_version(peer_id, &cap_ref.namespace);
132 peer_version >= required_version
133 }
134
135 pub fn find_compatible_providers(
137 &self,
138 cap_ref: &CapabilityRef,
139 required_state_version: u64,
140 ) -> Vec<&DistributedCapabilityAdvertisement> {
141 let mut compatible = Vec::new();
142
143 for (peer_id, capabilities) in &self.remote_capabilities {
144 for cap_ad in capabilities {
145 if cap_ad.capability_ref.canonical() == cap_ref.canonical() {
146 if self.check_state_compatibility(peer_id, cap_ref, required_state_version) {
147 compatible.push(cap_ad);
148 }
149 }
150 }
151 }
152
153 compatible
154 }
155
156 pub fn requires_state_acquisition(
158 &self,
159 provider_peer: &str,
160 cap_ref: &CapabilityRef,
161 required_state_version: u64,
162 ) -> bool {
163 let peer_version = self.get_peer_state_version(provider_peer, &cap_ref.namespace);
164 peer_version < required_state_version
165 }
166
167 pub fn plan_state_acquisition(
169 &self,
170 provider_peer: &str,
171 cap_ref: &CapabilityRef,
172 required_state_version: u64,
173 ) -> Vec<String> {
174 let peer_version = self.get_peer_state_version(provider_peer, &cap_ref.namespace);
175 if peer_version < required_state_version {
176 vec![cap_ref.namespace.clone()]
177 } else {
178 Vec::new()
179 }
180 }
181}
182
183impl Default for CapabilityRouter {
184 fn default() -> Self {
185 Self::new()
186 }
187}
188
189pub struct ExecutionClaimer {
191 claims: HashMap<String, ExecutionClaim>,
193}
194
195#[derive(Debug, Clone, Serialize, Deserialize)]
197pub struct ExecutionClaim {
198 pub execution_id: String,
200 pub claimant: String,
202 pub claimed_ms: u128,
204}
205
206impl ExecutionClaim {
207 pub fn new(execution_id: String, claimant: String) -> Self {
208 Self {
209 execution_id,
210 claimant,
211 claimed_ms: crate::now_ms(),
212 }
213 }
214}
215
216impl ExecutionClaimer {
217 pub fn new() -> Self {
218 Self {
219 claims: HashMap::new(),
220 }
221 }
222
223 pub fn try_claim(&mut self, execution_id: String, claimant: String) -> Option<ExecutionClaim> {
226 if self.claims.contains_key(&execution_id) {
227 None
228 } else {
229 let claim = ExecutionClaim::new(execution_id.clone(), claimant);
230 self.claims.insert(execution_id, claim.clone());
231 Some(claim)
232 }
233 }
234
235 pub fn get_claimant(&self, execution_id: &str) -> Option<&str> {
237 self.claims.get(execution_id).map(|c| c.claimant.as_str())
238 }
239
240 pub fn is_claimed_by(&self, execution_id: &str, claimant: &str) -> bool {
242 self.claims
243 .get(execution_id)
244 .map(|c| c.claimant == claimant)
245 .unwrap_or(false)
246 }
247}
248
249impl Default for ExecutionClaimer {
250 fn default() -> Self {
251 Self::new()
252 }
253}
254
255pub struct ExecutionResultPublisher {
257 results: HashMap<String, CapabilityExecutionResult>,
259}
260
261impl ExecutionResultPublisher {
262 pub fn new() -> Self {
263 Self {
264 results: HashMap::new(),
265 }
266 }
267
268 pub fn publish(&mut self, result: CapabilityExecutionResult) {
270 self.results
271 .insert(result.execution_ref.execution_id.clone(), result);
272 }
273
274 pub fn get_result(&self, execution_id: &str) -> Option<&CapabilityExecutionResult> {
276 self.results.get(execution_id)
277 }
278
279 pub fn has_result(&self, execution_id: &str) -> bool {
281 self.results.contains_key(execution_id)
282 }
283}
284
285impl Default for ExecutionResultPublisher {
286 fn default() -> Self {
287 Self::new()
288 }
289}
290
291#[cfg(test)]
292mod tests {
293 use super::*;
294 use crate::ExecutionRef;
295
296 fn make_cap_ref(namespace: &str, capability: &str, version: u32) -> CapabilityRef {
297 CapabilityRef::new(namespace.to_string(), capability.to_string(), version)
298 }
299
300 fn make_capability_ad(
301 namespace: &str,
302 capability: &str,
303 version: u32,
304 peer_id: &str,
305 ) -> DistributedCapabilityAdvertisement {
306 DistributedCapabilityAdvertisement::new(
307 make_cap_ref(namespace, capability, version),
308 peer_id.to_string(),
309 )
310 }
311
312 #[test]
313 fn test_router_local_capability() {
314 let mut router = CapabilityRouter::new();
315 let ad = make_capability_ad("docs", "vector-search", 1, "local");
316 router.register_local(ad);
317
318 router.update_peer_state("local".to_string(), "docs".to_string(), 851);
319
320 let cap_ref = make_cap_ref("docs", "vector-search", 1);
321 let decision = router.route(&cap_ref, 851, "local");
322 assert_eq!(decision, RouteDecision::Local);
323 }
324
325 #[test]
326 fn test_router_remote_capability() {
327 let mut router = CapabilityRouter::new();
328 let ad = make_capability_ad("docs", "vector-search", 1, "peer-b");
329 router.register_remote(ad);
330
331 router.update_peer_state("peer-b".to_string(), "docs".to_string(), 851);
332
333 let cap_ref = make_cap_ref("docs", "vector-search", 1);
334 let decision = router.route(&cap_ref, 851, "peer-a");
335 assert_eq!(decision, RouteDecision::Remote("peer-b".to_string()));
336 }
337
338 #[test]
339 fn test_router_unavailable() {
340 let router = CapabilityRouter::new();
341 let cap_ref = make_cap_ref("docs", "vector-search", 1);
342 let decision = router.route(&cap_ref, 851, "peer-a");
343 assert_eq!(decision, RouteDecision::Unavailable);
344 }
345
346 #[test]
347 fn test_router_state_mismatch() {
348 let mut router = CapabilityRouter::new();
349 let ad = make_capability_ad("docs", "vector-search", 1, "peer-b");
350 router.register_remote(ad);
351
352 router.update_peer_state("peer-b".to_string(), "docs".to_string(), 800);
354
355 let cap_ref = make_cap_ref("docs", "vector-search", 1);
356 let decision = router.route(&cap_ref, 851, "peer-a");
357 assert_eq!(decision, RouteDecision::Unavailable);
358 }
359
360 #[test]
361 fn test_router_requires_state_acquisition() {
362 let mut router = CapabilityRouter::new();
363 router.update_peer_state("peer-b".to_string(), "docs".to_string(), 800);
364
365 let cap_ref = make_cap_ref("docs", "vector-search", 1);
366 assert!(router.requires_state_acquisition("peer-b", &cap_ref, 851));
367 }
368
369 #[test]
370 fn test_execution_claimer_single_claim() {
371 let mut claimer = ExecutionClaimer::new();
372 let claim = claimer.try_claim("exec-123".to_string(), "peer-a".to_string());
373 assert!(claim.is_some());
374 assert_eq!(claim.unwrap().claimant, "peer-a");
375 }
376
377 #[test]
378 fn test_execution_claimer_duplicate_claim() {
379 let mut claimer = ExecutionClaimer::new();
380 let claim1 = claimer.try_claim("exec-123".to_string(), "peer-a".to_string());
381 let claim2 = claimer.try_claim("exec-123".to_string(), "peer-b".to_string());
382
383 assert!(claim1.is_some());
384 assert!(claim2.is_none());
385 }
386
387 #[test]
388 fn test_execution_claimer_get_claimant() {
389 let mut claimer = ExecutionClaimer::new();
390 claimer.try_claim("exec-123".to_string(), "peer-a".to_string());
391
392 let claimant = claimer.get_claimant("exec-123");
393 assert_eq!(claimant, Some("peer-a"));
394 }
395
396 #[test]
397 fn test_execution_result_publisher() {
398 let mut publisher = ExecutionResultPublisher::new();
399 let cap_ref = make_cap_ref("docs", "vector-search", 1);
400 let exec_ref = ExecutionRef::new("exec-123".to_string(), cap_ref, "peer-b".to_string());
401 let result = serde_json::json!({"results": [1, 2, 3]});
402 let exec_result = CapabilityExecutionResult::success(exec_ref, result);
403
404 publisher.publish(exec_result);
405
406 assert!(publisher.has_result("exec-123"));
407 assert!(publisher.get_result("exec-123").is_some());
408 }
409}