use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum Tier {
Short,
Mid,
Long,
}
impl Tier {
pub fn as_str(&self) -> &'static str {
match self {
Self::Short => "short",
Self::Mid => "mid",
Self::Long => "long",
}
}
pub fn from_str(s: &str) -> Option<Self> {
match s {
"short" => Some(Self::Short),
"mid" => Some(Self::Mid),
"long" => Some(Self::Long),
_ => None,
}
}
#[cfg(test)]
pub fn rank(&self) -> u8 {
match self {
Self::Short => 0,
Self::Mid => 1,
Self::Long => 2,
}
}
pub fn default_ttl_secs(&self) -> Option<i64> {
match self {
Self::Short => Some(6 * 3600),
Self::Mid => Some(7 * 24 * 3600),
Self::Long => None,
}
}
}
impl std::fmt::Display for Tier {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Memory {
pub id: String,
pub tier: Tier,
pub namespace: String,
pub title: String,
pub content: String,
pub tags: Vec<String>,
pub priority: i32,
pub confidence: f64,
pub source: String,
pub access_count: i64,
pub created_at: String,
pub updated_at: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub last_accessed_at: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub expires_at: Option<String>,
#[serde(default = "default_metadata")]
pub metadata: Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MemoryLink {
pub source_id: String,
pub target_id: String,
pub relation: String, pub created_at: String,
}
#[derive(Debug, Deserialize)]
pub struct CreateMemory {
#[serde(default = "default_tier")]
pub tier: Tier,
#[serde(default = "default_namespace")]
pub namespace: String,
pub title: String,
pub content: String,
#[serde(default)]
pub tags: Vec<String>,
#[serde(default = "default_priority")]
pub priority: i32,
#[serde(default = "default_confidence")]
pub confidence: f64,
#[serde(default = "default_source")]
pub source: String,
#[serde(default)]
pub expires_at: Option<String>,
#[serde(default)]
pub ttl_secs: Option<i64>,
#[serde(default = "default_metadata")]
pub metadata: Value,
#[serde(default)]
pub agent_id: Option<String>,
#[serde(default)]
pub scope: Option<String>,
#[serde(default)]
pub on_conflict: Option<String>,
}
fn default_tier() -> Tier {
Tier::Mid
}
fn default_namespace() -> String {
"global".to_string()
}
fn default_priority() -> i32 {
5
}
fn default_confidence() -> f64 {
1.0
}
fn default_source() -> String {
"api".to_string()
}
pub fn default_metadata() -> Value {
Value::Object(serde_json::Map::new())
}
#[derive(Debug, Deserialize)]
pub struct UpdateMemory {
pub title: Option<String>,
pub content: Option<String>,
pub tier: Option<Tier>,
pub namespace: Option<String>,
pub tags: Option<Vec<String>>,
pub priority: Option<i32>,
pub confidence: Option<f64>,
pub expires_at: Option<String>,
pub metadata: Option<Value>,
}
#[derive(Debug, Deserialize)]
pub struct SearchQuery {
pub q: String,
#[serde(default)]
pub namespace: Option<String>,
#[serde(default)]
pub tier: Option<Tier>,
#[serde(default = "default_limit")]
pub limit: Option<usize>,
#[serde(default)]
pub min_priority: Option<i32>,
#[serde(default)]
pub since: Option<String>,
#[serde(default)]
pub until: Option<String>,
#[serde(default)]
pub tags: Option<String>, #[serde(default)]
pub agent_id: Option<String>,
#[serde(default)]
pub as_agent: Option<String>,
}
#[allow(clippy::unnecessary_wraps)]
fn default_limit() -> Option<usize> {
Some(20)
}
#[derive(Debug, Deserialize)]
pub struct ListQuery {
#[serde(default)]
pub namespace: Option<String>,
#[serde(default)]
pub tier: Option<Tier>,
#[serde(default = "default_limit")]
pub limit: Option<usize>,
#[serde(default)]
pub offset: Option<usize>,
#[serde(default)]
pub min_priority: Option<i32>,
#[serde(default)]
pub since: Option<String>,
#[serde(default)]
pub until: Option<String>,
#[serde(default)]
pub tags: Option<String>,
#[serde(default)]
pub agent_id: Option<String>,
}
#[derive(Debug, Deserialize)]
pub struct RecallQuery {
pub context: Option<String>,
#[serde(default)]
pub namespace: Option<String>,
#[serde(default = "default_recall_limit")]
pub limit: Option<usize>,
#[serde(default)]
pub tags: Option<String>,
#[serde(default)]
pub since: Option<String>,
#[serde(default)]
pub until: Option<String>,
#[serde(default)]
pub as_agent: Option<String>,
#[serde(default)]
pub budget_tokens: Option<usize>,
}
#[allow(clippy::unnecessary_wraps)]
fn default_recall_limit() -> Option<usize> {
Some(10)
}
#[derive(Debug, Deserialize)]
pub struct RecallBody {
pub context: String,
#[serde(default)]
pub namespace: Option<String>,
#[serde(default = "default_recall_limit")]
pub limit: Option<usize>,
#[serde(default)]
pub tags: Option<String>,
#[serde(default)]
pub since: Option<String>,
#[serde(default)]
pub until: Option<String>,
#[serde(default)]
pub as_agent: Option<String>,
#[serde(default)]
pub budget_tokens: Option<usize>,
}
#[derive(Debug, Deserialize)]
pub struct LinkBody {
pub source_id: String,
pub target_id: String,
#[serde(default = "default_relation")]
pub relation: String,
}
fn default_relation() -> String {
"related_to".to_string()
}
#[derive(Debug, Deserialize)]
pub struct ForgetQuery {
#[serde(default)]
pub namespace: Option<String>,
#[serde(default)]
pub pattern: Option<String>, #[serde(default)]
pub tier: Option<Tier>,
}
#[derive(Debug, Serialize)]
pub struct Stats {
pub total: usize,
pub by_tier: Vec<TierCount>,
pub by_namespace: Vec<NamespaceCount>,
pub expiring_soon: usize,
pub links_count: usize,
pub db_size_bytes: u64,
#[serde(default)]
pub dim_violations: u64,
#[serde(default)]
pub index_evictions_total: u64,
}
#[derive(Debug, Clone, Serialize)]
pub struct RecallMeta {
pub recall_mode: String,
pub reranker_used: String,
pub candidate_counts: CandidateCounts,
pub blend_weight: f64,
}
#[derive(Debug, Clone, Serialize)]
pub struct CandidateCounts {
pub fts: usize,
pub hnsw: usize,
}
#[derive(Debug, Clone, Default)]
pub struct RecallTelemetry {
pub fts_candidates: usize,
pub hnsw_candidates: usize,
pub blend_weight_avg: f64,
}
#[derive(Debug, Serialize)]
pub struct TierCount {
pub tier: String,
pub count: usize,
}
#[derive(Debug, Serialize)]
pub struct NamespaceCount {
pub namespace: String,
pub count: usize,
}
#[derive(Debug, Clone, Serialize)]
pub struct TaxonomyNode {
pub namespace: String,
pub name: String,
pub count: usize,
pub subtree_count: usize,
pub children: Vec<TaxonomyNode>,
}
#[derive(Debug, Clone, Serialize)]
pub struct Taxonomy {
pub tree: TaxonomyNode,
pub total_count: usize,
pub truncated: bool,
}
#[derive(Debug, Clone, Serialize)]
pub struct DuplicateMatch {
pub id: String,
pub title: String,
pub namespace: String,
pub similarity: f32,
}
#[derive(Debug, Clone, Serialize)]
pub struct DuplicateCheck {
pub is_duplicate: bool,
pub threshold: f32,
pub nearest: Option<DuplicateMatch>,
pub candidates_scanned: usize,
}
pub const AGENTS_NAMESPACE: &str = "_agents";
pub const ENTITY_TAG: &str = "entity";
pub const ENTITY_KIND: &str = "entity";
#[derive(Debug, Clone, Serialize)]
pub struct EntityRecord {
pub entity_id: String,
pub canonical_name: String,
pub namespace: String,
pub aliases: Vec<String>,
}
#[derive(Debug, Clone, Serialize)]
pub struct EntityRegistration {
pub entity_id: String,
pub canonical_name: String,
pub namespace: String,
pub aliases: Vec<String>,
pub created: bool,
}
#[derive(Debug, Clone, Serialize)]
pub struct KgTimelineEvent {
pub target_id: String,
pub relation: String,
pub valid_from: String,
pub valid_until: Option<String>,
pub observed_by: Option<String>,
pub title: String,
pub target_namespace: String,
}
#[derive(Debug, Clone, Serialize)]
pub struct KgQueryNode {
pub target_id: String,
pub relation: String,
pub valid_from: Option<String>,
pub valid_until: Option<String>,
pub observed_by: Option<String>,
pub title: String,
pub target_namespace: String,
pub depth: usize,
pub path: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum GovernanceDecision {
Allow,
Deny(String),
Pending(String),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GovernedAction {
Store,
Delete,
Promote,
}
impl GovernedAction {
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
Self::Store => "store",
Self::Delete => "delete",
Self::Promote => "promote",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Approval {
pub agent_id: String,
pub approved_at: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PendingAction {
pub id: String,
pub action_type: String,
pub memory_id: Option<String>,
pub namespace: String,
pub payload: Value,
pub requested_by: String,
pub requested_at: String,
pub status: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub decided_by: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub decided_at: Option<String>,
#[serde(default)]
pub approvals: Vec<Approval>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PendingDecision {
pub id: String,
pub approved: bool,
pub decider: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NamespaceMetaEntry {
pub namespace: String,
pub standard_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub parent_namespace: Option<String>,
#[serde(default)]
pub updated_at: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum GovernanceLevel {
Any,
Registered,
Owner,
Approve,
}
impl GovernanceLevel {
#[allow(dead_code)]
#[must_use]
pub fn as_str(&self) -> &'static str {
match self {
Self::Any => "any",
Self::Registered => "registered",
Self::Owner => "owner",
Self::Approve => "approve",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ApproverType {
Human,
Agent(String),
Consensus(u32),
}
impl ApproverType {
#[allow(dead_code)]
#[must_use]
pub fn kind(&self) -> &'static str {
match self {
Self::Human => "human",
Self::Agent(_) => "agent",
Self::Consensus(_) => "consensus",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct GovernancePolicy {
pub write: GovernanceLevel,
#[serde(default = "default_promote_level")]
pub promote: GovernanceLevel,
#[serde(default = "default_delete_level")]
pub delete: GovernanceLevel,
#[serde(default = "default_approver")]
pub approver: ApproverType,
#[serde(default = "default_inherit")]
pub inherit: bool,
}
fn default_promote_level() -> GovernanceLevel {
GovernanceLevel::Any
}
fn default_delete_level() -> GovernanceLevel {
GovernanceLevel::Owner
}
fn default_approver() -> ApproverType {
ApproverType::Human
}
fn default_inherit() -> bool {
true
}
impl Default for GovernancePolicy {
fn default() -> Self {
Self {
write: GovernanceLevel::Any,
promote: default_promote_level(),
delete: default_delete_level(),
approver: default_approver(),
inherit: default_inherit(),
}
}
}
impl GovernancePolicy {
pub fn from_metadata(metadata: &Value) -> Option<Result<Self, serde_json::Error>> {
let gov = metadata.get("governance")?;
if gov.is_null() {
return None;
}
Some(serde_json::from_value(gov.clone()))
}
}
pub const VALID_SCOPES: &[&str] = &["private", "team", "unit", "org", "collective"];
pub const VALID_AGENT_TYPES: &[&str] = &[
"ai:claude-opus-4.6",
"ai:claude-opus-4.7",
"ai:codex-5.4",
"ai:grok-4.2",
"human",
"system",
];
#[derive(Debug, Deserialize)]
pub struct RegisterAgentBody {
pub agent_id: String,
pub agent_type: String,
#[serde(default)]
pub capabilities: Option<Vec<String>>,
}
#[derive(Debug, Serialize)]
pub struct AgentRegistration {
pub agent_id: String,
pub agent_type: String,
pub capabilities: Vec<String>,
pub registered_at: String,
pub last_seen_at: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
pub struct VectorClock {
#[serde(default)]
pub entries: std::collections::BTreeMap<String, String>,
}
impl VectorClock {
#[allow(dead_code)] pub fn observe(&mut self, peer_id: &str, at: &str) {
self.entries
.entry(peer_id.to_string())
.and_modify(|existing| {
if at > existing.as_str() {
*existing = at.to_string();
}
})
.or_insert_with(|| at.to_string());
}
#[must_use]
#[allow(dead_code)] pub fn latest_from(&self, peer_id: &str) -> Option<&str> {
self.entries.get(peer_id).map(String::as_str)
}
}
#[allow(dead_code)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SyncStateEntry {
pub agent_id: String,
pub peer_id: String,
pub last_seen_at: String,
pub last_pulled_at: String,
}
pub const MAX_CONTENT_SIZE: usize = 65_536;
pub const MAX_NAMESPACE_DEPTH: usize = 8;
#[must_use]
pub fn namespace_depth(ns: &str) -> usize {
if ns.is_empty() {
return 0;
}
ns.split('/').filter(|s| !s.is_empty()).count()
}
#[allow(dead_code)]
#[must_use]
pub fn namespace_parent(ns: &str) -> Option<String> {
ns.rsplit_once('/').map(|(parent, _)| parent.to_string())
}
#[allow(dead_code)]
#[must_use]
pub fn namespace_ancestors(ns: &str) -> Vec<String> {
if ns.is_empty() {
return Vec::new();
}
let mut out = Vec::with_capacity(namespace_depth(ns));
let mut current = ns.to_string();
loop {
out.push(current.clone());
match namespace_parent(¤t) {
Some(p) if !p.is_empty() => current = p,
_ => break,
}
}
out
}
pub const PROMOTION_THRESHOLD: i64 = 5;
pub const SHORT_TTL_EXTEND_SECS: i64 = 3600;
pub const MID_TTL_EXTEND_SECS: i64 = 86400;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tier_from_str_valid() {
assert_eq!(Tier::from_str("short"), Some(Tier::Short));
assert_eq!(Tier::from_str("mid"), Some(Tier::Mid));
assert_eq!(Tier::from_str("long"), Some(Tier::Long));
}
#[test]
fn tier_from_str_invalid() {
assert_eq!(Tier::from_str("invalid"), None);
assert_eq!(Tier::from_str(""), None);
assert_eq!(Tier::from_str("SHORT"), None); }
#[test]
fn tier_as_str_roundtrip() {
for tier in [Tier::Short, Tier::Mid, Tier::Long] {
let s = tier.as_str();
assert_eq!(Tier::from_str(s), Some(tier));
}
}
#[test]
fn tier_default_ttl() {
assert_eq!(Tier::Short.default_ttl_secs(), Some(6 * 3600));
assert_eq!(Tier::Mid.default_ttl_secs(), Some(7 * 24 * 3600));
assert_eq!(Tier::Long.default_ttl_secs(), None);
}
#[test]
fn tier_display() {
assert_eq!(format!("{}", Tier::Short), "short");
assert_eq!(format!("{}", Tier::Mid), "mid");
assert_eq!(format!("{}", Tier::Long), "long");
}
#[test]
fn constants_valid() {
const _: () = assert!(MAX_CONTENT_SIZE > 0);
const _: () = assert!(PROMOTION_THRESHOLD > 0);
assert_eq!(SHORT_TTL_EXTEND_SECS, 3600);
assert_eq!(MID_TTL_EXTEND_SECS, 86400);
}
#[test]
fn tier_rank_ordering() {
assert!(Tier::Short.rank() < Tier::Mid.rank());
assert!(Tier::Mid.rank() < Tier::Long.rank());
assert_eq!(Tier::Short.rank(), 0);
assert_eq!(Tier::Mid.rank(), 1);
assert_eq!(Tier::Long.rank(), 2);
}
#[test]
fn depth_flat_namespace() {
assert_eq!(namespace_depth("global"), 1);
assert_eq!(namespace_depth("ai-memory"), 1);
assert_eq!(namespace_depth("under_score"), 1);
}
#[test]
fn depth_hierarchical() {
assert_eq!(namespace_depth("a/b"), 2);
assert_eq!(namespace_depth("alphaone/engineering"), 2);
assert_eq!(namespace_depth("alphaone/engineering/platform"), 3);
assert_eq!(
namespace_depth("a/b/c/d/e/f/g/h"),
8,
"max depth of 8 counts each segment"
);
}
#[test]
fn depth_empty_is_zero() {
assert_eq!(namespace_depth(""), 0);
}
#[test]
fn parent_hierarchical() {
assert_eq!(
namespace_parent("alphaone/engineering/platform"),
Some("alphaone/engineering".to_string())
);
assert_eq!(
namespace_parent("alphaone/engineering"),
Some("alphaone".to_string())
);
}
#[test]
fn parent_flat_is_none() {
assert_eq!(namespace_parent("global"), None);
assert_eq!(namespace_parent("ai-memory"), None);
assert_eq!(namespace_parent(""), None);
}
#[test]
fn ancestors_three_levels() {
let a = namespace_ancestors("alphaone/engineering/platform");
assert_eq!(
a,
vec![
"alphaone/engineering/platform".to_string(),
"alphaone/engineering".to_string(),
"alphaone".to_string(),
],
"ancestors ordered most-specific-first"
);
}
#[test]
fn ancestors_flat_namespace() {
assert_eq!(namespace_ancestors("global"), vec!["global".to_string()]);
assert_eq!(
namespace_ancestors("ai-memory"),
vec!["ai-memory".to_string()]
);
}
#[test]
fn ancestors_empty_input() {
assert!(namespace_ancestors("").is_empty());
}
#[test]
fn ancestors_single_level() {
assert_eq!(namespace_ancestors("a"), vec!["a".to_string()]);
}
#[test]
fn ancestors_max_depth() {
let a = namespace_ancestors("a/b/c/d/e/f/g/h");
assert_eq!(a.len(), 8);
assert_eq!(a[0], "a/b/c/d/e/f/g/h");
assert_eq!(a[7], "a");
}
#[test]
fn governance_default_policy() {
let p = GovernancePolicy::default();
assert_eq!(p.write, GovernanceLevel::Any);
assert_eq!(p.promote, GovernanceLevel::Any);
assert_eq!(p.delete, GovernanceLevel::Owner);
assert_eq!(p.approver, ApproverType::Human);
assert!(p.inherit);
}
#[test]
fn governance_inherit_field_defaults_true_on_partial_payload() {
let json = r#"{"write":"approve"}"#;
let p: GovernancePolicy = serde_json::from_str(json).unwrap();
assert_eq!(p.write, GovernanceLevel::Approve);
assert!(p.inherit, "missing `inherit` must deserialize as true");
}
#[test]
fn governance_inherit_field_explicit_false_round_trip() {
let json = r#"{"write":"any","inherit":false}"#;
let p: GovernancePolicy = serde_json::from_str(json).unwrap();
assert!(!p.inherit);
let back = serde_json::to_value(&p).unwrap();
assert_eq!(back["inherit"], false);
}
#[test]
fn governance_level_serde_snake_case() {
for (level, expected) in [
(GovernanceLevel::Any, "any"),
(GovernanceLevel::Registered, "registered"),
(GovernanceLevel::Owner, "owner"),
(GovernanceLevel::Approve, "approve"),
] {
let json = serde_json::to_string(&level).unwrap();
assert_eq!(json, format!("\"{expected}\""));
let back: GovernanceLevel = serde_json::from_str(&json).unwrap();
assert_eq!(back, level);
}
}
#[test]
fn approver_type_serde_shapes() {
let json = serde_json::to_string(&ApproverType::Human).unwrap();
assert_eq!(json, "\"human\"");
let a = ApproverType::Agent("alice".to_string());
let json = serde_json::to_string(&a).unwrap();
assert_eq!(json, r#"{"agent":"alice"}"#);
let back: ApproverType = serde_json::from_str(&json).unwrap();
assert_eq!(back, a);
let c = ApproverType::Consensus(3);
let json = serde_json::to_string(&c).unwrap();
assert_eq!(json, r#"{"consensus":3}"#);
let back: ApproverType = serde_json::from_str(&json).unwrap();
assert_eq!(back, c);
}
#[test]
fn governance_policy_full_roundtrip() {
let p = GovernancePolicy {
write: GovernanceLevel::Registered,
promote: GovernanceLevel::Approve,
delete: GovernanceLevel::Owner,
approver: ApproverType::Agent("maintainer".to_string()),
inherit: true,
};
let json = serde_json::to_string(&p).unwrap();
let back: GovernancePolicy = serde_json::from_str(&json).unwrap();
assert_eq!(back, p);
}
#[test]
fn governance_from_metadata_missing() {
let meta = serde_json::json!({"agent_id": "alice"});
assert!(GovernancePolicy::from_metadata(&meta).is_none());
}
#[test]
fn governance_from_metadata_null() {
let meta = serde_json::json!({"governance": null});
assert!(GovernancePolicy::from_metadata(&meta).is_none());
}
#[test]
fn governance_from_metadata_default_shape() {
let default = GovernancePolicy::default();
let meta = serde_json::json!({"governance": serde_json::to_value(&default).unwrap()});
let parsed = GovernancePolicy::from_metadata(&meta)
.expect("present")
.expect("valid");
assert_eq!(parsed, default);
}
#[test]
fn governance_from_metadata_invalid_returns_err() {
let meta = serde_json::json!({
"governance": {"write": "bogus", "promote": "any", "delete": "any", "approver": "human"}
});
let result = GovernancePolicy::from_metadata(&meta).expect("present");
assert!(result.is_err(), "unknown enum value must fail deserialize");
}
#[test]
fn governance_partial_policy_write_only_uses_defaults() {
let json = serde_json::json!({"write": "owner"});
let parsed: GovernancePolicy = serde_json::from_value(json).expect("write-only parses");
assert_eq!(parsed.write, GovernanceLevel::Owner);
assert_eq!(parsed.promote, GovernanceLevel::Any);
assert_eq!(parsed.delete, GovernanceLevel::Owner);
assert_eq!(parsed.approver, ApproverType::Human);
}
#[test]
fn governance_partial_policy_write_and_promote() {
let json = serde_json::json!({"write": "any", "promote": "registered"});
let parsed: GovernancePolicy = serde_json::from_value(json).expect("parses");
assert_eq!(parsed.promote, GovernanceLevel::Registered);
assert_eq!(parsed.delete, GovernanceLevel::Owner);
assert_eq!(parsed.approver, ApproverType::Human);
}
#[test]
fn governance_missing_write_still_errors() {
let json = serde_json::json!({"promote": "owner"});
let err = serde_json::from_value::<GovernancePolicy>(json);
assert!(err.is_err(), "missing write must fail deserialize");
}
#[test]
fn governance_level_as_str_tags() {
assert_eq!(GovernanceLevel::Any.as_str(), "any");
assert_eq!(GovernanceLevel::Registered.as_str(), "registered");
assert_eq!(GovernanceLevel::Owner.as_str(), "owner");
assert_eq!(GovernanceLevel::Approve.as_str(), "approve");
}
#[test]
fn approver_type_kind_tags() {
assert_eq!(ApproverType::Human.kind(), "human");
assert_eq!(ApproverType::Agent("a".into()).kind(), "agent");
assert_eq!(ApproverType::Consensus(3).kind(), "consensus");
}
#[test]
fn default_metadata_is_empty_object() {
let v = default_metadata();
assert!(v.is_object());
assert!(v.as_object().unwrap().is_empty());
}
#[test]
fn governed_action_as_str_pinned() {
assert_eq!(GovernedAction::Store.as_str(), "store");
assert_eq!(GovernedAction::Delete.as_str(), "delete");
assert_eq!(GovernedAction::Promote.as_str(), "promote");
}
#[test]
fn governance_decision_equality() {
assert_eq!(GovernanceDecision::Allow, GovernanceDecision::Allow);
assert_ne!(
GovernanceDecision::Deny("a".into()),
GovernanceDecision::Deny("b".into()),
);
assert_eq!(
GovernanceDecision::Pending("p1".into()),
GovernanceDecision::Pending("p1".into())
);
}
#[test]
fn vector_clock_observe_monotonic() {
let mut vc = VectorClock::default();
vc.observe("peer-a", "2026-04-01T00:00:00+00:00");
vc.observe("peer-a", "2026-05-01T00:00:00+00:00");
vc.observe("peer-a", "2026-03-01T00:00:00+00:00");
assert_eq!(vc.latest_from("peer-a"), Some("2026-05-01T00:00:00+00:00"));
}
#[test]
fn vector_clock_latest_from_unknown_is_none() {
let vc = VectorClock::default();
assert!(vc.latest_from("never-seen").is_none());
}
#[test]
fn vector_clock_serde_roundtrip() {
let mut vc = VectorClock::default();
vc.observe("p1", "2026-04-01T00:00:00+00:00");
vc.observe("p2", "2026-04-02T00:00:00+00:00");
let json = serde_json::to_string(&vc).unwrap();
let back: VectorClock = serde_json::from_str(&json).unwrap();
assert_eq!(back.entries.len(), 2);
assert_eq!(back, vc);
}
#[test]
fn namespace_parent_with_trailing_slash() {
assert_eq!(namespace_parent("a/"), Some("a".to_string()));
}
#[test]
fn namespace_depth_skips_empty_segments() {
assert_eq!(namespace_depth("a//b"), 2);
assert_eq!(namespace_depth("/a"), 1);
assert_eq!(namespace_depth("a/"), 1);
}
#[test]
fn namespace_ancestors_two_levels() {
assert_eq!(
namespace_ancestors("a/b"),
vec!["a/b".to_string(), "a".to_string()]
);
}
#[test]
fn memory_serde_roundtrip_minimal() {
let m = Memory {
id: "abc".into(),
tier: Tier::Mid,
namespace: "global".into(),
title: "t".into(),
content: "c".into(),
tags: vec!["x".into()],
priority: 5,
confidence: 0.9,
source: "api".into(),
access_count: 0,
created_at: "2026-04-01T00:00:00+00:00".into(),
updated_at: "2026-04-01T00:00:00+00:00".into(),
last_accessed_at: None,
expires_at: None,
metadata: default_metadata(),
};
let json = serde_json::to_string(&m).unwrap();
let back: Memory = serde_json::from_str(&json).unwrap();
assert_eq!(back.id, m.id);
assert_eq!(back.tier, Tier::Mid);
}
#[test]
fn approver_type_kind_for_each_variant() {
assert_eq!(ApproverType::Human.kind(), "human");
assert_eq!(ApproverType::Agent(String::new()).kind(), "agent");
assert_eq!(ApproverType::Consensus(0).kind(), "consensus");
}
#[test]
fn governance_partial_policy_with_approver() {
let json = serde_json::json!({
"write": "owner",
"approver": {"agent": "alice"}
});
let parsed: GovernancePolicy = serde_json::from_value(json).expect("parses");
assert_eq!(parsed.write, GovernanceLevel::Owner);
assert_eq!(parsed.approver, ApproverType::Agent("alice".to_string()));
assert_eq!(parsed.promote, GovernanceLevel::Any);
assert_eq!(parsed.delete, GovernanceLevel::Owner);
}
}