Skip to main content

memnite_core/
relations.rs

1use std::collections::BTreeMap;
2
3use crate::event::{Event, EventKind, Relation};
4
5/// A projected relation edge (directed, from → to). Reconstructed from the log.
6#[derive(Clone, Debug, PartialEq)]
7pub struct RelationEdge {
8    pub from_id: String,
9    pub to_id: String,
10    pub relation: Relation,
11    pub confidence: f32,
12    pub reason: String,
13    pub judged_by: String,
14    pub lamport: u64,
15    pub event_id: String,
16}
17
18/// Fold `RelationAsserted` events into the current relation graph. Deterministic:
19/// last-writer-by-`(lamport, event_id)` per ordered `(from_id, to_id)` pair. A
20/// winning `NotConflict` assertion removes the edge (retraction) — it is absent
21/// from the map. Non-relation events are ignored.
22pub fn replay_relations(events: &[Event]) -> BTreeMap<(String, String), RelationEdge> {
23    let mut sorted: Vec<&Event> = events
24        .iter()
25        .filter(|e| matches!(e.kind, EventKind::RelationAsserted { .. }))
26        .collect();
27    sorted.sort_by(|a, b| {
28        a.lamport
29            .cmp(&b.lamport)
30            .then_with(|| a.event_id.cmp(&b.event_id))
31    });
32
33    let mut graph: BTreeMap<(String, String), RelationEdge> = BTreeMap::new();
34    for ev in sorted {
35        if let EventKind::RelationAsserted {
36            to_id,
37            relation,
38            confidence,
39            reason,
40            judged_by,
41        } = &ev.kind
42        {
43            let key = (ev.memory_id.clone(), to_id.clone());
44            if *relation == Relation::NotConflict {
45                graph.remove(&key);
46            } else {
47                graph.insert(
48                    key,
49                    RelationEdge {
50                        from_id: ev.memory_id.clone(),
51                        to_id: to_id.clone(),
52                        relation: relation.clone(),
53                        confidence: *confidence,
54                        reason: reason.clone(),
55                        judged_by: judged_by.clone(),
56                        lamport: ev.lamport,
57                        event_id: ev.event_id.clone(),
58                    },
59                );
60            }
61        }
62    }
63    graph
64}