Skip to main content

feltdb/
routing.rs

1/// Distributed Capability Routing module
2///
3/// Handles routing capability execution requests to appropriate peers based on:
4/// - Local vs remote capability availability
5/// - Required state version compatibility
6/// - Peer state acquisition capabilities
7/// - Execution ownership semantics
8use crate::{CapabilityExecutionResult, CapabilityRef, DistributedCapabilityAdvertisement};
9use serde::{Deserialize, Serialize};
10use std::collections::HashMap;
11
12/// Route decision for capability execution
13#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
14pub enum RouteDecision {
15    /// Execute locally
16    Local,
17    /// Execute remotely at specific peer
18    Remote(String), // peer_id
19    /// No suitable provider found
20    Unavailable,
21}
22
23/// State version tracking for a peer
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct PeerStateVersion {
26    /// Peer ID
27    pub peer_id: String,
28    /// Namespace
29    pub namespace: String,
30    /// Current version at this peer
31    pub version: u64,
32    /// When the version was last updated
33    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
47/// State-aware capability routing engine
48pub struct CapabilityRouter {
49    /// Local capabilities
50    local_capabilities: HashMap<String, DistributedCapabilityAdvertisement>,
51    /// Remote capabilities by peer
52    remote_capabilities: HashMap<String, Vec<DistributedCapabilityAdvertisement>>,
53    /// Peer state versions
54    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    /// Register a local capability
67    pub fn register_local(&mut self, ad: DistributedCapabilityAdvertisement) {
68        self.local_capabilities
69            .insert(ad.capability_ref.canonical(), ad);
70    }
71
72    /// Register a remote capability
73    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    /// Update peer state version
82    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    /// Get the state version a peer has for a namespace
89    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    /// Route a capability execution request
98    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        // Check if capability is available locally
107        if self.local_capabilities.contains_key(&canonical) {
108            // Check if local state meets requirements
109            if self.check_state_compatibility(local_peer_id, cap_ref, required_state_version) {
110                return RouteDecision::Local;
111            }
112        }
113
114        // Find remote providers with compatible state
115        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    /// Check if a peer has compatible state version
125    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    /// Find providers with compatible state versions
136    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    /// Check if capability requires state acquisition
157    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    /// Plan state acquisition - list namespaces that need to be acquired
168    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
189/// Execution claim tracker for single-owner semantics
190pub struct ExecutionClaimer {
191    /// Claims by execution ID
192    claims: HashMap<String, ExecutionClaim>,
193}
194
195/// A claim on an execution
196#[derive(Debug, Clone, Serialize, Deserialize)]
197pub struct ExecutionClaim {
198    /// Execution ID
199    pub execution_id: String,
200    /// Peer that claimed it
201    pub claimant: String,
202    /// Timestamp of claim
203    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    /// Attempt to claim an execution
224    /// Returns the claim if successful, None if already claimed
225    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    /// Get the claimant of an execution
236    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    /// Check if execution is claimed by a specific peer
241    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
255/// Result publication tracker
256pub struct ExecutionResultPublisher {
257    /// Published results by execution ID
258    results: HashMap<String, CapabilityExecutionResult>,
259}
260
261impl ExecutionResultPublisher {
262    pub fn new() -> Self {
263        Self {
264            results: HashMap::new(),
265        }
266    }
267
268    /// Publish an execution result
269    pub fn publish(&mut self, result: CapabilityExecutionResult) {
270        self.results
271            .insert(result.execution_ref.execution_id.clone(), result);
272    }
273
274    /// Get a published result
275    pub fn get_result(&self, execution_id: &str) -> Option<&CapabilityExecutionResult> {
276        self.results.get(execution_id)
277    }
278
279    /// Check if result exists
280    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        // Peer only has version 800, but we need 851
353        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}