1use serde::{Deserialize, Serialize};
6
7use crate::actionability::CheckRecord;
8use crate::definition::{OnFalse, ProvenanceClass};
9use crate::grade::{AbsentReason, Grade, MergeSide, Presence, ValueGrade, ValueMeta};
10use crate::ids::{DefinitionId, PlanId, SnapshotId};
11use crate::judgment::{Derivation, Judgment, Unresolved};
12use crate::limits::RECORD_V1;
13use crate::schema;
14use crate::time::Timestamp;
15
16#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
17#[serde(deny_unknown_fields)]
18pub struct EvidenceRecord {
19 pub schema: String,
20 pub decision_id: String,
22 pub plan_id: PlanId,
23 pub definition_id: DefinitionId,
24 pub snapshot_id: SnapshotId,
25 pub evaluation_time_ms: Timestamp,
26 pub inputs: Vec<InputEvidence>,
27 pub steps: Vec<StepEvidence>,
28 pub judgment: Judgment,
29 #[serde(default, skip_serializing_if = "Option::is_none")]
32 pub checks: Option<Vec<CheckRecord>>,
33 #[serde(
35 default,
36 skip_serializing_if = "Option::is_none",
37 deserialize_with = "crate::screening::evidence_list"
38 )]
39 #[schemars(with = "Vec<crate::screening::ScreeningEvidence>", length(min = 1))]
40 pub screening: Option<Vec<crate::screening::ScreeningEvidence>>,
41 #[serde(
43 default,
44 skip_serializing_if = "Option::is_none",
45 deserialize_with = "crate::acquisition_record::list"
46 )]
47 #[schemars(
48 with = "Vec<crate::acquisition_record::AcquisitionEvidence>",
49 length(min = 1, max = 4096)
50 )]
51 pub acquisitions: Option<Vec<crate::acquisition_record::AcquisitionEvidence>>,
52 #[serde(
54 default,
55 skip_serializing_if = "Option::is_none",
56 deserialize_with = "crate::generation_result::evidence"
57 )]
58 #[schemars(
59 with = "Vec<crate::generation_result::GenerationEvidence>",
60 length(min = 1, max = 8)
61 )]
62 pub generation: Option<Vec<crate::generation_result::GenerationEvidence>>,
63 #[serde(
64 default,
65 skip_serializing_if = "Option::is_none",
66 deserialize_with = "crate::screening::present"
67 )]
68 pub extraction: Option<Vec<crate::extraction_results::ExtractionEvidence>>,
69 #[serde(
70 default,
71 skip_serializing_if = "Option::is_none",
72 deserialize_with = "crate::screening::present"
73 )]
74 pub acceptance: Option<Vec<crate::extraction_results::AcceptanceEvidence>>,
75}
76
77crate::document::versioned_document!(
78 EvidenceRecord,
79 [schema::EVIDENCE, schema::EVIDENCE_V2],
80 "rustev.evidence/1 or rustev.evidence/2",
81 RECORD_V1,
82 check = EvidenceRecord::screening_version
83);
84
85impl EvidenceRecord {
86 pub(crate) fn screening_version(&self) -> Result<(), String> {
87 self.judgment.extraction_version()?;
88 if self.schema != schema::EVIDENCE_V2
89 && (self.extraction.is_some()
90 || self.acceptance.is_some()
91 || self.inputs.iter().any(|i| i.producer.is_some())
92 || self.judgment.lineage.producers.is_some())
93 {
94 return Err("extraction, acceptance and producers require rustev.evidence/2".into());
95 }
96 if self.schema != schema::EVIDENCE_V2
97 && (self.acquisitions.is_some()
98 || self
99 .steps
100 .iter()
101 .any(|s| s.constructed || matches!(s.status, StepStatus::Skipped { .. })))
102 {
103 return Err(
104 "acquisitions, constructed values and skipped steps require rustev.evidence/2"
105 .into(),
106 );
107 }
108 if self.schema != schema::EVIDENCE_V2
109 && self
110 .steps
111 .iter()
112 .any(|s| s.ranking.is_some() || s.selection.is_some())
113 {
114 return Err("ranking and selection require rustev.evidence/2".into());
115 }
116 if self.generation.is_some() && self.schema != schema::EVIDENCE_V2 {
117 return Err("generation requires rustev.evidence/2".into());
118 }
119 if self.screening.is_some() && self.schema != schema::EVIDENCE_V2 {
120 return Err("screening requires rustev.evidence/2".into());
121 }
122 Ok(())
123 }
124}
125
126#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
127#[serde(deny_unknown_fields)]
128pub struct InputEvidence {
129 pub field: String,
130 pub status: InputStatus,
131 #[serde(default, skip_serializing_if = "Option::is_none")]
133 pub meta: Option<ValueMeta>,
134 #[serde(default, skip_serializing_if = "Option::is_none")]
135 pub presence: Option<Presence>,
136 #[serde(default, skip_serializing_if = "Option::is_none")]
138 pub entries: Option<Vec<EntryEvidence>>,
139 #[serde(default, skip_serializing_if = "Option::is_none")]
141 pub resolution: Option<Resolution>,
142 #[serde(default, skip_serializing_if = "Option::is_none")]
145 pub dropped: Option<DroppedBy>,
146 #[serde(
147 default,
148 skip_serializing_if = "Option::is_none",
149 deserialize_with = "crate::screening::present"
150 )]
151 pub producer: Option<crate::snapshot::Producer>,
152}
153
154#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
156#[serde(deny_unknown_fields)]
157pub struct DroppedBy {
158 pub check: String,
159}
160
161#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
163#[serde(deny_unknown_fields)]
164pub struct EntryEvidence {
165 pub source: String,
166 pub provenance: ProvenanceClass,
167 #[serde(default, skip_serializing_if = "Option::is_none")]
168 pub claim: Option<Grade>,
169 pub ceiling: ValueGrade,
170 pub effective: ValueGrade,
171 pub digest: String,
172 pub as_of_ms: Timestamp,
173 pub disposition: Disposition,
174}
175
176#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
177#[serde(rename_all = "snake_case", deny_unknown_fields)]
178pub enum Disposition {
179 Used,
180 Outvoted,
181 Ignored { reason: String },
182 Rejected { reason: String },
183}
184
185#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
187#[serde(deny_unknown_fields)]
188pub struct Resolution {
189 pub rule: ResolutionRule,
190 pub used: UsedEntry,
191}
192
193#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
196#[serde(rename_all = "snake_case")]
197pub enum ResolutionRule {
198 RequireAgreement,
199 Prefer,
200 TreatAsHint,
201 Abstain,
202}
203
204#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
205#[serde(rename_all = "snake_case", deny_unknown_fields)]
206pub enum UsedEntry {
207 None,
208 Entry(u64),
210}
211
212#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
213#[serde(rename_all = "snake_case", deny_unknown_fields)]
214pub enum InputStatus {
215 Resolved {
216 provenance: ProvenanceClass,
217 as_of_ms: Timestamp,
218 source: String,
219 },
220 Unresolved(Unresolved),
221}
222
223#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
224#[serde(deny_unknown_fields)]
225pub struct StepEvidence {
226 #[serde(
228 default,
229 skip_serializing_if = "std::ops::Not::not",
230 deserialize_with = "crate::acquisition_record::constructed"
231 )]
232 #[schemars(schema_with = "crate::acquisition_record::constructed_schema")]
233 pub constructed: bool,
234 pub step: String,
235 pub instance: Vec<String>,
236 pub status: StepStatus,
237 #[serde(default, skip_serializing_if = "Option::is_none")]
239 pub meta: Option<ValueMeta>,
240 #[serde(default, skip_serializing_if = "Option::is_none")]
242 pub join: Option<Vec<JoinEvidence>>,
243 #[serde(default, skip_serializing_if = "Option::is_none")]
245 pub merge: Option<Vec<MergeEvidence>>,
246 #[serde(default, skip_serializing_if = "Option::is_none")]
248 pub corroboration: Option<Corroboration>,
249 #[serde(
251 default,
252 skip_serializing_if = "Option::is_none",
253 deserialize_with = "crate::screening::present"
254 )]
255 #[schemars(with = "crate::ranking_result::RankingEvidence")]
256 pub ranking: Option<crate::ranking_result::RankingEvidence>,
257 #[serde(
258 default,
259 skip_serializing_if = "Option::is_none",
260 deserialize_with = "crate::screening::present"
261 )]
262 #[schemars(with = "crate::ranking_result::Selection")]
263 pub selection: Option<crate::ranking_result::Selection>,
264}
265
266#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
267#[serde(rename_all = "snake_case")]
268pub enum MatchStatus {
269 Matched,
270 Unmatched,
271 MatchedAbsent,
272}
273
274#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
275#[serde(deny_unknown_fields)]
276pub struct JoinEvidence {
277 pub id: String,
278 pub status: MatchStatus,
279}
280
281#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
283#[serde(deny_unknown_fields)]
284pub struct MergeEvidence {
285 pub item: String,
287 pub field: String,
288 pub rule: String,
290 pub result: MergedField,
291}
292
293#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
294#[serde(rename_all = "snake_case", deny_unknown_fields)]
295pub enum MergedField {
296 Taken { side: MergeSide, grade: ValueGrade },
297 Absent { reason: AbsentReason },
298}
299
300#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
301#[serde(rename_all = "snake_case")]
302pub enum CorroborationOutcome {
303 Corroborated,
304 Uncorroborated,
305 Contradicted,
306}
307
308#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
311#[serde(deny_unknown_fields)]
312pub struct Corroboration {
313 pub claim: String,
314 pub claim_digest: String,
315 pub grade_before: ValueGrade,
316 pub grade_after: ValueGrade,
317 pub outcome: CorroborationOutcome,
318 pub checks: Vec<CheckEvidence>,
319}
320
321#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
322#[serde(rename_all = "snake_case")]
323pub enum CheckResult {
324 Supports,
325 Contradicts,
326 NoEvidence,
327}
328
329#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
330#[serde(deny_unknown_fields)]
331pub struct CheckEvidence {
332 pub result: CheckResult,
333 pub reason: CheckReason,
335 pub reads: Vec<ReadEvidence>,
336}
337
338#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
339#[serde(rename_all = "snake_case", deny_unknown_fields)]
340pub enum CheckReason {
341 True,
343 False(OnFalse),
345 Unresolved(Unresolved),
347 BelowGrade { read: String },
349}
350
351#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
352#[serde(deny_unknown_fields)]
353pub struct ReadEvidence {
354 pub reference: String,
355 #[serde(default, skip_serializing_if = "Option::is_none")]
357 pub digest: Option<String>,
358 pub grade: ValueGrade,
359}
360
361#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
362#[serde(rename_all = "snake_case", deny_unknown_fields)]
363pub enum StepStatus {
364 Resolved {
365 kind: String,
366 derivation: Derivation,
367 },
368 Unresolved(Unresolved),
369 Skipped {
370 guard: String,
371 },
372}
373
374impl StepEvidence {
375 pub fn new(step: String, instance: Vec<String>, status: StepStatus) -> Self {
377 StepEvidence {
378 constructed: false,
379 step,
380 instance,
381 status,
382 meta: None,
383 join: None,
384 merge: None,
385 corroboration: None,
386 ranking: None,
387 selection: None,
388 }
389 }
390}