Skip to main content

m1nd_core/
lib.rs

1#![allow(unused)]
2
3pub mod activation;
4pub mod antibody;
5pub mod builder;
6pub mod calibration;
7pub mod counterfactual;
8pub mod domain;
9#[cfg(feature = "embed")]
10pub mod embed;
11#[cfg(feature = "embed")]
12pub mod embed_cache;
13pub mod epidemic;
14pub mod error;
15pub mod flow;
16pub mod git_history;
17pub mod graph;
18pub mod layer;
19pub mod plasticity;
20pub mod query;
21pub mod refactor;
22pub mod resonance;
23pub mod runtime_overlay;
24pub mod seed;
25pub mod semantic;
26pub mod snapshot;
27pub mod snapshot_bin;
28pub mod taint;
29pub mod temporal;
30pub mod topology;
31pub mod tremor;
32pub mod trust;
33pub mod twins;
34pub mod types;
35pub mod xlr;
36
37#[cfg(test)]
38mod tests {
39    use crate::activation::*;
40    use crate::counterfactual::*;
41    use crate::error::*;
42    use crate::graph::*;
43    use crate::plasticity::*;
44    use crate::query::*;
45    use crate::resonance::*;
46    use crate::seed::*;
47    use crate::temporal::*;
48    use crate::topology::*;
49    use crate::types::*;
50    use crate::xlr::*;
51
52    // ===== STEP-001: types.rs tests =====
53
54    #[test]
55    #[cfg(debug_assertions)]
56    #[should_panic(expected = "non-finite")]
57    fn finite_f32_rejects_nan() {
58        // debug_assert! fires in test (debug) builds only
59        let _f = FiniteF32::new(f32::NAN);
60    }
61
62    #[test]
63    #[cfg(debug_assertions)]
64    #[should_panic(expected = "non-finite")]
65    fn finite_f32_rejects_inf() {
66        // debug_assert! fires in test (debug) builds only
67        let _f = FiniteF32::new(f32::INFINITY);
68    }
69
70    #[test]
71    fn finite_f32_accepts_normal() {
72        let f = FiniteF32::new(1.0);
73        assert_eq!(f.get(), 1.0);
74    }
75
76    #[test]
77    fn finite_f32_total_order() {
78        let a = FiniteF32::new(0.5);
79        let b = FiniteF32::new(0.7);
80        assert!(a < b);
81        assert_eq!(a.cmp(&a), std::cmp::Ordering::Equal);
82    }
83
84    #[test]
85    fn pos_f32_rejects_zero() {
86        assert!(PosF32::new(0.0).is_none());
87    }
88
89    #[test]
90    fn pos_f32_rejects_negative() {
91        assert!(PosF32::new(-1.0).is_none());
92    }
93
94    #[test]
95    fn pos_f32_accepts_positive() {
96        assert!(PosF32::new(0.001).is_some());
97    }
98
99    #[test]
100    fn learning_rate_range() {
101        assert!(LearningRate::new(0.0).is_none());
102        assert!(LearningRate::new(1.1).is_none());
103        assert!(LearningRate::new(0.5).is_some());
104        assert!(LearningRate::new(1.0).is_some());
105    }
106
107    #[test]
108    fn decay_factor_range() {
109        assert!(DecayFactor::new(0.0).is_none());
110        assert!(DecayFactor::new(1.1).is_none());
111        assert!(DecayFactor::new(0.55).is_some());
112    }
113
114    // ===== STEP-002: error.rs tests =====
115
116    #[test]
117    fn error_display_empty_graph() {
118        let e = M1ndError::EmptyGraph;
119        let msg = format!("{e}");
120        assert!(msg.contains("empty"), "Expected 'empty' in: {msg}");
121    }
122
123    #[test]
124    fn error_display_dangling_edge() {
125        let e = M1ndError::DanglingEdge {
126            edge: EdgeIdx::new(0),
127            node: NodeId::new(999),
128        };
129        let msg = format!("{e}");
130        assert!(msg.contains("dangling"));
131    }
132
133    // ===== STEP-003: graph.rs tests =====
134
135    fn build_test_graph() -> Graph {
136        let mut g = Graph::new();
137        g.add_node(
138            "mat_pe",
139            "Polietileno",
140            NodeType::Material,
141            &["plastico", "polimero"],
142            1000.0,
143            0.5,
144        )
145        .unwrap();
146        g.add_node(
147            "mat_pp",
148            "Polipropileno",
149            NodeType::Material,
150            &["plastico", "polimero"],
151            900.0,
152            0.3,
153        )
154        .unwrap();
155        g.add_node(
156            "mat_abs",
157            "ABS",
158            NodeType::Material,
159            &["plastico"],
160            800.0,
161            0.2,
162        )
163        .unwrap();
164        g.add_node(
165            "proc_inj",
166            "Injecao",
167            NodeType::Process,
168            &["processo"],
169            700.0,
170            0.4,
171        )
172        .unwrap();
173        g.add_node(
174            "proc_ext",
175            "Extrusao",
176            NodeType::Process,
177            &["processo"],
178            600.0,
179            0.1,
180        )
181        .unwrap();
182        g.add_node(
183            "prod_garrafa",
184            "Garrafa",
185            NodeType::Product,
186            &["produto"],
187            500.0,
188            0.6,
189        )
190        .unwrap();
191
192        g.add_edge(
193            NodeId::new(0),
194            NodeId::new(3),
195            "feeds_into",
196            FiniteF32::new(0.8),
197            EdgeDirection::Forward,
198            false,
199            FiniteF32::new(0.5),
200        )
201        .unwrap();
202        g.add_edge(
203            NodeId::new(1),
204            NodeId::new(3),
205            "feeds_into",
206            FiniteF32::new(0.7),
207            EdgeDirection::Forward,
208            false,
209            FiniteF32::new(0.3),
210        )
211        .unwrap();
212        g.add_edge(
213            NodeId::new(2),
214            NodeId::new(4),
215            "feeds_into",
216            FiniteF32::new(0.6),
217            EdgeDirection::Forward,
218            false,
219            FiniteF32::new(0.2),
220        )
221        .unwrap();
222        g.add_edge(
223            NodeId::new(3),
224            NodeId::new(5),
225            "produces",
226            FiniteF32::new(0.9),
227            EdgeDirection::Forward,
228            false,
229            FiniteF32::new(0.8),
230        )
231        .unwrap();
232        g.add_edge(
233            NodeId::new(0),
234            NodeId::new(1),
235            "similar_to",
236            FiniteF32::new(0.5),
237            EdgeDirection::Bidirectional,
238            false,
239            FiniteF32::ZERO,
240        )
241        .unwrap();
242
243        g.finalize().unwrap();
244        g
245    }
246
247    #[test]
248    fn graph_add_node_and_resolve() {
249        let mut g = Graph::new();
250        let n1 = g
251            .add_node("ext1", "Label1", NodeType::Module, &[], 0.0, 0.0)
252            .unwrap();
253        assert_eq!(n1, NodeId::new(0));
254        assert_eq!(g.num_nodes(), 1);
255        assert_eq!(g.resolve_id("ext1"), Some(NodeId::new(0)));
256    }
257
258    #[test]
259    fn graph_add_node_duplicate() {
260        let mut g = Graph::new();
261        g.add_node("ext1", "label1", NodeType::Module, &[], 0.0, 0.0)
262            .unwrap();
263        let n2 = g.add_node("ext1", "label2", NodeType::Module, &[], 0.0, 0.0);
264        assert!(matches!(n2, Err(M1ndError::DuplicateNode(_))));
265    }
266
267    #[test]
268    fn graph_add_edge_dangling() {
269        let mut g = Graph::new();
270        let n1 = g
271            .add_node("a", "A", NodeType::Module, &[], 0.0, 0.0)
272            .unwrap();
273        let bad = NodeId::new(999);
274        let e = g.add_edge(
275            n1,
276            bad,
277            "calls",
278            FiniteF32::ONE,
279            EdgeDirection::Forward,
280            false,
281            FiniteF32::ZERO,
282        );
283        assert!(matches!(e, Err(M1ndError::DanglingEdge { .. })));
284    }
285
286    #[test]
287    fn graph_finalize_builds_csr() {
288        let g = build_test_graph();
289        assert!(g.finalized);
290        assert_eq!(g.num_nodes(), 6);
291        assert!(g.num_edges() > 0);
292        assert!(g.pagerank_computed);
293    }
294
295    #[test]
296    fn graph_pagerank_computed() {
297        let g = build_test_graph();
298        let max_pr = (0..g.num_nodes() as usize)
299            .map(|i| g.nodes.pagerank[i].get())
300            .fold(0.0f32, f32::max);
301        assert!(max_pr > 0.0, "PageRank should have non-zero values");
302    }
303
304    #[test]
305    fn seed_finder_exact_match() {
306        let g = build_test_graph();
307        let seeds = SeedFinder::find_seeds(&g, "Polietileno", 200).unwrap();
308        assert!(!seeds.is_empty(), "Should find at least one seed");
309        assert_eq!(
310            seeds[0].1.get(),
311            1.0,
312            "Exact match should have relevance 1.0"
313        );
314    }
315
316    #[test]
317    fn seed_finder_tag_match() {
318        let g = build_test_graph();
319        let seeds = SeedFinder::find_seeds(&g, "plastico", 200).unwrap();
320        assert!(!seeds.is_empty(), "Should find seeds by tag");
321    }
322
323    #[test]
324    fn seed_finder_caps_at_max() {
325        let g = build_test_graph();
326        let seeds = SeedFinder::find_seeds(&g, "a", 2).unwrap();
327        assert!(seeds.len() <= 2);
328    }
329
330    #[test]
331    fn seed_finder_prefers_code_path_over_docs_for_same_label() {
332        let mut g = Graph::new();
333        let code = g
334            .add_node(
335                "code_resolve",
336                "resolve",
337                NodeType::Function,
338                &["m1nd"],
339                0.0,
340                0.0,
341            )
342            .unwrap();
343        let docs = g
344            .add_node(
345                "docs_resolve",
346                "resolve",
347                NodeType::File,
348                &["m1nd"],
349                0.0,
350                0.0,
351            )
352            .unwrap();
353
354        g.set_node_provenance(
355            code,
356            NodeProvenanceInput {
357                source_path: Some("~/m1nd/m1nd-core/src/seed.rs"),
358                line_start: Some(1),
359                line_end: Some(4),
360                excerpt: Some("fn resolve()"),
361                namespace: None,
362                canonical: true,
363            },
364        );
365        g.set_node_provenance(
366            docs,
367            NodeProvenanceInput {
368                source_path: Some("~/m1nd/docs/wiki/seed.md"),
369                line_start: Some(1),
370                line_end: Some(4),
371                excerpt: Some("resolve"),
372                namespace: None,
373                canonical: true,
374            },
375        );
376
377        g.finalize().unwrap();
378
379        let seeds = SeedFinder::find_seeds(&g, "resolve", 10).unwrap();
380        assert!(!seeds.is_empty());
381        assert_eq!(seeds[0].0, code, "code path should outrank docs path");
382    }
383
384    #[test]
385    fn seed_finder_prefers_multi_token_code_match_over_generic_single_token_hit() {
386        let mut g = Graph::new();
387        let precise = g
388            .add_node(
389                "file::m1nd-ingest/src/resolve.rs::struct::ResolutionStats",
390                "ResolutionStats",
391                NodeType::Struct,
392                &["rust", "m1nd"],
393                0.0,
394                0.0,
395            )
396            .unwrap();
397        let generic = g
398            .add_node(
399                "graph_concept",
400                "Graph",
401                NodeType::Concept,
402                &["graph"],
403                0.0,
404                0.0,
405            )
406            .unwrap();
407
408        g.set_node_provenance(
409            precise,
410            NodeProvenanceInput {
411                source_path: Some("~/m1nd/m1nd-ingest/src/resolve.rs"),
412                line_start: Some(1),
413                line_end: Some(4),
414                excerpt: Some("struct ResolutionStats"),
415                namespace: None,
416                canonical: true,
417            },
418        );
419        g.set_node_provenance(
420            generic,
421            NodeProvenanceInput {
422                source_path: Some("~/m1nd/docs/wiki/graph.md"),
423                line_start: Some(1),
424                line_end: Some(4),
425                excerpt: Some("Graph overview"),
426                namespace: None,
427                canonical: true,
428            },
429        );
430
431        g.finalize().unwrap();
432
433        let seeds =
434            SeedFinder::find_seeds(&g, "rust extractor impl ownership module resolution", 10)
435                .unwrap();
436        assert!(!seeds.is_empty());
437        assert_eq!(
438            seeds[0].0, precise,
439            "multi-token code symbol should outrank a generic concept hit"
440        );
441    }
442
443    #[test]
444    fn bloom_filter_basic() {
445        let mut bf = BloomFilter::with_capacity(1000, 0.01);
446        bf.insert(NodeId::new(42));
447        assert!(bf.probably_contains(NodeId::new(42)));
448    }
449
450    #[test]
451    fn wavefront_single_seed() {
452        let g = build_test_graph();
453        let engine = WavefrontEngine::new();
454        let config = PropagationConfig::default();
455        let seeds = vec![(NodeId::new(0), FiniteF32::ONE)];
456        let result = engine.propagate(&g, &seeds, &config).unwrap();
457        assert!(
458            !result.scores.is_empty(),
459            "Wavefront should activate at least one node"
460        );
461        assert!(result.scores[0].1.get() > 0.0);
462    }
463
464    #[test]
465    fn heap_single_seed() {
466        let g = build_test_graph();
467        let engine = HeapEngine::new();
468        let config = PropagationConfig::default();
469        let seeds = vec![(NodeId::new(0), FiniteF32::ONE)];
470        let result = engine.propagate(&g, &seeds, &config).unwrap();
471        assert!(
472            !result.scores.is_empty(),
473            "Heap should activate at least one node"
474        );
475    }
476
477    #[test]
478    fn hybrid_delegates_correctly() {
479        let g = build_test_graph();
480        let engine = HybridEngine::new();
481        let config = PropagationConfig::default();
482        let seeds = vec![(NodeId::new(0), FiniteF32::ONE)];
483        let result = engine.propagate(&g, &seeds, &config).unwrap();
484        assert!(!result.scores.is_empty());
485    }
486
487    #[test]
488    fn activation_empty_seeds_returns_empty() {
489        let g = build_test_graph();
490        let engine = WavefrontEngine::new();
491        let config = PropagationConfig::default();
492        let result = engine.propagate(&g, &[], &config).unwrap();
493        assert!(result.scores.is_empty());
494    }
495
496    #[test]
497    fn merge_dimensions_resonance_bonus() {
498        let make_dim = |dim: Dimension, scores: Vec<(NodeId, FiniteF32)>| DimensionResult {
499            scores,
500            dimension: dim,
501            elapsed_ns: 0,
502        };
503
504        let node = NodeId::new(0);
505        let score = FiniteF32::new(0.5);
506        let results = [
507            make_dim(Dimension::Structural, vec![(node, score)]),
508            make_dim(Dimension::Semantic, vec![(node, score)]),
509            make_dim(Dimension::Temporal, vec![(node, score)]),
510            make_dim(Dimension::Causal, vec![(node, score)]),
511        ];
512
513        let merged = merge_dimensions(&results, 10).unwrap();
514        assert!(!merged.activated.is_empty());
515        let activated = &merged.activated[0];
516        assert_eq!(activated.active_dimension_count, 4);
517        let base = 0.5 * 0.35 + 0.5 * 0.25 + 0.5 * 0.15 + 0.5 * 0.25;
518        let expected = base * RESONANCE_BONUS_4DIM;
519        assert!(
520            (activated.activation.get() - expected).abs() < 0.01,
521            "Expected ~{expected}, got {}",
522            activated.activation.get()
523        );
524    }
525
526    #[test]
527    fn xlr_sigmoid_gate() {
528        let zero = AdaptiveXlrEngine::sigmoid_gate(FiniteF32::ZERO);
529        assert!((zero.get() - 0.5).abs() < 0.01, "sigmoid(0) should be ~0.5");
530        let positive = AdaptiveXlrEngine::sigmoid_gate(FiniteF32::new(1.0));
531        assert!(positive.get() > 0.5, "sigmoid(+) should be > 0.5");
532        let negative = AdaptiveXlrEngine::sigmoid_gate(FiniteF32::new(-1.0));
533        assert!(negative.get() < 0.5, "sigmoid(-) should be < 0.5");
534    }
535
536    #[test]
537    fn xlr_pick_anti_seeds() {
538        let g = build_test_graph();
539        let xlr = AdaptiveXlrEngine::with_defaults();
540        let seeds = vec![NodeId::new(0)];
541        let anti = xlr.pick_anti_seeds(&g, &seeds).unwrap();
542        assert!(!anti.contains(&NodeId::new(0)));
543    }
544
545    #[test]
546    fn xlr_immunity_bfs() {
547        let g = build_test_graph();
548        let xlr = AdaptiveXlrEngine::with_defaults();
549        let seeds = vec![NodeId::new(0)];
550        let immunity = xlr.compute_immunity(&g, &seeds).unwrap();
551        assert!(immunity[0], "Seed itself should be immune");
552    }
553
554    #[test]
555    fn temporal_decay_clamps_negative_age() {
556        let scorer = TemporalDecayScorer::new(PosF32::new(168.0).unwrap());
557        let result = scorer.score_one(-10.0, FiniteF32::ZERO, None);
558        assert!(
559            (result.raw_decay.get() - 1.0).abs() < 0.01,
560            "Negative age should clamp to decay=1.0, got {}",
561            result.raw_decay.get()
562        );
563    }
564
565    #[test]
566    fn temporal_decay_exponential() {
567        let scorer = TemporalDecayScorer::new(PosF32::new(168.0).unwrap());
568        let result = scorer.score_one(168.0, FiniteF32::ZERO, None);
569        assert!(
570            (result.raw_decay.get() - 0.5).abs() < 0.05,
571            "After one half-life, decay ~0.5, got {}",
572            result.raw_decay.get()
573        );
574    }
575
576    #[test]
577    fn causal_chain_budget_limits() {
578        let g = build_test_graph();
579        let detector = CausalChainDetector::new(6, FiniteF32::new(0.01), 100);
580        let chains = detector.detect(&g, NodeId::new(0)).unwrap();
581        assert!(!chains.is_empty() || g.num_edges() == 0);
582    }
583
584    #[test]
585    fn louvain_detects_communities() {
586        let g = build_test_graph();
587        let detector = CommunityDetector::with_defaults();
588        let result = detector.detect(&g).unwrap();
589        assert!(result.num_communities >= 1);
590        assert_eq!(result.assignments.len(), g.num_nodes() as usize);
591    }
592
593    #[test]
594    fn louvain_empty_graph_error() {
595        let g = Graph::new();
596        let detector = CommunityDetector::with_defaults();
597        let result = detector.detect(&g);
598        assert!(matches!(result, Err(M1ndError::EmptyGraph)));
599    }
600
601    #[test]
602    fn bridge_detection() {
603        let g = build_test_graph();
604        let detector = CommunityDetector::with_defaults();
605        let communities = detector.detect(&g).unwrap();
606        let bridges = BridgeDetector::detect(&g, &communities).unwrap();
607        let _ = bridges;
608    }
609
610    #[test]
611    fn spectral_gap_empty_graph() {
612        let g = Graph::new();
613        let analyzer = SpectralGapAnalyzer::with_defaults();
614        let result = analyzer.analyze(&g);
615        assert!(matches!(result, Err(M1ndError::EmptyGraph)));
616    }
617
618    #[test]
619    fn wave_accumulator_complex_interference() {
620        let mut acc = WaveAccumulator::default();
621        let pulse1 = WavePulse {
622            node: NodeId::new(0),
623            amplitude: FiniteF32::ONE,
624            phase: FiniteF32::ZERO,
625            frequency: PosF32::new(1.0).unwrap(),
626            wavelength: PosF32::new(4.0).unwrap(),
627            hops: 0,
628            prev_node: NodeId::new(0),
629        };
630        acc.accumulate(&pulse1);
631        let amp = acc.amplitude().get();
632        assert!(
633            (amp - 1.0).abs() < 0.01,
634            "Single pulse amplitude should be ~1.0"
635        );
636    }
637
638    #[test]
639    fn standing_wave_propagation() {
640        let g = build_test_graph();
641        let propagator = StandingWavePropagator::new(5, FiniteF32::new(0.01), 10_000);
642        let seeds = vec![(NodeId::new(0), FiniteF32::ONE)];
643        let result = propagator
644            .propagate(
645                &g,
646                &seeds,
647                PosF32::new(1.0).unwrap(),
648                PosF32::new(4.0).unwrap(),
649            )
650            .unwrap();
651        assert!(result.pulses_processed > 0);
652        assert!(!result.antinodes.is_empty());
653    }
654
655    #[test]
656    fn query_memory_ring_buffer() {
657        let mut mem = QueryMemory::new(3, 10);
658        assert!(mem.is_empty());
659        for i in 0..5 {
660            mem.record(QueryRecord {
661                query_text: format!("query_{i}"),
662                seeds: vec![NodeId::new(i)],
663                activated_nodes: vec![NodeId::new(i), NodeId::new(i + 1)],
664                timestamp: i as f64,
665            });
666        }
667        assert_eq!(mem.len(), 3);
668    }
669
670    #[test]
671    fn plasticity_generation_check() {
672        let g = build_test_graph();
673        let engine = PlasticityEngine::new(&g, PlasticityConfig::default());
674        let _ = engine;
675    }
676
677    #[test]
678    fn removal_mask_basic() {
679        let g = build_test_graph();
680        let mut mask = RemovalMask::new(g.num_nodes(), g.num_edges());
681        assert!(!mask.is_node_removed(NodeId::new(0)));
682        mask.remove_node(&g, NodeId::new(0));
683        assert!(mask.is_node_removed(NodeId::new(0)));
684        mask.reset();
685        assert!(!mask.is_node_removed(NodeId::new(0)));
686    }
687}