Skip to main content

weavatrix_memory/domain/
fact.rs

1use 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    /// Creates an evidence-carrying temporal relation.
26    ///
27    /// # Errors
28    ///
29    /// Rejects invalid relation text or missing evidence.
30    #[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    /// Sets the exclusive end of the fact's valid interval.
62    ///
63    /// # Errors
64    ///
65    /// Rejects an end that is not later than `valid_from`.
66    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    /// Links this fact to the historical fact it replaces.
85    ///
86    /// # Errors
87    ///
88    /// Rejects self-supersession.
89    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}