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}
64
65crate::document::versioned_document!(
66 EvidenceRecord,
67 [schema::EVIDENCE, schema::EVIDENCE_V2],
68 "rustev.evidence/1 or rustev.evidence/2",
69 RECORD_V1,
70 check = EvidenceRecord::screening_version
71);
72
73impl EvidenceRecord {
74 fn screening_version(&self) -> Result<(), String> {
75 if self.schema != schema::EVIDENCE_V2
76 && (self.acquisitions.is_some()
77 || self
78 .steps
79 .iter()
80 .any(|s| s.constructed || matches!(s.status, StepStatus::Skipped { .. })))
81 {
82 return Err(
83 "acquisitions, constructed values and skipped steps require rustev.evidence/2"
84 .into(),
85 );
86 }
87 if self.generation.is_some() && self.schema != schema::EVIDENCE_V2 {
88 return Err("generation requires rustev.evidence/2".into());
89 }
90 if self.screening.is_some() && self.schema != schema::EVIDENCE_V2 {
91 return Err("screening requires rustev.evidence/2".into());
92 }
93 Ok(())
94 }
95}
96
97#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
98#[serde(deny_unknown_fields)]
99pub struct InputEvidence {
100 pub field: String,
101 pub status: InputStatus,
102 #[serde(default, skip_serializing_if = "Option::is_none")]
104 pub meta: Option<ValueMeta>,
105 #[serde(default, skip_serializing_if = "Option::is_none")]
106 pub presence: Option<Presence>,
107 #[serde(default, skip_serializing_if = "Option::is_none")]
109 pub entries: Option<Vec<EntryEvidence>>,
110 #[serde(default, skip_serializing_if = "Option::is_none")]
112 pub resolution: Option<Resolution>,
113 #[serde(default, skip_serializing_if = "Option::is_none")]
116 pub dropped: Option<DroppedBy>,
117}
118
119#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
121#[serde(deny_unknown_fields)]
122pub struct DroppedBy {
123 pub check: String,
124}
125
126#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
128#[serde(deny_unknown_fields)]
129pub struct EntryEvidence {
130 pub source: String,
131 pub provenance: ProvenanceClass,
132 #[serde(default, skip_serializing_if = "Option::is_none")]
133 pub claim: Option<Grade>,
134 pub ceiling: ValueGrade,
135 pub effective: ValueGrade,
136 pub digest: String,
137 pub as_of_ms: Timestamp,
138 pub disposition: Disposition,
139}
140
141#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
142#[serde(rename_all = "snake_case", deny_unknown_fields)]
143pub enum Disposition {
144 Used,
145 Outvoted,
146 Ignored { reason: String },
147 Rejected { reason: String },
148}
149
150#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
152#[serde(deny_unknown_fields)]
153pub struct Resolution {
154 pub rule: ResolutionRule,
155 pub used: UsedEntry,
156}
157
158#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
161#[serde(rename_all = "snake_case")]
162pub enum ResolutionRule {
163 RequireAgreement,
164 Prefer,
165 TreatAsHint,
166 Abstain,
167}
168
169#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
170#[serde(rename_all = "snake_case", deny_unknown_fields)]
171pub enum UsedEntry {
172 None,
173 Entry(u64),
175}
176
177#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
178#[serde(rename_all = "snake_case", deny_unknown_fields)]
179pub enum InputStatus {
180 Resolved {
181 provenance: ProvenanceClass,
182 as_of_ms: Timestamp,
183 source: String,
184 },
185 Unresolved(Unresolved),
186}
187
188#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
189#[serde(deny_unknown_fields)]
190pub struct StepEvidence {
191 #[serde(
193 default,
194 skip_serializing_if = "std::ops::Not::not",
195 deserialize_with = "crate::acquisition_record::constructed"
196 )]
197 #[schemars(schema_with = "crate::acquisition_record::constructed_schema")]
198 pub constructed: bool,
199 pub step: String,
200 pub instance: Vec<String>,
201 pub status: StepStatus,
202 #[serde(default, skip_serializing_if = "Option::is_none")]
204 pub meta: Option<ValueMeta>,
205 #[serde(default, skip_serializing_if = "Option::is_none")]
207 pub join: Option<Vec<JoinEvidence>>,
208 #[serde(default, skip_serializing_if = "Option::is_none")]
210 pub merge: Option<Vec<MergeEvidence>>,
211 #[serde(default, skip_serializing_if = "Option::is_none")]
213 pub corroboration: Option<Corroboration>,
214}
215
216#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
217#[serde(rename_all = "snake_case")]
218pub enum MatchStatus {
219 Matched,
220 Unmatched,
221 MatchedAbsent,
222}
223
224#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
225#[serde(deny_unknown_fields)]
226pub struct JoinEvidence {
227 pub id: String,
228 pub status: MatchStatus,
229}
230
231#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
233#[serde(deny_unknown_fields)]
234pub struct MergeEvidence {
235 pub item: String,
237 pub field: String,
238 pub rule: String,
240 pub result: MergedField,
241}
242
243#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
244#[serde(rename_all = "snake_case", deny_unknown_fields)]
245pub enum MergedField {
246 Taken { side: MergeSide, grade: ValueGrade },
247 Absent { reason: AbsentReason },
248}
249
250#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
251#[serde(rename_all = "snake_case")]
252pub enum CorroborationOutcome {
253 Corroborated,
254 Uncorroborated,
255 Contradicted,
256}
257
258#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
261#[serde(deny_unknown_fields)]
262pub struct Corroboration {
263 pub claim: String,
264 pub claim_digest: String,
265 pub grade_before: ValueGrade,
266 pub grade_after: ValueGrade,
267 pub outcome: CorroborationOutcome,
268 pub checks: Vec<CheckEvidence>,
269}
270
271#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
272#[serde(rename_all = "snake_case")]
273pub enum CheckResult {
274 Supports,
275 Contradicts,
276 NoEvidence,
277}
278
279#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
280#[serde(deny_unknown_fields)]
281pub struct CheckEvidence {
282 pub result: CheckResult,
283 pub reason: CheckReason,
285 pub reads: Vec<ReadEvidence>,
286}
287
288#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
289#[serde(rename_all = "snake_case", deny_unknown_fields)]
290pub enum CheckReason {
291 True,
293 False(OnFalse),
295 Unresolved(Unresolved),
297 BelowGrade { read: String },
299}
300
301#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
302#[serde(deny_unknown_fields)]
303pub struct ReadEvidence {
304 pub reference: String,
305 #[serde(default, skip_serializing_if = "Option::is_none")]
307 pub digest: Option<String>,
308 pub grade: ValueGrade,
309}
310
311#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
312#[serde(rename_all = "snake_case", deny_unknown_fields)]
313pub enum StepStatus {
314 Resolved {
315 kind: String,
316 derivation: Derivation,
317 },
318 Unresolved(Unresolved),
319 Skipped {
320 guard: String,
321 },
322}
323
324impl StepEvidence {
325 pub fn new(step: String, instance: Vec<String>, status: StepStatus) -> Self {
327 StepEvidence {
328 constructed: false,
329 step,
330 instance,
331 status,
332 meta: None,
333 join: None,
334 merge: None,
335 corroboration: None,
336 }
337 }
338}