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