Skip to main content

mempill_types/
edge.rs

1//! ClaimEdge: directed relationship between two claims (lineage, supersession, dependency).
2
3use crate::identity::{AgentId, ClaimRef};
4use crate::time::TransactionTime;
5
6/// A directed edge between two claims.
7#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
8pub struct ClaimEdge {
9    /// Unique ID for this edge (random UUID, minted at write time).
10    pub edge_id: uuid::Uuid,
11    /// The agent that owns both claims in this edge.
12    pub agent_id: AgentId,
13    /// The source claim of the directed edge.
14    pub from_claim: ClaimRef,
15    /// The target claim of the directed edge.
16    pub to_claim: ClaimRef,
17    /// The semantic relationship this edge represents.
18    pub kind: EdgeKind,
19    /// When this edge was recorded.
20    pub created_at: TransactionTime,
21}
22
23/// The semantic relationship carried by a ClaimEdge.
24#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
25#[non_exhaustive]
26pub enum EdgeKind {
27    /// `from_claim` was derived from `to_claim` (lineage tracking for provenance depth).
28    DerivedFrom,
29    /// `to_claim` supersedes `from_claim`.
30    Supersedes,
31    /// `from_claim` depends on `to_claim` — when `to_claim` is superseded, `from_claim`
32    /// is flagged `PendingReview`.
33    DependsOn,
34    /// Mutual exclusion between from and to (at most one valid at a time,
35    /// regardless of cardinality).
36    MutualExclusion,
37}
38
39#[cfg(test)]
40mod tests {
41    use super::*;
42    use crate::identity::AgentId;
43    use chrono::Utc;
44
45    fn make_edge(kind: EdgeKind) -> ClaimEdge {
46        ClaimEdge {
47            edge_id: uuid::Uuid::new_v4(),
48            agent_id: AgentId("agent-1".into()),
49            from_claim: ClaimRef::new_random(),
50            to_claim: ClaimRef::new_random(),
51            kind,
52            created_at: TransactionTime(Utc::now()),
53        }
54    }
55
56    #[test]
57    fn edge_kind_depends_on_is_present() {
58        let e = make_edge(EdgeKind::DependsOn);
59        assert_eq!(e.kind, EdgeKind::DependsOn);
60    }
61
62    #[test]
63    fn all_edge_kinds_round_trip_serde() {
64        let kinds = [
65            EdgeKind::DerivedFrom,
66            EdgeKind::Supersedes,
67            EdgeKind::DependsOn,
68            EdgeKind::MutualExclusion,
69        ];
70        for k in &kinds {
71            let json = serde_json::to_string(k).unwrap();
72            let back: EdgeKind = serde_json::from_str(&json).unwrap();
73            assert_eq!(k, &back);
74        }
75    }
76
77    #[test]
78    fn claim_edge_round_trip_serde() {
79        let edge = make_edge(EdgeKind::Supersedes);
80        let json = serde_json::to_string(&edge).unwrap();
81        let back: ClaimEdge = serde_json::from_str(&json).unwrap();
82        assert_eq!(edge.edge_id, back.edge_id);
83        assert_eq!(edge.kind, back.kind);
84    }
85}