Skip to main content

rustev_contract/
ranking_result.rs

1//! Wire-only ranking results (spec 039). No evaluation or kind conversions.
2use crate::ids::{ArtifactId, DescriptorId, PlanId};
3use crate::judgment::{Derivation, Lineage, Unresolved};
4use crate::ranking::{Bias, RankMode, TieBreak};
5use serde::{Deserialize, Deserializer, Serialize};
6
7#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
8#[serde(deny_unknown_fields)]
9pub struct RankBinding {
10    pub backend_id: String,
11    pub artifact: ArtifactId,
12    pub descriptor: DescriptorId,
13    pub mode: RankMode,
14    pub bias: Bias,
15    pub generator: PermutationGenerator,
16    pub tie_break: TieBreak,
17    #[serde(deserialize_with = "crate::ranking::bound::<_, 0, 1024>")]
18    #[schemars(range(min = 0, max = 1024))]
19    pub pool_bound: u64,
20    pub max_requests: u64,
21}
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
23pub enum PermutationGenerator {
24    #[serde(rename = "rustev.permute/1")]
25    V1,
26}
27
28/// A finite score with its request scope, never an unscoped probability.
29#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
30#[serde(deny_unknown_fields)]
31pub struct RankingScore {
32    pub candidate: String,
33    pub plan: PlanId,
34    pub step: String,
35    pub instance: Vec<String>,
36    #[serde(deserialize_with = "finite")]
37    pub value: f64,
38}
39#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
40#[serde(deny_unknown_fields)]
41pub struct ModelRanking {
42    pub mode: RankMode,
43    #[serde(deserialize_with = "list::<_, _, 1024>")]
44    #[schemars(length(max = 1024))]
45    pub pool: Vec<String>,
46    #[serde(deserialize_with = "list::<_, _, 1024>")]
47    #[schemars(length(max = 1024))]
48    pub entries: Vec<ModelRankEntry>,
49    #[serde(deserialize_with = "list::<_, _, 1024>")]
50    #[schemars(length(max = 1024))]
51    pub unranked: Vec<String>,
52    // Spec 039 3.5.3, 3.8.3 and 3.9: at most 1024 returned ids per
53    // listwise request times 8 orders; duplicate drops need not be unique.
54    // Pointwise drops are pool-bounded (3.6); pairwise fails instead (3.7).
55    #[serde(deserialize_with = "list::<_, _, 8192>")]
56    #[schemars(length(max = 8192))]
57    pub dropped: Vec<DroppedId>,
58    pub partial: bool,
59    pub score_disagreements: u64,
60    pub pairwise: Option<PairwiseCounts>,
61}
62#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
63#[serde(deny_unknown_fields)]
64pub struct ModelRankEntry {
65    pub candidate: String,
66    #[serde(deserialize_with = "crate::ranking::bound::<_, 1, 1024>")]
67    #[schemars(range(min = 1, max = 1024))]
68    pub position: u64,
69    pub score: Option<RankingScore>,
70    pub wins: Option<u64>,
71    pub points: Option<u64>,
72}
73#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
74#[serde(deny_unknown_fields)]
75pub struct DroppedId {
76    pub id: String,
77    pub reason: DropReason,
78}
79#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
80#[serde(rename_all = "snake_case", deny_unknown_fields)]
81pub enum DropReason {
82    UnknownId,
83    OutsidePool,
84    Duplicate,
85    Failed { reason: Unresolved },
86}
87#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
88#[serde(deny_unknown_fields)]
89pub struct PairwiseCounts {
90    #[serde(deserialize_with = "crate::ranking::bound::<_, 0, 4096>")]
91    #[schemars(range(min = 0, max = 4096))]
92    pub issued: u64,
93    #[serde(deserialize_with = "crate::ranking::bound::<_, 1, 4096>")]
94    #[schemars(range(min = 1, max = 4096))]
95    pub max_comparisons: u64,
96    pub cyclic_triples: CycleCount,
97    pub inconsistent_pairs: u64,
98}
99#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
100#[serde(rename_all = "snake_case", deny_unknown_fields)]
101pub enum CycleCount {
102    NotObservable,
103    // Variant-level `untagged` (serde 1.0.181 and later; the lockfile has
104    // 1.0.229): `not_observable` stays a tagged string and `Count` is the
105    // bare integer of spec 039 3.7. This is a newtype variant, so
106    // `deny_unknown_fields` does not apply to it.
107    #[serde(untagged)]
108    Count(u64),
109}
110#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
111#[serde(deny_unknown_fields)]
112pub struct Selection {
113    #[serde(deserialize_with = "crate::ranking::bound::<_, 1, 1024>")]
114    #[schemars(range(min = 1, max = 1024))]
115    pub k: u64,
116    #[serde(deserialize_with = "list::<_, _, 1024>")]
117    #[schemars(length(max = 1024))]
118    pub entries: Vec<SelectionEntry>,
119    pub shortfall: u64,
120    pub model: SelectionModel,
121    pub backfill: SelectionBackfill,
122}
123#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
124#[serde(deny_unknown_fields)]
125pub struct SelectionEntry {
126    #[serde(deserialize_with = "crate::ranking::bound::<_, 1, 1024>")]
127    #[schemars(range(min = 1, max = 1024))]
128    pub position: u64,
129    pub id: String,
130    pub origin: SelectionOrigin,
131    #[serde(deserialize_with = "crate::ranking::bound::<_, 1, {u64::MAX}>")]
132    #[schemars(range(min = 1))]
133    pub source_position: u64,
134    pub derivation: Derivation,
135    /// The supplying source's lineage (3.11.3).
136    pub lineage: Lineage,
137}
138#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
139#[serde(rename_all = "snake_case")]
140pub enum SelectionOrigin {
141    Model,
142    Backfill,
143}
144#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
145#[serde(rename_all = "snake_case", deny_unknown_fields)]
146pub enum SelectionModel {
147    Ranked { entries: u64, partial: bool },
148    Failed { reason: Unresolved },
149}
150#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
151#[serde(rename_all = "snake_case", deny_unknown_fields)]
152pub enum SelectionBackfill {
153    None,
154    NotNeeded,
155    Used {
156        taken: u64,
157        skipped_selected: u64,
158        skipped_outside_pool: u64,
159    },
160    Unresolved {
161        reason: Unresolved,
162    },
163}
164
165#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
166#[serde(deny_unknown_fields)]
167pub struct RankingEvidence {
168    /// Absent when unresolved; the step status retains the failure reason.
169    pub ranking: Option<ModelRanking>,
170    /// Request order. Request scores stay here when positions are averaged.
171    #[serde(deserialize_with = "list::<_, _, 4096>")]
172    #[schemars(length(max = 4096))]
173    pub requests: Vec<RankingRequestEvidence>,
174    /// Answers in schedule order, including failures, never item records.
175    #[serde(deserialize_with = "list::<_, _, 4096>")]
176    #[schemars(length(max = 4096))]
177    pub comparisons: Vec<ComparisonAnswer>,
178}
179#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
180#[serde(deny_unknown_fields)]
181pub struct RankingRequestEvidence {
182    #[serde(deserialize_with = "list::<_, _, 1024>")]
183    #[schemars(length(max = 1024))]
184    pub presented: Vec<String>,
185    #[serde(deserialize_with = "list::<_, _, 1024>")]
186    #[schemars(length(max = 1024))]
187    pub scores: Vec<RankingScore>,
188}
189#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
190#[serde(rename_all = "snake_case", deny_unknown_fields)]
191pub enum ComparisonAnswer {
192    First,
193    Second,
194    Failed { reason: Unresolved },
195}
196
197pub(crate) fn list<'de, D: Deserializer<'de>, T: Deserialize<'de>, const MAX: usize>(
198    d: D,
199) -> Result<Vec<T>, D::Error> {
200    let v = Vec::<T>::deserialize(d)?;
201    if v.len() > MAX {
202        return Err(serde::de::Error::custom("ranking evidence bound exceeded"));
203    }
204    Ok(v)
205}
206pub(crate) fn finite<'de, D: Deserializer<'de>>(d: D) -> Result<f64, D::Error> {
207    let v = f64::deserialize(d)?;
208    if !v.is_finite() {
209        return Err(serde::de::Error::custom("ranking score must be finite"));
210    }
211    Ok(v)
212}