Skip to main content

ai_memory/models/
mod.rs

1// Copyright 2026 AlphaOne LLC
2// SPDX-License-Identifier: Apache-2.0
3
4use serde_json::Value;
5
6pub mod audit;
7pub mod capture_turn;
8pub mod field_names;
9pub mod link;
10pub mod memory;
11pub mod namespace;
12pub mod recall_request;
13pub mod reflection;
14pub mod skill;
15pub mod tag;
16
17#[allow(unused_imports)]
18pub use audit::*;
19pub use capture_turn::*;
20pub use link::*;
21pub use memory::*;
22pub use namespace::*;
23#[allow(unused_imports)]
24pub use recall_request::*;
25#[allow(unused_imports)]
26pub use reflection::*;
27#[allow(unused_imports)]
28pub use tag::*;
29
30pub const MAX_CONTENT_SIZE: usize = 65_536;
31
32pub const PROMOTION_THRESHOLD: i64 = 5;
33/// How much to extend TTL on access (1 hour for short, 1 day for mid)
34pub const SHORT_TTL_EXTEND_SECS: i64 = crate::SECS_PER_HOUR;
35pub const MID_TTL_EXTEND_SECS: i64 = crate::SECS_PER_DAY;
36
37pub fn default_metadata() -> Value {
38    Value::Object(serde_json::Map::new())
39}
40
41#[cfg(test)]
42mod tests {
43    use super::*;
44
45    #[test]
46    fn tier_from_str_valid() {
47        assert_eq!(Tier::from_str("short"), Some(Tier::Short));
48        assert_eq!(Tier::from_str("mid"), Some(Tier::Mid));
49        assert_eq!(Tier::from_str("long"), Some(Tier::Long));
50    }
51
52    #[test]
53    fn tier_from_str_invalid() {
54        assert_eq!(Tier::from_str("invalid"), None);
55        assert_eq!(Tier::from_str(""), None);
56        assert_eq!(Tier::from_str("SHORT"), None); // case-sensitive
57    }
58
59    #[test]
60    fn tier_as_str_roundtrip() {
61        for tier in [Tier::Short, Tier::Mid, Tier::Long] {
62            let s = tier.as_str();
63            assert_eq!(Tier::from_str(s), Some(tier));
64        }
65    }
66
67    #[test]
68    fn tier_default_ttl() {
69        assert_eq!(
70            Tier::Short.default_ttl_secs(),
71            Some(6 * crate::SECS_PER_HOUR)
72        );
73        assert_eq!(Tier::Mid.default_ttl_secs(), Some(crate::SECS_PER_WEEK));
74        assert_eq!(Tier::Long.default_ttl_secs(), None);
75    }
76
77    #[test]
78    fn tier_display() {
79        assert_eq!(format!("{}", Tier::Short), "short");
80        assert_eq!(format!("{}", Tier::Mid), "mid");
81        assert_eq!(format!("{}", Tier::Long), "long");
82    }
83
84    #[test]
85    fn constants_valid() {
86        const _: () = assert!(MAX_CONTENT_SIZE > 0);
87        const _: () = assert!(PROMOTION_THRESHOLD > 0);
88        // PR3 (#1174) — byte-equal preservation: the named extends
89        // resolve to the same compiled integer the v0.6.x literals did.
90        assert_eq!(SHORT_TTL_EXTEND_SECS, 3600);
91        assert_eq!(MID_TTL_EXTEND_SECS, 86400);
92        assert_eq!(SHORT_TTL_EXTEND_SECS, crate::SECS_PER_HOUR);
93        assert_eq!(MID_TTL_EXTEND_SECS, crate::SECS_PER_DAY);
94    }
95
96    #[test]
97    fn tier_rank_ordering() {
98        assert!(Tier::Short.rank() < Tier::Mid.rank());
99        assert!(Tier::Mid.rank() < Tier::Long.rank());
100        assert_eq!(Tier::Short.rank(), 0);
101        assert_eq!(Tier::Mid.rank(), 1);
102        assert_eq!(Tier::Long.rank(), 2);
103    }
104
105    // ---- v0.7 Track H4 — AttestLevel enum -----------------------------------
106
107    #[test]
108    fn attest_level_from_str_canonical_strings() {
109        // Every canonical wire string a production emit-site writes to
110        // the `attest_level` column must round-trip. All five variants
111        // are covered because #1431 migrated every production emit-site
112        // off raw literals onto `AttestLevel::<Variant>.as_str()` —
113        // SignedByPeer (capture_turn host-signed) and DaemonSigned
114        // (governance audit) are exercised here so the SSOT can't drift.
115        assert_eq!(
116            AttestLevel::from_str("unsigned"),
117            Some(AttestLevel::Unsigned)
118        );
119        assert_eq!(
120            AttestLevel::from_str("self_signed"),
121            Some(AttestLevel::SelfSigned)
122        );
123        assert_eq!(
124            AttestLevel::from_str("peer_attested"),
125            Some(AttestLevel::PeerAttested)
126        );
127        assert_eq!(
128            AttestLevel::from_str("signed_by_peer"),
129            Some(AttestLevel::SignedByPeer)
130        );
131        assert_eq!(
132            AttestLevel::from_str("daemon_signed"),
133            Some(AttestLevel::DaemonSigned)
134        );
135    }
136
137    #[test]
138    fn attest_level_as_str_exact_wire_strings() {
139        // SSOT contract: the exact bytes every #1431-migrated production
140        // emit-site now writes. If any arm drifts, the federation
141        // receive path, capture_turn host-signing, the governance audit
142        // chain, persona stamping, skill registration, and reflection
143        // export all silently decouple from the DB column + the parser.
144        assert_eq!(AttestLevel::Unsigned.as_str(), "unsigned");
145        assert_eq!(AttestLevel::SelfSigned.as_str(), "self_signed");
146        assert_eq!(AttestLevel::PeerAttested.as_str(), "peer_attested");
147        assert_eq!(AttestLevel::SignedByPeer.as_str(), "signed_by_peer");
148        assert_eq!(AttestLevel::DaemonSigned.as_str(), "daemon_signed");
149    }
150
151    #[test]
152    fn attest_level_from_str_unknown_returns_none() {
153        assert_eq!(AttestLevel::from_str(""), None);
154        assert_eq!(AttestLevel::from_str("Unsigned"), None); // case-sensitive
155        assert_eq!(AttestLevel::from_str("self-signed"), None); // hyphen wrong
156        assert_eq!(AttestLevel::from_str("attested"), None);
157    }
158
159    #[test]
160    fn attest_level_as_str_round_trips_through_from_str() {
161        for lvl in [
162            AttestLevel::Unsigned,
163            AttestLevel::SelfSigned,
164            AttestLevel::PeerAttested,
165            AttestLevel::SignedByPeer,
166            AttestLevel::DaemonSigned,
167        ] {
168            let s = lvl.as_str();
169            assert_eq!(
170                AttestLevel::from_str(s),
171                Some(lvl),
172                "round-trip failed for {lvl:?}"
173            );
174        }
175    }
176
177    #[test]
178    fn attest_level_display_matches_as_str() {
179        assert_eq!(format!("{}", AttestLevel::Unsigned), "unsigned");
180        assert_eq!(format!("{}", AttestLevel::SelfSigned), "self_signed");
181        assert_eq!(format!("{}", AttestLevel::PeerAttested), "peer_attested");
182        assert_eq!(format!("{}", AttestLevel::SignedByPeer), "signed_by_peer");
183        assert_eq!(format!("{}", AttestLevel::DaemonSigned), "daemon_signed");
184    }
185
186    #[test]
187    fn attest_level_serde_wire_shape_matches_db_column() {
188        // Wire shape = the literal column value. If this drifts, H2/H3
189        // outputs and H4 inputs decouple silently.
190        let json = serde_json::to_string(&AttestLevel::PeerAttested).unwrap();
191        assert_eq!(json, "\"peer_attested\"");
192        let back: AttestLevel = serde_json::from_str("\"self_signed\"").unwrap();
193        assert_eq!(back, AttestLevel::SelfSigned);
194        // Unknown string must fail deserialization (closed-set enum).
195        assert!(serde_json::from_str::<AttestLevel>("\"bogus\"").is_err());
196    }
197
198    // Task 1.4 — hierarchical namespace helpers --------------------------------
199
200    #[test]
201    fn depth_flat_namespace() {
202        assert_eq!(namespace_depth("global"), 1);
203        assert_eq!(namespace_depth("ai-memory"), 1);
204        assert_eq!(namespace_depth("under_score"), 1);
205    }
206
207    #[test]
208    fn depth_hierarchical() {
209        assert_eq!(namespace_depth("a/b"), 2);
210        assert_eq!(namespace_depth("alphaone/engineering"), 2);
211        assert_eq!(namespace_depth("alphaone/engineering/platform"), 3);
212        assert_eq!(
213            namespace_depth("a/b/c/d/e/f/g/h"),
214            8,
215            "max depth of 8 counts each segment"
216        );
217    }
218
219    #[test]
220    fn depth_empty_is_zero() {
221        assert_eq!(namespace_depth(""), 0);
222    }
223
224    #[test]
225    fn parent_hierarchical() {
226        assert_eq!(
227            namespace_parent("alphaone/engineering/platform"),
228            Some("alphaone/engineering".to_string())
229        );
230        assert_eq!(
231            namespace_parent("alphaone/engineering"),
232            Some("alphaone".to_string())
233        );
234    }
235
236    #[test]
237    fn parent_flat_is_none() {
238        assert_eq!(namespace_parent("global"), None);
239        assert_eq!(namespace_parent("ai-memory"), None);
240        assert_eq!(namespace_parent(""), None);
241    }
242
243    #[test]
244    fn ancestors_three_levels() {
245        let a = namespace_ancestors("alphaone/engineering/platform");
246        assert_eq!(
247            a,
248            vec![
249                "alphaone/engineering/platform".to_string(),
250                "alphaone/engineering".to_string(),
251                "alphaone".to_string(),
252            ],
253            "ancestors ordered most-specific-first"
254        );
255    }
256
257    #[test]
258    fn ancestors_flat_namespace() {
259        assert_eq!(namespace_ancestors("global"), vec!["global".to_string()]);
260        assert_eq!(
261            namespace_ancestors("ai-memory"),
262            vec!["ai-memory".to_string()]
263        );
264    }
265
266    #[test]
267    fn ancestors_empty_input() {
268        assert!(namespace_ancestors("").is_empty());
269    }
270
271    #[test]
272    fn ancestors_single_level() {
273        assert_eq!(namespace_ancestors("a"), vec!["a".to_string()]);
274    }
275
276    #[test]
277    fn ancestors_max_depth() {
278        let a = namespace_ancestors("a/b/c/d/e/f/g/h");
279        assert_eq!(a.len(), 8);
280        assert_eq!(a[0], "a/b/c/d/e/f/g/h");
281        assert_eq!(a[7], "a");
282    }
283
284    // Task 1.8 — governance types ---------------------------------------
285
286    #[test]
287    fn governance_default_policy() {
288        let p = GovernancePolicy::default();
289        assert_eq!(p.core.write, GovernanceLevel::Any);
290        assert_eq!(p.core.promote, GovernanceLevel::Any);
291        assert_eq!(p.core.delete, GovernanceLevel::Owner);
292        assert_eq!(p.core.approver, ApproverType::Human);
293        // v0.6.3.1 (P4, G1): inheritance is the documented default. Existing
294        // rows are backfilled to true by migration 0012; new rows that omit
295        // the field deserialize as true via #[serde(default)].
296        assert!(p.core.inherit);
297    }
298
299    #[test]
300    fn governance_inherit_field_defaults_true_on_partial_payload() {
301        // P4 (G1): a partial-policy payload that omits `inherit` must
302        // default to true so legacy callers don't accidentally opt out
303        // of parent inheritance the moment they write a child policy.
304        let json = r#"{"write":"approve"}"#;
305        let p: GovernancePolicy = serde_json::from_str(json).unwrap();
306        assert_eq!(p.core.write, GovernanceLevel::Approve);
307        assert!(p.core.inherit, "missing `inherit` must deserialize as true");
308    }
309
310    #[test]
311    fn governance_inherit_field_explicit_false_round_trip() {
312        // P4 (G1): when an operator explicitly opts a subtree out of
313        // inheritance, the false value must round-trip and serialize.
314        let json = r#"{"write":"any","inherit":false}"#;
315        let p: GovernancePolicy = serde_json::from_str(json).unwrap();
316        assert!(!p.core.inherit);
317        let back = serde_json::to_value(&p).unwrap();
318        assert_eq!(back["inherit"], false);
319    }
320
321    #[test]
322    fn governance_level_serde_snake_case() {
323        // Serialize each level as a lowercase JSON string
324        for (level, expected) in [
325            (GovernanceLevel::Any, "any"),
326            (GovernanceLevel::Registered, "registered"),
327            (GovernanceLevel::Owner, "owner"),
328            (GovernanceLevel::Approve, "approve"),
329        ] {
330            let json = serde_json::to_string(&level).unwrap();
331            assert_eq!(json, format!("\"{expected}\""));
332            // Roundtrip
333            let back: GovernanceLevel = serde_json::from_str(&json).unwrap();
334            assert_eq!(back, level);
335        }
336    }
337
338    #[test]
339    fn approver_type_serde_shapes() {
340        // Human → unit variant serializes as bare string
341        let json = serde_json::to_string(&ApproverType::Human).unwrap();
342        assert_eq!(json, "\"human\"");
343
344        // Agent(s) → externally tagged
345        let a = ApproverType::Agent("alice".to_string());
346        let json = serde_json::to_string(&a).unwrap();
347        assert_eq!(json, r#"{"agent":"alice"}"#);
348        let back: ApproverType = serde_json::from_str(&json).unwrap();
349        assert_eq!(back, a);
350
351        // Consensus(n) → externally tagged, numeric payload
352        let c = ApproverType::Consensus(3);
353        let json = serde_json::to_string(&c).unwrap();
354        assert_eq!(json, r#"{"consensus":3}"#);
355        let back: ApproverType = serde_json::from_str(&json).unwrap();
356        assert_eq!(back, c);
357    }
358
359    #[test]
360    fn governance_policy_full_roundtrip() {
361        let p = GovernancePolicy {
362            core: CorePolicy {
363                write: GovernanceLevel::Registered,
364                promote: GovernanceLevel::Approve,
365                delete: GovernanceLevel::Owner,
366                approver: ApproverType::Agent("maintainer".to_string()),
367                inherit: true,
368                max_reflection_depth: None,
369            },
370            ..Default::default()
371        };
372        let json = serde_json::to_string(&p).unwrap();
373        let back: GovernancePolicy = serde_json::from_str(&json).unwrap();
374        assert_eq!(back, p);
375    }
376
377    #[test]
378    fn governance_from_metadata_missing() {
379        let meta = serde_json::json!({"agent_id": "alice"});
380        assert!(GovernancePolicy::from_metadata(&meta).is_none());
381    }
382
383    #[test]
384    fn governance_from_metadata_null() {
385        let meta = serde_json::json!({"governance": null});
386        assert!(GovernancePolicy::from_metadata(&meta).is_none());
387    }
388
389    #[test]
390    fn governance_from_metadata_default_shape() {
391        let default = GovernancePolicy::default();
392        let meta = serde_json::json!({"governance": serde_json::to_value(&default).unwrap()});
393        let parsed = GovernancePolicy::from_metadata(&meta)
394            .expect("present")
395            .expect("valid");
396        assert_eq!(parsed, default);
397    }
398
399    #[test]
400    fn governance_from_metadata_invalid_returns_err() {
401        let meta = serde_json::json!({
402            "governance": {"write": "bogus", "promote": "any", "delete": "any", "approver": "human"}
403        });
404        let result = GovernancePolicy::from_metadata(&meta).expect("present");
405        assert!(result.is_err(), "unknown enum value must fail deserialize");
406    }
407
408    // v0.6.2 (S34 defense): partial policy payloads fall back to the
409    // `Default for GovernancePolicy` values for any field the caller omitted.
410    // `write` remains required — it's the core knob the policy expresses.
411
412    #[test]
413    fn governance_partial_policy_write_only_uses_defaults() {
414        let json = serde_json::json!({"write": "owner"});
415        let parsed: GovernancePolicy = serde_json::from_value(json).expect("write-only parses");
416        assert_eq!(parsed.core.write, GovernanceLevel::Owner);
417        assert_eq!(parsed.core.promote, GovernanceLevel::Any);
418        assert_eq!(parsed.core.delete, GovernanceLevel::Owner);
419        assert_eq!(parsed.core.approver, ApproverType::Human);
420    }
421
422    #[test]
423    fn governance_partial_policy_write_and_promote() {
424        let json = serde_json::json!({"write": "any", "promote": "registered"});
425        let parsed: GovernancePolicy = serde_json::from_value(json).expect("parses");
426        assert_eq!(parsed.core.promote, GovernanceLevel::Registered);
427        // Absent fields still take defaults.
428        assert_eq!(parsed.core.delete, GovernanceLevel::Owner);
429        assert_eq!(parsed.core.approver, ApproverType::Human);
430    }
431
432    #[test]
433    fn governance_missing_write_still_errors() {
434        // `write` is the core policy knob — must remain required to avoid
435        // silently accepting an empty object as "any writes allowed".
436        let json = serde_json::json!({"promote": "owner"});
437        let err = serde_json::from_value::<GovernancePolicy>(json);
438        assert!(err.is_err(), "missing write must fail deserialize");
439    }
440
441    #[test]
442    fn governance_level_as_str_tags() {
443        assert_eq!(GovernanceLevel::Any.as_str(), "any");
444        assert_eq!(GovernanceLevel::Registered.as_str(), "registered");
445        assert_eq!(GovernanceLevel::Owner.as_str(), "owner");
446        assert_eq!(GovernanceLevel::Approve.as_str(), "approve");
447    }
448
449    #[test]
450    fn approver_type_kind_tags() {
451        assert_eq!(ApproverType::Human.kind(), "human");
452        assert_eq!(ApproverType::Agent("a".into()).kind(), "agent");
453        assert_eq!(ApproverType::Consensus(3).kind(), "consensus");
454    }
455
456    // -----------------------------------------------------------------
457    // W12-H — additional small-module pinning
458    // -----------------------------------------------------------------
459
460    #[test]
461    fn default_metadata_is_empty_object() {
462        let v = default_metadata();
463        assert!(v.is_object());
464        assert!(v.as_object().unwrap().is_empty());
465    }
466
467    #[test]
468    fn governed_action_as_str_pinned() {
469        assert_eq!(GovernedAction::Store.as_str(), "store");
470        assert_eq!(GovernedAction::Delete.as_str(), "delete");
471        assert_eq!(GovernedAction::Promote.as_str(), "promote");
472    }
473
474    #[test]
475    fn governance_decision_equality() {
476        use crate::governance::GovernanceRefusal;
477        assert_eq!(GovernanceDecision::Allow, GovernanceDecision::Allow);
478        // #963 Phase 2 — `Deny` carries the typed
479        // `GovernanceRefusal` envelope; equality reduces to struct equality
480        // so two refusals with different `reason` still compare unequal.
481        assert_ne!(
482            GovernanceDecision::Deny(GovernanceRefusal::new(
483                GovernedAction::Store,
484                GovernanceLevel::Owner,
485                "ai:x",
486                "a",
487            )),
488            GovernanceDecision::Deny(GovernanceRefusal::new(
489                GovernedAction::Store,
490                GovernanceLevel::Owner,
491                "ai:x",
492                "b",
493            )),
494        );
495        assert_eq!(
496            GovernanceDecision::Pending("p1".into()),
497            GovernanceDecision::Pending("p1".into())
498        );
499    }
500
501    #[test]
502    fn vector_clock_observe_monotonic() {
503        let mut vc = VectorClock::default();
504        vc.observe("peer-a", "2026-04-01T00:00:00+00:00");
505        vc.observe("peer-a", "2026-05-01T00:00:00+00:00");
506        // Older never overwrites newer.
507        vc.observe("peer-a", "2026-03-01T00:00:00+00:00");
508        assert_eq!(vc.latest_from("peer-a"), Some("2026-05-01T00:00:00+00:00"));
509    }
510
511    #[test]
512    fn vector_clock_latest_from_unknown_is_none() {
513        let vc = VectorClock::default();
514        assert!(vc.latest_from("never-seen").is_none());
515    }
516
517    #[test]
518    fn vector_clock_serde_roundtrip() {
519        let mut vc = VectorClock::default();
520        vc.observe("p1", "2026-04-01T00:00:00+00:00");
521        vc.observe("p2", "2026-04-02T00:00:00+00:00");
522        let json = serde_json::to_string(&vc).unwrap();
523        let back: VectorClock = serde_json::from_str(&json).unwrap();
524        assert_eq!(back.entries.len(), 2);
525        assert_eq!(back, vc);
526    }
527
528    #[test]
529    fn namespace_parent_with_trailing_slash() {
530        // "a/" splits to parent="a" and tail="". The function returns the
531        // parent regardless of whether the final segment is empty.
532        assert_eq!(namespace_parent("a/"), Some("a".to_string()));
533    }
534
535    #[test]
536    fn namespace_depth_skips_empty_segments() {
537        // Multiple slashes do not inflate the depth count.
538        assert_eq!(namespace_depth("a//b"), 2);
539        assert_eq!(namespace_depth("/a"), 1);
540        assert_eq!(namespace_depth("a/"), 1);
541    }
542
543    #[test]
544    fn namespace_ancestors_two_levels() {
545        // Two-level namespace produces self + parent.
546        assert_eq!(
547            namespace_ancestors("a/b"),
548            vec!["a/b".to_string(), "a".to_string()]
549        );
550    }
551
552    #[test]
553    fn memory_serde_roundtrip_minimal() {
554        let m = Memory {
555            id: "abc".into(),
556            tier: Tier::Mid,
557            namespace: "global".into(),
558            title: "t".into(),
559            content: "c".into(),
560            tags: vec!["x".into()],
561            priority: 5,
562            confidence: 0.9,
563            source: "api".into(),
564            access_count: 0,
565            created_at: "2026-04-01T00:00:00+00:00".into(),
566            updated_at: "2026-04-01T00:00:00+00:00".into(),
567            last_accessed_at: None,
568            expires_at: None,
569            metadata: default_metadata(),
570            reflection_depth: 0,
571            memory_kind: MemoryKind::Observation,
572            entity_id: None,
573            persona_version: None,
574            citations: Vec::new(),
575            source_uri: None,
576            source_span: None,
577            confidence_source: ConfidenceSource::CallerProvided,
578            confidence_signals: None,
579            confidence_decayed_at: None,
580            version: 1,
581        };
582        let json = serde_json::to_string(&m).unwrap();
583        let back: Memory = serde_json::from_str(&json).unwrap();
584        assert_eq!(back.id, m.id);
585        assert_eq!(back.tier, Tier::Mid);
586    }
587
588    #[test]
589    fn approver_type_kind_for_each_variant() {
590        // Hits all three discriminant arms. Mirrors the existing test but
591        // ensures we cover a Consensus(0) which is the lower edge.
592        assert_eq!(ApproverType::Human.kind(), "human");
593        assert_eq!(ApproverType::Agent(String::new()).kind(), "agent");
594        assert_eq!(ApproverType::Consensus(0).kind(), "consensus");
595    }
596
597    #[test]
598    fn governance_partial_policy_with_approver() {
599        // Partial policy with `approver` set and other fields defaulted.
600        let json = serde_json::json!({
601            "write": "owner",
602            "approver": {"agent": "alice"}
603        });
604        let parsed: GovernancePolicy = serde_json::from_value(json).expect("parses");
605        assert_eq!(parsed.core.write, GovernanceLevel::Owner);
606        assert_eq!(
607            parsed.core.approver,
608            ApproverType::Agent("alice".to_string())
609        );
610        assert_eq!(parsed.core.promote, GovernanceLevel::Any);
611        assert_eq!(parsed.core.delete, GovernanceLevel::Owner);
612    }
613}