mempill_types/
provenance.rs1#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
14#[serde(tag = "type", content = "kind")]
15#[non_exhaustive]
16pub enum ProvenanceLabel {
17 External(ExternalKind),
19 RecallReEntry,
22 ModelDerived,
25}
26
27#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
29#[non_exhaustive]
30pub enum ExternalKind {
31 UserAsserted,
33 ExternalFirstHand,
35}
36
37impl ProvenanceLabel {
38 pub fn is_cheap_path_eligible(&self) -> bool {
41 matches!(self, Self::External(_))
42 }
43
44 pub fn is_recall_reentry(&self) -> bool {
46 matches!(self, Self::RecallReEntry)
47 }
48}
49
50#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
55pub struct ExternalAnchor {
56 pub nearest_external_anchor: Option<crate::identity::ClaimRef>,
58 pub derivation_depth: u32,
60}
61
62#[cfg(test)]
63mod tests {
64 use super::*;
65
66 #[test]
67 fn external_user_asserted_is_cheap_path_eligible() {
68 let p = ProvenanceLabel::External(ExternalKind::UserAsserted);
69 assert!(p.is_cheap_path_eligible());
70 assert!(!p.is_recall_reentry());
71 }
72
73 #[test]
74 fn external_first_hand_is_cheap_path_eligible() {
75 let p = ProvenanceLabel::External(ExternalKind::ExternalFirstHand);
76 assert!(p.is_cheap_path_eligible());
77 assert!(!p.is_recall_reentry());
78 }
79
80 #[test]
81 fn recall_reentry_is_not_cheap_path_eligible() {
82 let p = ProvenanceLabel::RecallReEntry;
83 assert!(!p.is_cheap_path_eligible());
84 assert!(p.is_recall_reentry());
85 }
86
87 #[test]
88 fn model_derived_is_neither() {
89 let p = ProvenanceLabel::ModelDerived;
90 assert!(!p.is_cheap_path_eligible());
91 assert!(!p.is_recall_reentry());
92 }
93
94 #[test]
95 fn provenance_label_round_trip_serde() {
96 let labels = [
97 ProvenanceLabel::External(ExternalKind::UserAsserted),
98 ProvenanceLabel::External(ExternalKind::ExternalFirstHand),
99 ProvenanceLabel::RecallReEntry,
100 ProvenanceLabel::ModelDerived,
101 ];
102 for label in &labels {
103 let json = serde_json::to_string(label).unwrap();
104 let back: ProvenanceLabel = serde_json::from_str(&json).unwrap();
105 assert_eq!(label, &back);
106 }
107 }
108
109 #[test]
110 fn provenance_label_python_friendly_json_shapes() {
111 let ext_ua = ProvenanceLabel::External(ExternalKind::UserAsserted);
113 let json = serde_json::to_string(&ext_ua).unwrap();
114 assert_eq!(json, r#"{"type":"External","kind":"UserAsserted"}"#);
115 let back: ProvenanceLabel = serde_json::from_str(&json).unwrap();
116 assert_eq!(ext_ua, back);
117
118 let ext_fh = ProvenanceLabel::External(ExternalKind::ExternalFirstHand);
120 let json = serde_json::to_string(&ext_fh).unwrap();
121 assert_eq!(json, r#"{"type":"External","kind":"ExternalFirstHand"}"#);
122 let back: ProvenanceLabel = serde_json::from_str(&json).unwrap();
123 assert_eq!(ext_fh, back);
124
125 let rre = ProvenanceLabel::RecallReEntry;
127 let json = serde_json::to_string(&rre).unwrap();
128 assert_eq!(json, r#"{"type":"RecallReEntry"}"#);
129 let back: ProvenanceLabel = serde_json::from_str(&json).unwrap();
130 assert_eq!(rre, back);
131
132 let md = ProvenanceLabel::ModelDerived;
134 let json = serde_json::to_string(&md).unwrap();
135 assert_eq!(json, r#"{"type":"ModelDerived"}"#);
136 let back: ProvenanceLabel = serde_json::from_str(&json).unwrap();
137 assert_eq!(md, back);
138 }
139
140 #[test]
141 fn external_anchor_round_trip_serde() {
142 let anchor = ExternalAnchor {
143 nearest_external_anchor: Some(crate::identity::ClaimRef::new_random()),
144 derivation_depth: 2,
145 };
146 let json = serde_json::to_string(&anchor).unwrap();
147 let back: ExternalAnchor = serde_json::from_str(&json).unwrap();
148 assert_eq!(anchor, back);
149 }
150}