rustev_eval/
remote_evidence.rs1use std::panic::{AssertUnwindSafe, catch_unwind};
14
15use rustev_contract::Document;
16use rustev_contract::limits::EXCHANGE_V1;
17use rustev_contract::remote::ExchangeRecord;
18use rustev_contract::run::{EvidenceUnavailable, ExchangeReference, RunRecord};
19
20#[derive(Debug, Clone, PartialEq, Eq)]
22pub enum ExchangeLookup {
23 Bytes(Vec<u8>),
25 Missing,
26 Expired,
28 Forbidden,
30}
31
32pub trait ExchangeStore {
35 fn lookup(&self, locator: &str, max_bytes: usize) -> ExchangeLookup;
36}
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
40pub enum RemoteAvailability {
41 Available,
42 Missing,
43 Expired,
44 Forbidden,
45 Corrupt,
47 Inaccessible,
49 NotReferenced(EvidenceUnavailable),
51}
52
53#[derive(Debug, Clone, PartialEq, Eq)]
55pub struct AttemptAvailability {
56 pub attempt_id: String,
57 pub availability: RemoteAvailability,
58}
59
60pub fn availability(run: &RunRecord, store: &dyn ExchangeStore) -> Vec<AttemptAvailability> {
63 let Some(entries) = &run.remote_evidence else {
64 return vec![];
65 };
66 entries
67 .iter()
68 .map(|e| AttemptAvailability {
69 attempt_id: e.attempt_id.clone(),
70 availability: match &e.exchange {
71 ExchangeReference::Unavailable { reason } => {
72 RemoteAvailability::NotReferenced(*reason)
73 }
74 ExchangeReference::Retained {
75 digest,
76 schema,
77 locator,
78 ..
79 } => check(store, locator, schema, digest),
80 },
81 })
82 .collect()
83}
84
85fn check(
86 store: &dyn ExchangeStore,
87 locator: &str,
88 schema: &str,
89 digest: &str,
90) -> RemoteAvailability {
91 if schema != <ExchangeRecord as Document>::SCHEMA {
92 return RemoteAvailability::Corrupt;
93 }
94 let max = EXCHANGE_V1.max_bytes;
95 let found = catch_unwind(AssertUnwindSafe(|| store.lookup(locator, max)));
96 match found {
97 Err(_) => RemoteAvailability::Inaccessible,
98 Ok(ExchangeLookup::Missing) => RemoteAvailability::Missing,
99 Ok(ExchangeLookup::Expired) => RemoteAvailability::Expired,
100 Ok(ExchangeLookup::Forbidden) => RemoteAvailability::Forbidden,
101 Ok(ExchangeLookup::Bytes(b)) if b.len() > max => RemoteAvailability::Corrupt,
102 Ok(ExchangeLookup::Bytes(b)) => match ExchangeRecord::parse_checked(&b) {
103 Ok(r)
104 if r.record_digest().ok().map(|d| d.as_str().to_string())
105 == Some(digest.to_string())
106 && r.record_canonical().ok().as_deref() == Some(&b[..]) =>
107 {
108 RemoteAvailability::Available
109 }
110 _ => RemoteAvailability::Corrupt,
111 },
112 }
113}