1pub mod session;
29
30pub use session::{
31 DistributedInferenceSession, DistributedReasonerConfig, DistributedReasonerV2,
32 InferenceRequest, InferenceResponse, InferenceResultStream, PartialResult, ReasoningError,
33 RemoteResult, SessionMetrics, SessionStats,
34};
35
36use crate::ir::{KnowledgeBase, Predicate, Rule, Term};
37use crate::proof_storage::{ProofFragment, ProofFragmentRef};
38use crate::reasoning::{Proof, Substitution};
39use async_trait::async_trait;
40use ipfrs_core::Cid;
41use serde::{Deserialize, Serialize};
42use std::collections::{HashMap, HashSet};
43use std::sync::Arc;
44use thiserror::Error;
45
46#[derive(Debug, Error)]
48pub enum RemoteReasoningError {
49 #[error("Network error: {0}")]
50 NetworkError(String),
51
52 #[error("Timeout waiting for remote response")]
53 Timeout,
54
55 #[error("Invalid response from peer: {0}")]
56 InvalidResponse(String),
57
58 #[error("Peer not found: {0}")]
59 PeerNotFound(String),
60
61 #[error("No peers available for query")]
62 NoPeersAvailable,
63
64 #[error("Serialization error: {0}")]
65 SerializationError(String),
66
67 #[error("Remote query failed: {0}")]
68 QueryFailed(String),
69}
70
71#[derive(Debug, Clone, Serialize, Deserialize)]
73pub struct QueryRequest {
74 pub predicate_name: String,
76
77 pub ground_args: Vec<String>,
79
80 pub max_results: usize,
82
83 pub max_depth: usize,
85
86 pub request_id: String,
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize)]
92pub struct QueryResponse {
93 pub request_id: String,
95
96 pub predicates: Vec<Predicate>,
98
99 pub rules: Vec<Rule>,
101
102 pub proof_fragments: Vec<ProofFragmentRef>,
104
105 pub peer_id: String,
107
108 pub has_more: bool,
110
111 pub continuation_token: Option<String>,
113}
114
115#[derive(Debug, Clone, Serialize, Deserialize)]
117pub struct FactDiscoveryRequest {
118 pub predicate_name: String,
120
121 pub arg_patterns: Vec<Option<String>>,
123
124 pub max_hops: usize,
126
127 pub ttl: u32,
129
130 pub exclude_peers: HashSet<String>,
132}
133
134#[derive(Debug, Clone, Serialize, Deserialize)]
136pub struct FactDiscoveryResponse {
137 pub facts: Vec<Predicate>,
139
140 pub sources: HashMap<usize, String>, pub peers_queried: usize,
145
146 pub hops: HashMap<usize, usize>, }
149
150#[derive(Debug, Clone, Serialize, Deserialize)]
152pub struct IncrementalLoadRequest {
153 pub predicate_name: String,
155
156 pub batch_size: usize,
158
159 pub offset: usize,
161
162 pub filter: Option<HashMap<String, String>>,
164}
165
166#[derive(Debug, Clone, Serialize, Deserialize)]
168pub struct IncrementalLoadResponse {
169 pub batch: Vec<Predicate>,
171
172 pub total_count: usize,
174
175 pub next_offset: Option<usize>,
177
178 pub is_last: bool,
180}
181
182#[derive(Debug, Clone, Serialize, Deserialize)]
184pub struct GoalResolutionRequest {
185 pub goal: Predicate,
187
188 pub substitution: HashMap<String, Term>,
190
191 pub depth: usize,
193
194 pub requester: String,
196
197 pub request_id: String,
199}
200
201#[derive(Debug, Clone, Serialize, Deserialize)]
203pub struct GoalResolutionResponse {
204 pub request_id: String,
206
207 pub solved: bool,
209
210 pub solutions: Vec<HashMap<String, Term>>,
212
213 pub proof: Option<Proof>,
215
216 pub proof_fragments: Vec<ProofFragmentRef>,
218}
219
220#[async_trait]
222pub trait RemoteKnowledgeProvider: Send + Sync {
223 async fn query_predicate(
225 &self,
226 request: QueryRequest,
227 ) -> Result<QueryResponse, RemoteReasoningError>;
228
229 async fn discover_facts(
231 &self,
232 request: FactDiscoveryRequest,
233 ) -> Result<FactDiscoveryResponse, RemoteReasoningError>;
234
235 async fn load_incremental(
237 &self,
238 request: IncrementalLoadRequest,
239 ) -> Result<IncrementalLoadResponse, RemoteReasoningError>;
240
241 async fn resolve_goal(
243 &self,
244 request: GoalResolutionRequest,
245 ) -> Result<GoalResolutionResponse, RemoteReasoningError>;
246
247 async fn get_available_peers(&self) -> Result<Vec<String>, RemoteReasoningError>;
249}
250
251pub struct DistributedGoalResolver {
253 local_kb: Arc<KnowledgeBase>,
255
256 remote_provider: Option<Arc<dyn RemoteKnowledgeProvider>>,
258
259 max_depth: usize,
261
262 timeout_ms: u64,
264
265 remote_fact_cache: HashMap<String, Vec<Predicate>>,
267}
268
269impl DistributedGoalResolver {
270 pub fn new(local_kb: Arc<KnowledgeBase>) -> Self {
272 Self {
273 local_kb,
274 remote_provider: None,
275 max_depth: 10,
276 timeout_ms: 5000,
277 remote_fact_cache: HashMap::new(),
278 }
279 }
280
281 pub fn with_provider(mut self, provider: Arc<dyn RemoteKnowledgeProvider>) -> Self {
283 self.remote_provider = Some(provider);
284 self
285 }
286
287 pub fn with_max_depth(mut self, max_depth: usize) -> Self {
289 self.max_depth = max_depth;
290 self
291 }
292
293 pub fn with_timeout(mut self, timeout_ms: u64) -> Self {
295 self.timeout_ms = timeout_ms;
296 self
297 }
298
299 pub async fn resolve(
301 &mut self,
302 goal: &Predicate,
303 substitution: &Substitution,
304 ) -> Result<Vec<Substitution>, RemoteReasoningError> {
305 let local_solutions = self.resolve_local(goal, substitution);
307
308 if !local_solutions.is_empty() {
309 return Ok(local_solutions);
310 }
311
312 if let Some(provider) = self.remote_provider.clone() {
314 let remote_solutions = self.resolve_remote(goal, substitution, &provider).await?;
315 Ok(remote_solutions)
316 } else {
317 Ok(Vec::new())
318 }
319 }
320
321 fn resolve_local(&self, goal: &Predicate, _substitution: &Substitution) -> Vec<Substitution> {
323 let facts = self.local_kb.get_predicates(&goal.name);
325
326 let mut solutions = Vec::new();
327 for fact in facts {
328 if let Some(subst) =
329 crate::reasoning::unify_predicates(goal, fact, &Substitution::new())
330 {
331 solutions.push(subst);
332 }
333 }
334
335 solutions
336 }
337
338 async fn resolve_remote(
340 &mut self,
341 goal: &Predicate,
342 substitution: &Substitution,
343 provider: &Arc<dyn RemoteKnowledgeProvider>,
344 ) -> Result<Vec<Substitution>, RemoteReasoningError> {
345 let request = GoalResolutionRequest {
347 goal: goal.clone(),
348 substitution: substitution.clone(),
349 depth: 0,
350 requester: "local".to_string(),
351 request_id: uuid::Uuid::new_v4().to_string(),
352 };
353
354 let response = provider.resolve_goal(request).await?;
356
357 Ok(response.solutions)
359 }
360
361 pub async fn prefetch_facts(
363 &mut self,
364 predicate_name: &str,
365 ) -> Result<usize, RemoteReasoningError> {
366 let Some(provider) = &self.remote_provider else {
367 return Ok(0);
368 };
369
370 let request = FactDiscoveryRequest {
372 predicate_name: predicate_name.to_string(),
373 arg_patterns: Vec::new(),
374 max_hops: 3,
375 ttl: 30,
376 exclude_peers: HashSet::new(),
377 };
378
379 let response = provider.discover_facts(request).await?;
381
382 let count = response.facts.len();
384 self.remote_fact_cache
385 .insert(predicate_name.to_string(), response.facts);
386
387 Ok(count)
388 }
389
390 pub fn get_cached_facts(&self, predicate_name: &str) -> Option<&[Predicate]> {
392 self.remote_fact_cache
393 .get(predicate_name)
394 .map(|v| v.as_slice())
395 }
396
397 pub fn clear_cache(&mut self) {
399 self.remote_fact_cache.clear();
400 }
401}
402
403pub struct DistributedProofAssembler {
405 remote_provider: Arc<dyn RemoteKnowledgeProvider>,
407
408 fragment_cache: HashMap<Cid, ProofFragment>,
410
411 #[allow(dead_code)]
413 max_depth: usize,
414}
415
416impl DistributedProofAssembler {
417 pub fn new(remote_provider: Arc<dyn RemoteKnowledgeProvider>) -> Self {
419 Self {
420 remote_provider,
421 fragment_cache: HashMap::new(),
422 max_depth: 100,
423 }
424 }
425
426 pub async fn assemble_proof(
428 &mut self,
429 goal: &Predicate,
430 ) -> Result<Option<Proof>, RemoteReasoningError> {
431 let request = GoalResolutionRequest {
433 goal: goal.clone(),
434 substitution: HashMap::new(),
435 depth: 0,
436 requester: "local".to_string(),
437 request_id: uuid::Uuid::new_v4().to_string(),
438 };
439
440 let response = self.remote_provider.resolve_goal(request).await?;
441
442 if response.solved {
443 Ok(response.proof)
444 } else {
445 Ok(None)
446 }
447 }
448
449 pub async fn fetch_fragment(
451 &mut self,
452 cid: Cid,
453 ) -> Result<ProofFragment, RemoteReasoningError> {
454 if let Some(fragment) = self.fragment_cache.get(&cid) {
456 return Ok(fragment.clone());
457 }
458
459 Err(RemoteReasoningError::NetworkError(
462 "Fragment fetch not yet implemented".to_string(),
463 ))
464 }
465}
466
467pub struct MockRemoteKnowledgeProvider {
469 mock_kb: Arc<KnowledgeBase>,
471}
472
473impl MockRemoteKnowledgeProvider {
474 pub fn new(mock_kb: Arc<KnowledgeBase>) -> Self {
476 Self { mock_kb }
477 }
478}
479
480#[async_trait]
481impl RemoteKnowledgeProvider for MockRemoteKnowledgeProvider {
482 async fn query_predicate(
483 &self,
484 request: QueryRequest,
485 ) -> Result<QueryResponse, RemoteReasoningError> {
486 let predicates = self
487 .mock_kb
488 .get_predicates(&request.predicate_name)
489 .into_iter()
490 .take(request.max_results)
491 .cloned()
492 .collect();
493
494 let rules = self
495 .mock_kb
496 .get_rules(&request.predicate_name)
497 .into_iter()
498 .take(request.max_results)
499 .cloned()
500 .collect();
501
502 Ok(QueryResponse {
503 request_id: request.request_id,
504 predicates,
505 rules,
506 proof_fragments: Vec::new(),
507 peer_id: "mock_peer".to_string(),
508 has_more: false,
509 continuation_token: None,
510 })
511 }
512
513 async fn discover_facts(
514 &self,
515 request: FactDiscoveryRequest,
516 ) -> Result<FactDiscoveryResponse, RemoteReasoningError> {
517 let facts: Vec<Predicate> = self
518 .mock_kb
519 .get_predicates(&request.predicate_name)
520 .into_iter()
521 .cloned()
522 .collect();
523
524 let sources: HashMap<usize, String> = (0..facts.len())
525 .map(|i| (i, "mock_peer".to_string()))
526 .collect();
527
528 let hops: HashMap<usize, usize> = (0..facts.len()).map(|i| (i, 0)).collect();
529
530 Ok(FactDiscoveryResponse {
531 facts,
532 sources,
533 peers_queried: 1,
534 hops,
535 })
536 }
537
538 async fn load_incremental(
539 &self,
540 request: IncrementalLoadRequest,
541 ) -> Result<IncrementalLoadResponse, RemoteReasoningError> {
542 let all_facts: Vec<Predicate> = self
543 .mock_kb
544 .get_predicates(&request.predicate_name)
545 .into_iter()
546 .cloned()
547 .collect();
548
549 let total_count = all_facts.len();
550 let start = request.offset;
551 let end = (start + request.batch_size).min(total_count);
552
553 let batch = all_facts[start..end].to_vec();
554 let is_last = end >= total_count;
555 let next_offset = if is_last { None } else { Some(end) };
556
557 Ok(IncrementalLoadResponse {
558 batch,
559 total_count,
560 next_offset,
561 is_last,
562 })
563 }
564
565 async fn resolve_goal(
566 &self,
567 request: GoalResolutionRequest,
568 ) -> Result<GoalResolutionResponse, RemoteReasoningError> {
569 let facts = self.mock_kb.get_predicates(&request.goal.name);
571 let mut solutions = Vec::new();
572
573 for fact in facts {
574 if let Some(subst) =
575 crate::reasoning::unify_predicates(&request.goal, fact, &Substitution::new())
576 {
577 solutions.push(subst);
578 }
579 }
580
581 let solved = !solutions.is_empty();
582 let proof = if solved {
583 Some(Proof::fact(request.goal.clone()))
584 } else {
585 None
586 };
587
588 Ok(GoalResolutionResponse {
589 request_id: request.request_id,
590 solved,
591 solutions,
592 proof,
593 proof_fragments: Vec::new(),
594 })
595 }
596
597 async fn get_available_peers(&self) -> Result<Vec<String>, RemoteReasoningError> {
598 Ok(vec!["mock_peer".to_string()])
599 }
600}
601
602#[cfg(test)]
603mod tests {
604 use super::*;
605 use crate::ir::Constant;
606
607 #[tokio::test]
608 async fn test_query_request_serialization() {
609 let request = QueryRequest {
610 predicate_name: "parent".to_string(),
611 ground_args: vec!["Alice".to_string()],
612 max_results: 10,
613 max_depth: 5,
614 request_id: "test_123".to_string(),
615 };
616
617 let json = serde_json::to_string(&request).expect("test: should succeed");
618 let decoded: QueryRequest = serde_json::from_str(&json).expect("test: should succeed");
619
620 assert_eq!(request.predicate_name, decoded.predicate_name);
621 assert_eq!(request.ground_args, decoded.ground_args);
622 }
623
624 #[tokio::test]
625 async fn test_mock_provider_query() {
626 let mut kb = KnowledgeBase::new();
627 kb.add_fact(Predicate::new(
628 "parent".to_string(),
629 vec![
630 Term::Const(Constant::String("Alice".to_string())),
631 Term::Const(Constant::String("Bob".to_string())),
632 ],
633 ));
634
635 let provider = MockRemoteKnowledgeProvider::new(Arc::new(kb));
636
637 let request = QueryRequest {
638 predicate_name: "parent".to_string(),
639 ground_args: vec![],
640 max_results: 10,
641 max_depth: 5,
642 request_id: "test_123".to_string(),
643 };
644
645 let response = provider
646 .query_predicate(request)
647 .await
648 .expect("test: should succeed");
649 assert_eq!(response.predicates.len(), 1);
650 assert_eq!(response.predicates[0].name, "parent");
651 }
652
653 #[tokio::test]
654 async fn test_distributed_resolver() {
655 let mut local_kb = KnowledgeBase::new();
656 local_kb.add_fact(Predicate::new(
657 "parent".to_string(),
658 vec![
659 Term::Const(Constant::String("Alice".to_string())),
660 Term::Const(Constant::String("Bob".to_string())),
661 ],
662 ));
663
664 let mut resolver = DistributedGoalResolver::new(Arc::new(local_kb));
665
666 let goal = Predicate::new(
667 "parent".to_string(),
668 vec![
669 Term::Const(Constant::String("Alice".to_string())),
670 Term::Var("X".to_string()),
671 ],
672 );
673
674 let solutions = resolver
675 .resolve(&goal, &Substitution::new())
676 .await
677 .expect("test: should succeed");
678 assert!(!solutions.is_empty());
679 }
680
681 #[tokio::test]
682 async fn test_fact_discovery() {
683 let mut kb = KnowledgeBase::new();
684 kb.add_fact(Predicate::new(
685 "parent".to_string(),
686 vec![
687 Term::Const(Constant::String("Alice".to_string())),
688 Term::Const(Constant::String("Bob".to_string())),
689 ],
690 ));
691 kb.add_fact(Predicate::new(
692 "parent".to_string(),
693 vec![
694 Term::Const(Constant::String("Bob".to_string())),
695 Term::Const(Constant::String("Charlie".to_string())),
696 ],
697 ));
698
699 let provider = MockRemoteKnowledgeProvider::new(Arc::new(kb));
700
701 let request = FactDiscoveryRequest {
702 predicate_name: "parent".to_string(),
703 arg_patterns: vec![],
704 max_hops: 3,
705 ttl: 30,
706 exclude_peers: HashSet::new(),
707 };
708
709 let response = provider
710 .discover_facts(request)
711 .await
712 .expect("test: should succeed");
713 assert_eq!(response.facts.len(), 2);
714 assert_eq!(response.peers_queried, 1);
715 }
716
717 #[tokio::test]
718 async fn test_incremental_loading() {
719 let mut kb = KnowledgeBase::new();
720 for i in 0..10 {
721 kb.add_fact(Predicate::new(
722 "number".to_string(),
723 vec![Term::Const(Constant::Int(i))],
724 ));
725 }
726
727 let provider = MockRemoteKnowledgeProvider::new(Arc::new(kb));
728
729 let request = IncrementalLoadRequest {
731 predicate_name: "number".to_string(),
732 batch_size: 3,
733 offset: 0,
734 filter: None,
735 };
736
737 let response = provider
738 .load_incremental(request)
739 .await
740 .expect("test: should succeed");
741 assert_eq!(response.batch.len(), 3);
742 assert_eq!(response.total_count, 10);
743 assert!(!response.is_last);
744 assert_eq!(response.next_offset, Some(3));
745 }
746
747 #[tokio::test]
748 async fn test_goal_resolution() {
749 let mut kb = KnowledgeBase::new();
750 kb.add_fact(Predicate::new(
751 "parent".to_string(),
752 vec![
753 Term::Const(Constant::String("Alice".to_string())),
754 Term::Const(Constant::String("Bob".to_string())),
755 ],
756 ));
757
758 let provider = MockRemoteKnowledgeProvider::new(Arc::new(kb));
759
760 let goal = Predicate::new(
761 "parent".to_string(),
762 vec![
763 Term::Const(Constant::String("Alice".to_string())),
764 Term::Var("X".to_string()),
765 ],
766 );
767
768 let request = GoalResolutionRequest {
769 goal,
770 substitution: HashMap::new(),
771 depth: 0,
772 requester: "test".to_string(),
773 request_id: "test_123".to_string(),
774 };
775
776 let response = provider
777 .resolve_goal(request)
778 .await
779 .expect("test: should succeed");
780 assert!(response.solved);
781 assert!(!response.solutions.is_empty());
782 assert!(response.proof.is_some());
783 }
784}