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 #[serde(default)]
127 pub on_conflict: Option<String>,
128}
129
130fn default_tier() -> Tier {
131 Tier::Mid
132}
133fn default_namespace() -> String {
134 "global".to_string()
135}
136fn default_priority() -> i32 {
137 5
138}
139fn default_confidence() -> f64 {
140 1.0
141}
142fn default_source() -> String {
143 "api".to_string()
144}
145pub fn default_metadata() -> Value {
146 Value::Object(serde_json::Map::new())
147}
148
149#[derive(Debug, Deserialize)]
150pub struct UpdateMemory {
151 pub title: Option<String>,
152 pub content: Option<String>,
153 pub tier: Option<Tier>,
154 pub namespace: Option<String>,
155 pub tags: Option<Vec<String>>,
156 pub priority: Option<i32>,
157 pub confidence: Option<f64>,
158 pub expires_at: Option<String>,
159 pub metadata: Option<Value>,
160}
161
162#[derive(Debug, Deserialize)]
163pub struct SearchQuery {
164 pub q: String,
165 #[serde(default)]
166 pub namespace: Option<String>,
167 #[serde(default)]
168 pub tier: Option<Tier>,
169 #[serde(default = "default_limit")]
170 pub limit: Option<usize>,
171 #[serde(default)]
172 pub min_priority: Option<i32>,
173 #[serde(default)]
174 pub since: Option<String>,
175 #[serde(default)]
176 pub until: Option<String>,
177 #[serde(default)]
178 pub tags: Option<String>, #[serde(default)]
181 pub agent_id: Option<String>,
182 #[serde(default)]
185 pub as_agent: Option<String>,
186}
187
188#[allow(clippy::unnecessary_wraps)]
189fn default_limit() -> Option<usize> {
190 Some(20)
191}
192
193#[derive(Debug, Deserialize)]
194pub struct ListQuery {
195 #[serde(default)]
196 pub namespace: Option<String>,
197 #[serde(default)]
198 pub tier: Option<Tier>,
199 #[serde(default = "default_limit")]
200 pub limit: Option<usize>,
201 #[serde(default)]
202 pub offset: Option<usize>,
203 #[serde(default)]
204 pub min_priority: Option<i32>,
205 #[serde(default)]
206 pub since: Option<String>,
207 #[serde(default)]
208 pub until: Option<String>,
209 #[serde(default)]
210 pub tags: Option<String>,
211 #[serde(default)]
213 pub agent_id: Option<String>,
214}
215
216#[derive(Debug, Deserialize)]
217pub struct RecallQuery {
218 pub context: Option<String>,
219 #[serde(default)]
220 pub namespace: Option<String>,
221 #[serde(default = "default_recall_limit")]
222 pub limit: Option<usize>,
223 #[serde(default)]
224 pub tags: Option<String>,
225 #[serde(default)]
226 pub since: Option<String>,
227 #[serde(default)]
228 pub until: Option<String>,
229 #[serde(default)]
231 pub as_agent: Option<String>,
232 #[serde(default)]
236 pub budget_tokens: Option<usize>,
237}
238
239#[allow(clippy::unnecessary_wraps)]
240fn default_recall_limit() -> Option<usize> {
241 Some(10)
242}
243
244#[derive(Debug, Deserialize)]
245pub struct RecallBody {
246 pub context: String,
247 #[serde(default)]
248 pub namespace: Option<String>,
249 #[serde(default = "default_recall_limit")]
250 pub limit: Option<usize>,
251 #[serde(default)]
252 pub tags: Option<String>,
253 #[serde(default)]
254 pub since: Option<String>,
255 #[serde(default)]
256 pub until: Option<String>,
257 #[serde(default)]
259 pub as_agent: Option<String>,
260 #[serde(default)]
262 pub budget_tokens: Option<usize>,
263}
264
265#[derive(Debug, Deserialize)]
266pub struct LinkBody {
267 pub source_id: String,
268 pub target_id: String,
269 #[serde(default = "default_relation")]
270 pub relation: String,
271}
272
273fn default_relation() -> String {
274 "related_to".to_string()
275}
276
277#[derive(Debug, Deserialize)]
278pub struct ForgetQuery {
279 #[serde(default)]
280 pub namespace: Option<String>,
281 #[serde(default)]
282 pub pattern: Option<String>, #[serde(default)]
284 pub tier: Option<Tier>,
285}
286
287#[derive(Debug, Serialize)]
288pub struct Stats {
289 pub total: usize,
290 pub by_tier: Vec<TierCount>,
291 pub by_namespace: Vec<NamespaceCount>,
292 pub expiring_soon: usize,
293 pub links_count: usize,
294 pub db_size_bytes: u64,
295 #[serde(default)]
300 pub dim_violations: u64,
301 #[serde(default)]
307 pub index_evictions_total: u64,
308}
309
310#[derive(Debug, Clone, Serialize)]
322pub struct RecallMeta {
323 pub recall_mode: String,
328 pub reranker_used: String,
334 pub candidate_counts: CandidateCounts,
337 pub blend_weight: f64,
341}
342
343#[derive(Debug, Clone, Serialize)]
345pub struct CandidateCounts {
346 pub fts: usize,
348 pub hnsw: usize,
351}
352
353#[derive(Debug, Clone, Default)]
360pub struct RecallTelemetry {
361 pub fts_candidates: usize,
363 pub hnsw_candidates: usize,
366 pub blend_weight_avg: f64,
369}
370
371#[derive(Debug, Serialize)]
372pub struct TierCount {
373 pub tier: String,
374 pub count: usize,
375}
376
377#[derive(Debug, Serialize)]
378pub struct NamespaceCount {
379 pub namespace: String,
380 pub count: usize,
381}
382
383#[derive(Debug, Clone, Serialize)]
391pub struct TaxonomyNode {
392 pub namespace: String,
395 pub name: String,
398 pub count: usize,
400 pub subtree_count: usize,
404 pub children: Vec<TaxonomyNode>,
406}
407
408#[derive(Debug, Clone, Serialize)]
416pub struct Taxonomy {
417 pub tree: TaxonomyNode,
418 pub total_count: usize,
419 pub truncated: bool,
420}
421
422#[derive(Debug, Clone, Serialize)]
426pub struct DuplicateMatch {
427 pub id: String,
428 pub title: String,
429 pub namespace: String,
430 pub similarity: f32,
431}
432
433#[derive(Debug, Clone, Serialize)]
441pub struct DuplicateCheck {
442 pub is_duplicate: bool,
443 pub threshold: f32,
444 pub nearest: Option<DuplicateMatch>,
445 pub candidates_scanned: usize,
446}
447
448pub const AGENTS_NAMESPACE: &str = "_agents";
450
451pub const ENTITY_TAG: &str = "entity";
455
456pub const ENTITY_KIND: &str = "entity";
461
462#[derive(Debug, Clone, Serialize)]
467pub struct EntityRecord {
468 pub entity_id: String,
469 pub canonical_name: String,
470 pub namespace: String,
471 pub aliases: Vec<String>,
472}
473
474#[derive(Debug, Clone, Serialize)]
479pub struct EntityRegistration {
480 pub entity_id: String,
481 pub canonical_name: String,
482 pub namespace: String,
483 pub aliases: Vec<String>,
484 pub created: bool,
485}
486
487#[derive(Debug, Clone, Serialize)]
497pub struct KgTimelineEvent {
498 pub target_id: String,
499 pub relation: String,
500 pub valid_from: String,
501 pub valid_until: Option<String>,
502 pub observed_by: Option<String>,
503 pub title: String,
504 pub target_namespace: String,
505}
506
507#[derive(Debug, Clone, Serialize)]
515pub struct KgQueryNode {
516 pub target_id: String,
517 pub relation: String,
518 pub valid_from: Option<String>,
519 pub valid_until: Option<String>,
520 pub observed_by: Option<String>,
521 pub title: String,
522 pub target_namespace: String,
523 pub depth: usize,
524 pub path: String,
525}
526
527#[derive(Debug, Clone, PartialEq, Eq)]
535pub enum GovernanceDecision {
536 Allow,
538 Deny(String),
540 Pending(String),
542}
543
544#[derive(Debug, Clone, Copy, PartialEq, Eq)]
547pub enum GovernedAction {
548 Store,
549 Delete,
550 Promote,
551}
552
553impl GovernedAction {
554 #[must_use]
555 pub fn as_str(self) -> &'static str {
556 match self {
557 Self::Store => "store",
558 Self::Delete => "delete",
559 Self::Promote => "promote",
560 }
561 }
562}
563
564#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
566pub struct Approval {
567 pub agent_id: String,
568 pub approved_at: String,
569}
570
571#[derive(Debug, Clone, Serialize, Deserialize)]
573pub struct PendingAction {
574 pub id: String,
575 pub action_type: String,
576 pub memory_id: Option<String>,
577 pub namespace: String,
578 pub payload: Value,
579 pub requested_by: String,
580 pub requested_at: String,
581 pub status: String,
582 #[serde(skip_serializing_if = "Option::is_none")]
583 pub decided_by: Option<String>,
584 #[serde(skip_serializing_if = "Option::is_none")]
585 pub decided_at: Option<String>,
586 #[serde(default)]
588 pub approvals: Vec<Approval>,
589}
590
591#[derive(Debug, Clone, Serialize, Deserialize)]
598pub struct PendingDecision {
599 pub id: String,
600 pub approved: bool,
601 pub decider: String,
602}
603
604#[derive(Debug, Clone, Serialize, Deserialize)]
615pub struct NamespaceMetaEntry {
616 pub namespace: String,
617 pub standard_id: String,
618 #[serde(default, skip_serializing_if = "Option::is_none")]
619 pub parent_namespace: Option<String>,
620 #[serde(default)]
621 pub updated_at: String,
622}
623
624#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
634#[serde(rename_all = "snake_case")]
635pub enum GovernanceLevel {
636 Any,
638 Registered,
640 Owner,
642 Approve,
644}
645
646impl GovernanceLevel {
647 #[allow(dead_code)]
650 #[must_use]
651 pub fn as_str(&self) -> &'static str {
652 match self {
653 Self::Any => "any",
654 Self::Registered => "registered",
655 Self::Owner => "owner",
656 Self::Approve => "approve",
657 }
658 }
659}
660
661#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
669#[serde(rename_all = "snake_case")]
670pub enum ApproverType {
671 Human,
673 Agent(String),
675 Consensus(u32),
677}
678
679impl ApproverType {
680 #[allow(dead_code)]
683 #[must_use]
684 pub fn kind(&self) -> &'static str {
685 match self {
686 Self::Human => "human",
687 Self::Agent(_) => "agent",
688 Self::Consensus(_) => "consensus",
689 }
690 }
691}
692
693#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
713pub struct GovernancePolicy {
714 pub write: GovernanceLevel,
715 #[serde(default = "default_promote_level")]
716 pub promote: GovernanceLevel,
717 #[serde(default = "default_delete_level")]
718 pub delete: GovernanceLevel,
719 #[serde(default = "default_approver")]
720 pub approver: ApproverType,
721 #[serde(default = "default_inherit")]
728 pub inherit: bool,
729}
730
731fn default_promote_level() -> GovernanceLevel {
732 GovernanceLevel::Any
733}
734
735fn default_delete_level() -> GovernanceLevel {
736 GovernanceLevel::Owner
737}
738
739fn default_approver() -> ApproverType {
740 ApproverType::Human
741}
742
743fn default_inherit() -> bool {
746 true
747}
748
749impl Default for GovernancePolicy {
750 fn default() -> Self {
751 Self {
752 write: GovernanceLevel::Any,
753 promote: default_promote_level(),
754 delete: default_delete_level(),
755 approver: default_approver(),
756 inherit: default_inherit(),
757 }
758 }
759}
760
761impl GovernancePolicy {
762 pub fn from_metadata(metadata: &Value) -> Option<Result<Self, serde_json::Error>> {
766 let gov = metadata.get("governance")?;
767 if gov.is_null() {
768 return None;
769 }
770 Some(serde_json::from_value(gov.clone()))
771 }
772}
773
774pub const VALID_SCOPES: &[&str] = &["private", "team", "unit", "org", "collective"];
778
779pub const VALID_AGENT_TYPES: &[&str] = &[
781 "ai:claude-opus-4.6",
782 "ai:claude-opus-4.7",
783 "ai:codex-5.4",
784 "ai:grok-4.2",
785 "human",
786 "system",
787];
788
789#[derive(Debug, Deserialize)]
790pub struct RegisterAgentBody {
791 pub agent_id: String,
792 pub agent_type: String,
793 #[serde(default)]
794 pub capabilities: Option<Vec<String>>,
795}
796
797#[derive(Debug, Serialize)]
798pub struct AgentRegistration {
799 pub agent_id: String,
800 pub agent_type: String,
801 pub capabilities: Vec<String>,
802 pub registered_at: String,
803 pub last_seen_at: String,
804}
805
806#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
817pub struct VectorClock {
818 #[serde(default)]
822 pub entries: std::collections::BTreeMap<String, String>,
823}
824
825impl VectorClock {
826 #[allow(dead_code)] pub fn observe(&mut self, peer_id: &str, at: &str) {
830 self.entries
831 .entry(peer_id.to_string())
832 .and_modify(|existing| {
833 if at > existing.as_str() {
834 *existing = at.to_string();
835 }
836 })
837 .or_insert_with(|| at.to_string());
838 }
839
840 #[must_use]
842 #[allow(dead_code)] pub fn latest_from(&self, peer_id: &str) -> Option<&str> {
844 self.entries.get(peer_id).map(String::as_str)
845 }
846}
847
848#[allow(dead_code)] #[derive(Debug, Clone, Serialize, Deserialize)]
852pub struct SyncStateEntry {
853 pub agent_id: String,
854 pub peer_id: String,
855 pub last_seen_at: String,
856 pub last_pulled_at: String,
857}
858
859pub const MAX_CONTENT_SIZE: usize = 65_536;
860
861pub const MAX_NAMESPACE_DEPTH: usize = 8;
864
865#[must_use]
878pub fn namespace_depth(ns: &str) -> usize {
879 if ns.is_empty() {
880 return 0;
881 }
882 ns.split('/').filter(|s| !s.is_empty()).count()
883}
884
885#[allow(dead_code)]
891#[must_use]
903pub fn namespace_parent(ns: &str) -> Option<String> {
904 ns.rsplit_once('/').map(|(parent, _)| parent.to_string())
905}
906
907#[allow(dead_code)]
913#[must_use]
929pub fn namespace_ancestors(ns: &str) -> Vec<String> {
930 if ns.is_empty() {
931 return Vec::new();
932 }
933 let mut out = Vec::with_capacity(namespace_depth(ns));
934 let mut current = ns.to_string();
935 loop {
936 out.push(current.clone());
937 match namespace_parent(¤t) {
938 Some(p) if !p.is_empty() => current = p,
939 _ => break,
940 }
941 }
942 out
943}
944pub const PROMOTION_THRESHOLD: i64 = 5;
945pub const SHORT_TTL_EXTEND_SECS: i64 = 3600;
947pub const MID_TTL_EXTEND_SECS: i64 = 86400;
948
949#[cfg(test)]
950mod tests {
951 use super::*;
952
953 #[test]
954 fn tier_from_str_valid() {
955 assert_eq!(Tier::from_str("short"), Some(Tier::Short));
956 assert_eq!(Tier::from_str("mid"), Some(Tier::Mid));
957 assert_eq!(Tier::from_str("long"), Some(Tier::Long));
958 }
959
960 #[test]
961 fn tier_from_str_invalid() {
962 assert_eq!(Tier::from_str("invalid"), None);
963 assert_eq!(Tier::from_str(""), None);
964 assert_eq!(Tier::from_str("SHORT"), None); }
966
967 #[test]
968 fn tier_as_str_roundtrip() {
969 for tier in [Tier::Short, Tier::Mid, Tier::Long] {
970 let s = tier.as_str();
971 assert_eq!(Tier::from_str(s), Some(tier));
972 }
973 }
974
975 #[test]
976 fn tier_default_ttl() {
977 assert_eq!(Tier::Short.default_ttl_secs(), Some(6 * 3600));
978 assert_eq!(Tier::Mid.default_ttl_secs(), Some(7 * 24 * 3600));
979 assert_eq!(Tier::Long.default_ttl_secs(), None);
980 }
981
982 #[test]
983 fn tier_display() {
984 assert_eq!(format!("{}", Tier::Short), "short");
985 assert_eq!(format!("{}", Tier::Mid), "mid");
986 assert_eq!(format!("{}", Tier::Long), "long");
987 }
988
989 #[test]
990 fn constants_valid() {
991 const _: () = assert!(MAX_CONTENT_SIZE > 0);
992 const _: () = assert!(PROMOTION_THRESHOLD > 0);
993 assert_eq!(SHORT_TTL_EXTEND_SECS, 3600);
994 assert_eq!(MID_TTL_EXTEND_SECS, 86400);
995 }
996
997 #[test]
998 fn tier_rank_ordering() {
999 assert!(Tier::Short.rank() < Tier::Mid.rank());
1000 assert!(Tier::Mid.rank() < Tier::Long.rank());
1001 assert_eq!(Tier::Short.rank(), 0);
1002 assert_eq!(Tier::Mid.rank(), 1);
1003 assert_eq!(Tier::Long.rank(), 2);
1004 }
1005
1006 #[test]
1009 fn depth_flat_namespace() {
1010 assert_eq!(namespace_depth("global"), 1);
1011 assert_eq!(namespace_depth("ai-memory"), 1);
1012 assert_eq!(namespace_depth("under_score"), 1);
1013 }
1014
1015 #[test]
1016 fn depth_hierarchical() {
1017 assert_eq!(namespace_depth("a/b"), 2);
1018 assert_eq!(namespace_depth("alphaone/engineering"), 2);
1019 assert_eq!(namespace_depth("alphaone/engineering/platform"), 3);
1020 assert_eq!(
1021 namespace_depth("a/b/c/d/e/f/g/h"),
1022 8,
1023 "max depth of 8 counts each segment"
1024 );
1025 }
1026
1027 #[test]
1028 fn depth_empty_is_zero() {
1029 assert_eq!(namespace_depth(""), 0);
1030 }
1031
1032 #[test]
1033 fn parent_hierarchical() {
1034 assert_eq!(
1035 namespace_parent("alphaone/engineering/platform"),
1036 Some("alphaone/engineering".to_string())
1037 );
1038 assert_eq!(
1039 namespace_parent("alphaone/engineering"),
1040 Some("alphaone".to_string())
1041 );
1042 }
1043
1044 #[test]
1045 fn parent_flat_is_none() {
1046 assert_eq!(namespace_parent("global"), None);
1047 assert_eq!(namespace_parent("ai-memory"), None);
1048 assert_eq!(namespace_parent(""), None);
1049 }
1050
1051 #[test]
1052 fn ancestors_three_levels() {
1053 let a = namespace_ancestors("alphaone/engineering/platform");
1054 assert_eq!(
1055 a,
1056 vec![
1057 "alphaone/engineering/platform".to_string(),
1058 "alphaone/engineering".to_string(),
1059 "alphaone".to_string(),
1060 ],
1061 "ancestors ordered most-specific-first"
1062 );
1063 }
1064
1065 #[test]
1066 fn ancestors_flat_namespace() {
1067 assert_eq!(namespace_ancestors("global"), vec!["global".to_string()]);
1068 assert_eq!(
1069 namespace_ancestors("ai-memory"),
1070 vec!["ai-memory".to_string()]
1071 );
1072 }
1073
1074 #[test]
1075 fn ancestors_empty_input() {
1076 assert!(namespace_ancestors("").is_empty());
1077 }
1078
1079 #[test]
1080 fn ancestors_single_level() {
1081 assert_eq!(namespace_ancestors("a"), vec!["a".to_string()]);
1082 }
1083
1084 #[test]
1085 fn ancestors_max_depth() {
1086 let a = namespace_ancestors("a/b/c/d/e/f/g/h");
1087 assert_eq!(a.len(), 8);
1088 assert_eq!(a[0], "a/b/c/d/e/f/g/h");
1089 assert_eq!(a[7], "a");
1090 }
1091
1092 #[test]
1095 fn governance_default_policy() {
1096 let p = GovernancePolicy::default();
1097 assert_eq!(p.write, GovernanceLevel::Any);
1098 assert_eq!(p.promote, GovernanceLevel::Any);
1099 assert_eq!(p.delete, GovernanceLevel::Owner);
1100 assert_eq!(p.approver, ApproverType::Human);
1101 assert!(p.inherit);
1105 }
1106
1107 #[test]
1108 fn governance_inherit_field_defaults_true_on_partial_payload() {
1109 let json = r#"{"write":"approve"}"#;
1113 let p: GovernancePolicy = serde_json::from_str(json).unwrap();
1114 assert_eq!(p.write, GovernanceLevel::Approve);
1115 assert!(p.inherit, "missing `inherit` must deserialize as true");
1116 }
1117
1118 #[test]
1119 fn governance_inherit_field_explicit_false_round_trip() {
1120 let json = r#"{"write":"any","inherit":false}"#;
1123 let p: GovernancePolicy = serde_json::from_str(json).unwrap();
1124 assert!(!p.inherit);
1125 let back = serde_json::to_value(&p).unwrap();
1126 assert_eq!(back["inherit"], false);
1127 }
1128
1129 #[test]
1130 fn governance_level_serde_snake_case() {
1131 for (level, expected) in [
1133 (GovernanceLevel::Any, "any"),
1134 (GovernanceLevel::Registered, "registered"),
1135 (GovernanceLevel::Owner, "owner"),
1136 (GovernanceLevel::Approve, "approve"),
1137 ] {
1138 let json = serde_json::to_string(&level).unwrap();
1139 assert_eq!(json, format!("\"{expected}\""));
1140 let back: GovernanceLevel = serde_json::from_str(&json).unwrap();
1142 assert_eq!(back, level);
1143 }
1144 }
1145
1146 #[test]
1147 fn approver_type_serde_shapes() {
1148 let json = serde_json::to_string(&ApproverType::Human).unwrap();
1150 assert_eq!(json, "\"human\"");
1151
1152 let a = ApproverType::Agent("alice".to_string());
1154 let json = serde_json::to_string(&a).unwrap();
1155 assert_eq!(json, r#"{"agent":"alice"}"#);
1156 let back: ApproverType = serde_json::from_str(&json).unwrap();
1157 assert_eq!(back, a);
1158
1159 let c = ApproverType::Consensus(3);
1161 let json = serde_json::to_string(&c).unwrap();
1162 assert_eq!(json, r#"{"consensus":3}"#);
1163 let back: ApproverType = serde_json::from_str(&json).unwrap();
1164 assert_eq!(back, c);
1165 }
1166
1167 #[test]
1168 fn governance_policy_full_roundtrip() {
1169 let p = GovernancePolicy {
1170 write: GovernanceLevel::Registered,
1171 promote: GovernanceLevel::Approve,
1172 delete: GovernanceLevel::Owner,
1173 approver: ApproverType::Agent("maintainer".to_string()),
1174 inherit: true,
1175 };
1176 let json = serde_json::to_string(&p).unwrap();
1177 let back: GovernancePolicy = serde_json::from_str(&json).unwrap();
1178 assert_eq!(back, p);
1179 }
1180
1181 #[test]
1182 fn governance_from_metadata_missing() {
1183 let meta = serde_json::json!({"agent_id": "alice"});
1184 assert!(GovernancePolicy::from_metadata(&meta).is_none());
1185 }
1186
1187 #[test]
1188 fn governance_from_metadata_null() {
1189 let meta = serde_json::json!({"governance": null});
1190 assert!(GovernancePolicy::from_metadata(&meta).is_none());
1191 }
1192
1193 #[test]
1194 fn governance_from_metadata_default_shape() {
1195 let default = GovernancePolicy::default();
1196 let meta = serde_json::json!({"governance": serde_json::to_value(&default).unwrap()});
1197 let parsed = GovernancePolicy::from_metadata(&meta)
1198 .expect("present")
1199 .expect("valid");
1200 assert_eq!(parsed, default);
1201 }
1202
1203 #[test]
1204 fn governance_from_metadata_invalid_returns_err() {
1205 let meta = serde_json::json!({
1206 "governance": {"write": "bogus", "promote": "any", "delete": "any", "approver": "human"}
1207 });
1208 let result = GovernancePolicy::from_metadata(&meta).expect("present");
1209 assert!(result.is_err(), "unknown enum value must fail deserialize");
1210 }
1211
1212 #[test]
1217 fn governance_partial_policy_write_only_uses_defaults() {
1218 let json = serde_json::json!({"write": "owner"});
1219 let parsed: GovernancePolicy = serde_json::from_value(json).expect("write-only parses");
1220 assert_eq!(parsed.write, GovernanceLevel::Owner);
1221 assert_eq!(parsed.promote, GovernanceLevel::Any);
1222 assert_eq!(parsed.delete, GovernanceLevel::Owner);
1223 assert_eq!(parsed.approver, ApproverType::Human);
1224 }
1225
1226 #[test]
1227 fn governance_partial_policy_write_and_promote() {
1228 let json = serde_json::json!({"write": "any", "promote": "registered"});
1229 let parsed: GovernancePolicy = serde_json::from_value(json).expect("parses");
1230 assert_eq!(parsed.promote, GovernanceLevel::Registered);
1231 assert_eq!(parsed.delete, GovernanceLevel::Owner);
1233 assert_eq!(parsed.approver, ApproverType::Human);
1234 }
1235
1236 #[test]
1237 fn governance_missing_write_still_errors() {
1238 let json = serde_json::json!({"promote": "owner"});
1241 let err = serde_json::from_value::<GovernancePolicy>(json);
1242 assert!(err.is_err(), "missing write must fail deserialize");
1243 }
1244
1245 #[test]
1246 fn governance_level_as_str_tags() {
1247 assert_eq!(GovernanceLevel::Any.as_str(), "any");
1248 assert_eq!(GovernanceLevel::Registered.as_str(), "registered");
1249 assert_eq!(GovernanceLevel::Owner.as_str(), "owner");
1250 assert_eq!(GovernanceLevel::Approve.as_str(), "approve");
1251 }
1252
1253 #[test]
1254 fn approver_type_kind_tags() {
1255 assert_eq!(ApproverType::Human.kind(), "human");
1256 assert_eq!(ApproverType::Agent("a".into()).kind(), "agent");
1257 assert_eq!(ApproverType::Consensus(3).kind(), "consensus");
1258 }
1259
1260 #[test]
1265 fn default_metadata_is_empty_object() {
1266 let v = default_metadata();
1267 assert!(v.is_object());
1268 assert!(v.as_object().unwrap().is_empty());
1269 }
1270
1271 #[test]
1272 fn governed_action_as_str_pinned() {
1273 assert_eq!(GovernedAction::Store.as_str(), "store");
1274 assert_eq!(GovernedAction::Delete.as_str(), "delete");
1275 assert_eq!(GovernedAction::Promote.as_str(), "promote");
1276 }
1277
1278 #[test]
1279 fn governance_decision_equality() {
1280 assert_eq!(GovernanceDecision::Allow, GovernanceDecision::Allow);
1281 assert_ne!(
1282 GovernanceDecision::Deny("a".into()),
1283 GovernanceDecision::Deny("b".into()),
1284 );
1285 assert_eq!(
1286 GovernanceDecision::Pending("p1".into()),
1287 GovernanceDecision::Pending("p1".into())
1288 );
1289 }
1290
1291 #[test]
1292 fn vector_clock_observe_monotonic() {
1293 let mut vc = VectorClock::default();
1294 vc.observe("peer-a", "2026-04-01T00:00:00+00:00");
1295 vc.observe("peer-a", "2026-05-01T00:00:00+00:00");
1296 vc.observe("peer-a", "2026-03-01T00:00:00+00:00");
1298 assert_eq!(vc.latest_from("peer-a"), Some("2026-05-01T00:00:00+00:00"));
1299 }
1300
1301 #[test]
1302 fn vector_clock_latest_from_unknown_is_none() {
1303 let vc = VectorClock::default();
1304 assert!(vc.latest_from("never-seen").is_none());
1305 }
1306
1307 #[test]
1308 fn vector_clock_serde_roundtrip() {
1309 let mut vc = VectorClock::default();
1310 vc.observe("p1", "2026-04-01T00:00:00+00:00");
1311 vc.observe("p2", "2026-04-02T00:00:00+00:00");
1312 let json = serde_json::to_string(&vc).unwrap();
1313 let back: VectorClock = serde_json::from_str(&json).unwrap();
1314 assert_eq!(back.entries.len(), 2);
1315 assert_eq!(back, vc);
1316 }
1317
1318 #[test]
1319 fn namespace_parent_with_trailing_slash() {
1320 assert_eq!(namespace_parent("a/"), Some("a".to_string()));
1323 }
1324
1325 #[test]
1326 fn namespace_depth_skips_empty_segments() {
1327 assert_eq!(namespace_depth("a//b"), 2);
1329 assert_eq!(namespace_depth("/a"), 1);
1330 assert_eq!(namespace_depth("a/"), 1);
1331 }
1332
1333 #[test]
1334 fn namespace_ancestors_two_levels() {
1335 assert_eq!(
1337 namespace_ancestors("a/b"),
1338 vec!["a/b".to_string(), "a".to_string()]
1339 );
1340 }
1341
1342 #[test]
1343 fn memory_serde_roundtrip_minimal() {
1344 let m = Memory {
1345 id: "abc".into(),
1346 tier: Tier::Mid,
1347 namespace: "global".into(),
1348 title: "t".into(),
1349 content: "c".into(),
1350 tags: vec!["x".into()],
1351 priority: 5,
1352 confidence: 0.9,
1353 source: "api".into(),
1354 access_count: 0,
1355 created_at: "2026-04-01T00:00:00+00:00".into(),
1356 updated_at: "2026-04-01T00:00:00+00:00".into(),
1357 last_accessed_at: None,
1358 expires_at: None,
1359 metadata: default_metadata(),
1360 };
1361 let json = serde_json::to_string(&m).unwrap();
1362 let back: Memory = serde_json::from_str(&json).unwrap();
1363 assert_eq!(back.id, m.id);
1364 assert_eq!(back.tier, Tier::Mid);
1365 }
1366
1367 #[test]
1368 fn approver_type_kind_for_each_variant() {
1369 assert_eq!(ApproverType::Human.kind(), "human");
1372 assert_eq!(ApproverType::Agent(String::new()).kind(), "agent");
1373 assert_eq!(ApproverType::Consensus(0).kind(), "consensus");
1374 }
1375
1376 #[test]
1377 fn governance_partial_policy_with_approver() {
1378 let json = serde_json::json!({
1380 "write": "owner",
1381 "approver": {"agent": "alice"}
1382 });
1383 let parsed: GovernancePolicy = serde_json::from_value(json).expect("parses");
1384 assert_eq!(parsed.write, GovernanceLevel::Owner);
1385 assert_eq!(parsed.approver, ApproverType::Agent("alice".to_string()));
1386 assert_eq!(parsed.promote, GovernanceLevel::Any);
1387 assert_eq!(parsed.delete, GovernanceLevel::Owner);
1388 }
1389}