1use serde::{Deserialize, Serialize};
5use serde_json::Value;
6
7#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
9#[serde(rename_all = "snake_case")]
10pub enum Tier {
11 Short,
12 Mid,
13 Long,
14}
15
16impl Tier {
17 pub fn as_str(&self) -> &'static str {
18 match self {
19 Self::Short => "short",
20 Self::Mid => "mid",
21 Self::Long => "long",
22 }
23 }
24
25 pub fn from_str(s: &str) -> Option<Self> {
26 match s {
27 "short" => Some(Self::Short),
28 "mid" => Some(Self::Mid),
29 "long" => Some(Self::Long),
30 _ => None,
31 }
32 }
33
34 #[cfg(test)]
36 pub fn rank(&self) -> u8 {
37 match self {
38 Self::Short => 0,
39 Self::Mid => 1,
40 Self::Long => 2,
41 }
42 }
43
44 pub fn default_ttl_secs(&self) -> Option<i64> {
45 match self {
46 Self::Short => Some(6 * 3600),
47 Self::Mid => Some(7 * 24 * 3600),
48 Self::Long => None,
49 }
50 }
51}
52
53impl std::fmt::Display for Tier {
54 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55 f.write_str(self.as_str())
56 }
57}
58
59#[derive(Debug, Clone, Serialize, Deserialize)]
60pub struct Memory {
61 pub id: String,
62 pub tier: Tier,
63 pub namespace: String,
64 pub title: String,
65 pub content: String,
66 pub tags: Vec<String>,
67 pub priority: i32,
68 pub confidence: f64,
70 pub source: String,
72 pub access_count: i64,
73 pub created_at: String,
74 pub updated_at: String,
75 #[serde(skip_serializing_if = "Option::is_none")]
76 pub last_accessed_at: Option<String>,
77 #[serde(skip_serializing_if = "Option::is_none")]
78 pub expires_at: Option<String>,
79 #[serde(default = "default_metadata")]
80 pub metadata: Value,
81}
82
83#[derive(Debug, Clone, Serialize, Deserialize)]
84pub struct MemoryLink {
85 pub source_id: String,
86 pub target_id: String,
87 pub relation: String, pub created_at: String,
89}
90
91#[derive(Debug, Deserialize)]
92pub struct CreateMemory {
93 #[serde(default = "default_tier")]
94 pub tier: Tier,
95 #[serde(default = "default_namespace")]
96 pub namespace: String,
97 pub title: String,
98 pub content: String,
99 #[serde(default)]
100 pub tags: Vec<String>,
101 #[serde(default = "default_priority")]
102 pub priority: i32,
103 #[serde(default = "default_confidence")]
104 pub confidence: f64,
105 #[serde(default = "default_source")]
106 pub source: String,
107 #[serde(default)]
108 pub expires_at: Option<String>,
109 #[serde(default)]
110 pub ttl_secs: Option<i64>,
111 #[serde(default = "default_metadata")]
112 pub metadata: Value,
113 #[serde(default)]
116 pub agent_id: Option<String>,
117 #[serde(default)]
120 pub scope: Option<String>,
121}
122
123fn default_tier() -> Tier {
124 Tier::Mid
125}
126fn default_namespace() -> String {
127 "global".to_string()
128}
129fn default_priority() -> i32 {
130 5
131}
132fn default_confidence() -> f64 {
133 1.0
134}
135fn default_source() -> String {
136 "api".to_string()
137}
138pub fn default_metadata() -> Value {
139 Value::Object(serde_json::Map::new())
140}
141
142#[derive(Debug, Deserialize)]
143pub struct UpdateMemory {
144 pub title: Option<String>,
145 pub content: Option<String>,
146 pub tier: Option<Tier>,
147 pub namespace: Option<String>,
148 pub tags: Option<Vec<String>>,
149 pub priority: Option<i32>,
150 pub confidence: Option<f64>,
151 pub expires_at: Option<String>,
152 pub metadata: Option<Value>,
153}
154
155#[derive(Debug, Deserialize)]
156pub struct SearchQuery {
157 pub q: String,
158 #[serde(default)]
159 pub namespace: Option<String>,
160 #[serde(default)]
161 pub tier: Option<Tier>,
162 #[serde(default = "default_limit")]
163 pub limit: Option<usize>,
164 #[serde(default)]
165 pub min_priority: Option<i32>,
166 #[serde(default)]
167 pub since: Option<String>,
168 #[serde(default)]
169 pub until: Option<String>,
170 #[serde(default)]
171 pub tags: Option<String>, #[serde(default)]
174 pub agent_id: Option<String>,
175 #[serde(default)]
178 pub as_agent: Option<String>,
179}
180
181#[allow(clippy::unnecessary_wraps)]
182fn default_limit() -> Option<usize> {
183 Some(20)
184}
185
186#[derive(Debug, Deserialize)]
187pub struct ListQuery {
188 #[serde(default)]
189 pub namespace: Option<String>,
190 #[serde(default)]
191 pub tier: Option<Tier>,
192 #[serde(default = "default_limit")]
193 pub limit: Option<usize>,
194 #[serde(default)]
195 pub offset: Option<usize>,
196 #[serde(default)]
197 pub min_priority: Option<i32>,
198 #[serde(default)]
199 pub since: Option<String>,
200 #[serde(default)]
201 pub until: Option<String>,
202 #[serde(default)]
203 pub tags: Option<String>,
204 #[serde(default)]
206 pub agent_id: Option<String>,
207}
208
209#[derive(Debug, Deserialize)]
210pub struct RecallQuery {
211 pub context: Option<String>,
212 #[serde(default)]
213 pub namespace: Option<String>,
214 #[serde(default = "default_recall_limit")]
215 pub limit: Option<usize>,
216 #[serde(default)]
217 pub tags: Option<String>,
218 #[serde(default)]
219 pub since: Option<String>,
220 #[serde(default)]
221 pub until: Option<String>,
222 #[serde(default)]
224 pub as_agent: Option<String>,
225 #[serde(default)]
229 pub budget_tokens: Option<usize>,
230}
231
232#[allow(clippy::unnecessary_wraps)]
233fn default_recall_limit() -> Option<usize> {
234 Some(10)
235}
236
237#[derive(Debug, Deserialize)]
238pub struct RecallBody {
239 pub context: String,
240 #[serde(default)]
241 pub namespace: Option<String>,
242 #[serde(default = "default_recall_limit")]
243 pub limit: Option<usize>,
244 #[serde(default)]
245 pub tags: Option<String>,
246 #[serde(default)]
247 pub since: Option<String>,
248 #[serde(default)]
249 pub until: Option<String>,
250 #[serde(default)]
252 pub as_agent: Option<String>,
253 #[serde(default)]
255 pub budget_tokens: Option<usize>,
256}
257
258#[derive(Debug, Deserialize)]
259pub struct LinkBody {
260 pub source_id: String,
261 pub target_id: String,
262 #[serde(default = "default_relation")]
263 pub relation: String,
264}
265
266fn default_relation() -> String {
267 "related_to".to_string()
268}
269
270#[derive(Debug, Deserialize)]
271pub struct ForgetQuery {
272 #[serde(default)]
273 pub namespace: Option<String>,
274 #[serde(default)]
275 pub pattern: Option<String>, #[serde(default)]
277 pub tier: Option<Tier>,
278}
279
280#[derive(Debug, Serialize)]
281pub struct Stats {
282 pub total: usize,
283 pub by_tier: Vec<TierCount>,
284 pub by_namespace: Vec<NamespaceCount>,
285 pub expiring_soon: usize,
286 pub links_count: usize,
287 pub db_size_bytes: u64,
288}
289
290#[derive(Debug, Serialize)]
291pub struct TierCount {
292 pub tier: String,
293 pub count: usize,
294}
295
296#[derive(Debug, Serialize)]
297pub struct NamespaceCount {
298 pub namespace: String,
299 pub count: usize,
300}
301
302#[derive(Debug, Clone, Serialize)]
310pub struct TaxonomyNode {
311 pub namespace: String,
314 pub name: String,
317 pub count: usize,
319 pub subtree_count: usize,
323 pub children: Vec<TaxonomyNode>,
325}
326
327#[derive(Debug, Clone, Serialize)]
335pub struct Taxonomy {
336 pub tree: TaxonomyNode,
337 pub total_count: usize,
338 pub truncated: bool,
339}
340
341#[derive(Debug, Clone, Serialize)]
345pub struct DuplicateMatch {
346 pub id: String,
347 pub title: String,
348 pub namespace: String,
349 pub similarity: f32,
350}
351
352#[derive(Debug, Clone, Serialize)]
360pub struct DuplicateCheck {
361 pub is_duplicate: bool,
362 pub threshold: f32,
363 pub nearest: Option<DuplicateMatch>,
364 pub candidates_scanned: usize,
365}
366
367pub const AGENTS_NAMESPACE: &str = "_agents";
369
370pub const ENTITY_TAG: &str = "entity";
374
375pub const ENTITY_KIND: &str = "entity";
380
381#[derive(Debug, Clone, Serialize)]
386pub struct EntityRecord {
387 pub entity_id: String,
388 pub canonical_name: String,
389 pub namespace: String,
390 pub aliases: Vec<String>,
391}
392
393#[derive(Debug, Clone, Serialize)]
398pub struct EntityRegistration {
399 pub entity_id: String,
400 pub canonical_name: String,
401 pub namespace: String,
402 pub aliases: Vec<String>,
403 pub created: bool,
404}
405
406#[derive(Debug, Clone, Serialize)]
416pub struct KgTimelineEvent {
417 pub target_id: String,
418 pub relation: String,
419 pub valid_from: String,
420 pub valid_until: Option<String>,
421 pub observed_by: Option<String>,
422 pub title: String,
423 pub target_namespace: String,
424}
425
426#[derive(Debug, Clone, Serialize)]
434pub struct KgQueryNode {
435 pub target_id: String,
436 pub relation: String,
437 pub valid_from: Option<String>,
438 pub valid_until: Option<String>,
439 pub observed_by: Option<String>,
440 pub title: String,
441 pub target_namespace: String,
442 pub depth: usize,
443 pub path: String,
444}
445
446#[derive(Debug, Clone, PartialEq, Eq)]
454pub enum GovernanceDecision {
455 Allow,
457 Deny(String),
459 Pending(String),
461}
462
463#[derive(Debug, Clone, Copy, PartialEq, Eq)]
466pub enum GovernedAction {
467 Store,
468 Delete,
469 Promote,
470}
471
472impl GovernedAction {
473 #[must_use]
474 pub fn as_str(self) -> &'static str {
475 match self {
476 Self::Store => "store",
477 Self::Delete => "delete",
478 Self::Promote => "promote",
479 }
480 }
481}
482
483#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
485pub struct Approval {
486 pub agent_id: String,
487 pub approved_at: String,
488}
489
490#[derive(Debug, Clone, Serialize, Deserialize)]
492pub struct PendingAction {
493 pub id: String,
494 pub action_type: String,
495 pub memory_id: Option<String>,
496 pub namespace: String,
497 pub payload: Value,
498 pub requested_by: String,
499 pub requested_at: String,
500 pub status: String,
501 #[serde(skip_serializing_if = "Option::is_none")]
502 pub decided_by: Option<String>,
503 #[serde(skip_serializing_if = "Option::is_none")]
504 pub decided_at: Option<String>,
505 #[serde(default)]
507 pub approvals: Vec<Approval>,
508}
509
510#[derive(Debug, Clone, Serialize, Deserialize)]
517pub struct PendingDecision {
518 pub id: String,
519 pub approved: bool,
520 pub decider: String,
521}
522
523#[derive(Debug, Clone, Serialize, Deserialize)]
534pub struct NamespaceMetaEntry {
535 pub namespace: String,
536 pub standard_id: String,
537 #[serde(default, skip_serializing_if = "Option::is_none")]
538 pub parent_namespace: Option<String>,
539 #[serde(default)]
540 pub updated_at: String,
541}
542
543#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
553#[serde(rename_all = "snake_case")]
554pub enum GovernanceLevel {
555 Any,
557 Registered,
559 Owner,
561 Approve,
563}
564
565impl GovernanceLevel {
566 #[allow(dead_code)]
569 #[must_use]
570 pub fn as_str(&self) -> &'static str {
571 match self {
572 Self::Any => "any",
573 Self::Registered => "registered",
574 Self::Owner => "owner",
575 Self::Approve => "approve",
576 }
577 }
578}
579
580#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
588#[serde(rename_all = "snake_case")]
589pub enum ApproverType {
590 Human,
592 Agent(String),
594 Consensus(u32),
596}
597
598impl ApproverType {
599 #[allow(dead_code)]
602 #[must_use]
603 pub fn kind(&self) -> &'static str {
604 match self {
605 Self::Human => "human",
606 Self::Agent(_) => "agent",
607 Self::Consensus(_) => "consensus",
608 }
609 }
610}
611
612#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
624pub struct GovernancePolicy {
625 pub write: GovernanceLevel,
626 #[serde(default = "default_promote_level")]
627 pub promote: GovernanceLevel,
628 #[serde(default = "default_delete_level")]
629 pub delete: GovernanceLevel,
630 #[serde(default = "default_approver")]
631 pub approver: ApproverType,
632}
633
634fn default_promote_level() -> GovernanceLevel {
635 GovernanceLevel::Any
636}
637
638fn default_delete_level() -> GovernanceLevel {
639 GovernanceLevel::Owner
640}
641
642fn default_approver() -> ApproverType {
643 ApproverType::Human
644}
645
646impl Default for GovernancePolicy {
647 fn default() -> Self {
648 Self {
649 write: GovernanceLevel::Any,
650 promote: default_promote_level(),
651 delete: default_delete_level(),
652 approver: default_approver(),
653 }
654 }
655}
656
657impl GovernancePolicy {
658 pub fn from_metadata(metadata: &Value) -> Option<Result<Self, serde_json::Error>> {
662 let gov = metadata.get("governance")?;
663 if gov.is_null() {
664 return None;
665 }
666 Some(serde_json::from_value(gov.clone()))
667 }
668}
669
670pub const VALID_SCOPES: &[&str] = &["private", "team", "unit", "org", "collective"];
674
675pub const VALID_AGENT_TYPES: &[&str] = &[
677 "ai:claude-opus-4.6",
678 "ai:claude-opus-4.7",
679 "ai:codex-5.4",
680 "ai:grok-4.2",
681 "human",
682 "system",
683];
684
685#[derive(Debug, Deserialize)]
686pub struct RegisterAgentBody {
687 pub agent_id: String,
688 pub agent_type: String,
689 #[serde(default)]
690 pub capabilities: Option<Vec<String>>,
691}
692
693#[derive(Debug, Serialize)]
694pub struct AgentRegistration {
695 pub agent_id: String,
696 pub agent_type: String,
697 pub capabilities: Vec<String>,
698 pub registered_at: String,
699 pub last_seen_at: String,
700}
701
702#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
713pub struct VectorClock {
714 #[serde(default)]
718 pub entries: std::collections::BTreeMap<String, String>,
719}
720
721impl VectorClock {
722 #[allow(dead_code)] pub fn observe(&mut self, peer_id: &str, at: &str) {
726 self.entries
727 .entry(peer_id.to_string())
728 .and_modify(|existing| {
729 if at > existing.as_str() {
730 *existing = at.to_string();
731 }
732 })
733 .or_insert_with(|| at.to_string());
734 }
735
736 #[must_use]
738 #[allow(dead_code)] pub fn latest_from(&self, peer_id: &str) -> Option<&str> {
740 self.entries.get(peer_id).map(String::as_str)
741 }
742}
743
744#[allow(dead_code)] #[derive(Debug, Clone, Serialize, Deserialize)]
748pub struct SyncStateEntry {
749 pub agent_id: String,
750 pub peer_id: String,
751 pub last_seen_at: String,
752 pub last_pulled_at: String,
753}
754
755pub const MAX_CONTENT_SIZE: usize = 65_536;
756
757pub const MAX_NAMESPACE_DEPTH: usize = 8;
760
761#[must_use]
774pub fn namespace_depth(ns: &str) -> usize {
775 if ns.is_empty() {
776 return 0;
777 }
778 ns.split('/').filter(|s| !s.is_empty()).count()
779}
780
781#[allow(dead_code)]
787#[must_use]
799pub fn namespace_parent(ns: &str) -> Option<String> {
800 ns.rsplit_once('/').map(|(parent, _)| parent.to_string())
801}
802
803#[allow(dead_code)]
809#[must_use]
825pub fn namespace_ancestors(ns: &str) -> Vec<String> {
826 if ns.is_empty() {
827 return Vec::new();
828 }
829 let mut out = Vec::with_capacity(namespace_depth(ns));
830 let mut current = ns.to_string();
831 loop {
832 out.push(current.clone());
833 match namespace_parent(¤t) {
834 Some(p) if !p.is_empty() => current = p,
835 _ => break,
836 }
837 }
838 out
839}
840pub const PROMOTION_THRESHOLD: i64 = 5;
841pub const SHORT_TTL_EXTEND_SECS: i64 = 3600;
843pub const MID_TTL_EXTEND_SECS: i64 = 86400;
844
845#[cfg(test)]
846mod tests {
847 use super::*;
848
849 #[test]
850 fn tier_from_str_valid() {
851 assert_eq!(Tier::from_str("short"), Some(Tier::Short));
852 assert_eq!(Tier::from_str("mid"), Some(Tier::Mid));
853 assert_eq!(Tier::from_str("long"), Some(Tier::Long));
854 }
855
856 #[test]
857 fn tier_from_str_invalid() {
858 assert_eq!(Tier::from_str("invalid"), None);
859 assert_eq!(Tier::from_str(""), None);
860 assert_eq!(Tier::from_str("SHORT"), None); }
862
863 #[test]
864 fn tier_as_str_roundtrip() {
865 for tier in [Tier::Short, Tier::Mid, Tier::Long] {
866 let s = tier.as_str();
867 assert_eq!(Tier::from_str(s), Some(tier));
868 }
869 }
870
871 #[test]
872 fn tier_default_ttl() {
873 assert_eq!(Tier::Short.default_ttl_secs(), Some(6 * 3600));
874 assert_eq!(Tier::Mid.default_ttl_secs(), Some(7 * 24 * 3600));
875 assert_eq!(Tier::Long.default_ttl_secs(), None);
876 }
877
878 #[test]
879 fn tier_display() {
880 assert_eq!(format!("{}", Tier::Short), "short");
881 assert_eq!(format!("{}", Tier::Mid), "mid");
882 assert_eq!(format!("{}", Tier::Long), "long");
883 }
884
885 #[test]
886 fn constants_valid() {
887 const _: () = assert!(MAX_CONTENT_SIZE > 0);
888 const _: () = assert!(PROMOTION_THRESHOLD > 0);
889 assert_eq!(SHORT_TTL_EXTEND_SECS, 3600);
890 assert_eq!(MID_TTL_EXTEND_SECS, 86400);
891 }
892
893 #[test]
894 fn tier_rank_ordering() {
895 assert!(Tier::Short.rank() < Tier::Mid.rank());
896 assert!(Tier::Mid.rank() < Tier::Long.rank());
897 assert_eq!(Tier::Short.rank(), 0);
898 assert_eq!(Tier::Mid.rank(), 1);
899 assert_eq!(Tier::Long.rank(), 2);
900 }
901
902 #[test]
905 fn depth_flat_namespace() {
906 assert_eq!(namespace_depth("global"), 1);
907 assert_eq!(namespace_depth("ai-memory"), 1);
908 assert_eq!(namespace_depth("under_score"), 1);
909 }
910
911 #[test]
912 fn depth_hierarchical() {
913 assert_eq!(namespace_depth("a/b"), 2);
914 assert_eq!(namespace_depth("alphaone/engineering"), 2);
915 assert_eq!(namespace_depth("alphaone/engineering/platform"), 3);
916 assert_eq!(
917 namespace_depth("a/b/c/d/e/f/g/h"),
918 8,
919 "max depth of 8 counts each segment"
920 );
921 }
922
923 #[test]
924 fn depth_empty_is_zero() {
925 assert_eq!(namespace_depth(""), 0);
926 }
927
928 #[test]
929 fn parent_hierarchical() {
930 assert_eq!(
931 namespace_parent("alphaone/engineering/platform"),
932 Some("alphaone/engineering".to_string())
933 );
934 assert_eq!(
935 namespace_parent("alphaone/engineering"),
936 Some("alphaone".to_string())
937 );
938 }
939
940 #[test]
941 fn parent_flat_is_none() {
942 assert_eq!(namespace_parent("global"), None);
943 assert_eq!(namespace_parent("ai-memory"), None);
944 assert_eq!(namespace_parent(""), None);
945 }
946
947 #[test]
948 fn ancestors_three_levels() {
949 let a = namespace_ancestors("alphaone/engineering/platform");
950 assert_eq!(
951 a,
952 vec![
953 "alphaone/engineering/platform".to_string(),
954 "alphaone/engineering".to_string(),
955 "alphaone".to_string(),
956 ],
957 "ancestors ordered most-specific-first"
958 );
959 }
960
961 #[test]
962 fn ancestors_flat_namespace() {
963 assert_eq!(namespace_ancestors("global"), vec!["global".to_string()]);
964 assert_eq!(
965 namespace_ancestors("ai-memory"),
966 vec!["ai-memory".to_string()]
967 );
968 }
969
970 #[test]
971 fn ancestors_empty_input() {
972 assert!(namespace_ancestors("").is_empty());
973 }
974
975 #[test]
976 fn ancestors_single_level() {
977 assert_eq!(namespace_ancestors("a"), vec!["a".to_string()]);
978 }
979
980 #[test]
981 fn ancestors_max_depth() {
982 let a = namespace_ancestors("a/b/c/d/e/f/g/h");
983 assert_eq!(a.len(), 8);
984 assert_eq!(a[0], "a/b/c/d/e/f/g/h");
985 assert_eq!(a[7], "a");
986 }
987
988 #[test]
991 fn governance_default_policy() {
992 let p = GovernancePolicy::default();
993 assert_eq!(p.write, GovernanceLevel::Any);
994 assert_eq!(p.promote, GovernanceLevel::Any);
995 assert_eq!(p.delete, GovernanceLevel::Owner);
996 assert_eq!(p.approver, ApproverType::Human);
997 }
998
999 #[test]
1000 fn governance_level_serde_snake_case() {
1001 for (level, expected) in [
1003 (GovernanceLevel::Any, "any"),
1004 (GovernanceLevel::Registered, "registered"),
1005 (GovernanceLevel::Owner, "owner"),
1006 (GovernanceLevel::Approve, "approve"),
1007 ] {
1008 let json = serde_json::to_string(&level).unwrap();
1009 assert_eq!(json, format!("\"{expected}\""));
1010 let back: GovernanceLevel = serde_json::from_str(&json).unwrap();
1012 assert_eq!(back, level);
1013 }
1014 }
1015
1016 #[test]
1017 fn approver_type_serde_shapes() {
1018 let json = serde_json::to_string(&ApproverType::Human).unwrap();
1020 assert_eq!(json, "\"human\"");
1021
1022 let a = ApproverType::Agent("alice".to_string());
1024 let json = serde_json::to_string(&a).unwrap();
1025 assert_eq!(json, r#"{"agent":"alice"}"#);
1026 let back: ApproverType = serde_json::from_str(&json).unwrap();
1027 assert_eq!(back, a);
1028
1029 let c = ApproverType::Consensus(3);
1031 let json = serde_json::to_string(&c).unwrap();
1032 assert_eq!(json, r#"{"consensus":3}"#);
1033 let back: ApproverType = serde_json::from_str(&json).unwrap();
1034 assert_eq!(back, c);
1035 }
1036
1037 #[test]
1038 fn governance_policy_full_roundtrip() {
1039 let p = GovernancePolicy {
1040 write: GovernanceLevel::Registered,
1041 promote: GovernanceLevel::Approve,
1042 delete: GovernanceLevel::Owner,
1043 approver: ApproverType::Agent("maintainer".to_string()),
1044 };
1045 let json = serde_json::to_string(&p).unwrap();
1046 let back: GovernancePolicy = serde_json::from_str(&json).unwrap();
1047 assert_eq!(back, p);
1048 }
1049
1050 #[test]
1051 fn governance_from_metadata_missing() {
1052 let meta = serde_json::json!({"agent_id": "alice"});
1053 assert!(GovernancePolicy::from_metadata(&meta).is_none());
1054 }
1055
1056 #[test]
1057 fn governance_from_metadata_null() {
1058 let meta = serde_json::json!({"governance": null});
1059 assert!(GovernancePolicy::from_metadata(&meta).is_none());
1060 }
1061
1062 #[test]
1063 fn governance_from_metadata_default_shape() {
1064 let default = GovernancePolicy::default();
1065 let meta = serde_json::json!({"governance": serde_json::to_value(&default).unwrap()});
1066 let parsed = GovernancePolicy::from_metadata(&meta)
1067 .expect("present")
1068 .expect("valid");
1069 assert_eq!(parsed, default);
1070 }
1071
1072 #[test]
1073 fn governance_from_metadata_invalid_returns_err() {
1074 let meta = serde_json::json!({
1075 "governance": {"write": "bogus", "promote": "any", "delete": "any", "approver": "human"}
1076 });
1077 let result = GovernancePolicy::from_metadata(&meta).expect("present");
1078 assert!(result.is_err(), "unknown enum value must fail deserialize");
1079 }
1080
1081 #[test]
1086 fn governance_partial_policy_write_only_uses_defaults() {
1087 let json = serde_json::json!({"write": "owner"});
1088 let parsed: GovernancePolicy = serde_json::from_value(json).expect("write-only parses");
1089 assert_eq!(parsed.write, GovernanceLevel::Owner);
1090 assert_eq!(parsed.promote, GovernanceLevel::Any);
1091 assert_eq!(parsed.delete, GovernanceLevel::Owner);
1092 assert_eq!(parsed.approver, ApproverType::Human);
1093 }
1094
1095 #[test]
1096 fn governance_partial_policy_write_and_promote() {
1097 let json = serde_json::json!({"write": "any", "promote": "registered"});
1098 let parsed: GovernancePolicy = serde_json::from_value(json).expect("parses");
1099 assert_eq!(parsed.promote, GovernanceLevel::Registered);
1100 assert_eq!(parsed.delete, GovernanceLevel::Owner);
1102 assert_eq!(parsed.approver, ApproverType::Human);
1103 }
1104
1105 #[test]
1106 fn governance_missing_write_still_errors() {
1107 let json = serde_json::json!({"promote": "owner"});
1110 let err = serde_json::from_value::<GovernancePolicy>(json);
1111 assert!(err.is_err(), "missing write must fail deserialize");
1112 }
1113
1114 #[test]
1115 fn governance_level_as_str_tags() {
1116 assert_eq!(GovernanceLevel::Any.as_str(), "any");
1117 assert_eq!(GovernanceLevel::Registered.as_str(), "registered");
1118 assert_eq!(GovernanceLevel::Owner.as_str(), "owner");
1119 assert_eq!(GovernanceLevel::Approve.as_str(), "approve");
1120 }
1121
1122 #[test]
1123 fn approver_type_kind_tags() {
1124 assert_eq!(ApproverType::Human.kind(), "human");
1125 assert_eq!(ApproverType::Agent("a".into()).kind(), "agent");
1126 assert_eq!(ApproverType::Consensus(3).kind(), "consensus");
1127 }
1128
1129 #[test]
1134 fn default_metadata_is_empty_object() {
1135 let v = default_metadata();
1136 assert!(v.is_object());
1137 assert!(v.as_object().unwrap().is_empty());
1138 }
1139
1140 #[test]
1141 fn governed_action_as_str_pinned() {
1142 assert_eq!(GovernedAction::Store.as_str(), "store");
1143 assert_eq!(GovernedAction::Delete.as_str(), "delete");
1144 assert_eq!(GovernedAction::Promote.as_str(), "promote");
1145 }
1146
1147 #[test]
1148 fn governance_decision_equality() {
1149 assert_eq!(GovernanceDecision::Allow, GovernanceDecision::Allow);
1150 assert_ne!(
1151 GovernanceDecision::Deny("a".into()),
1152 GovernanceDecision::Deny("b".into()),
1153 );
1154 assert_eq!(
1155 GovernanceDecision::Pending("p1".into()),
1156 GovernanceDecision::Pending("p1".into())
1157 );
1158 }
1159
1160 #[test]
1161 fn vector_clock_observe_monotonic() {
1162 let mut vc = VectorClock::default();
1163 vc.observe("peer-a", "2026-04-01T00:00:00+00:00");
1164 vc.observe("peer-a", "2026-05-01T00:00:00+00:00");
1165 vc.observe("peer-a", "2026-03-01T00:00:00+00:00");
1167 assert_eq!(vc.latest_from("peer-a"), Some("2026-05-01T00:00:00+00:00"));
1168 }
1169
1170 #[test]
1171 fn vector_clock_latest_from_unknown_is_none() {
1172 let vc = VectorClock::default();
1173 assert!(vc.latest_from("never-seen").is_none());
1174 }
1175
1176 #[test]
1177 fn vector_clock_serde_roundtrip() {
1178 let mut vc = VectorClock::default();
1179 vc.observe("p1", "2026-04-01T00:00:00+00:00");
1180 vc.observe("p2", "2026-04-02T00:00:00+00:00");
1181 let json = serde_json::to_string(&vc).unwrap();
1182 let back: VectorClock = serde_json::from_str(&json).unwrap();
1183 assert_eq!(back.entries.len(), 2);
1184 assert_eq!(back, vc);
1185 }
1186
1187 #[test]
1188 fn namespace_parent_with_trailing_slash() {
1189 assert_eq!(namespace_parent("a/"), Some("a".to_string()));
1192 }
1193
1194 #[test]
1195 fn namespace_depth_skips_empty_segments() {
1196 assert_eq!(namespace_depth("a//b"), 2);
1198 assert_eq!(namespace_depth("/a"), 1);
1199 assert_eq!(namespace_depth("a/"), 1);
1200 }
1201
1202 #[test]
1203 fn namespace_ancestors_two_levels() {
1204 assert_eq!(
1206 namespace_ancestors("a/b"),
1207 vec!["a/b".to_string(), "a".to_string()]
1208 );
1209 }
1210
1211 #[test]
1212 fn memory_serde_roundtrip_minimal() {
1213 let m = Memory {
1214 id: "abc".into(),
1215 tier: Tier::Mid,
1216 namespace: "global".into(),
1217 title: "t".into(),
1218 content: "c".into(),
1219 tags: vec!["x".into()],
1220 priority: 5,
1221 confidence: 0.9,
1222 source: "api".into(),
1223 access_count: 0,
1224 created_at: "2026-04-01T00:00:00+00:00".into(),
1225 updated_at: "2026-04-01T00:00:00+00:00".into(),
1226 last_accessed_at: None,
1227 expires_at: None,
1228 metadata: default_metadata(),
1229 };
1230 let json = serde_json::to_string(&m).unwrap();
1231 let back: Memory = serde_json::from_str(&json).unwrap();
1232 assert_eq!(back.id, m.id);
1233 assert_eq!(back.tier, Tier::Mid);
1234 }
1235
1236 #[test]
1237 fn approver_type_kind_for_each_variant() {
1238 assert_eq!(ApproverType::Human.kind(), "human");
1241 assert_eq!(ApproverType::Agent(String::new()).kind(), "agent");
1242 assert_eq!(ApproverType::Consensus(0).kind(), "consensus");
1243 }
1244
1245 #[test]
1246 fn governance_partial_policy_with_approver() {
1247 let json = serde_json::json!({
1249 "write": "owner",
1250 "approver": {"agent": "alice"}
1251 });
1252 let parsed: GovernancePolicy = serde_json::from_value(json).expect("parses");
1253 assert_eq!(parsed.write, GovernanceLevel::Owner);
1254 assert_eq!(parsed.approver, ApproverType::Agent("alice".to_string()));
1255 assert_eq!(parsed.promote, GovernanceLevel::Any);
1256 assert_eq!(parsed.delete, GovernanceLevel::Owner);
1257 }
1258}