Skip to main content

ant_types/
evidence.rs

1//! Antares-native first-class evidence.
2//!
3//! Evidence is a kernel concept, not a user-defined entity. Every fact
4//! (Edge) may reference one or more Evidence records via `evidenced_by`.
5//! Tripwires recursively accumulate evidence from their causes.
6
7use chrono::{DateTime, Utc};
8use serde::{Deserialize, Serialize};
9
10use crate::author::AuthorStamp;
11use crate::ids::{ProjectId, TenantId};
12
13/// Evidence identifier (newtype over String). Distinct from VertexId
14/// because evidence lives in its own storage plane.
15#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
16#[serde(transparent)]
17pub struct EvidenceId(pub String);
18
19#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
20pub struct Evidence {
21    pub id: EvidenceId,
22    pub tenant_id: TenantId,
23    pub project_id: ProjectId,
24    /// URI/path of the source artifact (e.g. "s3://antares/calls/2026-04-29.vtt"
25    /// or "antares://transcripts/meeting_001"). Required.
26    pub source_uri: String,
27    /// What kind of source: "transcript" | "email_event" | "crm_field" | ...
28    pub source_type: String,
29    /// Identifier of the source event/artifact (e.g. "meeting_001",
30    /// "email_001"). Required.
31    pub source_id: String,
32    /// The literal text/data the evidence points at. Required.
33    pub content: String,
34    #[serde(default, skip_serializing_if = "Option::is_none")]
35    pub char_start: Option<u32>,
36    #[serde(default, skip_serializing_if = "Option::is_none")]
37    pub char_end: Option<u32>,
38    #[serde(default, skip_serializing_if = "Option::is_none")]
39    pub byte_start: Option<u64>,
40    #[serde(default, skip_serializing_if = "Option::is_none")]
41    pub byte_end: Option<u64>,
42    #[serde(default, skip_serializing_if = "Option::is_none")]
43    pub observed_at: Option<DateTime<Utc>>,
44    #[serde(default, skip_serializing_if = "Option::is_none")]
45    pub extracted_at: Option<DateTime<Utc>>,
46    #[serde(default, skip_serializing_if = "Option::is_none")]
47    pub extractor_version: Option<String>,
48    /// Confidence in `[0,1]`. `None` = treated as 1.0.
49    #[serde(default, skip_serializing_if = "Option::is_none")]
50    pub confidence: Option<f32>,
51    /// Free-form metadata. Use sparingly — first-class fields above
52    /// are preferred.
53    #[serde(default, skip_serializing_if = "serde_json::Value::is_null")]
54    pub metadata: serde_json::Value,
55
56    /// Which user persisted this evidence. `None` for older
57    /// records and for anonymous calls.
58    #[serde(default, skip_serializing_if = "Option::is_none")]
59    pub author: Option<AuthorStamp>,
60}
61
62impl Evidence {
63    /// Shorthand constructor for tests/fixtures.
64    pub fn quick(
65        id: &str,
66        tenant: TenantId,
67        project: ProjectId,
68        source_type: &str,
69        source_id: &str,
70        content: &str,
71    ) -> Self {
72        Self {
73            id: EvidenceId(id.to_string()),
74            tenant_id: tenant,
75            project_id: project,
76            source_uri: format!("antares://{source_type}/{source_id}"),
77            source_type: source_type.to_string(),
78            source_id: source_id.to_string(),
79            content: content.to_string(),
80            char_start: None,
81            char_end: None,
82            byte_start: None,
83            byte_end: None,
84            observed_at: None,
85            extracted_at: None,
86            extractor_version: None,
87            confidence: None,
88            metadata: serde_json::Value::Null,
89            author: None,
90        }
91    }
92}