1use crate::errors::SisterResult;
18use crate::types::{Metadata, SisterType};
19use chrono::{DateTime, Utc};
20use serde::{Deserialize, Serialize};
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
28#[serde(rename_all = "snake_case")]
29pub enum GroundingStatus {
30 Verified,
32
33 Partial,
35
36 Ungrounded,
38}
39
40impl std::fmt::Display for GroundingStatus {
41 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
42 match self {
43 Self::Verified => write!(f, "verified"),
44 Self::Partial => write!(f, "partial"),
45 Self::Ungrounded => write!(f, "ungrounded"),
46 }
47 }
48}
49
50#[derive(Debug, Clone, Serialize, Deserialize)]
54pub struct GroundingResult {
55 pub status: GroundingStatus,
57
58 pub claim: String,
60
61 pub confidence: f64,
63
64 pub evidence: Vec<GroundingEvidence>,
66
67 pub reason: String,
69
70 #[serde(default)]
72 pub suggestions: Vec<String>,
73
74 pub timestamp: DateTime<Utc>,
76}
77
78impl GroundingResult {
79 pub fn verified(claim: impl Into<String>, confidence: f64) -> Self {
81 Self {
82 status: GroundingStatus::Verified,
83 claim: claim.into(),
84 confidence,
85 evidence: vec![],
86 reason: String::new(),
87 suggestions: vec![],
88 timestamp: Utc::now(),
89 }
90 }
91
92 pub fn ungrounded(claim: impl Into<String>, reason: impl Into<String>) -> Self {
94 Self {
95 status: GroundingStatus::Ungrounded,
96 claim: claim.into(),
97 confidence: 0.0,
98 evidence: vec![],
99 reason: reason.into(),
100 suggestions: vec![],
101 timestamp: Utc::now(),
102 }
103 }
104
105 pub fn partial(claim: impl Into<String>, confidence: f64) -> Self {
107 Self {
108 status: GroundingStatus::Partial,
109 claim: claim.into(),
110 confidence,
111 evidence: vec![],
112 reason: String::new(),
113 suggestions: vec![],
114 timestamp: Utc::now(),
115 }
116 }
117
118 pub fn with_evidence(mut self, evidence: Vec<GroundingEvidence>) -> Self {
120 self.evidence = evidence;
121 self
122 }
123
124 pub fn with_suggestions(mut self, suggestions: Vec<String>) -> Self {
126 self.suggestions = suggestions;
127 self
128 }
129
130 pub fn with_reason(mut self, reason: impl Into<String>) -> Self {
132 self.reason = reason.into();
133 self
134 }
135
136 pub fn is_strongly_grounded(&self) -> bool {
138 self.status == GroundingStatus::Verified && self.confidence > 0.8
139 }
140
141 pub fn is_weakly_grounded(&self) -> bool {
143 self.status != GroundingStatus::Ungrounded && self.confidence > 0.5
144 }
145}
146
147#[derive(Debug, Clone, Serialize, Deserialize)]
153pub struct GroundingEvidence {
154 pub evidence_type: String,
157
158 pub id: String,
160
161 pub score: f64,
163
164 pub summary: String,
166
167 #[serde(default)]
169 pub data: Metadata,
170}
171
172impl GroundingEvidence {
173 pub fn new(
175 evidence_type: impl Into<String>,
176 id: impl Into<String>,
177 score: f64,
178 summary: impl Into<String>,
179 ) -> Self {
180 Self {
181 evidence_type: evidence_type.into(),
182 id: id.into(),
183 score,
184 summary: summary.into(),
185 data: Metadata::new(),
186 }
187 }
188
189 pub fn with_data(mut self, key: impl Into<String>, value: impl Serialize) -> Self {
191 if let Ok(v) = serde_json::to_value(value) {
192 self.data.insert(key.into(), v);
193 }
194 self
195 }
196}
197
198#[derive(Debug, Clone, Serialize, Deserialize)]
207pub struct EvidenceDetail {
208 pub evidence_type: String,
210
211 pub id: String,
213
214 pub score: f64,
216
217 pub created_at: DateTime<Utc>,
219
220 pub source_sister: SisterType,
222
223 pub content: String,
225
226 #[serde(default)]
228 pub data: Metadata,
229}
230
231#[derive(Debug, Clone, Serialize, Deserialize)]
237pub struct GroundingSuggestion {
238 pub item_type: String,
240
241 pub id: String,
243
244 pub relevance_score: f64,
246
247 pub description: String,
249
250 #[serde(default)]
252 pub data: Metadata,
253}
254
255pub trait Grounding {
267 fn ground(&self, claim: &str) -> SisterResult<GroundingResult>;
275
276 fn evidence(&self, query: &str, max_results: usize) -> SisterResult<Vec<EvidenceDetail>>;
281
282 fn suggest(&self, query: &str, limit: usize) -> SisterResult<Vec<GroundingSuggestion>>;
287}
288
289#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
296#[serde(rename_all = "snake_case")]
297pub enum EvidenceType {
298 MemoryNode,
300 MemoryRelation,
301 MemorySession,
302
303 Screenshot,
305 DomFingerprint,
306 VisualDiff,
307 VisualComparison,
308
309 CodeNode,
311 ImpactAnalysis,
312 Prophecy,
313 DependencyGraph,
314
315 Receipt,
317 TrustGrant,
318 CompetenceProof,
319 Signature,
320
321 TimelineEvent,
323 DurationProof,
324 DeadlineCheck,
325
326 Agreement,
328 PolicyCheck,
329 BoundaryVerification,
330
331 Custom(String),
333}
334
335impl std::fmt::Display for EvidenceType {
336 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
337 match self {
338 Self::Custom(s) => write!(f, "{}", s),
339 other => write!(f, "{:?}", other),
340 }
341 }
342}
343
344#[cfg(test)]
345mod tests {
346 use super::*;
347
348 #[test]
349 fn test_grounding_result_verified() {
350 let result = GroundingResult::verified("the sky is blue", 0.95)
351 .with_evidence(vec![GroundingEvidence::new(
352 "memory_node",
353 "node_42",
354 0.95,
355 "Sky color observation from session 1",
356 )])
357 .with_reason("Found strong evidence in memory");
358
359 assert_eq!(result.status, GroundingStatus::Verified);
360 assert!(result.is_strongly_grounded());
361 assert_eq!(result.evidence.len(), 1);
362 }
363
364 #[test]
365 fn test_grounding_result_ungrounded() {
366 let result = GroundingResult::ungrounded("cats can fly", "No evidence found")
367 .with_suggestions(vec!["cats can jump".into(), "birds can fly".into()]);
368
369 assert_eq!(result.status, GroundingStatus::Ungrounded);
370 assert!(!result.is_strongly_grounded());
371 assert!(!result.is_weakly_grounded());
372 assert_eq!(result.suggestions.len(), 2);
373 }
374
375 #[test]
376 fn test_grounding_evidence_builder() {
377 let evidence =
378 GroundingEvidence::new("trust_grant", "atrust_123", 0.8, "Deploy capability")
379 .with_data("capabilities", vec!["deploy:prod"]);
380
381 assert_eq!(evidence.evidence_type, "trust_grant");
382 assert_eq!(evidence.score, 0.8);
383 assert!(evidence.data.contains_key("capabilities"));
384 }
385
386 #[test]
387 fn test_grounding_status_display() {
388 assert_eq!(GroundingStatus::Verified.to_string(), "verified");
389 assert_eq!(GroundingStatus::Partial.to_string(), "partial");
390 assert_eq!(GroundingStatus::Ungrounded.to_string(), "ungrounded");
391 }
392}