Skip to main content

rustev_contract/
acquisition_record.rs

1//! Acquisition plan bindings and observations (spec 036, 3.10 and 3.14).
2//! These records contain digests and metadata, never page contents.
3use crate::{
4    grade::ValueMeta,
5    ids::{AcquisitionRequestId, ContentDigest},
6    judgment::{Derivation, Unresolved},
7    plan::BoundSource,
8    time::Timestamp,
9};
10use serde::{Deserialize, Serialize};
11use std::collections::BTreeMap;
12
13#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
14#[serde(deny_unknown_fields)]
15pub struct PlanAcquisition {
16    pub step: String,
17    pub kind: AcquisitionKind,
18    pub derivation: Derivation,
19    #[serde(deserialize_with = "outcomes")]
20    #[schemars(length(max = 10))]
21    pub outcomes: Vec<AcquisitionPossibleOutcome>,
22    #[serde(deserialize_with = "calls")]
23    #[schemars(range(min = 1, max = 4096))]
24    pub max_calls: u64,
25    #[serde(deserialize_with = "sources")]
26    #[schemars(length(min = 1, max = 4))]
27    pub sources: Vec<AcquisitionBinding>,
28}
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
31#[serde(rename_all = "snake_case")]
32pub enum AcquisitionKind {
33    Acquire,
34    Widen,
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
38#[serde(rename_all = "snake_case")]
39pub enum AcquisitionPossibleOutcome {
40    Resolved,
41    Skipped,
42    MissingEvidence,
43    StaleEvidence,
44    InvalidInput,
45    BackendUnavailable,
46    InvalidBackendOutput,
47    BudgetExhausted,
48    DeadlineExceeded,
49    InsufficientGrade,
50}
51
52#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
53#[serde(deny_unknown_fields)]
54pub struct AcquisitionBinding {
55    pub source: BoundSource,
56    pub operation: String,
57    /// Pool field to response field; empty for an acquisition without mapping.
58    pub map: BTreeMap<String, String>,
59    pub unmapped: Vec<String>,
60}
61
62#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
63#[serde(deny_unknown_fields)]
64pub struct AcquisitionEvidence {
65    pub step: String,
66    pub kind: AcquisitionKind,
67    pub outcome: AcquisitionOutcome,
68    pub stopped_by: AcquisitionStop,
69    pub truncated: u64,
70    pub request_constructed: bool,
71    #[serde(deserialize_with = "tiers")]
72    #[schemars(length(min = 1, max = 8))]
73    pub tiers: Vec<TierEvidence>,
74    pub items: Vec<AcquiredItemEvidence>,
75}
76
77#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
78#[serde(rename_all = "snake_case", deny_unknown_fields)]
79pub enum AcquisitionOutcome {
80    Resolved,
81    Skipped { guard: String },
82    Unresolved(Unresolved),
83}
84
85#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
86#[serde(rename_all = "snake_case")]
87pub enum AcquisitionStop {
88    None,
89    Exhausted,
90    StopCondition,
91    PageLimit,
92    ItemLimit,
93    TiersExhausted,
94    TierFailure,
95}
96
97#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
98#[serde(deny_unknown_fields)]
99pub struct TierEvidence {
100    pub name: String,
101    pub outcome: TierOutcome,
102    pub relaxation: u64,
103    pub source: String,
104    pub operation: String,
105    pub request_constructed: bool,
106    pub items_received: u64,
107    pub unique_items_added: u64,
108    pub duplicates: u64,
109    #[serde(deserialize_with = "pages")]
110    #[schemars(length(max = 64))]
111    pub pages: Vec<PageEvidence>,
112}
113
114#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
115#[serde(rename_all = "snake_case", deny_unknown_fields)]
116pub enum TierOutcome {
117    Ok,
118    Empty,
119    Failed { reason: Unresolved },
120    Skipped,
121    NotNeeded,
122}
123
124#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
125#[serde(deny_unknown_fields)]
126pub struct PageEvidence {
127    pub request: AcquisitionRequestId,
128    pub digest: PageDigest,
129    pub items: u64,
130    pub unique_items_added: u64,
131}
132
133#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
134#[serde(rename_all = "snake_case", deny_unknown_fields)]
135pub enum PageDigest {
136    None,
137    Sha256(ContentDigest),
138}
139
140#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
141#[serde(deny_unknown_fields)]
142pub struct AcquiredItemEvidence {
143    pub digest: ContentDigest,
144    pub meta: ValueMeta,
145    #[serde(
146        default,
147        skip_serializing_if = "Option::is_none",
148        deserialize_with = "crate::screening::present"
149    )]
150    #[schemars(with = "ItemOrigin")]
151    pub origin: Option<ItemOrigin>,
152}
153
154#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
155#[serde(deny_unknown_fields)]
156pub struct ItemOrigin {
157    pub tier: String,
158    pub tier_index: u32,
159    pub relaxation: u64,
160    pub source: String,
161    pub operation: String,
162    pub page: u32,
163    pub rank: u64,
164    pub as_of_ms: Timestamp,
165}
166
167#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
168#[serde(deny_unknown_fields)]
169pub struct AcquisitionRecord {
170    pub step: String,
171    pub tier: u32,
172    pub page: u32,
173    pub request: AcquisitionRequestId,
174    #[serde(deserialize_with = "attempts")]
175    #[schemars(length(max = 8))]
176    pub attempts: Vec<AcquisitionAttempt>,
177    pub cache: AcquisitionCache,
178    pub result: AcquisitionResult,
179}
180
181#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
182#[serde(deny_unknown_fields)]
183pub struct AcquisitionAttempt {
184    /// Escaped decision/step/tier,page/ordinal (spec 036, 3.14.2).
185    pub attempt_id: String,
186    pub dispatched_ms: u64,
187    pub ended_ms: u64,
188    pub end: AcquisitionAttemptEnd,
189    pub cost: crate::run::AttemptCost,
190    pub cancellation: crate::run::Cancellation,
191    pub remote: crate::run::RemoteState,
192}
193
194#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
195#[serde(rename_all = "snake_case", deny_unknown_fields)]
196pub enum AcquisitionAttemptEnd {
197    Page,
198    InvalidPage,
199    Transient,
200    Overloaded,
201    Permanent,
202    Denied,
203    TimedOut,
204    AdapterFault,
205    Deadline,
206    Cancelled,
207}
208
209#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
210#[serde(rename_all = "snake_case", deny_unknown_fields)]
211pub enum AcquisitionCache {
212    None,
213    Hit { age_ms: u64 },
214    Stored,
215}
216
217#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
218#[serde(rename_all = "snake_case", deny_unknown_fields)]
219pub enum AcquisitionResult {
220    Page,
221    Failed(Unresolved),
222    NotSupplied,
223}
224
225pub(crate) fn list<'de, D: serde::Deserializer<'de>, T: Deserialize<'de>>(
226    d: D,
227) -> Result<Option<Vec<T>>, D::Error> {
228    bounded::<D, T, 1, 4096>(d).map(Some)
229}
230fn bounded<
231    'de,
232    D: serde::Deserializer<'de>,
233    T: Deserialize<'de>,
234    const MIN: usize,
235    const MAX: usize,
236>(
237    d: D,
238) -> Result<Vec<T>, D::Error> {
239    let v = Vec::<T>::deserialize(d)?;
240    if !(MIN..=MAX).contains(&v.len()) {
241        return Err(serde::de::Error::custom("acquisition list outside bounds"));
242    }
243    Ok(v)
244}
245fn outcomes<'de, D: serde::Deserializer<'de>>(
246    d: D,
247) -> Result<Vec<AcquisitionPossibleOutcome>, D::Error> {
248    bounded::<D, _, 0, 10>(d)
249}
250fn sources<'de, D: serde::Deserializer<'de>>(d: D) -> Result<Vec<AcquisitionBinding>, D::Error> {
251    bounded::<D, _, 1, 4>(d)
252}
253fn tiers<'de, D: serde::Deserializer<'de>>(d: D) -> Result<Vec<TierEvidence>, D::Error> {
254    bounded::<D, _, 1, 8>(d)
255}
256fn pages<'de, D: serde::Deserializer<'de>>(d: D) -> Result<Vec<PageEvidence>, D::Error> {
257    bounded::<D, _, 0, 64>(d)
258}
259fn attempts<'de, D: serde::Deserializer<'de>>(d: D) -> Result<Vec<AcquisitionAttempt>, D::Error> {
260    let v: Vec<AcquisitionAttempt> = bounded::<D, _, 0, 8>(d)?;
261    if v.iter().any(|a| a.ended_ms < a.dispatched_ms) {
262        return Err(serde::de::Error::custom("attempt ends before dispatch"));
263    }
264    Ok(v)
265}
266fn calls<'de, D: serde::Deserializer<'de>>(d: D) -> Result<u64, D::Error> {
267    let n = u64::deserialize(d)?;
268    if !(1..=4096).contains(&n) {
269        return Err(serde::de::Error::custom(
270            "acquisition calls outside 1..=4096",
271        ));
272    }
273    Ok(n)
274}
275pub(crate) fn constructed<'de, D: serde::Deserializer<'de>>(d: D) -> Result<bool, D::Error> {
276    if !bool::deserialize(d)? {
277        return Err(serde::de::Error::custom("omit constructed when false"));
278    }
279    Ok(true)
280}
281
282pub(crate) fn constructed_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
283    schemars::json_schema!({"type":"boolean", "const":true})
284}