1use serde::{Deserialize, Serialize};
4
5use super::{
6 canonical_runtime_policy_fingerprint, CapabilityCatalog, ContractVersion, ExecutionPlan,
7 KvStorageFormat, ModelFamilyDefinition, NumericalExecutionPolicy, NumericalProfileId,
8 PreparedModelFamily, ResolvedRuntimePolicy, VNextError,
9};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
12#[serde(rename_all = "snake_case")]
13pub enum NumericalProfileRejectionStage {
14 FamilyPreparation,
15 WeightMaterializer,
16 ProgramCompilation,
17}
18
19#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
20#[serde(deny_unknown_fields)]
21pub struct NumericalProfileRejection {
22 pub profile_id: NumericalProfileId,
23 pub stage: NumericalProfileRejectionStage,
24 pub reason: String,
25}
26
27#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
31#[serde(transparent)]
32pub struct NumericalProfileResolution {
33 parts: NumericalProfileResolutionWire,
34}
35
36#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
37#[serde(deny_unknown_fields)]
38pub(crate) struct NumericalProfileResolutionWire {
39 requested: NumericalExecutionPolicy,
40 requested_kv_storage: KvStorageFormat,
41 selected_kv_storage: Option<KvStorageFormat>,
42 selected_profile: NumericalProfileId,
43 selected_version: ContractVersion,
44 profile_fingerprint: String,
45 qualification_version: ContractVersion,
46 definition_fingerprint: String,
47 prepared_family_fingerprint: String,
48 capability_catalog_fingerprint: String,
49 runtime_policy_fingerprint: String,
50 execution_plan_hash: String,
51 rejected: Vec<NumericalProfileRejection>,
52}
53
54fn invalid(reason: impl Into<String>) -> VNextError {
55 VNextError::InvalidExecutionPlan {
56 reason: format!("numerical resolution: {}", reason.into()),
57 }
58}
59
60impl NumericalProfileResolution {
61 #[allow(clippy::too_many_arguments)]
66 pub fn from_static_plan(
67 requested: NumericalExecutionPolicy,
68 requested_kv_storage: KvStorageFormat,
69 definition: &ModelFamilyDefinition,
70 family: &PreparedModelFamily,
71 capabilities: &CapabilityCatalog,
72 runtime: &ResolvedRuntimePolicy,
73 plan: &ExecutionPlan,
74 rejected: Vec<NumericalProfileRejection>,
75 ) -> Result<Self, VNextError> {
76 let profiles = definition.numerical_profiles();
77 let selected = family.numerical_profile();
78 if definition.family_id() != family.family_id()
79 || definition.canonical_config() != family.canonical_config()
80 || profiles.resolve(&selected.id)? != selected
81 {
82 return Err(invalid("selected family differs from its typed definition"));
83 }
84 let candidates = profiles.candidates(&requested, requested_kv_storage)?;
85 let selected_position = candidates
86 .iter()
87 .position(|profile| profile.id == selected.id)
88 .ok_or_else(|| invalid("selected profile is not a candidate for the request"))?;
89 if rejected.len() != selected_position
90 || rejected
91 .iter()
92 .zip(&candidates)
93 .any(|(rejection, profile)| {
94 rejection.profile_id != profile.id || rejection.reason.trim().is_empty()
95 })
96 {
97 return Err(invalid(
98 "rejections must explain exactly the declared candidates before the selected profile",
99 ));
100 }
101 let prepared_family_fingerprint = family.fingerprint()?;
102 let capability_catalog_fingerprint = capabilities.fingerprint()?;
103 let runtime_policy_fingerprint = canonical_runtime_policy_fingerprint(runtime)?;
104 let payload = plan.payload();
105 if payload.prepared_family_fingerprint() != prepared_family_fingerprint
106 || payload.program_fingerprint() != family.program().fingerprint()?
107 || payload.capability_catalog_fingerprint() != capability_catalog_fingerprint
108 || payload.policy_fingerprint() != runtime_policy_fingerprint
109 {
110 return Err(invalid(
111 "static plan belongs to another family, catalog or runtime policy",
112 ));
113 }
114 Ok(Self {
115 parts: NumericalProfileResolutionWire {
116 requested,
117 requested_kv_storage,
118 selected_kv_storage: selected.kv_storage_format()?,
119 selected_profile: selected.id.clone(),
120 selected_version: selected.version,
121 profile_fingerprint: selected.fingerprint()?,
122 qualification_version: profiles.version(),
123 definition_fingerprint: definition.fingerprint()?,
124 prepared_family_fingerprint,
125 capability_catalog_fingerprint,
126 runtime_policy_fingerprint,
127 execution_plan_hash: plan.plan_hash().as_str().to_owned(),
128 rejected,
129 },
130 })
131 }
132
133 pub fn requested(&self) -> &NumericalExecutionPolicy {
134 &self.parts.requested
135 }
136
137 pub fn requested_kv_storage(&self) -> KvStorageFormat {
138 self.parts.requested_kv_storage
139 }
140
141 pub fn selected_kv_storage(&self) -> Option<KvStorageFormat> {
142 self.parts.selected_kv_storage
143 }
144
145 pub fn selected_profile(&self) -> &NumericalProfileId {
146 &self.parts.selected_profile
147 }
148
149 pub fn rejected(&self) -> &[NumericalProfileRejection] {
150 &self.parts.rejected
151 }
152
153 pub(crate) fn matches_wire(&self, wire: &NumericalProfileResolutionWire) -> bool {
154 &self.parts == wire
155 }
156
157 pub(crate) fn validate_for(
158 &self,
159 definition: &ModelFamilyDefinition,
160 family: &PreparedModelFamily,
161 capabilities: &CapabilityCatalog,
162 runtime: &ResolvedRuntimePolicy,
163 plan: &ExecutionPlan,
164 ) -> Result<(), VNextError> {
165 let rebuilt = Self::from_static_plan(
166 self.parts.requested.clone(),
167 self.parts.requested_kv_storage,
168 definition,
169 family,
170 capabilities,
171 runtime,
172 plan,
173 self.parts.rejected.clone(),
174 )?;
175 if &rebuilt != self {
176 return Err(invalid(
177 "selection evidence differs from typed reconstruction",
178 ));
179 }
180 Ok(())
181 }
182}