Skip to main content

cortex_sdk/
verification.rs

1use crate::aql::{Aql, AqlBuildError};
2use crate::http::path;
3use crate::types::{EvidenceResponse, NumericConflictResponse, VerificationReportResponse};
4
5#[derive(Clone, Copy, Debug, PartialEq, Eq)]
6pub enum VerifyOutputFormat {
7    Json,
8    Markdown,
9    Audit,
10}
11
12impl VerifyOutputFormat {
13    pub fn as_str(self) -> &'static str {
14        match self {
15            Self::Json => "json",
16            Self::Markdown => "markdown",
17            Self::Audit => "audit",
18        }
19    }
20}
21
22#[derive(Clone, Debug, PartialEq, Eq)]
23pub struct VerifyRequest {
24    scope: String,
25    statement: String,
26    format: VerifyOutputFormat,
27}
28
29impl VerifyRequest {
30    pub fn new(scope: impl Into<String>, statement: impl Into<String>) -> Self {
31        Self {
32            scope: scope.into(),
33            statement: statement.into(),
34            format: VerifyOutputFormat::Json,
35        }
36    }
37
38    pub fn fact(
39        scope: impl Into<String>,
40        fact: impl Into<String>,
41        brain: impl Into<String>,
42    ) -> Result<Self, AqlBuildError> {
43        let statement = Aql::verify_fact(fact, brain).build()?;
44        Ok(Self::new(scope, statement))
45    }
46
47    pub fn json(mut self) -> Self {
48        self.format = VerifyOutputFormat::Json;
49        self
50    }
51
52    pub fn markdown(mut self) -> Self {
53        self.format = VerifyOutputFormat::Markdown;
54        self
55    }
56
57    pub fn audit(mut self) -> Self {
58        self.format = VerifyOutputFormat::Audit;
59        self
60    }
61
62    pub fn with_format(mut self, format: VerifyOutputFormat) -> Self {
63        self.format = format;
64        self
65    }
66
67    pub fn scope(&self) -> &str {
68        &self.scope
69    }
70
71    pub fn statement(&self) -> &str {
72        &self.statement
73    }
74
75    pub fn format(&self) -> VerifyOutputFormat {
76        self.format
77    }
78
79    pub fn path(&self) -> String {
80        path(
81            "/v1/verify",
82            &[
83                ("scope", self.scope.as_str()),
84                ("format", self.format.as_str()),
85            ],
86        )
87    }
88}
89
90#[derive(Clone, Debug, PartialEq, Eq)]
91pub enum VerifyResult {
92    Supported,
93    Insufficient,
94    Contradicted,
95    MixedEvidence,
96    Unknown(String),
97}
98
99impl VerifyResult {
100    pub fn from_wire(value: &str) -> Self {
101        match value {
102            "supported" => Self::Supported,
103            "insufficient" => Self::Insufficient,
104            "contradicted" => Self::Contradicted,
105            "mixed" | "mixed_evidence" => Self::MixedEvidence,
106            other => Self::Unknown(other.to_owned()),
107        }
108    }
109
110    pub fn as_str(&self) -> &str {
111        match self {
112            Self::Supported => "supported",
113            Self::Insufficient => "insufficient",
114            Self::Contradicted => "contradicted",
115            Self::MixedEvidence => "mixed_evidence",
116            Self::Unknown(value) => value.as_str(),
117        }
118    }
119
120    pub fn is_actionable_conflict(&self) -> bool {
121        matches!(self, Self::Contradicted | Self::MixedEvidence)
122    }
123}
124
125#[derive(Clone, Debug, PartialEq, Eq)]
126pub enum VerifyConflict {
127    ContradictingEvidence(VerifyEvidenceConflict),
128    Numeric(VerifyNumericConflict),
129}
130
131#[derive(Clone, Debug, PartialEq, Eq)]
132pub struct VerifyEvidenceConflict {
133    pub cell_id: u64,
134    pub matched_terms: u32,
135    pub match_score_q16: u16,
136    pub match_kind: String,
137    pub source_trust_q16: u16,
138    pub source_trust_category: String,
139    pub citation: Option<String>,
140    pub payload_text: String,
141}
142
143#[derive(Clone, Debug, PartialEq, Eq)]
144pub struct VerifyNumericConflict {
145    pub metric: String,
146    pub left: String,
147    pub right: String,
148}
149
150impl From<EvidenceResponse> for VerifyEvidenceConflict {
151    fn from(value: EvidenceResponse) -> Self {
152        Self {
153            cell_id: value.cell_id,
154            matched_terms: value.matched_terms,
155            match_score_q16: value.match_score_q16,
156            match_kind: value.match_kind,
157            source_trust_q16: value.source_trust_q16,
158            source_trust_category: value.source_trust_category,
159            citation: value.citation,
160            payload_text: value.payload_text,
161        }
162    }
163}
164
165impl From<NumericConflictResponse> for VerifyNumericConflict {
166    fn from(value: NumericConflictResponse) -> Self {
167        Self {
168            metric: value.metric,
169            left: value.left,
170            right: value.right,
171        }
172    }
173}
174
175pub trait VerificationReportExt {
176    fn result(&self) -> VerifyResult;
177    fn has_conflicts(&self) -> bool;
178    fn conflicts(&self) -> Vec<VerifyConflict>;
179}
180
181impl VerificationReportExt for VerificationReportResponse {
182    fn result(&self) -> VerifyResult {
183        let status = VerifyResult::from_wire(&self.status);
184        match status {
185            VerifyResult::Unknown(_) => VerifyResult::from_wire(&self.verdict),
186            known => known,
187        }
188    }
189
190    fn has_conflicts(&self) -> bool {
191        !self.contradicting_evidence.is_empty() || !self.numeric_conflicts.is_empty()
192    }
193
194    fn conflicts(&self) -> Vec<VerifyConflict> {
195        let evidence = self
196            .contradicting_evidence
197            .iter()
198            .cloned()
199            .map(VerifyEvidenceConflict::from)
200            .map(VerifyConflict::ContradictingEvidence);
201        let numeric = self
202            .numeric_conflicts
203            .iter()
204            .cloned()
205            .map(VerifyNumericConflict::from)
206            .map(VerifyConflict::Numeric);
207
208        evidence.chain(numeric).collect()
209    }
210}