weavatrix_memory/domain/
fact.rs1use super::{Confidence, Evidence};
2use crate::{AgentId, EntityId, FactId, MemoryError, Result, SessionId, Timestamp};
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
6pub struct MemoryFact {
7 pub id: FactId,
8 pub source: EntityId,
9 pub relation: String,
10 pub target: EntityId,
11 pub valid_from: Timestamp,
12 #[serde(skip_serializing_if = "Option::is_none")]
13 pub valid_until: Option<Timestamp>,
14 pub observed_at: Timestamp,
15 pub recorded_at: Timestamp,
16 pub agent_id: AgentId,
17 pub session_id: SessionId,
18 pub confidence: Confidence,
19 pub evidence: Vec<Evidence>,
20 #[serde(skip_serializing_if = "Option::is_none")]
21 pub supersedes: Option<FactId>,
22}
23
24impl MemoryFact {
25 #[allow(clippy::too_many_arguments)]
31 pub fn new(
32 id: FactId,
33 source: EntityId,
34 relation: impl Into<String>,
35 target: EntityId,
36 valid_from: Timestamp,
37 recorded_at: Timestamp,
38 agent_id: AgentId,
39 session_id: SessionId,
40 evidence: Evidence,
41 ) -> Result<Self> {
42 let fact = Self {
43 id,
44 source,
45 relation: relation.into(),
46 target,
47 valid_from,
48 valid_until: None,
49 observed_at: recorded_at,
50 recorded_at,
51 agent_id,
52 session_id,
53 confidence: Confidence::CERTAIN,
54 evidence: vec![evidence],
55 supersedes: None,
56 };
57 fact.validate()?;
58 Ok(fact)
59 }
60
61 pub fn valid_until(mut self, value: Timestamp) -> Result<Self> {
67 self.valid_until = Some(value);
68 self.validate()?;
69 Ok(self)
70 }
71
72 #[must_use]
73 pub const fn observed_at(mut self, value: Timestamp) -> Self {
74 self.observed_at = value;
75 self
76 }
77
78 #[must_use]
79 pub const fn with_confidence(mut self, value: Confidence) -> Self {
80 self.confidence = value;
81 self
82 }
83
84 pub fn supersedes(mut self, fact: FactId) -> Result<Self> {
90 if fact == self.id {
91 return Err(MemoryError::InvalidValue {
92 field: "fact.supersedes",
93 reason: "a fact cannot supersede itself",
94 });
95 }
96 self.supersedes = Some(fact);
97 Ok(self)
98 }
99
100 #[must_use]
101 pub fn with_evidence(mut self, evidence: Evidence) -> Self {
102 self.evidence.push(evidence);
103 self
104 }
105
106 pub(crate) fn validate(&self) -> Result<()> {
107 super::validate_text("fact.relation", &self.relation)?;
108 if self.observed_at > self.recorded_at {
109 return Err(MemoryError::InvalidValue {
110 field: "fact.observed_at",
111 reason: "must not be later than recorded_at",
112 });
113 }
114 if self
115 .valid_until
116 .is_some_and(|until| until <= self.valid_from)
117 {
118 return Err(MemoryError::InvalidValue {
119 field: "fact.valid_until",
120 reason: "must be later than valid_from",
121 });
122 }
123 if self.evidence.is_empty() {
124 return Err(MemoryError::InvalidValue {
125 field: "fact.evidence",
126 reason: "at least one evidence item is required",
127 });
128 }
129 self.evidence.iter().try_for_each(Evidence::validate)
130 }
131}