1use ferrum_types::AttentionExecutionPolicy;
2use serde::de::{DeserializeSeed, MapAccess, SeqAccess, Visitor};
3use serde::{Deserialize, Deserializer, Serialize};
4use sha2::{Digest, Sha256};
5use std::cmp::Ordering;
6use std::collections::{BTreeMap, BTreeSet};
7use std::fmt;
8use std::io::{self, Write};
9
10use super::model::PreparedModelFamilyWire;
11use super::numerical_resolution::NumericalProfileResolutionWire;
12use super::NumericalProfileResolution;
13use super::{
14 AdmissionFitPolicy, CapabilityCatalog, CheckpointCapacityPolicy, CompletionRetentionSpec,
15 ContractVersion, DeviceDescriptor, DynamicStorageProfile, ExecutablePlanView,
16 ExecutionDeterminismRequirement, ExecutionPlan, ModelFamilyRegistry, PlanNodeResolution,
17 PreparedModelFamily, ProviderId, ReusableExecutionPolicy, RuntimePolicy, SpecialTokenRole,
18 TokenizerDescriptor, UnvalidatedExecutionPlan, UnvalidatedExecutionPlanWire,
19 UnvalidatedPreparedModelFamily, VNextError,
20};
21
22pub const MAX_RESOLUTION_SOURCE_BYTES: usize = 32 * 1024 * 1024;
24pub const MAX_RESOLVED_MODEL_PLAN_WIRE_BYTES: usize = 16 * 1024 * 1024;
26pub const MAX_RESOLUTION_JSON_DEPTH: usize = 128;
28pub const MAX_RESOLUTION_JSON_NODES: usize = 1_000_000;
30pub const MAX_RESOLUTION_JSON_KEY_AND_STRING_BYTES: usize = MAX_RESOLUTION_SOURCE_BYTES;
32pub const MAX_RESOLUTION_PROVENANCE_BYTES: usize = 4 * 1024;
34pub const MAX_RESOLUTION_FIELD_PATHS: usize = 4_096;
36pub const MAX_RESOLUTION_FIELD_PATH_BYTES: usize = 512;
38pub const MAX_RESOLUTION_FIELD_PATH_TOTAL_BYTES: usize = 1024 * 1024;
40
41fn is_canonical_sha256(value: &str) -> bool {
42 value.len() == 64
43 && value
44 .bytes()
45 .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))
46}
47
48fn canonical_fingerprint<T: Serialize + ?Sized>(
49 value: &T,
50 context: &'static str,
51) -> Result<String, VNextError> {
52 let value = serde_json::to_value(value).map_err(|error| VNextError::Serialization {
53 context,
54 message: error.to_string(),
55 })?;
56 canonical_value_fingerprint(&canonicalize_json(value), context)
57}
58
59struct Sha256Writer(Sha256);
60
61impl Write for Sha256Writer {
62 fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
63 self.0.update(bytes);
64 Ok(bytes.len())
65 }
66
67 fn flush(&mut self) -> io::Result<()> {
68 Ok(())
69 }
70}
71
72fn canonical_value_fingerprint(
73 value: &serde_json::Value,
74 context: &'static str,
75) -> Result<String, VNextError> {
76 let mut writer = Sha256Writer(Sha256::new());
77 serde_json::to_writer(&mut writer, value).map_err(|error| VNextError::Serialization {
78 context,
79 message: error.to_string(),
80 })?;
81 Ok(format!("{:x}", writer.0.finalize()))
82}
83
84fn canonical_json_value<T: Serialize + ?Sized>(
85 value: &T,
86 context: &'static str,
87) -> Result<serde_json::Value, VNextError> {
88 serde_json::to_value(value)
89 .map(canonicalize_json)
90 .map_err(|error| VNextError::Serialization {
91 context,
92 message: error.to_string(),
93 })
94}
95
96fn canonicalize_json(value: serde_json::Value) -> serde_json::Value {
97 match value {
98 serde_json::Value::Array(values) => {
99 serde_json::Value::Array(values.into_iter().map(canonicalize_json).collect())
100 }
101 serde_json::Value::Object(values) => serde_json::Value::Object(
102 values
103 .into_iter()
104 .map(|(key, value)| (key, canonicalize_json(value)))
105 .collect::<BTreeMap<_, _>>()
106 .into_iter()
107 .collect(),
108 ),
109 value => value,
110 }
111}
112
113fn invalid_plan(field: impl Into<String>, reason: impl Into<String>) -> VNextError {
114 VNextError::InvalidResolvedModelPlan {
115 field: field.into(),
116 reason: reason.into(),
117 }
118}
119
120fn validate_resolution_source_bytes(source_bytes: &[u8]) -> Result<(), VNextError> {
121 if source_bytes.is_empty() || source_bytes.len() > MAX_RESOLUTION_SOURCE_BYTES {
122 return Err(invalid_plan(
123 "resolution_source_evidence.source_bytes",
124 format!("must contain between 1 and {MAX_RESOLUTION_SOURCE_BYTES} bytes"),
125 ));
126 }
127 Ok(())
128}
129
130#[derive(Clone, Copy)]
131struct ResolutionJsonBudget {
132 maximum_depth: usize,
133 maximum_nodes: usize,
134 maximum_key_and_string_bytes: usize,
135}
136
137impl ResolutionJsonBudget {
138 const SOURCE: Self = Self {
139 maximum_depth: MAX_RESOLUTION_JSON_DEPTH,
140 maximum_nodes: MAX_RESOLUTION_JSON_NODES,
141 maximum_key_and_string_bytes: MAX_RESOLUTION_JSON_KEY_AND_STRING_BYTES,
142 };
143}
144
145struct ResolutionJsonPreflight {
146 budget: ResolutionJsonBudget,
147 nodes: usize,
148 key_and_string_bytes: usize,
149 violation: Option<VNextError>,
150}
151
152impl ResolutionJsonPreflight {
153 fn new(budget: ResolutionJsonBudget) -> Self {
154 Self {
155 budget,
156 nodes: 0,
157 key_and_string_bytes: 0,
158 violation: None,
159 }
160 }
161
162 fn fail<E: serde::de::Error>(&mut self, error: VNextError) -> Result<(), E> {
163 self.violation = Some(error);
164 Err(E::custom(
165 "resolution source JSON exceeds its structural budget",
166 ))
167 }
168
169 fn account_node<E: serde::de::Error>(&mut self, depth: usize) -> Result<(), E> {
170 if depth > self.budget.maximum_depth {
171 return self.fail(invalid_plan(
172 "resolution_source_evidence.document.depth",
173 format!("must not exceed {}", self.budget.maximum_depth),
174 ));
175 }
176 let Some(nodes) = self.nodes.checked_add(1) else {
177 return self.fail(invalid_plan(
178 "resolution_source_evidence.document.nodes",
179 "node count overflowed",
180 ));
181 };
182 if nodes > self.budget.maximum_nodes {
183 return self.fail(invalid_plan(
184 "resolution_source_evidence.document.nodes",
185 format!("must not exceed {}", self.budget.maximum_nodes),
186 ));
187 }
188 self.nodes = nodes;
189 Ok(())
190 }
191
192 fn account_text<E: serde::de::Error>(&mut self, bytes: usize) -> Result<(), E> {
193 let Some(key_and_string_bytes) = self.key_and_string_bytes.checked_add(bytes) else {
194 return self.fail(invalid_plan(
195 "resolution_source_evidence.document.key_and_string_bytes",
196 "byte count overflowed",
197 ));
198 };
199 if key_and_string_bytes > self.budget.maximum_key_and_string_bytes {
200 return self.fail(invalid_plan(
201 "resolution_source_evidence.document.key_and_string_bytes",
202 format!(
203 "must not exceed {}",
204 self.budget.maximum_key_and_string_bytes
205 ),
206 ));
207 }
208 self.key_and_string_bytes = key_and_string_bytes;
209 Ok(())
210 }
211}
212
213struct ResolutionJsonValueSeed<'a> {
214 preflight: &'a mut ResolutionJsonPreflight,
215 depth: usize,
216}
217
218impl<'de> DeserializeSeed<'de> for ResolutionJsonValueSeed<'_> {
219 type Value = ();
220
221 fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
222 where
223 D: Deserializer<'de>,
224 {
225 self.preflight.account_node::<D::Error>(self.depth)?;
226 deserializer.deserialize_any(ResolutionJsonValueVisitor {
227 preflight: self.preflight,
228 depth: self.depth,
229 })
230 }
231}
232
233struct ResolutionJsonValueVisitor<'a> {
234 preflight: &'a mut ResolutionJsonPreflight,
235 depth: usize,
236}
237
238impl<'de> Visitor<'de> for ResolutionJsonValueVisitor<'_> {
239 type Value = ();
240
241 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
242 formatter.write_str("one JSON value")
243 }
244
245 fn visit_unit<E>(self) -> Result<Self::Value, E> {
246 Ok(())
247 }
248
249 fn visit_bool<E>(self, _value: bool) -> Result<Self::Value, E> {
250 Ok(())
251 }
252
253 fn visit_i64<E>(self, _value: i64) -> Result<Self::Value, E> {
254 Ok(())
255 }
256
257 fn visit_u64<E>(self, _value: u64) -> Result<Self::Value, E> {
258 Ok(())
259 }
260
261 fn visit_f64<E>(self, _value: f64) -> Result<Self::Value, E> {
262 Ok(())
263 }
264
265 fn visit_borrowed_str<E>(self, value: &'de str) -> Result<Self::Value, E>
266 where
267 E: serde::de::Error,
268 {
269 self.preflight.account_text::<E>(value.len())
270 }
271
272 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
273 where
274 E: serde::de::Error,
275 {
276 self.preflight.account_text::<E>(value.len())
277 }
278
279 fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
280 where
281 E: serde::de::Error,
282 {
283 self.preflight.account_text::<E>(value.len())
284 }
285
286 fn visit_seq<A>(self, mut sequence: A) -> Result<Self::Value, A::Error>
287 where
288 A: SeqAccess<'de>,
289 {
290 let child_depth = self
291 .depth
292 .checked_add(1)
293 .ok_or_else(|| serde::de::Error::custom("resolution source JSON depth overflowed"))?;
294 while sequence
295 .next_element_seed(ResolutionJsonValueSeed {
296 preflight: &mut *self.preflight,
297 depth: child_depth,
298 })?
299 .is_some()
300 {}
301 Ok(())
302 }
303
304 fn visit_map<A>(self, mut object: A) -> Result<Self::Value, A::Error>
305 where
306 A: MapAccess<'de>,
307 {
308 let child_depth = self
309 .depth
310 .checked_add(1)
311 .ok_or_else(|| serde::de::Error::custom("resolution source JSON depth overflowed"))?;
312 while object
313 .next_key_seed(ResolutionJsonKeySeed {
314 preflight: &mut *self.preflight,
315 })?
316 .is_some()
317 {
318 object.next_value_seed(ResolutionJsonValueSeed {
319 preflight: &mut *self.preflight,
320 depth: child_depth,
321 })?;
322 }
323 Ok(())
324 }
325}
326
327struct ResolutionJsonKeySeed<'a> {
328 preflight: &'a mut ResolutionJsonPreflight,
329}
330
331impl<'de> DeserializeSeed<'de> for ResolutionJsonKeySeed<'_> {
332 type Value = ();
333
334 fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
335 where
336 D: Deserializer<'de>,
337 {
338 deserializer.deserialize_str(ResolutionJsonKeyVisitor {
339 preflight: self.preflight,
340 })
341 }
342}
343
344struct ResolutionJsonKeyVisitor<'a> {
345 preflight: &'a mut ResolutionJsonPreflight,
346}
347
348impl<'de> Visitor<'de> for ResolutionJsonKeyVisitor<'_> {
349 type Value = ();
350
351 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
352 formatter.write_str("a JSON object key")
353 }
354
355 fn visit_borrowed_str<E>(self, value: &'de str) -> Result<Self::Value, E>
356 where
357 E: serde::de::Error,
358 {
359 self.preflight.account_text::<E>(value.len())
360 }
361
362 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
363 where
364 E: serde::de::Error,
365 {
366 self.preflight.account_text::<E>(value.len())
367 }
368
369 fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
370 where
371 E: serde::de::Error,
372 {
373 self.preflight.account_text::<E>(value.len())
374 }
375}
376
377fn preflight_resolution_json_depth(
378 source_bytes: &[u8],
379 maximum_depth: usize,
380) -> Result<(), VNextError> {
381 let mut closing_delimiters = Vec::with_capacity(maximum_depth.saturating_add(1));
382 let mut in_string = false;
383 let mut escaped = false;
384
385 for byte in source_bytes.iter().copied() {
386 if in_string {
387 if escaped {
388 escaped = false;
389 } else if byte == b'\\' {
390 escaped = true;
391 } else if byte == b'"' {
392 in_string = false;
393 }
394 continue;
395 }
396
397 match byte {
398 b'"' => {
399 if closing_delimiters.len() > maximum_depth {
400 return Err(invalid_plan(
401 "resolution_source_evidence.document.depth",
402 format!("must not exceed {maximum_depth}"),
403 ));
404 }
405 in_string = true;
406 }
407 b'[' | b'{' => {
408 if closing_delimiters.len() > maximum_depth {
409 return Err(invalid_plan(
410 "resolution_source_evidence.document.depth",
411 format!("must not exceed {maximum_depth}"),
412 ));
413 }
414 closing_delimiters.push(if byte == b'[' { b']' } else { b'}' });
415 }
416 b']' | b'}' => {
417 if closing_delimiters.last() == Some(&byte) {
418 closing_delimiters.pop();
419 }
420 }
421 b' ' | b'\t' | b'\r' | b'\n' | b',' | b':' => {}
422 _ if closing_delimiters.len() > maximum_depth => {
423 return Err(invalid_plan(
424 "resolution_source_evidence.document.depth",
425 format!("must not exceed {maximum_depth}"),
426 ));
427 }
428 _ => {}
429 }
430 }
431 Ok(())
432}
433
434fn preflight_resolution_json(
435 source_bytes: &[u8],
436 budget: ResolutionJsonBudget,
437) -> Result<(), VNextError> {
438 preflight_resolution_json_depth(source_bytes, budget.maximum_depth)?;
442
443 let mut preflight = ResolutionJsonPreflight::new(budget);
444 let result = {
445 let mut deserializer = serde_json::Deserializer::from_slice(source_bytes);
446 ResolutionJsonValueSeed {
447 preflight: &mut preflight,
448 depth: 0,
449 }
450 .deserialize(&mut deserializer)
451 .and_then(|()| deserializer.end())
452 };
453 if let Some(error) = preflight.violation {
454 return Err(error);
455 }
456 result.map_err(|error| VNextError::Serialization {
457 context: "parse resolution source JSON",
458 message: error.to_string(),
459 })
460}
461
462fn validate_resolution_json_tree(document: &serde_json::Value) -> Result<(), VNextError> {
463 let mut stack = vec![(document, 0usize)];
464 let mut nodes = 0usize;
465 let mut key_and_string_bytes = 0usize;
466
467 while let Some((value, depth)) = stack.pop() {
468 if depth > MAX_RESOLUTION_JSON_DEPTH {
469 return Err(invalid_plan(
470 "resolution_source_evidence.document.depth",
471 format!("must not exceed {MAX_RESOLUTION_JSON_DEPTH}"),
472 ));
473 }
474 nodes = nodes.checked_add(1).ok_or_else(|| {
475 invalid_plan(
476 "resolution_source_evidence.document.nodes",
477 "node count overflowed",
478 )
479 })?;
480 if nodes > MAX_RESOLUTION_JSON_NODES {
481 return Err(invalid_plan(
482 "resolution_source_evidence.document.nodes",
483 format!("must not exceed {MAX_RESOLUTION_JSON_NODES}"),
484 ));
485 }
486
487 match value {
488 serde_json::Value::String(value) => {
489 key_and_string_bytes =
490 key_and_string_bytes
491 .checked_add(value.len())
492 .ok_or_else(|| {
493 invalid_plan(
494 "resolution_source_evidence.document.key_and_string_bytes",
495 "byte count overflowed",
496 )
497 })?;
498 }
499 serde_json::Value::Array(values) => {
500 let child_depth = depth.checked_add(1).ok_or_else(|| {
501 invalid_plan(
502 "resolution_source_evidence.document.depth",
503 "depth overflowed",
504 )
505 })?;
506 stack.extend(values.iter().map(|value| (value, child_depth)));
507 }
508 serde_json::Value::Object(values) => {
509 let child_depth = depth.checked_add(1).ok_or_else(|| {
510 invalid_plan(
511 "resolution_source_evidence.document.depth",
512 "depth overflowed",
513 )
514 })?;
515 for (key, value) in values {
516 key_and_string_bytes =
517 key_and_string_bytes.checked_add(key.len()).ok_or_else(|| {
518 invalid_plan(
519 "resolution_source_evidence.document.key_and_string_bytes",
520 "byte count overflowed",
521 )
522 })?;
523 stack.push((value, child_depth));
524 }
525 }
526 serde_json::Value::Null | serde_json::Value::Bool(_) | serde_json::Value::Number(_) => {
527 }
528 }
529
530 if key_and_string_bytes > MAX_RESOLUTION_JSON_KEY_AND_STRING_BYTES {
531 return Err(invalid_plan(
532 "resolution_source_evidence.document.key_and_string_bytes",
533 format!("must not exceed {MAX_RESOLUTION_JSON_KEY_AND_STRING_BYTES}"),
534 ));
535 }
536 }
537
538 Ok(())
539}
540
541fn drop_json_iteratively(document: serde_json::Value) {
542 let mut stack = vec![document];
543 while let Some(value) = stack.pop() {
544 match value {
545 serde_json::Value::Array(mut values) => stack.append(&mut values),
546 serde_json::Value::Object(values) => stack.extend(values.into_values()),
547 serde_json::Value::Null
548 | serde_json::Value::Bool(_)
549 | serde_json::Value::Number(_)
550 | serde_json::Value::String(_) => {}
551 }
552 }
553}
554
555fn validate_and_canonicalize_resolution_json(
556 document: serde_json::Value,
557 fingerprint_context: &'static str,
558) -> Result<(serde_json::Value, String), VNextError> {
559 if let Err(error) = validate_resolution_json_tree(&document) {
560 drop_json_iteratively(document);
561 return Err(error);
562 }
563 let document = canonicalize_json(document);
564 let fingerprint = canonical_value_fingerprint(&document, fingerprint_context)?;
565 Ok((document, fingerprint))
566}
567
568fn validate_portable_identifier(
569 kind: &'static str,
570 value: &str,
571 maximum_length: usize,
572) -> Result<(), VNextError> {
573 if value.is_empty() || value.len() > maximum_length {
574 return Err(invalid_plan(
575 kind,
576 format!("must contain between 1 and {maximum_length} bytes"),
577 ));
578 }
579 if !value.bytes().all(|byte| {
580 byte.is_ascii_alphanumeric() || matches!(byte, b'.' | b'_' | b'-' | b':' | b'/')
581 }) {
582 return Err(invalid_plan(kind, "contains a non-portable character"));
583 }
584 Ok(())
585}
586
587fn validate_source_path(path: &str) -> bool {
588 !path.is_empty()
589 && !path.starts_with('/')
590 && !path.ends_with('/')
591 && !path.contains('\\')
592 && path
593 .split('/')
594 .all(|component| !matches!(component, "" | "." | ".."))
595}
596
597#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
598#[serde(rename_all = "snake_case")]
599pub enum ModelSourceKind {
600 LocalDirectory,
601 LocalFile,
602 Repository,
603 ReleaseArtifact,
604}
605
606#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
607pub struct OriginalModelSource {
608 pub kind: ModelSourceKind,
609 pub location: String,
610 pub requested_revision: Option<String>,
611}
612
613#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
614pub struct FileFingerprint {
615 pub relative_path: String,
616 pub size_bytes: u64,
617 pub sha256: String,
618}
619
620#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
621pub struct ResolvedModelSource {
622 pub canonical_location: String,
623 pub resolved_revision: String,
624 pub files: Vec<FileFingerprint>,
625}
626
627#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
628#[serde(rename_all = "snake_case")]
629pub enum ModelArtifactSourceRole {
630 Semantic,
631 Tokenizer,
632 Weights,
633}
634
635impl ModelArtifactSourceRole {
636 pub const ALL: [Self; 3] = [Self::Semantic, Self::Tokenizer, Self::Weights];
637
638 pub const fn as_str(self) -> &'static str {
639 match self {
640 Self::Semantic => "semantic",
641 Self::Tokenizer => "tokenizer",
642 Self::Weights => "weights",
643 }
644 }
645}
646
647#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
648pub struct OriginalModelSources {
649 pub semantic: OriginalModelSource,
650 pub tokenizer: OriginalModelSource,
651 pub weights: OriginalModelSource,
652}
653
654impl OriginalModelSources {
655 pub const fn for_role(&self, role: ModelArtifactSourceRole) -> &OriginalModelSource {
656 match role {
657 ModelArtifactSourceRole::Semantic => &self.semantic,
658 ModelArtifactSourceRole::Tokenizer => &self.tokenizer,
659 ModelArtifactSourceRole::Weights => &self.weights,
660 }
661 }
662}
663
664#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
665pub struct ResolvedModelSources {
666 pub semantic: ResolvedModelSource,
667 pub tokenizer: ResolvedModelSource,
668 pub weights: ResolvedModelSource,
669}
670
671impl ResolvedModelSources {
672 pub const fn for_role(&self, role: ModelArtifactSourceRole) -> &ResolvedModelSource {
673 match role {
674 ModelArtifactSourceRole::Semantic => &self.semantic,
675 ModelArtifactSourceRole::Tokenizer => &self.tokenizer,
676 ModelArtifactSourceRole::Weights => &self.weights,
677 }
678 }
679
680 fn for_role_mut(&mut self, role: ModelArtifactSourceRole) -> &mut ResolvedModelSource {
681 match role {
682 ModelArtifactSourceRole::Semantic => &mut self.semantic,
683 ModelArtifactSourceRole::Tokenizer => &mut self.tokenizer,
684 ModelArtifactSourceRole::Weights => &mut self.weights,
685 }
686 }
687}
688
689pub const PRODUCT_MODEL_SOURCE_IDENTITY_SCHEMA_VERSION: u32 = 1;
690
691#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
697#[serde(deny_unknown_fields)]
698pub struct ProductModelArtifactBinding {
699 pub role: ModelArtifactSourceRole,
700 pub source_file: String,
701 pub container_sha256: String,
702 #[serde(skip_serializing_if = "Option::is_none")]
703 pub content_sha256: Option<String>,
704}
705
706impl ProductModelArtifactBinding {
707 pub fn new(
708 role: ModelArtifactSourceRole,
709 source_file: impl Into<String>,
710 container_sha256: impl Into<String>,
711 content_sha256: Option<String>,
712 ) -> Result<Self, VNextError> {
713 let binding = Self {
714 role,
715 source_file: source_file.into(),
716 container_sha256: container_sha256.into(),
717 content_sha256,
718 };
719 binding.validate("product_model_artifact_binding")?;
720 Ok(binding)
721 }
722
723 fn validate(&self, field: &str) -> Result<(), VNextError> {
724 if !validate_source_path(&self.source_file) {
725 return Err(invalid_plan(
726 format!("{field}.source_file"),
727 "must be a portable relative source path",
728 ));
729 }
730 if !is_canonical_sha256(&self.container_sha256) {
731 return Err(invalid_plan(
732 format!("{field}.container_sha256"),
733 "must be a canonical SHA-256",
734 ));
735 }
736 if self
737 .content_sha256
738 .as_deref()
739 .is_some_and(|sha256| !is_canonical_sha256(sha256))
740 {
741 return Err(invalid_plan(
742 format!("{field}.content_sha256"),
743 "must be a canonical SHA-256",
744 ));
745 }
746 Ok(())
747 }
748}
749
750#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
758#[serde(deny_unknown_fields)]
759pub struct ProductModelSourceIdentity {
760 pub schema_version: u32,
761 pub requested_model: String,
762 pub resolved_model: String,
763 pub original_sources: OriginalModelSources,
764 pub resolved_sources: ResolvedModelSources,
765 pub semantic_config: ProductModelArtifactBinding,
766 pub tokenizer: ProductModelArtifactBinding,
767 pub template: ProductModelArtifactBinding,
768 #[serde(skip_serializing_if = "Option::is_none")]
769 pub weight_config: Option<ProductModelArtifactBinding>,
770}
771
772impl ProductModelSourceIdentity {
773 #[allow(clippy::too_many_arguments)]
774 pub fn new(
775 requested_model: impl Into<String>,
776 resolved_model: impl Into<String>,
777 original_sources: OriginalModelSources,
778 resolved_sources: ResolvedModelSources,
779 semantic_config: ProductModelArtifactBinding,
780 tokenizer: ProductModelArtifactBinding,
781 template: ProductModelArtifactBinding,
782 weight_config: Option<ProductModelArtifactBinding>,
783 ) -> Result<Self, VNextError> {
784 let identity = Self {
785 schema_version: PRODUCT_MODEL_SOURCE_IDENTITY_SCHEMA_VERSION,
786 requested_model: requested_model.into(),
787 resolved_model: resolved_model.into(),
788 original_sources,
789 resolved_sources,
790 semantic_config,
791 tokenizer,
792 template,
793 weight_config,
794 };
795 identity.validate()?;
796 Ok(identity)
797 }
798
799 pub fn validate(&self) -> Result<(), VNextError> {
800 if self.schema_version != PRODUCT_MODEL_SOURCE_IDENTITY_SCHEMA_VERSION {
801 return Err(invalid_plan(
802 "product_model_source_identity.schema_version",
803 format!("must be {}", PRODUCT_MODEL_SOURCE_IDENTITY_SCHEMA_VERSION),
804 ));
805 }
806 for (field, value) in [
807 ("requested_model", self.requested_model.as_str()),
808 ("resolved_model", self.resolved_model.as_str()),
809 ] {
810 if value.is_empty() || value.trim() != value {
811 return Err(invalid_plan(
812 format!("product_model_source_identity.{field}"),
813 "must be non-empty and have no surrounding whitespace",
814 ));
815 }
816 }
817 for role in ModelArtifactSourceRole::ALL {
818 self.validate_original_source(role)?;
819 self.validate_resolved_source(role)?;
820 }
821 self.validate_binding(
822 "semantic_config",
823 &self.semantic_config,
824 ModelArtifactSourceRole::Semantic,
825 )?;
826 self.validate_binding(
827 "tokenizer",
828 &self.tokenizer,
829 ModelArtifactSourceRole::Tokenizer,
830 )?;
831 self.validate_binding(
832 "template",
833 &self.template,
834 ModelArtifactSourceRole::Tokenizer,
835 )?;
836 if self.template.content_sha256.is_none() {
837 return Err(invalid_plan(
838 "product_model_source_identity.template.content_sha256",
839 "selected template content must be fingerprinted",
840 ));
841 }
842 if let Some(binding) = &self.weight_config {
843 self.validate_binding("weight_config", binding, ModelArtifactSourceRole::Weights)?;
844 }
845 Ok(())
846 }
847
848 fn validate_original_source(&self, role: ModelArtifactSourceRole) -> Result<(), VNextError> {
849 let source = self.original_sources.for_role(role);
850 if source.location.is_empty() || source.location.trim() != source.location {
851 return Err(invalid_plan(
852 format!(
853 "product_model_source_identity.original_sources.{}.location",
854 role.as_str()
855 ),
856 "must be non-empty and have no surrounding whitespace",
857 ));
858 }
859 if source
860 .requested_revision
861 .as_deref()
862 .is_some_and(|revision| revision.is_empty() || revision.trim() != revision)
863 {
864 return Err(invalid_plan(
865 format!(
866 "product_model_source_identity.original_sources.{}.requested_revision",
867 role.as_str()
868 ),
869 "must be non-empty and have no surrounding whitespace when present",
870 ));
871 }
872 Ok(())
873 }
874
875 fn validate_resolved_source(&self, role: ModelArtifactSourceRole) -> Result<(), VNextError> {
876 let source = self.resolved_sources.for_role(role);
877 for (field, value) in [
878 ("canonical_location", source.canonical_location.as_str()),
879 ("resolved_revision", source.resolved_revision.as_str()),
880 ] {
881 if value.is_empty() || value.trim() != value {
882 return Err(invalid_plan(
883 format!(
884 "product_model_source_identity.resolved_sources.{}.{field}",
885 role.as_str()
886 ),
887 "must be non-empty and have no surrounding whitespace",
888 ));
889 }
890 }
891 if source.files.is_empty() {
892 return Err(invalid_plan(
893 format!(
894 "product_model_source_identity.resolved_sources.{}.files",
895 role.as_str()
896 ),
897 "must not be empty",
898 ));
899 }
900 for (index, file) in source.files.iter().enumerate() {
901 let field = format!(
902 "product_model_source_identity.resolved_sources.{}.files[{index}]",
903 role.as_str()
904 );
905 if !validate_source_path(&file.relative_path) {
906 return Err(invalid_plan(
907 format!("{field}.relative_path"),
908 "must be a portable relative source path",
909 ));
910 }
911 if file.size_bytes == 0 {
912 return Err(invalid_plan(
913 format!("{field}.size_bytes"),
914 "must be positive",
915 ));
916 }
917 if !is_canonical_sha256(&file.sha256) {
918 return Err(invalid_plan(
919 format!("{field}.sha256"),
920 "must be a canonical SHA-256",
921 ));
922 }
923 }
924 if source
925 .files
926 .windows(2)
927 .any(|pair| pair[0].relative_path >= pair[1].relative_path)
928 {
929 return Err(invalid_plan(
930 format!(
931 "product_model_source_identity.resolved_sources.{}.files",
932 role.as_str()
933 ),
934 "must be strictly sorted by relative_path without duplicates",
935 ));
936 }
937 Ok(())
938 }
939
940 fn validate_binding(
941 &self,
942 field: &str,
943 binding: &ProductModelArtifactBinding,
944 expected_role: ModelArtifactSourceRole,
945 ) -> Result<(), VNextError> {
946 binding.validate(&format!("product_model_source_identity.{field}"))?;
947 if binding.role != expected_role {
948 return Err(invalid_plan(
949 format!("product_model_source_identity.{field}.role"),
950 format!("must be {expected_role:?}"),
951 ));
952 }
953 let source = self.resolved_sources.for_role(binding.role);
954 match source
955 .files
956 .iter()
957 .find(|file| file.relative_path == binding.source_file)
958 {
959 Some(file) if file.sha256 == binding.container_sha256 => Ok(()),
960 Some(file) => Err(invalid_plan(
961 format!("product_model_source_identity.{field}.container_sha256"),
962 format!("does not match resolved source file {}", file.relative_path),
963 )),
964 None => Err(invalid_plan(
965 format!("product_model_source_identity.{field}.source_file"),
966 format!("is absent from resolved {} source", binding.role.as_str()),
967 )),
968 }
969 }
970}
971
972#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
973pub struct ModelConfigFingerprint {
974 pub source_file: String,
975 pub sha256: String,
976 pub typed_config_sha256: String,
977}
978
979#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
980pub struct EngineSelection {
981 pub provider_id: ProviderId,
982 pub contract_version: ContractVersion,
983 pub implementation_fingerprint: String,
984}
985
986#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
987#[serde(rename_all = "snake_case")]
988pub enum SchedulingDiscipline {
989 FirstReady,
990 Priority,
991 Deadline,
992}
993
994#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
995pub struct RuntimeMemoryPolicy {
996 pub capacity_bytes: u64,
997 pub reserve_bytes: u64,
998 pub maximum_active_sequences: u32,
999 pub dynamic_storage_profile_order: Vec<DynamicStorageProfile>,
1000 #[serde(default, skip_serializing_if = "Option::is_none")]
1001 pub checkpoint_capacity: Option<CheckpointCapacityPolicy>,
1002}
1003
1004#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1005pub struct AdmissionPolicy {
1006 pub maximum_queue_depth: u32,
1007 pub maximum_scheduled_tokens: u64,
1008 pub sequence_fit_policy: AdmissionFitPolicy,
1009 pub allow_defer: bool,
1010 pub cancellation_check_interval_steps: u32,
1011}
1012
1013#[derive(Serialize)]
1014struct RuntimePolicyFingerprintPayload<'a> {
1015 policy_id: &'a str,
1016 version: ContractVersion,
1017 scheduling: SchedulingDiscipline,
1018 memory: &'a RuntimeMemoryPolicy,
1019 admission: &'a AdmissionPolicy,
1020 attention_execution: AttentionExecutionPolicy,
1021 execution_determinism: ExecutionDeterminismRequirement,
1022 reusable_execution: &'a Option<ReusableExecutionPolicy>,
1023}
1024
1025#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1026pub struct ResolvedRuntimePolicy {
1027 policy_id: String,
1028 version: ContractVersion,
1029 scheduling: SchedulingDiscipline,
1030 memory: RuntimeMemoryPolicy,
1031 admission: AdmissionPolicy,
1032 attention_execution: AttentionExecutionPolicy,
1033 execution_determinism: ExecutionDeterminismRequirement,
1034 reusable_execution: Option<ReusableExecutionPolicy>,
1035 fingerprint: String,
1036}
1037
1038#[derive(Deserialize)]
1039#[serde(deny_unknown_fields)]
1040struct ResolvedRuntimePolicyWire {
1041 policy_id: String,
1042 version: ContractVersion,
1043 scheduling: SchedulingDiscipline,
1044 memory: RuntimeMemoryPolicy,
1045 admission: AdmissionPolicy,
1046 attention_execution: AttentionExecutionPolicy,
1047 execution_determinism: ExecutionDeterminismRequirement,
1048 reusable_execution: Option<ReusableExecutionPolicy>,
1049 fingerprint: String,
1050}
1051
1052impl<'de> Deserialize<'de> for ResolvedRuntimePolicy {
1053 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1054 where
1055 D: Deserializer<'de>,
1056 {
1057 let wire = ResolvedRuntimePolicyWire::deserialize(deserializer)?;
1058 let policy = Self::new(
1059 wire.policy_id,
1060 wire.version,
1061 wire.scheduling,
1062 wire.memory,
1063 wire.admission,
1064 wire.attention_execution,
1065 wire.execution_determinism,
1066 wire.reusable_execution,
1067 )
1068 .map_err(serde::de::Error::custom)?;
1069 if wire.fingerprint != policy.fingerprint {
1070 return Err(serde::de::Error::custom(format!(
1071 "runtime policy fingerprint mismatch: expected `{}`, actual `{}`",
1072 policy.fingerprint, wire.fingerprint
1073 )));
1074 }
1075 Ok(policy)
1076 }
1077}
1078
1079impl ResolvedRuntimePolicy {
1080 pub fn new(
1081 policy_id: impl Into<String>,
1082 version: ContractVersion,
1083 scheduling: SchedulingDiscipline,
1084 memory: RuntimeMemoryPolicy,
1085 admission: AdmissionPolicy,
1086 attention_execution: AttentionExecutionPolicy,
1087 execution_determinism: ExecutionDeterminismRequirement,
1088 reusable_execution: Option<ReusableExecutionPolicy>,
1089 ) -> Result<Self, VNextError> {
1090 let policy_id = policy_id.into();
1091 Self::validate_fields(
1092 &policy_id,
1093 version,
1094 &memory,
1095 &admission,
1096 attention_execution,
1097 reusable_execution.as_ref(),
1098 )?;
1099 let fingerprint = Self::compute_fingerprint(
1100 &policy_id,
1101 version,
1102 scheduling,
1103 &memory,
1104 &admission,
1105 attention_execution,
1106 execution_determinism,
1107 &reusable_execution,
1108 )?;
1109 Ok(Self {
1110 policy_id,
1111 version,
1112 scheduling,
1113 memory,
1114 admission,
1115 attention_execution,
1116 execution_determinism,
1117 reusable_execution,
1118 fingerprint,
1119 })
1120 }
1121
1122 fn validate_fields(
1123 policy_id: &str,
1124 version: ContractVersion,
1125 memory: &RuntimeMemoryPolicy,
1126 admission: &AdmissionPolicy,
1127 attention_execution: AttentionExecutionPolicy,
1128 reusable_execution: Option<&ReusableExecutionPolicy>,
1129 ) -> Result<(), VNextError> {
1130 validate_portable_identifier("runtime_policy.policy_id", policy_id, 160)?;
1131 if version.major == 0 {
1132 return Err(invalid_plan(
1133 "runtime_policy.version",
1134 "major version must be non-zero",
1135 ));
1136 }
1137 if memory.capacity_bytes == 0
1138 || memory.reserve_bytes >= memory.capacity_bytes
1139 || memory.maximum_active_sequences == 0
1140 || memory.dynamic_storage_profile_order.is_empty()
1141 || memory
1142 .dynamic_storage_profile_order
1143 .iter()
1144 .enumerate()
1145 .any(|(index, profile)| {
1146 memory.dynamic_storage_profile_order[index + 1..].contains(profile)
1147 })
1148 {
1149 return Err(invalid_plan(
1150 "runtime_policy.memory",
1151 "capacity and reserve must be valid and maximum_active_sequences must be non-zero",
1152 ));
1153 }
1154 if admission.maximum_queue_depth == 0
1155 || admission.maximum_scheduled_tokens == 0
1156 || admission.cancellation_check_interval_steps == 0
1157 {
1158 return Err(invalid_plan(
1159 "runtime_policy.admission",
1160 "queue depth, scheduled-token ceiling, and cancellation interval must be non-zero",
1161 ));
1162 }
1163 if !attention_execution.is_resolved() {
1164 return Err(invalid_plan(
1165 "runtime_policy.attention_execution",
1166 "attention execution policy must be resolved before plan compilation",
1167 ));
1168 }
1169 if let Some(reusable_execution) = reusable_execution {
1170 reusable_execution.validate()?;
1171 if reusable_execution.buckets().iter().any(|bucket| {
1172 bucket.capacity().maximum_sequences() > memory.maximum_active_sequences
1173 || bucket.capacity().maximum_tokens() > admission.maximum_scheduled_tokens
1174 }) {
1175 return Err(invalid_plan(
1176 "runtime_policy.reusable_execution",
1177 "bucket capacity exceeds the scheduler policy ceiling",
1178 ));
1179 }
1180 if reusable_execution
1181 .program_policy()
1182 .is_some_and(|program_policy| {
1183 program_policy.programs().iter().any(|program| {
1184 let shape = program.shape();
1185 shape.request_capacity() > memory.maximum_active_sequences
1186 || shape.token_capacity() > admission.maximum_scheduled_tokens
1187 })
1188 })
1189 {
1190 return Err(invalid_plan(
1191 "runtime_policy.reusable_execution",
1192 "program shape exceeds the scheduler policy ceiling",
1193 ));
1194 }
1195 }
1196 Ok(())
1197 }
1198
1199 fn compute_fingerprint(
1200 policy_id: &str,
1201 version: ContractVersion,
1202 scheduling: SchedulingDiscipline,
1203 memory: &RuntimeMemoryPolicy,
1204 admission: &AdmissionPolicy,
1205 attention_execution: AttentionExecutionPolicy,
1206 execution_determinism: ExecutionDeterminismRequirement,
1207 reusable_execution: &Option<ReusableExecutionPolicy>,
1208 ) -> Result<String, VNextError> {
1209 canonical_fingerprint(
1210 &RuntimePolicyFingerprintPayload {
1211 policy_id,
1212 version,
1213 scheduling,
1214 memory,
1215 admission,
1216 attention_execution,
1217 execution_determinism,
1218 reusable_execution,
1219 },
1220 "serialize resolved runtime policy",
1221 )
1222 }
1223
1224 pub fn policy_id(&self) -> &str {
1225 &self.policy_id
1226 }
1227
1228 pub fn version(&self) -> ContractVersion {
1229 self.version
1230 }
1231
1232 pub fn scheduling(&self) -> SchedulingDiscipline {
1233 self.scheduling
1234 }
1235
1236 pub fn memory(&self) -> &RuntimeMemoryPolicy {
1237 &self.memory
1238 }
1239
1240 pub fn admission(&self) -> &AdmissionPolicy {
1241 &self.admission
1242 }
1243
1244 pub const fn attention_execution(&self) -> AttentionExecutionPolicy {
1245 self.attention_execution
1246 }
1247
1248 pub const fn execution_determinism(&self) -> ExecutionDeterminismRequirement {
1249 self.execution_determinism
1250 }
1251
1252 pub fn reusable_execution(&self) -> Option<&ReusableExecutionPolicy> {
1253 self.reusable_execution.as_ref()
1254 }
1255
1256 pub fn fingerprint_str(&self) -> &str {
1257 &self.fingerprint
1258 }
1259}
1260
1261impl RuntimePolicy for ResolvedRuntimePolicy {
1262 fn version(&self) -> ContractVersion {
1263 self.version
1264 }
1265
1266 fn memory_capacity_bytes(&self) -> u64 {
1267 self.memory.capacity_bytes
1268 }
1269
1270 fn memory_reserve_bytes(&self) -> u64 {
1271 self.memory.reserve_bytes
1272 }
1273
1274 fn maximum_active_sequences(&self) -> u32 {
1275 self.memory.maximum_active_sequences
1276 }
1277
1278 fn maximum_scheduled_tokens(&self) -> u64 {
1279 self.admission.maximum_scheduled_tokens
1280 }
1281
1282 fn attention_execution_policy(&self) -> AttentionExecutionPolicy {
1283 self.attention_execution
1284 }
1285
1286 fn execution_determinism_requirement(&self) -> ExecutionDeterminismRequirement {
1287 self.execution_determinism
1288 }
1289
1290 fn dynamic_storage_profile_order(&self) -> &[DynamicStorageProfile] {
1291 &self.memory.dynamic_storage_profile_order
1292 }
1293
1294 fn reusable_execution_policy(&self) -> Option<&ReusableExecutionPolicy> {
1295 self.reusable_execution.as_ref()
1296 }
1297
1298 fn checkpoint_capacity_policy(&self) -> Option<&CheckpointCapacityPolicy> {
1299 self.memory.checkpoint_capacity.as_ref()
1300 }
1301
1302 fn validate(&self) -> Result<(), VNextError> {
1303 Self::validate_fields(
1304 &self.policy_id,
1305 self.version,
1306 &self.memory,
1307 &self.admission,
1308 self.attention_execution,
1309 self.reusable_execution.as_ref(),
1310 )?;
1311 let computed = Self::compute_fingerprint(
1312 &self.policy_id,
1313 self.version,
1314 self.scheduling,
1315 &self.memory,
1316 &self.admission,
1317 self.attention_execution,
1318 self.execution_determinism,
1319 &self.reusable_execution,
1320 )?;
1321 if self.fingerprint != computed {
1322 return Err(invalid_plan(
1323 "runtime_policy.fingerprint",
1324 format!(
1325 "does not match typed fields: expected `{computed}`, actual `{}`",
1326 self.fingerprint
1327 ),
1328 ));
1329 }
1330 Ok(())
1331 }
1332}
1333
1334#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
1335pub struct RationalValue {
1336 numerator: i64,
1337 denominator: u64,
1338}
1339
1340#[derive(Deserialize)]
1341#[serde(deny_unknown_fields)]
1342struct RationalValueWire {
1343 numerator: i64,
1344 denominator: u64,
1345}
1346
1347impl<'de> Deserialize<'de> for RationalValue {
1348 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1349 where
1350 D: Deserializer<'de>,
1351 {
1352 let wire = RationalValueWire::deserialize(deserializer)?;
1353 Self::new(wire.numerator, wire.denominator).map_err(serde::de::Error::custom)
1354 }
1355}
1356
1357impl RationalValue {
1358 pub fn new(numerator: i64, denominator: u64) -> Result<Self, VNextError> {
1359 if denominator == 0 {
1360 return Err(invalid_plan(
1361 "sampling.rational",
1362 "denominator must be non-zero",
1363 ));
1364 }
1365 let divisor = greatest_common_divisor(numerator.unsigned_abs(), denominator);
1366 let numerator = ((numerator as i128) / (divisor as i128)) as i64;
1367 let denominator = denominator / divisor;
1368 Ok(Self {
1369 numerator,
1370 denominator,
1371 })
1372 }
1373
1374 fn validate(&self, field: &str) -> Result<(), VNextError> {
1375 if self.denominator == 0
1376 || greatest_common_divisor(self.numerator.unsigned_abs(), self.denominator) != 1
1377 || (self.numerator == 0 && self.denominator != 1)
1378 {
1379 return Err(invalid_plan(
1380 field,
1381 "rational value must be reduced with a non-zero denominator",
1382 ));
1383 }
1384 Ok(())
1385 }
1386
1387 fn compare(&self, numerator: i64, denominator: u64) -> Ordering {
1388 ((self.numerator as i128) * (denominator as i128))
1389 .cmp(&((numerator as i128) * (self.denominator as i128)))
1390 }
1391
1392 pub fn numerator(&self) -> i64 {
1393 self.numerator
1394 }
1395
1396 pub fn denominator(&self) -> u64 {
1397 self.denominator
1398 }
1399}
1400
1401fn greatest_common_divisor(mut left: u64, mut right: u64) -> u64 {
1402 while right != 0 {
1403 let remainder = left % right;
1404 left = right;
1405 right = remainder;
1406 }
1407 left
1408}
1409
1410#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1411#[serde(rename_all = "snake_case")]
1412pub enum TriStatePolicy {
1413 ModelDefault,
1414 Enabled,
1415 Disabled,
1416}
1417
1418#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1419pub struct SamplingPolicy {
1420 temperature: RationalValue,
1421 top_p: RationalValue,
1422 top_k: Option<u32>,
1423 min_p: RationalValue,
1424 presence_penalty: RationalValue,
1425 repetition_penalty: RationalValue,
1426 seed: u64,
1427 thinking_policy: TriStatePolicy,
1428}
1429
1430#[derive(Deserialize)]
1431#[serde(deny_unknown_fields)]
1432struct SamplingPolicyWire {
1433 temperature: RationalValue,
1434 top_p: RationalValue,
1435 top_k: Option<u32>,
1436 min_p: RationalValue,
1437 presence_penalty: RationalValue,
1438 repetition_penalty: RationalValue,
1439 seed: u64,
1440 thinking_policy: TriStatePolicy,
1441}
1442
1443impl<'de> Deserialize<'de> for SamplingPolicy {
1444 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1445 where
1446 D: Deserializer<'de>,
1447 {
1448 let wire = SamplingPolicyWire::deserialize(deserializer)?;
1449 Self::new(
1450 wire.temperature,
1451 wire.top_p,
1452 wire.top_k,
1453 wire.min_p,
1454 wire.presence_penalty,
1455 wire.repetition_penalty,
1456 wire.seed,
1457 wire.thinking_policy,
1458 )
1459 .map_err(serde::de::Error::custom)
1460 }
1461}
1462
1463impl SamplingPolicy {
1464 #[allow(clippy::too_many_arguments)]
1465 pub fn new(
1466 temperature: RationalValue,
1467 top_p: RationalValue,
1468 top_k: Option<u32>,
1469 min_p: RationalValue,
1470 presence_penalty: RationalValue,
1471 repetition_penalty: RationalValue,
1472 seed: u64,
1473 thinking_policy: TriStatePolicy,
1474 ) -> Result<Self, VNextError> {
1475 let policy = Self {
1476 temperature,
1477 top_p,
1478 top_k,
1479 min_p,
1480 presence_penalty,
1481 repetition_penalty,
1482 seed,
1483 thinking_policy,
1484 };
1485 policy.validate()?;
1486 Ok(policy)
1487 }
1488
1489 fn validate(&self) -> Result<(), VNextError> {
1490 self.temperature.validate("sampling.temperature")?;
1491 self.top_p.validate("sampling.top_p")?;
1492 self.min_p.validate("sampling.min_p")?;
1493 self.presence_penalty
1494 .validate("sampling.presence_penalty")?;
1495 self.repetition_penalty
1496 .validate("sampling.repetition_penalty")?;
1497
1498 if self.temperature.compare(0, 1) == Ordering::Less {
1499 return Err(invalid_plan(
1500 "sampling.temperature",
1501 "must be greater than or equal to zero",
1502 ));
1503 }
1504 if self.top_p.compare(0, 1) != Ordering::Greater
1505 || self.top_p.compare(1, 1) == Ordering::Greater
1506 {
1507 return Err(invalid_plan(
1508 "sampling.top_p",
1509 "must be in the interval (0, 1]",
1510 ));
1511 }
1512 if self.top_k == Some(0) {
1513 return Err(invalid_plan(
1514 "sampling.top_k",
1515 "must be absent or greater than zero",
1516 ));
1517 }
1518 if self.min_p.compare(0, 1) == Ordering::Less
1519 || self.min_p.compare(1, 1) == Ordering::Greater
1520 {
1521 return Err(invalid_plan(
1522 "sampling.min_p",
1523 "must be in the interval [0, 1]",
1524 ));
1525 }
1526 if self.presence_penalty.compare(-2, 1) == Ordering::Less
1527 || self.presence_penalty.compare(2, 1) == Ordering::Greater
1528 {
1529 return Err(invalid_plan(
1530 "sampling.presence_penalty",
1531 "must be in the interval [-2, 2]",
1532 ));
1533 }
1534 if self.repetition_penalty.compare(0, 1) != Ordering::Greater {
1535 return Err(invalid_plan(
1536 "sampling.repetition_penalty",
1537 "must be greater than zero",
1538 ));
1539 }
1540 Ok(())
1541 }
1542
1543 pub fn temperature(&self) -> RationalValue {
1544 self.temperature
1545 }
1546
1547 pub fn top_p(&self) -> RationalValue {
1548 self.top_p
1549 }
1550
1551 pub fn top_k(&self) -> Option<u32> {
1552 self.top_k
1553 }
1554
1555 pub fn min_p(&self) -> RationalValue {
1556 self.min_p
1557 }
1558
1559 pub fn presence_penalty(&self) -> RationalValue {
1560 self.presence_penalty
1561 }
1562
1563 pub fn repetition_penalty(&self) -> RationalValue {
1564 self.repetition_penalty
1565 }
1566
1567 pub fn seed(&self) -> u64 {
1568 self.seed
1569 }
1570
1571 pub fn thinking_policy(&self) -> TriStatePolicy {
1572 self.thinking_policy
1573 }
1574}
1575
1576#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1577pub struct StopTokenCollisionPolicy {
1578 allowed_model_roles: BTreeSet<SpecialTokenRole>,
1579}
1580
1581impl StopTokenCollisionPolicy {
1582 pub fn new(allowed_model_roles: BTreeSet<SpecialTokenRole>) -> Result<Self, VNextError> {
1583 if allowed_model_roles.contains(&SpecialTokenRole::Stop) {
1584 return Err(invalid_plan(
1585 "stop.collision_policy",
1586 "a stop-token collision policy can only name model-owned roles",
1587 ));
1588 }
1589 Ok(Self {
1590 allowed_model_roles,
1591 })
1592 }
1593
1594 pub fn require_distinct() -> Self {
1595 Self {
1596 allowed_model_roles: BTreeSet::new(),
1597 }
1598 }
1599
1600 pub fn allows(&self, model_role: SpecialTokenRole) -> bool {
1601 self.allowed_model_roles.contains(&model_role)
1602 }
1603
1604 pub fn allowed_model_roles(&self) -> &BTreeSet<SpecialTokenRole> {
1605 &self.allowed_model_roles
1606 }
1607}
1608
1609#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1610pub struct StopPolicy {
1611 pub maximum_output_tokens: u32,
1612 pub token_ids: BTreeSet<u32>,
1613 pub strings: Vec<String>,
1614 pub collision_policy: StopTokenCollisionPolicy,
1615}
1616
1617#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1618#[serde(rename_all = "snake_case")]
1619pub enum StructuredOutputPolicy {
1620 Disabled,
1621 JsonObject,
1622 JsonSchema { schema_sha256: String },
1623}
1624
1625#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
1626#[serde(rename_all = "snake_case")]
1627pub enum ResolutionField {
1628 OriginalSources,
1629 ResolvedSources,
1630 Config,
1631 ExternalMetadata,
1632 Family,
1633 NumericalExecution,
1634 WeightSchema,
1635 WeightFormat,
1636 Tokenizer,
1637 Template,
1638 SpecialTokens,
1639 Device,
1640 Capabilities,
1641 RuntimePreset,
1642 RuntimeMemory,
1643 Admission,
1644 Engine,
1645 ExecutionPlan,
1646 Sampling,
1647 Stop,
1648 StructuredOutput,
1649}
1650
1651#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
1652#[serde(rename_all = "snake_case")]
1653pub enum ResolutionDecisionSource {
1654 UserInput,
1655 CommandLine,
1656 ConfigFile,
1657 ModelMetadata,
1658 TypedModelResolution,
1659 ProductDefault,
1660 RuntimePreset,
1661 CapabilityResolution,
1662 Planner,
1663}
1664
1665impl ResolutionField {
1666 pub const fn as_str(self) -> &'static str {
1667 match self {
1668 Self::OriginalSources => "original_sources",
1669 Self::ResolvedSources => "resolved_sources",
1670 Self::Config => "config",
1671 Self::ExternalMetadata => "external_metadata",
1672 Self::Family => "family",
1673 Self::NumericalExecution => "numerical_execution",
1674 Self::WeightSchema => "weight_schema",
1675 Self::WeightFormat => "weight_format",
1676 Self::Tokenizer => "tokenizer",
1677 Self::Template => "template",
1678 Self::SpecialTokens => "special_tokens",
1679 Self::Device => "device",
1680 Self::Capabilities => "capabilities",
1681 Self::RuntimePreset => "runtime_preset",
1682 Self::RuntimeMemory => "runtime_memory",
1683 Self::Admission => "admission",
1684 Self::Engine => "engine",
1685 Self::ExecutionPlan => "execution_plan",
1686 Self::Sampling => "sampling",
1687 Self::Stop => "stop",
1688 Self::StructuredOutput => "structured_output",
1689 }
1690 }
1691
1692 pub const fn accepts_source(self, source: ResolutionDecisionSource) -> bool {
1696 use ResolutionDecisionSource as Source;
1697 use ResolutionField as Field;
1698
1699 match self {
1700 Field::OriginalSources => matches!(
1701 source,
1702 Source::UserInput | Source::CommandLine | Source::ConfigFile
1703 ),
1704 Field::ResolvedSources => {
1705 matches!(source, Source::ModelMetadata | Source::TypedModelResolution)
1706 }
1707 Field::Config => matches!(
1708 source,
1709 Source::ConfigFile | Source::ModelMetadata | Source::TypedModelResolution
1710 ),
1711 Field::ExternalMetadata
1712 | Field::Family
1713 | Field::WeightSchema
1714 | Field::Tokenizer
1715 | Field::Template
1716 | Field::SpecialTokens => {
1717 matches!(source, Source::ModelMetadata | Source::TypedModelResolution)
1718 }
1719 Field::WeightFormat => matches!(
1720 source,
1721 Source::CommandLine
1722 | Source::ConfigFile
1723 | Source::ModelMetadata
1724 | Source::TypedModelResolution
1725 ),
1726 Field::Device => matches!(
1727 source,
1728 Source::CommandLine
1729 | Source::ConfigFile
1730 | Source::ProductDefault
1731 | Source::CapabilityResolution
1732 ),
1733 Field::Capabilities => matches!(source, Source::CapabilityResolution),
1734 Field::RuntimePreset => matches!(
1735 source,
1736 Source::CommandLine
1737 | Source::ConfigFile
1738 | Source::ProductDefault
1739 | Source::RuntimePreset
1740 ),
1741 Field::RuntimeMemory | Field::Admission => matches!(
1742 source,
1743 Source::CommandLine | Source::ConfigFile | Source::RuntimePreset
1744 ),
1745 Field::Engine => {
1746 matches!(source, Source::RuntimePreset | Source::CapabilityResolution)
1747 }
1748 Field::ExecutionPlan | Field::NumericalExecution => matches!(source, Source::Planner),
1749 Field::Sampling | Field::StructuredOutput => matches!(
1750 source,
1751 Source::UserInput
1752 | Source::CommandLine
1753 | Source::ConfigFile
1754 | Source::ProductDefault
1755 ),
1756 Field::Stop => matches!(
1757 source,
1758 Source::UserInput
1759 | Source::CommandLine
1760 | Source::ConfigFile
1761 | Source::ModelMetadata
1762 | Source::ProductDefault
1763 ),
1764 }
1765 }
1766}
1767
1768#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
1769#[serde(try_from = "String", into = "String")]
1770pub struct ResolutionReasonId(String);
1771
1772impl ResolutionReasonId {
1773 pub fn new(value: impl Into<String>) -> Result<Self, VNextError> {
1774 let value = value.into();
1775 validate_portable_identifier("resolution_reason_id", &value, 160)?;
1776 Ok(Self(value))
1777 }
1778
1779 pub fn as_str(&self) -> &str {
1780 &self.0
1781 }
1782}
1783
1784impl TryFrom<String> for ResolutionReasonId {
1785 type Error = VNextError;
1786
1787 fn try_from(value: String) -> Result<Self, Self::Error> {
1788 Self::new(value)
1789 }
1790}
1791
1792impl From<ResolutionReasonId> for String {
1793 fn from(value: ResolutionReasonId) -> Self {
1794 value.0
1795 }
1796}
1797
1798impl fmt::Display for ResolutionReasonId {
1799 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1800 formatter.write_str(&self.0)
1801 }
1802}
1803
1804#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
1805#[serde(try_from = "String", into = "String")]
1806pub struct ResolutionFingerprint(String);
1807
1808impl ResolutionFingerprint {
1809 pub fn new(value: impl Into<String>) -> Result<Self, VNextError> {
1810 let value = value.into();
1811 if !is_canonical_sha256(&value) {
1812 return Err(invalid_plan(
1813 "resolution_fingerprint",
1814 "must be a canonical lowercase SHA-256",
1815 ));
1816 }
1817 Ok(Self(value))
1818 }
1819
1820 pub fn as_str(&self) -> &str {
1821 &self.0
1822 }
1823}
1824
1825impl TryFrom<String> for ResolutionFingerprint {
1826 type Error = VNextError;
1827
1828 fn try_from(value: String) -> Result<Self, Self::Error> {
1829 Self::new(value)
1830 }
1831}
1832
1833impl From<ResolutionFingerprint> for String {
1834 fn from(value: ResolutionFingerprint) -> Self {
1835 value.0
1836 }
1837}
1838
1839impl fmt::Display for ResolutionFingerprint {
1840 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1841 formatter.write_str(&self.0)
1842 }
1843}
1844
1845#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
1846#[serde(try_from = "String", into = "String")]
1847pub struct ResolutionArtifactId(String);
1848
1849impl ResolutionArtifactId {
1850 pub fn new(value: impl Into<String>) -> Result<Self, VNextError> {
1851 let value = value.into();
1852 validate_portable_identifier("resolution_artifact_id", &value, 160)?;
1853 Ok(Self(value))
1854 }
1855
1856 pub fn as_str(&self) -> &str {
1857 &self.0
1858 }
1859}
1860
1861impl TryFrom<String> for ResolutionArtifactId {
1862 type Error = VNextError;
1863
1864 fn try_from(value: String) -> Result<Self, Self::Error> {
1865 Self::new(value)
1866 }
1867}
1868
1869impl From<ResolutionArtifactId> for String {
1870 fn from(value: ResolutionArtifactId) -> Self {
1871 value.0
1872 }
1873}
1874
1875impl fmt::Display for ResolutionArtifactId {
1876 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1877 formatter.write_str(&self.0)
1878 }
1879}
1880
1881#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1885#[serde(rename_all = "snake_case", tag = "kind")]
1886pub enum ResolutionSourceProvenance {
1887 LockedModelFile {
1888 source_role: ModelArtifactSourceRole,
1889 relative_path: String,
1890 },
1891 Upstream {
1892 producer_id: String,
1893 producer_version: ContractVersion,
1894 producer_implementation_fingerprint: ResolutionFingerprint,
1895 revision: String,
1896 artifact_locator: String,
1897 },
1898}
1899
1900impl ResolutionSourceProvenance {
1901 fn validate(&self) -> Result<(), VNextError> {
1902 match self {
1903 Self::LockedModelFile {
1904 source_role: _,
1905 relative_path,
1906 } => {
1907 if !validate_source_path(relative_path) {
1908 return Err(invalid_plan(
1909 "resolution_source_provenance.locked_model_file",
1910 "relative path must identify one portable locked model file",
1911 ));
1912 }
1913 }
1914 Self::Upstream {
1915 producer_id,
1916 producer_version,
1917 revision,
1918 artifact_locator,
1919 ..
1920 } => {
1921 validate_portable_identifier(
1922 "resolution_source_provenance.producer_id",
1923 producer_id,
1924 160,
1925 )?;
1926 if producer_version.major == 0 {
1927 return Err(invalid_plan(
1928 "resolution_source_provenance.producer_version",
1929 "producer contract major version must be non-zero",
1930 ));
1931 }
1932 validate_portable_identifier(
1933 "resolution_source_provenance.revision",
1934 revision,
1935 256,
1936 )?;
1937 validate_portable_identifier(
1938 "resolution_source_provenance.artifact_locator",
1939 artifact_locator,
1940 512,
1941 )?;
1942 }
1943 }
1944 Ok(())
1945 }
1946
1947 pub fn locator(&self) -> &str {
1948 match self {
1949 Self::LockedModelFile {
1950 source_role: _,
1951 relative_path,
1952 } => relative_path,
1953 Self::Upstream {
1954 artifact_locator, ..
1955 } => artifact_locator,
1956 }
1957 }
1958}
1959
1960fn resolution_provenance_bytes(
1961 provenance: &ResolutionSourceProvenance,
1962) -> Result<usize, VNextError> {
1963 match provenance {
1964 ResolutionSourceProvenance::LockedModelFile {
1965 source_role,
1966 relative_path,
1967 } => source_role
1968 .as_str()
1969 .len()
1970 .checked_add(relative_path.len())
1971 .ok_or_else(|| {
1972 invalid_plan(
1973 "resolution_source_evidence.provenance",
1974 "provenance byte count overflowed",
1975 )
1976 }),
1977 ResolutionSourceProvenance::Upstream {
1978 producer_id,
1979 producer_implementation_fingerprint,
1980 revision,
1981 artifact_locator,
1982 ..
1983 } => [
1984 producer_id.len(),
1985 producer_implementation_fingerprint.as_str().len(),
1986 revision.len(),
1987 artifact_locator.len(),
1988 ]
1989 .into_iter()
1990 .try_fold(0usize, |total, length| {
1991 total.checked_add(length).ok_or_else(|| {
1992 invalid_plan(
1993 "resolution_source_evidence.provenance",
1994 "provenance byte count overflowed",
1995 )
1996 })
1997 }),
1998 }
1999}
2000
2001fn validate_resolution_field_paths(field_paths: &BTreeSet<String>) -> Result<(), VNextError> {
2002 if field_paths.is_empty() || field_paths.len() > MAX_RESOLUTION_FIELD_PATHS {
2003 return Err(invalid_plan(
2004 "resolution_source_evidence.field_paths",
2005 format!("must contain between 1 and {MAX_RESOLUTION_FIELD_PATHS} unique paths"),
2006 ));
2007 }
2008
2009 let mut total_bytes = 0usize;
2010 for path in field_paths {
2011 if !path.starts_with('/')
2012 || path.len() > MAX_RESOLUTION_FIELD_PATH_BYTES
2013 || path.trim() != path
2014 || path.bytes().any(|byte| byte.is_ascii_control())
2015 {
2016 return Err(invalid_plan(
2017 "resolution_source_evidence.field_paths",
2018 format!(
2019 "each path must be a portable JSON pointer of at most {MAX_RESOLUTION_FIELD_PATH_BYTES} bytes"
2020 ),
2021 ));
2022 }
2023 total_bytes = total_bytes.checked_add(path.len()).ok_or_else(|| {
2024 invalid_plan(
2025 "resolution_source_evidence.field_paths",
2026 "field-path byte count overflowed",
2027 )
2028 })?;
2029 if total_bytes > MAX_RESOLUTION_FIELD_PATH_TOTAL_BYTES {
2030 return Err(invalid_plan(
2031 "resolution_source_evidence.field_paths",
2032 format!("total path bytes must not exceed {MAX_RESOLUTION_FIELD_PATH_TOTAL_BYTES}"),
2033 ));
2034 }
2035 }
2036 Ok(())
2037}
2038
2039fn validate_resolution_source_availability(
2040 source_bytes: &[u8],
2041 provenance: &ResolutionSourceProvenance,
2042 field_paths: &BTreeSet<String>,
2043) -> Result<(), VNextError> {
2044 validate_resolution_source_bytes(source_bytes)?;
2045 let provenance_bytes = resolution_provenance_bytes(provenance)?;
2046 if provenance_bytes > MAX_RESOLUTION_PROVENANCE_BYTES {
2047 return Err(invalid_plan(
2048 "resolution_source_evidence.provenance",
2049 format!("must not exceed {MAX_RESOLUTION_PROVENANCE_BYTES} bytes"),
2050 ));
2051 }
2052 validate_resolution_field_paths(field_paths)?;
2053 provenance.validate()
2054}
2055
2056#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
2057pub struct ResolutionSourceArtifact {
2058 id: ResolutionArtifactId,
2059 source: ResolutionDecisionSource,
2060 provenance: ResolutionSourceProvenance,
2061 parser: ResolutionParserDescriptor,
2062 content_size_bytes: u64,
2063 content_fingerprint: ResolutionFingerprint,
2064 canonical_document_fingerprint: ResolutionFingerprint,
2065 fields: BTreeMap<String, ResolutionFingerprint>,
2066}
2067
2068#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
2069#[serde(deny_unknown_fields)]
2070pub struct UnvalidatedResolutionSourceArtifact {
2071 pub id: ResolutionArtifactId,
2072 pub source: ResolutionDecisionSource,
2073 pub provenance: ResolutionSourceProvenance,
2074 pub parser: ResolutionParserDescriptor,
2075 pub content_size_bytes: u64,
2076 pub content_fingerprint: ResolutionFingerprint,
2077 pub canonical_document_fingerprint: ResolutionFingerprint,
2078 pub fields: BTreeMap<String, ResolutionFingerprint>,
2079}
2080
2081#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
2082pub struct ResolutionParserDescriptor {
2083 id: String,
2084 version: ContractVersion,
2085 implementation_fingerprint: ResolutionFingerprint,
2086}
2087
2088#[derive(Deserialize)]
2089#[serde(deny_unknown_fields)]
2090struct ResolutionParserDescriptorWire {
2091 id: String,
2092 version: ContractVersion,
2093 implementation_fingerprint: ResolutionFingerprint,
2094}
2095
2096impl<'de> Deserialize<'de> for ResolutionParserDescriptor {
2097 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2098 where
2099 D: Deserializer<'de>,
2100 {
2101 let wire = ResolutionParserDescriptorWire::deserialize(deserializer)?;
2102 Self::new(wire.id, wire.version, wire.implementation_fingerprint)
2103 .map_err(serde::de::Error::custom)
2104 }
2105}
2106
2107impl ResolutionParserDescriptor {
2108 pub fn new(
2109 id: impl Into<String>,
2110 version: ContractVersion,
2111 implementation_fingerprint: ResolutionFingerprint,
2112 ) -> Result<Self, VNextError> {
2113 let descriptor = Self {
2114 id: id.into(),
2115 version,
2116 implementation_fingerprint,
2117 };
2118 descriptor.validate()?;
2119 Ok(descriptor)
2120 }
2121
2122 fn validate(&self) -> Result<(), VNextError> {
2123 validate_portable_identifier("resolution_parser.id", &self.id, 160)?;
2124 if self.version.major == 0 {
2125 return Err(invalid_plan(
2126 "resolution_parser.version",
2127 "parser contract major version must be non-zero",
2128 ));
2129 }
2130 Ok(())
2131 }
2132
2133 pub fn id(&self) -> &str {
2134 &self.id
2135 }
2136
2137 pub const fn version(&self) -> ContractVersion {
2138 self.version
2139 }
2140
2141 pub fn implementation_fingerprint(&self) -> &ResolutionFingerprint {
2142 &self.implementation_fingerprint
2143 }
2144}
2145
2146pub trait ResolutionSourceParser: Send + Sync {
2149 fn descriptor(&self) -> Result<ResolutionParserDescriptor, VNextError>;
2150
2151 fn parse(
2152 &self,
2153 source: ResolutionDecisionSource,
2154 provenance: &ResolutionSourceProvenance,
2155 source_bytes: &[u8],
2156 ) -> Result<serde_json::Value, VNextError>;
2157}
2158
2159pub struct JsonResolutionSourceParser;
2160
2161pub static JSON_RESOLUTION_SOURCE_PARSER: JsonResolutionSourceParser = JsonResolutionSourceParser;
2162
2163impl ResolutionSourceParser for JsonResolutionSourceParser {
2164 fn descriptor(&self) -> Result<ResolutionParserDescriptor, VNextError> {
2165 ResolutionParserDescriptor::new(
2166 "resolution-parser.core-json",
2167 ContractVersion::new(1, 0),
2168 ResolutionFingerprint::new(canonical_fingerprint(
2169 &"ferrum.resolution-parser.core-json.v1",
2170 "fingerprint core JSON resolution parser",
2171 )?)?,
2172 )
2173 }
2174
2175 fn parse(
2176 &self,
2177 _source: ResolutionDecisionSource,
2178 _provenance: &ResolutionSourceProvenance,
2179 source_bytes: &[u8],
2180 ) -> Result<serde_json::Value, VNextError> {
2181 validate_resolution_source_bytes(source_bytes)?;
2182 preflight_resolution_json(source_bytes, ResolutionJsonBudget::SOURCE)?;
2183 serde_json::from_slice(source_bytes).map_err(|error| VNextError::Serialization {
2184 context: "parse resolution source JSON",
2185 message: error.to_string(),
2186 })
2187 }
2188}
2189
2190#[derive(Clone)]
2193pub struct ResolutionSourceEvidence<'a> {
2194 id: ResolutionArtifactId,
2195 source: ResolutionDecisionSource,
2196 provenance: ResolutionSourceProvenance,
2197 source_bytes: Vec<u8>,
2198 field_paths: BTreeSet<String>,
2199 parser: &'a dyn ResolutionSourceParser,
2200}
2201
2202impl<'a> ResolutionSourceEvidence<'a> {
2203 pub fn new(
2204 id: ResolutionArtifactId,
2205 source: ResolutionDecisionSource,
2206 provenance: ResolutionSourceProvenance,
2207 source_bytes: Vec<u8>,
2208 field_paths: BTreeSet<String>,
2209 parser: &'a dyn ResolutionSourceParser,
2210 ) -> Result<Self, VNextError> {
2211 validate_resolution_source_availability(&source_bytes, &provenance, &field_paths)?;
2212 Ok(Self {
2213 id,
2214 source,
2215 provenance,
2216 source_bytes,
2217 field_paths,
2218 parser,
2219 })
2220 }
2221
2222 pub fn validate(&self) -> Result<(), VNextError> {
2226 self.verify().map(drop)
2227 }
2228
2229 fn verify(&self) -> Result<ResolutionSourceArtifact, VNextError> {
2230 validate_resolution_source_availability(
2231 &self.source_bytes,
2232 &self.provenance,
2233 &self.field_paths,
2234 )?;
2235 let parser = self.parser.descriptor()?;
2236 parser.validate()?;
2237 let first_document =
2238 self.parser
2239 .parse(self.source, &self.provenance, &self.source_bytes)?;
2240 let (first_document, first_fingerprint) = validate_and_canonicalize_resolution_json(
2241 first_document,
2242 "fingerprint first parsed resolution source document",
2243 )?;
2244 let parser_after_first_parse = self.parser.descriptor()?;
2245 parser_after_first_parse.validate()?;
2246 let second_document =
2247 self.parser
2248 .parse(self.source, &self.provenance, &self.source_bytes)?;
2249 let (second_document, second_fingerprint) = validate_and_canonicalize_resolution_json(
2250 second_document,
2251 "fingerprint second parsed resolution source document",
2252 )?;
2253 let parser_after_second_parse = self.parser.descriptor()?;
2254 parser_after_second_parse.validate()?;
2255 if parser != parser_after_first_parse
2256 || parser != parser_after_second_parse
2257 || first_fingerprint != second_fingerprint
2258 || first_document != second_document
2259 {
2260 return Err(invalid_plan(
2261 "resolution_source_evidence.parser",
2262 "parser identity changed or repeated parsing of identical bytes was nondeterministic",
2263 ));
2264 }
2265 ResolutionSourceArtifact::from_verified_document(
2266 self.id.clone(),
2267 self.source,
2268 self.provenance.clone(),
2269 &self.source_bytes,
2270 parser,
2271 &first_document,
2272 first_fingerprint,
2273 self.field_paths.clone(),
2274 )
2275 }
2276
2277 pub fn id(&self) -> &ResolutionArtifactId {
2278 &self.id
2279 }
2280
2281 pub const fn source(&self) -> ResolutionDecisionSource {
2282 self.source
2283 }
2284
2285 pub fn provenance(&self) -> &ResolutionSourceProvenance {
2286 &self.provenance
2287 }
2288
2289 pub fn locator(&self) -> &str {
2290 self.provenance.locator()
2291 }
2292
2293 pub fn source_bytes(&self) -> &[u8] {
2294 &self.source_bytes
2295 }
2296
2297 pub fn field_paths(&self) -> &BTreeSet<String> {
2298 &self.field_paths
2299 }
2300}
2301
2302impl ResolutionSourceArtifact {
2303 fn from_verified_document(
2304 id: ResolutionArtifactId,
2305 source: ResolutionDecisionSource,
2306 provenance: ResolutionSourceProvenance,
2307 bytes: &[u8],
2308 parser: ResolutionParserDescriptor,
2309 document: &serde_json::Value,
2310 canonical_document_fingerprint: String,
2311 field_paths: BTreeSet<String>,
2312 ) -> Result<Self, VNextError> {
2313 validate_resolution_source_availability(bytes, &provenance, &field_paths)?;
2314 let content_size_bytes = u64::try_from(bytes.len()).map_err(|_| {
2315 invalid_plan(
2316 "resolution_source_artifact.content_size_bytes",
2317 "source byte length does not fit u64",
2318 )
2319 })?;
2320 let fields = field_paths
2321 .into_iter()
2322 .map(|path| {
2323 let value = document.pointer(&path).ok_or_else(|| {
2324 invalid_plan(
2325 "resolution_source_artifact.fields",
2326 format!("JSON pointer `{path}` is absent from the source document"),
2327 )
2328 })?;
2329 ResolutionFingerprint::new(canonical_value_fingerprint(
2330 value,
2331 "fingerprint resolution source field",
2332 )?)
2333 .map(|fingerprint| (path, fingerprint))
2334 })
2335 .collect::<Result<BTreeMap<_, _>, VNextError>>()?;
2336 Ok(Self {
2337 id,
2338 source,
2339 provenance,
2340 parser,
2341 content_size_bytes,
2342 content_fingerprint: ResolutionFingerprint::new(format!(
2343 "{:x}",
2344 Sha256::digest(bytes)
2345 ))?,
2346 canonical_document_fingerprint: ResolutionFingerprint::new(
2347 canonical_document_fingerprint,
2348 )?,
2349 fields,
2350 })
2351 }
2352
2353 pub fn id(&self) -> &ResolutionArtifactId {
2354 &self.id
2355 }
2356
2357 pub fn source(&self) -> ResolutionDecisionSource {
2358 self.source
2359 }
2360
2361 pub fn provenance(&self) -> &ResolutionSourceProvenance {
2362 &self.provenance
2363 }
2364
2365 pub fn locator(&self) -> &str {
2366 self.provenance.locator()
2367 }
2368
2369 pub fn content_size_bytes(&self) -> u64 {
2370 self.content_size_bytes
2371 }
2372
2373 pub fn content_fingerprint(&self) -> &ResolutionFingerprint {
2374 &self.content_fingerprint
2375 }
2376
2377 pub fn parser(&self) -> &ResolutionParserDescriptor {
2378 &self.parser
2379 }
2380
2381 pub fn canonical_document_fingerprint(&self) -> &ResolutionFingerprint {
2382 &self.canonical_document_fingerprint
2383 }
2384
2385 pub fn fields(&self) -> &BTreeMap<String, ResolutionFingerprint> {
2386 &self.fields
2387 }
2388}
2389
2390impl UnvalidatedResolutionSourceArtifact {
2391 fn revalidate(
2392 self,
2393 expected: &ResolutionSourceArtifact,
2394 ) -> Result<ResolutionSourceArtifact, VNextError> {
2395 if self.id != expected.id
2396 || self.source != expected.source
2397 || self.provenance != expected.provenance
2398 || self.parser != expected.parser
2399 || self.content_size_bytes != expected.content_size_bytes
2400 || self.content_fingerprint != expected.content_fingerprint
2401 || self.canonical_document_fingerprint != expected.canonical_document_fingerprint
2402 || self.fields != expected.fields
2403 {
2404 return Err(invalid_plan(
2405 "source_artifacts",
2406 format!(
2407 "serialized source artifact `{}` differs from externally verified evidence",
2408 self.id
2409 ),
2410 ));
2411 }
2412 Ok(expected.clone())
2413 }
2414}
2415
2416#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
2417pub struct ResolutionDecisionEvidence {
2418 source_artifact_id: ResolutionArtifactId,
2419 source_field_path: String,
2420 chosen_value_fingerprint: ResolutionFingerprint,
2421}
2422
2423#[derive(Deserialize)]
2424#[serde(deny_unknown_fields)]
2425struct ResolutionDecisionEvidenceWire {
2426 source_artifact_id: ResolutionArtifactId,
2427 source_field_path: String,
2428 chosen_value_fingerprint: ResolutionFingerprint,
2429}
2430
2431impl<'de> Deserialize<'de> for ResolutionDecisionEvidence {
2432 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2433 where
2434 D: Deserializer<'de>,
2435 {
2436 let wire = ResolutionDecisionEvidenceWire::deserialize(deserializer)?;
2437 Self::new(
2438 wire.source_artifact_id,
2439 wire.source_field_path,
2440 wire.chosen_value_fingerprint,
2441 )
2442 .map_err(serde::de::Error::custom)
2443 }
2444}
2445
2446impl ResolutionDecisionEvidence {
2447 fn new(
2448 source_artifact_id: ResolutionArtifactId,
2449 source_field_path: impl Into<String>,
2450 chosen_value_fingerprint: ResolutionFingerprint,
2451 ) -> Result<Self, VNextError> {
2452 let source_field_path = source_field_path.into();
2453 if source_field_path.is_empty()
2454 || source_field_path.len() > 512
2455 || source_field_path.trim() != source_field_path
2456 || source_field_path
2457 .bytes()
2458 .any(|byte| byte.is_ascii_control())
2459 {
2460 return Err(invalid_plan(
2461 "decisions.evidence.source_field_path",
2462 "must be a non-empty portable path of at most 512 bytes",
2463 ));
2464 }
2465 Ok(Self {
2466 source_artifact_id,
2467 source_field_path,
2468 chosen_value_fingerprint,
2469 })
2470 }
2471
2472 pub fn source_artifact_id(&self) -> &ResolutionArtifactId {
2473 &self.source_artifact_id
2474 }
2475
2476 pub fn source_field_path(&self) -> &str {
2477 &self.source_field_path
2478 }
2479
2480 pub fn chosen_value_fingerprint(&self) -> &ResolutionFingerprint {
2481 &self.chosen_value_fingerprint
2482 }
2483}
2484
2485#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
2486pub struct ResolutionDecision {
2487 field: ResolutionField,
2488 source: ResolutionDecisionSource,
2489 reason_id: ResolutionReasonId,
2490 evidence: ResolutionDecisionEvidence,
2491}
2492
2493#[derive(Deserialize)]
2494#[serde(deny_unknown_fields)]
2495struct ResolutionDecisionWire {
2496 field: ResolutionField,
2497 source: ResolutionDecisionSource,
2498 reason_id: ResolutionReasonId,
2499 evidence: ResolutionDecisionEvidence,
2500}
2501
2502impl<'de> Deserialize<'de> for ResolutionDecision {
2503 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2504 where
2505 D: Deserializer<'de>,
2506 {
2507 let wire = ResolutionDecisionWire::deserialize(deserializer)?;
2508 Ok(Self::new(
2509 wire.field,
2510 wire.source,
2511 wire.reason_id,
2512 wire.evidence,
2513 ))
2514 }
2515}
2516
2517impl ResolutionDecision {
2518 fn new(
2519 field: ResolutionField,
2520 source: ResolutionDecisionSource,
2521 reason_id: ResolutionReasonId,
2522 evidence: ResolutionDecisionEvidence,
2523 ) -> Self {
2524 Self {
2525 field,
2526 source,
2527 reason_id,
2528 evidence,
2529 }
2530 }
2531
2532 pub fn field(&self) -> ResolutionField {
2533 self.field
2534 }
2535
2536 pub fn source(&self) -> ResolutionDecisionSource {
2537 self.source
2538 }
2539
2540 pub fn reason_id(&self) -> &ResolutionReasonId {
2541 &self.reason_id
2542 }
2543
2544 pub fn evidence(&self) -> &ResolutionDecisionEvidence {
2545 &self.evidence
2546 }
2547}
2548
2549#[derive(Debug, Clone, PartialEq, Eq)]
2553pub struct ResolutionDecisionBinding {
2554 field: ResolutionField,
2555 source: ResolutionDecisionSource,
2556 reason_id: ResolutionReasonId,
2557 source_artifact_id: ResolutionArtifactId,
2558 source_field_path: String,
2559}
2560
2561impl ResolutionDecisionBinding {
2562 pub fn new(
2563 field: ResolutionField,
2564 source: ResolutionDecisionSource,
2565 reason_id: ResolutionReasonId,
2566 source_artifact_id: ResolutionArtifactId,
2567 source_field_path: impl Into<String>,
2568 ) -> Result<Self, VNextError> {
2569 let source_field_path = source_field_path.into();
2570 if !field.accepts_source(source) {
2571 return Err(invalid_plan(
2572 "decision_bindings.source",
2573 format!("source `{source:?}` cannot author field `{field:?}`"),
2574 ));
2575 }
2576 if !source_field_path.starts_with('/')
2577 || source_field_path.len() > 512
2578 || source_field_path.trim() != source_field_path
2579 || source_field_path
2580 .bytes()
2581 .any(|byte| byte.is_ascii_control())
2582 {
2583 return Err(invalid_plan(
2584 "decision_bindings.source_field_path",
2585 "must be a portable JSON pointer of at most 512 bytes",
2586 ));
2587 }
2588 Ok(Self {
2589 field,
2590 source,
2591 reason_id,
2592 source_artifact_id,
2593 source_field_path,
2594 })
2595 }
2596
2597 pub fn field(&self) -> ResolutionField {
2598 self.field
2599 }
2600
2601 pub fn source(&self) -> ResolutionDecisionSource {
2602 self.source
2603 }
2604
2605 pub fn reason_id(&self) -> &ResolutionReasonId {
2606 &self.reason_id
2607 }
2608
2609 pub fn source_artifact_id(&self) -> &ResolutionArtifactId {
2610 &self.source_artifact_id
2611 }
2612
2613 pub fn source_field_path(&self) -> &str {
2614 &self.source_field_path
2615 }
2616}
2617
2618#[derive(Debug, Clone, PartialEq, Eq)]
2619pub struct ResolvedModelPlanInputs {
2620 pub original_sources: OriginalModelSources,
2621 pub resolved_sources: ResolvedModelSources,
2622 pub config: ModelConfigFingerprint,
2623 pub external_metadata_id: super::ExternalModelMetadataId,
2624 pub prepared_family: PreparedModelFamily,
2625 pub numerical_execution: NumericalProfileResolution,
2626 pub tokenizer: TokenizerDescriptor,
2627 pub device: DeviceDescriptor,
2628 pub capabilities: CapabilityCatalog,
2629 pub runtime: ResolvedRuntimePolicy,
2630 pub engine: EngineSelection,
2631 pub execution_plan: ExecutionPlan,
2632 pub sampling: SamplingPolicy,
2633 pub stop: StopPolicy,
2634 pub structured_output: StructuredOutputPolicy,
2635}
2636
2637#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
2638pub struct ResolvedModelPlanParts {
2639 source_artifacts: Vec<ResolutionSourceArtifact>,
2640 pub original_sources: OriginalModelSources,
2641 pub resolved_sources: ResolvedModelSources,
2642 pub config: ModelConfigFingerprint,
2643 pub external_metadata_id: super::ExternalModelMetadataId,
2644 pub prepared_family: PreparedModelFamily,
2645 pub numerical_execution: NumericalProfileResolution,
2646 pub tokenizer: TokenizerDescriptor,
2647 pub device: DeviceDescriptor,
2648 pub capabilities: CapabilityCatalog,
2649 pub runtime: ResolvedRuntimePolicy,
2650 pub engine: EngineSelection,
2651 pub execution_plan: ExecutionPlan,
2652 pub sampling: SamplingPolicy,
2653 pub stop: StopPolicy,
2654 pub structured_output: StructuredOutputPolicy,
2655 decisions: Vec<ResolutionDecision>,
2656}
2657
2658impl ResolvedModelPlanParts {
2659 pub fn source_artifacts(&self) -> &[ResolutionSourceArtifact] {
2660 &self.source_artifacts
2661 }
2662
2663 pub fn decisions(&self) -> &[ResolutionDecision] {
2664 &self.decisions
2665 }
2666}
2667
2668pub struct ResolvedPlanValidationContext<'a> {
2673 registry: &'a dyn ModelFamilyRegistry,
2674 source_evidence: &'a [ResolutionSourceEvidence<'a>],
2675 node_resolutions: &'a [PlanNodeResolution],
2676 device: &'a DeviceDescriptor,
2677 capabilities: &'a CapabilityCatalog,
2678 runtime: &'a ResolvedRuntimePolicy,
2679 numerical_execution: NumericalProfileResolution,
2680 completion_retention: CompletionRetentionSpec,
2681}
2682
2683impl<'a> ResolvedPlanValidationContext<'a> {
2684 pub fn new(
2685 registry: &'a dyn ModelFamilyRegistry,
2686 source_evidence: &'a [ResolutionSourceEvidence<'a>],
2687 node_resolutions: &'a [PlanNodeResolution],
2688 device: &'a DeviceDescriptor,
2689 capabilities: &'a CapabilityCatalog,
2690 runtime: &'a ResolvedRuntimePolicy,
2691 numerical_execution: &NumericalProfileResolution,
2692 ) -> Self {
2693 Self {
2694 registry,
2695 source_evidence,
2696 node_resolutions,
2697 device,
2698 capabilities,
2699 runtime,
2700 numerical_execution: numerical_execution.clone(),
2701 completion_retention: CompletionRetentionSpec::default(),
2702 }
2703 }
2704
2705 pub fn with_completion_retention(
2709 mut self,
2710 completion_retention: CompletionRetentionSpec,
2711 ) -> Self {
2712 self.completion_retention = completion_retention;
2713 self
2714 }
2715
2716 pub fn registry(&self) -> &dyn ModelFamilyRegistry {
2717 self.registry
2718 }
2719
2720 fn verify_source_artifacts(&self) -> Result<Vec<ResolutionSourceArtifact>, VNextError> {
2721 self.source_evidence
2722 .iter()
2723 .map(ResolutionSourceEvidence::verify)
2724 .collect()
2725 }
2726
2727 pub fn node_resolutions(&self) -> &[PlanNodeResolution] {
2728 self.node_resolutions
2729 }
2730
2731 pub fn device(&self) -> &DeviceDescriptor {
2732 self.device
2733 }
2734
2735 pub fn capabilities(&self) -> &CapabilityCatalog {
2736 self.capabilities
2737 }
2738
2739 pub fn runtime(&self) -> &ResolvedRuntimePolicy {
2740 self.runtime
2741 }
2742
2743 pub fn numerical_execution(&self) -> &NumericalProfileResolution {
2744 &self.numerical_execution
2745 }
2746
2747 pub fn completion_retention(&self) -> &CompletionRetentionSpec {
2748 &self.completion_retention
2749 }
2750}
2751
2752#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
2754pub struct ResolvedModelPlan {
2755 wire_version: u32,
2756 parts: ResolvedModelPlanParts,
2757 fingerprint: ResolutionFingerprint,
2758}
2759
2760impl ExecutablePlanView for ResolvedModelPlan {
2761 fn execution_plan(&self) -> &ExecutionPlan {
2762 &self.parts.execution_plan
2763 }
2764
2765 fn device(&self) -> &DeviceDescriptor {
2766 &self.parts.device
2767 }
2768
2769 fn capabilities(&self) -> &CapabilityCatalog {
2770 &self.parts.capabilities
2771 }
2772}
2773
2774#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
2775#[serde(deny_unknown_fields)]
2776pub struct UnvalidatedResolvedModelPlanParts {
2777 source_artifacts: Vec<UnvalidatedResolutionSourceArtifact>,
2778 original_sources: OriginalModelSources,
2779 resolved_sources: ResolvedModelSources,
2780 config: ModelConfigFingerprint,
2781 external_metadata_id: super::ExternalModelMetadataId,
2782 prepared_family: PreparedModelFamilyWire,
2783 numerical_execution: NumericalProfileResolutionWire,
2784 tokenizer: TokenizerDescriptor,
2785 device: DeviceDescriptor,
2786 capabilities: CapabilityCatalog,
2787 runtime: ResolvedRuntimePolicy,
2788 engine: EngineSelection,
2789 execution_plan: UnvalidatedExecutionPlanWire,
2790 sampling: SamplingPolicy,
2791 stop: StopPolicy,
2792 structured_output: StructuredOutputPolicy,
2793 decisions: Vec<ResolutionDecision>,
2794}
2795
2796#[derive(Debug, Clone, PartialEq, Eq)]
2797pub struct UnvalidatedResolvedModelPlan {
2798 parts: UnvalidatedResolvedModelPlanParts,
2799 fingerprint: ResolutionFingerprint,
2800}
2801
2802#[derive(Serialize)]
2803struct ResolvedModelPlanWire {
2804 wire_version: u32,
2805 parts: UnvalidatedResolvedModelPlanParts,
2806 fingerprint: ResolutionFingerprint,
2807}
2808
2809#[derive(Deserialize, Serialize)]
2810#[serde(deny_unknown_fields)]
2811struct ResolvedModelPlanWireFields {
2812 wire_version: u32,
2813 parts: UnvalidatedResolvedModelPlanParts,
2814 fingerprint: ResolutionFingerprint,
2815}
2816
2817impl<'de> Deserialize<'de> for ResolvedModelPlanWire {
2818 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2819 where
2820 D: Deserializer<'de>,
2821 {
2822 let raw = serde_json::Value::deserialize(deserializer)?;
2823 if raw.get("wire_version").and_then(serde_json::Value::as_u64) != Some(2) {
2824 return Err(serde::de::Error::custom(
2825 "resolved model plan requires wire version 2 with explicit numerical resolution; resolve the model again",
2826 ));
2827 }
2828 let fields =
2829 ResolvedModelPlanWireFields::deserialize(&raw).map_err(serde::de::Error::custom)?;
2830 let canonical = serde_json::to_value(&fields).map_err(serde::de::Error::custom)?;
2831 if canonical != raw {
2832 return Err(serde::de::Error::custom(
2833 "resolved model plan wire contains unknown or non-canonical nested fields",
2834 ));
2835 }
2836 Ok(Self {
2837 wire_version: fields.wire_version,
2838 parts: fields.parts,
2839 fingerprint: fields.fingerprint,
2840 })
2841 }
2842}
2843
2844impl From<ResolvedModelPlanWire> for UnvalidatedResolvedModelPlan {
2845 fn from(wire: ResolvedModelPlanWire) -> Self {
2846 Self {
2847 parts: wire.parts,
2848 fingerprint: wire.fingerprint,
2849 }
2850 }
2851}
2852
2853impl UnvalidatedResolvedModelPlan {
2854 pub fn revalidate(
2855 self,
2856 context: &ResolvedPlanValidationContext<'_>,
2857 ) -> Result<ResolvedModelPlan, VNextError> {
2858 let UnvalidatedResolvedModelPlanParts {
2859 source_artifacts,
2860 original_sources,
2861 resolved_sources,
2862 config,
2863 external_metadata_id,
2864 prepared_family,
2865 numerical_execution,
2866 tokenizer,
2867 device,
2868 capabilities,
2869 runtime,
2870 engine,
2871 execution_plan,
2872 sampling,
2873 stop,
2874 structured_output,
2875 decisions,
2876 } = self.parts;
2877 let verified_source_artifacts = context.verify_source_artifacts()?;
2878 if !context
2879 .numerical_execution()
2880 .matches_wire(&numerical_execution)
2881 {
2882 return Err(invalid_plan(
2883 "validation_context.numerical_execution",
2884 "serialized numerical request or selection differs from external trusted resolution",
2885 ));
2886 }
2887 let numerical_execution = context.numerical_execution().clone();
2888 let source_artifacts =
2889 Self::revalidate_source_artifacts(source_artifacts, &verified_source_artifacts)?;
2890 let prepared_family =
2891 UnvalidatedPreparedModelFamily::from(prepared_family).revalidate(context.registry())?;
2892 if device != *context.device()
2893 || capabilities != *context.capabilities()
2894 || runtime != *context.runtime()
2895 {
2896 return Err(invalid_plan(
2897 "validation_context",
2898 "serialized device, capability catalog, or runtime policy differs from external trusted inputs",
2899 ));
2900 }
2901 let execution_plan = UnvalidatedExecutionPlan::from(execution_plan)
2902 .revalidate_with_completion_retention(
2903 &prepared_family,
2904 context.capabilities(),
2905 context.runtime(),
2906 context.node_resolutions().to_vec(),
2907 context.completion_retention().clone(),
2908 )?;
2909 let serialized_decisions = decisions;
2910 let decision_bindings = serialized_decisions
2911 .iter()
2912 .map(|decision| {
2913 ResolutionDecisionBinding::new(
2914 decision.field,
2915 decision.source,
2916 decision.reason_id.clone(),
2917 decision.evidence.source_artifact_id.clone(),
2918 decision.evidence.source_field_path.clone(),
2919 )
2920 })
2921 .collect::<Result<Vec<_>, VNextError>>()?;
2922 let rebuilt = ResolvedModelPlan::from_verified_inputs(
2923 ResolvedModelPlanInputs {
2924 original_sources,
2925 resolved_sources,
2926 config,
2927 external_metadata_id,
2928 prepared_family,
2929 numerical_execution,
2930 tokenizer,
2931 device: context.device().clone(),
2932 capabilities: context.capabilities().clone(),
2933 runtime: context.runtime().clone(),
2934 engine,
2935 execution_plan,
2936 sampling,
2937 stop,
2938 structured_output,
2939 },
2940 decision_bindings,
2941 source_artifacts,
2942 context,
2943 )?;
2944 if rebuilt.parts.decisions != serialized_decisions {
2945 return Err(invalid_plan(
2946 "decisions",
2947 "serialized decisions differ from decisions rebuilt from external raw evidence",
2948 ));
2949 }
2950 if rebuilt.fingerprint != self.fingerprint {
2951 return Err(invalid_plan(
2952 "fingerprint",
2953 format!(
2954 "does not match typed reconstruction: expected `{}`, actual `{}`",
2955 rebuilt.fingerprint, self.fingerprint
2956 ),
2957 ));
2958 }
2959 Ok(rebuilt)
2960 }
2961
2962 fn revalidate_source_artifacts(
2963 serialized: Vec<UnvalidatedResolutionSourceArtifact>,
2964 expected: &[ResolutionSourceArtifact],
2965 ) -> Result<Vec<ResolutionSourceArtifact>, VNextError> {
2966 let mut expected_by_id = BTreeMap::new();
2967 for artifact in expected {
2968 if expected_by_id
2969 .insert(artifact.id().clone(), artifact)
2970 .is_some()
2971 {
2972 return Err(invalid_plan(
2973 "validation_context.source_artifacts",
2974 format!("duplicate externally verified artifact `{}`", artifact.id()),
2975 ));
2976 }
2977 }
2978 if serialized.len() != expected_by_id.len() {
2979 return Err(invalid_plan(
2980 "source_artifacts",
2981 "serialized and externally verified source artifact sets differ",
2982 ));
2983 }
2984
2985 let mut seen = BTreeSet::new();
2986 let mut validated = Vec::with_capacity(serialized.len());
2987 for artifact in serialized {
2988 if !seen.insert(artifact.id.clone()) {
2989 return Err(invalid_plan(
2990 "source_artifacts",
2991 format!("duplicate serialized artifact `{}`", artifact.id),
2992 ));
2993 }
2994 let expected = expected_by_id.get(&artifact.id).ok_or_else(|| {
2995 invalid_plan(
2996 "source_artifacts",
2997 format!(
2998 "serialized artifact `{}` has no externally verified evidence",
2999 artifact.id
3000 ),
3001 )
3002 })?;
3003 validated.push(artifact.revalidate(expected)?);
3004 }
3005 Ok(validated)
3006 }
3007}
3008
3009impl ResolvedModelPlan {
3010 pub fn new(
3011 inputs: ResolvedModelPlanInputs,
3012 decision_bindings: Vec<ResolutionDecisionBinding>,
3013 context: &ResolvedPlanValidationContext<'_>,
3014 ) -> Result<Self, VNextError> {
3015 let source_artifacts = context.verify_source_artifacts()?;
3018 Self::from_verified_inputs(inputs, decision_bindings, source_artifacts, context)
3019 }
3020
3021 fn from_verified_inputs(
3022 inputs: ResolvedModelPlanInputs,
3023 decision_bindings: Vec<ResolutionDecisionBinding>,
3024 source_artifacts: Vec<ResolutionSourceArtifact>,
3025 context: &ResolvedPlanValidationContext<'_>,
3026 ) -> Result<Self, VNextError> {
3027 Self::validate_external_inputs(&inputs, context)?;
3028 let ResolvedModelPlanInputs {
3029 original_sources,
3030 resolved_sources,
3031 config,
3032 external_metadata_id,
3033 prepared_family,
3034 numerical_execution,
3035 tokenizer,
3036 device,
3037 capabilities,
3038 runtime,
3039 engine,
3040 execution_plan,
3041 sampling,
3042 stop,
3043 structured_output,
3044 } = inputs;
3045 let mut parts = ResolvedModelPlanParts {
3046 source_artifacts,
3047 original_sources,
3048 resolved_sources,
3049 config,
3050 external_metadata_id,
3051 prepared_family,
3052 numerical_execution,
3053 tokenizer,
3054 device,
3055 capabilities,
3056 runtime,
3057 engine,
3058 execution_plan,
3059 sampling,
3060 stop,
3061 structured_output,
3062 decisions: Vec::new(),
3063 };
3064 Self::normalize(&mut parts);
3065 parts.decisions = Self::bind_decisions(&parts, decision_bindings)?;
3066 Self::normalize(&mut parts);
3067 Self::validate(
3068 &parts,
3069 context.node_resolutions(),
3070 context.completion_retention(),
3071 )?;
3072 let fingerprint = ResolutionFingerprint::new(canonical_fingerprint(
3073 &parts,
3074 "serialize resolved model plan",
3075 )?)?;
3076 Ok(Self {
3077 wire_version: 2,
3078 parts,
3079 fingerprint,
3080 })
3081 }
3082
3083 fn validate_external_inputs(
3084 inputs: &ResolvedModelPlanInputs,
3085 context: &ResolvedPlanValidationContext<'_>,
3086 ) -> Result<(), VNextError> {
3087 let family_registration = context
3088 .registry()
3089 .resolve(inputs.prepared_family.family_id())?;
3090 let metadata_registration = context
3091 .registry()
3092 .resolve_external(&inputs.external_metadata_id)?;
3093 if !family_registration
3094 .external_metadata_ids()
3095 .contains(&inputs.external_metadata_id)
3096 || !std::ptr::eq(family_registration, metadata_registration)
3097 || inputs.prepared_family.external_metadata_id() != &inputs.external_metadata_id
3098 {
3099 return Err(invalid_plan(
3100 "external_metadata_id",
3101 format!(
3102 "external metadata `{}`, prepared identity `{}`, and internal family `{}` must identify the same registration and exact typed configuration",
3103 inputs.external_metadata_id,
3104 inputs.prepared_family.external_metadata_id(),
3105 inputs.prepared_family.family_id(),
3106 ),
3107 ));
3108 }
3109 let definition = family_registration.define(inputs.prepared_family.canonical_config())?;
3110 let externally_prepared = family_registration
3111 .prepare(&definition, &inputs.prepared_family.numerical_profile().id)?;
3112 if externally_prepared != inputs.prepared_family {
3113 return Err(invalid_plan(
3114 "validation_context.model_registry",
3115 "prepared model family differs from external typed registry reconstruction",
3116 ));
3117 }
3118 if inputs.device != *context.device()
3119 || inputs.capabilities != *context.capabilities()
3120 || inputs.runtime != *context.runtime()
3121 {
3122 return Err(invalid_plan(
3123 "validation_context",
3124 "device, capability catalog, or runtime policy differs from external trusted inputs",
3125 ));
3126 }
3127 if &inputs.numerical_execution != context.numerical_execution() {
3128 return Err(invalid_plan(
3129 "validation_context.numerical_execution",
3130 "numerical request or selection differs from external trusted resolution",
3131 ));
3132 }
3133 inputs.numerical_execution.validate_for(
3134 &definition,
3135 &inputs.prepared_family,
3136 context.capabilities(),
3137 context.runtime(),
3138 &inputs.execution_plan,
3139 )?;
3140 Ok(())
3141 }
3142
3143 fn bind_decisions(
3144 parts: &ResolvedModelPlanParts,
3145 bindings: Vec<ResolutionDecisionBinding>,
3146 ) -> Result<Vec<ResolutionDecision>, VNextError> {
3147 let expected_fingerprints = Self::decision_fingerprints(parts)?;
3148 let mut artifacts = BTreeMap::new();
3149 for artifact in &parts.source_artifacts {
3150 if artifacts.insert(artifact.id.clone(), artifact).is_some() {
3151 return Err(invalid_plan(
3152 "source_artifacts",
3153 format!("duplicate source artifact `{}`", artifact.id),
3154 ));
3155 }
3156 }
3157 let mut fields = BTreeSet::new();
3158 let mut used_artifacts = BTreeSet::new();
3159 let mut used_artifact_fields = BTreeSet::new();
3160 let mut decisions = Vec::with_capacity(bindings.len());
3161 for binding in bindings {
3162 if !fields.insert(binding.field) {
3163 return Err(invalid_plan(
3164 "decision_bindings",
3165 format!("field `{:?}` has more than one binding", binding.field),
3166 ));
3167 }
3168 if !binding.field.accepts_source(binding.source) {
3169 return Err(invalid_plan(
3170 "decision_bindings.source",
3171 format!(
3172 "source `{:?}` cannot author field `{:?}`",
3173 binding.source, binding.field
3174 ),
3175 ));
3176 }
3177 let expected = expected_fingerprints.get(&binding.field).ok_or_else(|| {
3178 invalid_plan(
3179 "decision_bindings.field",
3180 format!("field `{:?}` is not resolvable", binding.field),
3181 )
3182 })?;
3183 let artifact = artifacts.get(&binding.source_artifact_id).ok_or_else(|| {
3184 invalid_plan(
3185 "decision_bindings.source_artifact_id",
3186 format!(
3187 "binding for `{:?}` references unknown source artifact `{}`",
3188 binding.field, binding.source_artifact_id
3189 ),
3190 )
3191 })?;
3192 if artifact.source != binding.source {
3193 return Err(invalid_plan(
3194 "decision_bindings.source",
3195 format!(
3196 "binding for `{:?}` source differs from artifact `{}`",
3197 binding.field, artifact.id
3198 ),
3199 ));
3200 }
3201 let source_fingerprint =
3202 artifact
3203 .fields
3204 .get(&binding.source_field_path)
3205 .ok_or_else(|| {
3206 invalid_plan(
3207 "decision_bindings.source_field_path",
3208 format!(
3209 "binding for `{:?}` references a field absent from artifact `{}`",
3210 binding.field, artifact.id
3211 ),
3212 )
3213 })?;
3214 if source_fingerprint.as_str() != expected {
3215 return Err(invalid_plan(
3216 "decision_bindings.source_field_path",
3217 format!(
3218 "externally parsed field for `{:?}` differs from the resolved value",
3219 binding.field
3220 ),
3221 ));
3222 }
3223 used_artifacts.insert(artifact.id.clone());
3224 if !used_artifact_fields
3225 .insert((artifact.id.clone(), binding.source_field_path.clone()))
3226 {
3227 return Err(invalid_plan(
3228 "decision_bindings.source_field_path",
3229 "one parsed artifact field cannot issue more than one decision",
3230 ));
3231 }
3232 decisions.push(ResolutionDecision::new(
3233 binding.field,
3234 binding.source,
3235 binding.reason_id,
3236 ResolutionDecisionEvidence::new(
3237 binding.source_artifact_id,
3238 binding.source_field_path,
3239 source_fingerprint.clone(),
3240 )?,
3241 ));
3242 }
3243 if fields
3244 != expected_fingerprints
3245 .keys()
3246 .copied()
3247 .collect::<BTreeSet<_>>()
3248 {
3249 return Err(invalid_plan(
3250 "decision_bindings",
3251 "every resolved field requires exactly one external evidence binding",
3252 ));
3253 }
3254 if used_artifacts != artifacts.keys().cloned().collect::<BTreeSet<_>>() {
3255 return Err(invalid_plan(
3256 "source_artifacts",
3257 "externally verified source evidence and referenced binding sets differ",
3258 ));
3259 }
3260 let available_artifact_fields = artifacts
3261 .values()
3262 .flat_map(|artifact| {
3263 artifact
3264 .fields
3265 .keys()
3266 .cloned()
3267 .map(|path| (artifact.id.clone(), path))
3268 })
3269 .collect::<BTreeSet<_>>();
3270 if used_artifact_fields != available_artifact_fields {
3271 return Err(invalid_plan(
3272 "source_artifacts.fields",
3273 "externally parsed source fields and decision binding fields differ",
3274 ));
3275 }
3276 Ok(decisions)
3277 }
3278
3279 fn normalize(parts: &mut ResolvedModelPlanParts) {
3280 parts
3281 .source_artifacts
3282 .sort_by(|left, right| left.id.cmp(&right.id));
3283 for role in ModelArtifactSourceRole::ALL {
3284 parts
3285 .resolved_sources
3286 .for_role_mut(role)
3287 .files
3288 .sort_by(|left, right| left.relative_path.cmp(&right.relative_path));
3289 }
3290 parts.stop.strings.sort();
3291 parts.decisions.sort_by_key(|decision| decision.field);
3292 }
3293
3294 fn validate(
3295 parts: &ResolvedModelPlanParts,
3296 node_resolutions: &[PlanNodeResolution],
3297 completion_retention: &CompletionRetentionSpec,
3298 ) -> Result<(), VNextError> {
3299 let mut source_artifacts = BTreeMap::new();
3300 for artifact in &parts.source_artifacts {
3301 if source_artifacts
3302 .insert(artifact.id.clone(), artifact)
3303 .is_some()
3304 {
3305 return Err(invalid_plan(
3306 "source_artifacts",
3307 format!("duplicate source artifact `{}`", artifact.id),
3308 ));
3309 }
3310 artifact.provenance.validate()?;
3311 match &artifact.provenance {
3312 ResolutionSourceProvenance::LockedModelFile {
3313 source_role,
3314 relative_path,
3315 } => {
3316 let Some(file) = parts
3317 .resolved_sources
3318 .for_role(*source_role)
3319 .files
3320 .iter()
3321 .find(|file| &file.relative_path == relative_path)
3322 else {
3323 return Err(invalid_plan(
3324 "source_artifacts.provenance",
3325 format!(
3326 "artifact `{}` does not resolve to {source_role:?} locked file `{relative_path}`",
3327 artifact.id,
3328 ),
3329 ));
3330 };
3331 if file.sha256 != artifact.content_fingerprint.as_str()
3332 || file.size_bytes != artifact.content_size_bytes
3333 {
3334 return Err(invalid_plan(
3335 "source_artifacts.provenance",
3336 format!(
3337 "artifact `{}` bytes differ from locked file `{relative_path}` SHA or size",
3338 artifact.id
3339 ),
3340 ));
3341 }
3342 }
3343 ResolutionSourceProvenance::Upstream { .. }
3344 if artifact.source == ResolutionDecisionSource::ModelMetadata =>
3345 {
3346 return Err(invalid_plan(
3347 "source_artifacts.provenance",
3348 format!(
3349 "model-metadata artifact `{}` must bind a locked model file",
3350 artifact.id
3351 ),
3352 ));
3353 }
3354 ResolutionSourceProvenance::Upstream { .. } => {}
3355 }
3356 }
3357 for role in ModelArtifactSourceRole::ALL {
3358 let original = parts.original_sources.for_role(role);
3359 let resolved = parts.resolved_sources.for_role(role);
3360 if original.location.trim().is_empty()
3361 || matches!(
3362 original.requested_revision.as_deref(),
3363 Some(revision) if revision.trim().is_empty()
3364 )
3365 || resolved.canonical_location.trim().is_empty()
3366 || resolved.resolved_revision.trim().is_empty()
3367 {
3368 return Err(invalid_plan(
3369 format!("{}_source", role.as_str()),
3370 "source locations and revisions must be non-empty",
3371 ));
3372 }
3373 if resolved.files.is_empty() {
3374 return Err(invalid_plan(
3375 format!("resolved_sources.{}.files", role.as_str()),
3376 "at least one fingerprinted file is required",
3377 ));
3378 }
3379 let mut paths = BTreeSet::new();
3380 if resolved.files.iter().any(|file| {
3381 !validate_source_path(&file.relative_path)
3382 || file.size_bytes == 0
3383 || !is_canonical_sha256(&file.sha256)
3384 || !paths.insert(file.relative_path.clone())
3385 }) {
3386 return Err(invalid_plan(
3387 format!("resolved_sources.{}.files", role.as_str()),
3388 "file paths, sizes, and canonical hashes must be valid and unique",
3389 ));
3390 }
3391 }
3392 let family = &parts.prepared_family;
3393 let program_fingerprint = family.program().fingerprint()?;
3394 let family_fingerprint = family.fingerprint()?;
3395 if parts.tokenizer.vocabulary_size == 0
3396 || family.metadata().template.template.trim().is_empty()
3397 || family.metadata().special_tokens.eos_token_ids.is_empty()
3398 {
3399 return Err(invalid_plan(
3400 "model_metadata",
3401 "program, tokenizer, template, and special-token metadata are incomplete",
3402 ));
3403 }
3404 Self::validate_source_file_binding(
3405 ModelArtifactSourceRole::Semantic,
3406 &parts.resolved_sources.semantic,
3407 &parts.config.source_file,
3408 &parts.config.sha256,
3409 "config",
3410 )?;
3411 if parts.config.typed_config_sha256 != family.config_fingerprint() {
3412 return Err(invalid_plan(
3413 "config.typed_config_sha256",
3414 "does not match the typed model family configuration",
3415 ));
3416 }
3417 Self::validate_source_file_binding(
3418 ModelArtifactSourceRole::Tokenizer,
3419 &parts.resolved_sources.tokenizer,
3420 &parts.tokenizer.source_file,
3421 &parts.tokenizer.sha256,
3422 "tokenizer",
3423 )?;
3424 Self::validate_source_file_binding(
3425 ModelArtifactSourceRole::Tokenizer,
3426 &parts.resolved_sources.tokenizer,
3427 &family.metadata().template.source_file,
3428 &family.metadata().template.sha256,
3429 "template",
3430 )?;
3431 Self::validate_token_contract(parts)?;
3432 parts.runtime.validate()?;
3433 parts
3434 .execution_plan
3435 .validate_against_with_completion_retention(
3436 family,
3437 &parts.capabilities,
3438 &parts.runtime,
3439 node_resolutions,
3440 completion_retention.clone(),
3441 )?;
3442 let runtime_fingerprint = super::canonical_runtime_policy_fingerprint(&parts.runtime)?;
3443 let capability_fingerprint = parts.capabilities.fingerprint()?;
3444 if &parts.device != parts.capabilities.device()
3445 || &parts.device.id != parts.execution_plan.payload().device_id()
3446 || family.family_id() != parts.execution_plan.payload().family_id()
3447 || family_fingerprint != parts.execution_plan.payload().prepared_family_fingerprint()
3448 || runtime_fingerprint != parts.execution_plan.payload().policy_fingerprint()
3449 || program_fingerprint != parts.execution_plan.payload().program_fingerprint()
3450 || &parts
3451 .execution_plan
3452 .payload()
3453 .execution_weights()
3454 .schema()
3455 .format_id
3456 != parts.execution_plan.payload().weight_format()
3457 || parts
3458 .execution_plan
3459 .payload()
3460 .execution_weights()
3461 .schema()
3462 .quantization_formats()
3463 != *parts.execution_plan.payload().quantization_formats()
3464 || capability_fingerprint
3465 != parts
3466 .execution_plan
3467 .payload()
3468 .capability_catalog_fingerprint()
3469 {
3470 return Err(invalid_plan(
3471 "resolved_contract_links",
3472 "family, full device descriptor, policy, capability, engine, and plan links disagree",
3473 ));
3474 }
3475 let engine = parts
3476 .capabilities
3477 .engine_provider(&parts.engine.provider_id, parts.engine.contract_version)?;
3478 if !is_canonical_sha256(&parts.engine.implementation_fingerprint)
3479 || engine.contract_version() != parts.engine.contract_version
3480 || engine.implementation_fingerprint() != parts.engine.implementation_fingerprint
3481 || engine.device_id() != &parts.device.id
3482 {
3483 return Err(invalid_plan(
3484 "engine",
3485 "engine provider version and implementation are not exactly bound to the resolved device",
3486 ));
3487 }
3488 for node in parts.execution_plan.payload().nodes() {
3489 let providers = parts
3490 .capabilities
3491 .providers_for_node(node.id(), node.operation_id())?;
3492 let selected = providers
3493 .iter()
3494 .find(|provider| provider.provider_id() == node.selection().selected_provider())
3495 .ok_or_else(|| VNextError::UnsupportedOperation {
3496 node_id: Some(node.id().to_string()),
3497 operation_id: node.operation_id().to_string(),
3498 device_id: parts.device.id.to_string(),
3499 reason: format!(
3500 "selected provider `{}` is absent from the resolved catalog",
3501 node.selection().selected_provider()
3502 ),
3503 })?;
3504 if !selected.version().satisfies(node.operation_version()) {
3505 return Err(VNextError::IncompatibleOperationVersion {
3506 node_id: Some(node.id().to_string()),
3507 operation_id: node.operation_id().to_string(),
3508 required_major: node.operation_version().major,
3509 required_minor: node.operation_version().minor,
3510 available_major: selected.version().major,
3511 available_minor: selected.version().minor,
3512 });
3513 }
3514 }
3519 parts.sampling.validate()?;
3520 if parts.stop.maximum_output_tokens == 0
3521 || parts.stop.strings.iter().any(|stop| stop.is_empty())
3522 || parts.stop.strings.windows(2).any(|pair| pair[0] == pair[1])
3523 || matches!(
3524 &parts.structured_output,
3525 StructuredOutputPolicy::JsonSchema { schema_sha256 }
3526 if !is_canonical_sha256(schema_sha256)
3527 )
3528 {
3529 return Err(invalid_plan(
3530 "generation_policy",
3531 "stop values must be non-empty and unique and structured-output hashes must be canonical",
3532 ));
3533 }
3534 let expected_fingerprints = Self::decision_fingerprints(parts)?;
3535 let mut actual_decisions = BTreeSet::new();
3536 let mut used_source_artifacts = BTreeSet::new();
3537 let mut used_source_fields = BTreeSet::new();
3538 for decision in &parts.decisions {
3539 if !actual_decisions.insert(decision.field) {
3540 return Err(invalid_plan(
3541 "decisions",
3542 format!("field `{:?}` has more than one decision", decision.field),
3543 ));
3544 }
3545 let expected = expected_fingerprints.get(&decision.field).ok_or_else(|| {
3546 invalid_plan(
3547 "decisions",
3548 format!("field `{:?}` is not resolvable", decision.field),
3549 )
3550 })?;
3551 if !decision.field.accepts_source(decision.source) {
3552 return Err(invalid_plan(
3553 "decisions.source",
3554 format!(
3555 "source `{:?}` is not allowed to author field `{:?}`",
3556 decision.source, decision.field
3557 ),
3558 ));
3559 }
3560 if decision.evidence.chosen_value_fingerprint.as_str() != expected {
3561 return Err(invalid_plan(
3562 format!("decisions.{:?}.chosen_value_fingerprint", decision.field),
3563 format!(
3564 "does not match the resolved value: expected `{expected}`, actual `{}`",
3565 decision.evidence.chosen_value_fingerprint
3566 ),
3567 ));
3568 }
3569 let artifact = source_artifacts
3570 .get(decision.evidence.source_artifact_id())
3571 .ok_or_else(|| {
3572 invalid_plan(
3573 "decisions.evidence.source_artifact_id",
3574 format!(
3575 "decision for `{:?}` references unknown source artifact `{}`",
3576 decision.field,
3577 decision.evidence.source_artifact_id()
3578 ),
3579 )
3580 })?;
3581 used_source_artifacts.insert(artifact.id.clone());
3582 if !used_source_fields.insert((
3583 artifact.id.clone(),
3584 decision.evidence.source_field_path.clone(),
3585 )) {
3586 return Err(invalid_plan(
3587 "decisions.evidence.source_field_path",
3588 "one parsed artifact field cannot issue more than one decision",
3589 ));
3590 }
3591 if artifact.source != decision.source {
3592 return Err(invalid_plan(
3593 "decisions.source",
3594 format!(
3595 "decision for `{:?}` source differs from artifact `{}`",
3596 decision.field, artifact.id
3597 ),
3598 ));
3599 }
3600 if let ResolutionSourceProvenance::LockedModelFile { source_role, .. } =
3601 &artifact.provenance
3602 {
3603 if !Self::locked_source_role_allowed(decision.field, *source_role) {
3604 return Err(invalid_plan(
3605 "decisions.source_role",
3606 format!(
3607 "decision for `{:?}` cannot use a {source_role:?} locked source",
3608 decision.field
3609 ),
3610 ));
3611 }
3612 }
3613 let source_field = artifact
3614 .fields
3615 .get(decision.evidence.source_field_path())
3616 .ok_or_else(|| {
3617 invalid_plan(
3618 "decisions.evidence.source_field_path",
3619 format!(
3620 "decision for `{:?}` references a field absent from artifact `{}`",
3621 decision.field, artifact.id
3622 ),
3623 )
3624 })?;
3625 if source_field != &decision.evidence.chosen_value_fingerprint {
3626 return Err(invalid_plan(
3627 "decisions.evidence.chosen_value_fingerprint",
3628 format!(
3629 "decision for `{:?}` differs from source artifact field `{}`",
3630 decision.field,
3631 decision.evidence.source_field_path()
3632 ),
3633 ));
3634 }
3635 }
3636 let required_decisions = expected_fingerprints
3637 .keys()
3638 .copied()
3639 .collect::<BTreeSet<_>>();
3640 if actual_decisions != required_decisions {
3641 return Err(invalid_plan(
3642 "decisions",
3643 "every resolved field requires exactly one typed, fingerprinted decision",
3644 ));
3645 }
3646 if used_source_artifacts != source_artifacts.keys().cloned().collect::<BTreeSet<_>>() {
3647 return Err(invalid_plan(
3648 "source_artifacts",
3649 "every source artifact must be referenced by at least one resolution decision",
3650 ));
3651 }
3652 let available_source_fields = source_artifacts
3653 .values()
3654 .flat_map(|artifact| {
3655 artifact
3656 .fields
3657 .keys()
3658 .cloned()
3659 .map(|path| (artifact.id.clone(), path))
3660 })
3661 .collect::<BTreeSet<_>>();
3662 if used_source_fields != available_source_fields {
3663 return Err(invalid_plan(
3664 "source_artifacts.fields",
3665 "parsed source fields and resolution decision fields differ",
3666 ));
3667 }
3668 Ok(())
3669 }
3670
3671 fn validate_source_file_binding(
3672 role: ModelArtifactSourceRole,
3673 source: &ResolvedModelSource,
3674 source_file: &str,
3675 sha256: &str,
3676 field: &str,
3677 ) -> Result<(), VNextError> {
3678 if !validate_source_path(source_file) || !is_canonical_sha256(sha256) {
3679 return Err(invalid_plan(
3680 format!("{field}.source_file"),
3681 "source path or hash is not canonical",
3682 ));
3683 }
3684 match source
3685 .files
3686 .iter()
3687 .find(|file| file.relative_path == source_file)
3688 {
3689 Some(file) if file.sha256 == sha256 => Ok(()),
3690 Some(file) => Err(invalid_plan(
3691 format!("{field}.sha256"),
3692 format!(
3693 "does not match resolved source row `{source_file}`: expected `{}`, actual `{sha256}`",
3694 file.sha256
3695 ),
3696 )),
3697 None => Err(invalid_plan(
3698 format!("{field}.source_file"),
3699 format!(
3700 "`{source_file}` is absent from resolved_sources.{}.files",
3701 role.as_str()
3702 ),
3703 )),
3704 }
3705 }
3706
3707 const fn locked_source_role_allowed(
3708 field: ResolutionField,
3709 role: ModelArtifactSourceRole,
3710 ) -> bool {
3711 match field {
3712 ResolutionField::Config
3713 | ResolutionField::ExternalMetadata
3714 | ResolutionField::Family => matches!(role, ModelArtifactSourceRole::Semantic),
3715 ResolutionField::Tokenizer | ResolutionField::Template => {
3716 matches!(role, ModelArtifactSourceRole::Tokenizer)
3717 }
3718 ResolutionField::SpecialTokens => matches!(
3719 role,
3720 ModelArtifactSourceRole::Semantic | ModelArtifactSourceRole::Tokenizer
3721 ),
3722 ResolutionField::WeightSchema => matches!(
3723 role,
3724 ModelArtifactSourceRole::Semantic | ModelArtifactSourceRole::Weights
3725 ),
3726 ResolutionField::WeightFormat => matches!(role, ModelArtifactSourceRole::Weights),
3727 ResolutionField::OriginalSources
3728 | ResolutionField::ResolvedSources
3729 | ResolutionField::Device
3730 | ResolutionField::Capabilities
3731 | ResolutionField::RuntimePreset
3732 | ResolutionField::RuntimeMemory
3733 | ResolutionField::Admission
3734 | ResolutionField::Engine
3735 | ResolutionField::ExecutionPlan
3736 | ResolutionField::Sampling
3737 | ResolutionField::Stop
3738 | ResolutionField::StructuredOutput
3739 | ResolutionField::NumericalExecution => true,
3740 }
3741 }
3742
3743 fn validate_token_contract(parts: &ResolvedModelPlanParts) -> Result<(), VNextError> {
3744 let vocabulary_size = parts.tokenizer.vocabulary_size;
3745 let special = &parts.prepared_family.metadata().special_tokens;
3746 if special.collision_policy.allowed().iter().any(|collision| {
3747 collision.first() == SpecialTokenRole::Stop
3748 || collision.second() == SpecialTokenRole::Stop
3749 }) {
3750 return Err(invalid_plan(
3751 "special_tokens.collision_policy",
3752 "model metadata cannot authorize product-owned stop-token collisions",
3753 ));
3754 }
3755 let mut roles = Vec::new();
3756 if let Some(token_id) = special.bos_token_id {
3757 roles.push((SpecialTokenRole::Bos, token_id));
3758 }
3759 roles.extend(
3760 special
3761 .eos_token_ids
3762 .iter()
3763 .copied()
3764 .map(|token_id| (SpecialTokenRole::Eos, token_id)),
3765 );
3766 if let Some(token_id) = special.pad_token_id {
3767 roles.push((SpecialTokenRole::Pad, token_id));
3768 }
3769 roles.extend(
3770 parts
3771 .stop
3772 .token_ids
3773 .iter()
3774 .copied()
3775 .map(|token_id| (SpecialTokenRole::Stop, token_id)),
3776 );
3777 if roles
3778 .iter()
3779 .any(|(_, token_id)| u64::from(*token_id) >= vocabulary_size)
3780 {
3781 return Err(invalid_plan(
3782 "special_tokens",
3783 "a model or stop token id exceeds the tokenizer vocabulary",
3784 ));
3785 }
3786 let mut observed_stop_collisions = BTreeSet::new();
3787 for (index, (role, token_id)) in roles.iter().enumerate() {
3788 for (other_role, other_token_id) in &roles[..index] {
3789 if token_id == other_token_id && role != other_role {
3790 let (policy_field, allowed) = if *role == SpecialTokenRole::Stop
3791 || *other_role == SpecialTokenRole::Stop
3792 {
3793 let model_role = if *role == SpecialTokenRole::Stop {
3794 *other_role
3795 } else {
3796 *role
3797 };
3798 observed_stop_collisions.insert(model_role);
3799 (
3800 "stop.collision_policy",
3801 parts.stop.collision_policy.allows(model_role),
3802 )
3803 } else {
3804 (
3805 "special_tokens.collision_policy",
3806 special.collision_policy.allows(*role, *other_role),
3807 )
3808 };
3809 if !allowed {
3810 return Err(invalid_plan(
3811 policy_field,
3812 format!(
3813 "token id {token_id} is shared by {role:?} and {other_role:?} without an explicit policy"
3814 ),
3815 ));
3816 }
3817 }
3818 }
3819 }
3820 if observed_stop_collisions != *parts.stop.collision_policy.allowed_model_roles() {
3821 return Err(invalid_plan(
3822 "stop.collision_policy",
3823 "declared model-role collisions must exactly match observed stop-token aliases",
3824 ));
3825 }
3826 Ok(())
3827 }
3828
3829 fn decision_values(
3830 parts: &ResolvedModelPlanParts,
3831 ) -> Result<BTreeMap<ResolutionField, serde_json::Value>, VNextError> {
3832 #[derive(Serialize)]
3833 struct RuntimePresetValue<'a> {
3834 policy_id: &'a str,
3835 version: ContractVersion,
3836 scheduling: SchedulingDiscipline,
3837 }
3838
3839 let mut values = BTreeMap::new();
3840 macro_rules! insert_value {
3841 ($field:expr, $value:expr, $context:literal) => {
3842 values.insert($field, canonical_json_value($value, $context)?);
3843 };
3844 }
3845
3846 insert_value!(
3847 ResolutionField::OriginalSources,
3848 &parts.original_sources,
3849 "serialize original model sources decision"
3850 );
3851 insert_value!(
3852 ResolutionField::ResolvedSources,
3853 &parts.resolved_sources,
3854 "serialize resolved model sources decision"
3855 );
3856 insert_value!(
3857 ResolutionField::Config,
3858 &parts.config,
3859 "serialize model config decision"
3860 );
3861 insert_value!(
3862 ResolutionField::ExternalMetadata,
3863 &parts.external_metadata_id,
3864 "serialize external metadata decision"
3865 );
3866 insert_value!(
3867 ResolutionField::Family,
3868 parts.prepared_family.family_id(),
3869 "serialize model family decision"
3870 );
3871 insert_value!(
3872 ResolutionField::NumericalExecution,
3873 &parts.numerical_execution,
3874 "serialize numerical execution decision"
3875 );
3876 insert_value!(
3877 ResolutionField::WeightSchema,
3878 parts.prepared_family.weight_schema(),
3879 "serialize weight schema decision"
3880 );
3881 insert_value!(
3882 ResolutionField::WeightFormat,
3883 &parts.prepared_family.weight_schema().format_id,
3884 "serialize weight format decision"
3885 );
3886 insert_value!(
3887 ResolutionField::Tokenizer,
3888 &parts.tokenizer,
3889 "serialize tokenizer decision"
3890 );
3891 insert_value!(
3892 ResolutionField::Template,
3893 &parts.prepared_family.metadata().template,
3894 "serialize template decision"
3895 );
3896 insert_value!(
3897 ResolutionField::SpecialTokens,
3898 &parts.prepared_family.metadata().special_tokens,
3899 "serialize special-token decision"
3900 );
3901 insert_value!(
3902 ResolutionField::Device,
3903 &parts.device,
3904 "serialize device decision"
3905 );
3906 insert_value!(
3907 ResolutionField::Capabilities,
3908 &parts.capabilities,
3909 "serialize capability decision"
3910 );
3911 insert_value!(
3912 ResolutionField::RuntimePreset,
3913 &RuntimePresetValue {
3914 policy_id: parts.runtime.policy_id(),
3915 version: parts.runtime.version(),
3916 scheduling: parts.runtime.scheduling(),
3917 },
3918 "serialize runtime preset decision"
3919 );
3920 insert_value!(
3921 ResolutionField::RuntimeMemory,
3922 parts.runtime.memory(),
3923 "serialize runtime memory decision"
3924 );
3925 insert_value!(
3926 ResolutionField::Admission,
3927 parts.runtime.admission(),
3928 "serialize admission decision"
3929 );
3930 insert_value!(
3931 ResolutionField::Engine,
3932 &parts.engine,
3933 "serialize engine decision"
3934 );
3935 insert_value!(
3936 ResolutionField::ExecutionPlan,
3937 parts.execution_plan.plan_hash().as_str(),
3938 "serialize execution-plan decision"
3939 );
3940 insert_value!(
3941 ResolutionField::Sampling,
3942 &parts.sampling,
3943 "serialize sampling decision"
3944 );
3945 insert_value!(
3946 ResolutionField::Stop,
3947 &parts.stop,
3948 "serialize stop decision"
3949 );
3950 insert_value!(
3951 ResolutionField::StructuredOutput,
3952 &parts.structured_output,
3953 "serialize structured-output decision"
3954 );
3955 Ok(values)
3956 }
3957
3958 fn decision_fingerprints(
3959 parts: &ResolvedModelPlanParts,
3960 ) -> Result<BTreeMap<ResolutionField, String>, VNextError> {
3961 Self::decision_values(parts)?
3962 .into_iter()
3963 .map(|(field, value)| {
3964 canonical_fingerprint(&value, "fingerprint resolved decision value")
3965 .map(|fingerprint| (field, fingerprint))
3966 })
3967 .collect()
3968 }
3969
3970 pub fn parts(&self) -> &ResolvedModelPlanParts {
3971 &self.parts
3972 }
3973
3974 pub fn execution_plan(&self) -> &ExecutionPlan {
3975 &self.parts.execution_plan
3976 }
3977
3978 pub fn fingerprint(&self) -> &str {
3979 self.fingerprint.as_str()
3980 }
3981
3982 pub fn to_json(&self) -> Result<Vec<u8>, VNextError> {
3983 serde_json::to_vec(self).map_err(|error| VNextError::Serialization {
3984 context: "serialize resolved model plan",
3985 message: error.to_string(),
3986 })
3987 }
3988
3989 pub fn decode_untrusted(bytes: &[u8]) -> Result<UnvalidatedResolvedModelPlan, VNextError> {
3990 if bytes.len() > MAX_RESOLVED_MODEL_PLAN_WIRE_BYTES {
3991 return Err(invalid_plan(
3992 "resolved_model_plan.wire_bytes",
3993 format!("must not exceed {MAX_RESOLVED_MODEL_PLAN_WIRE_BYTES} bytes"),
3994 ));
3995 }
3996 serde_json::from_slice::<ResolvedModelPlanWire>(bytes)
3997 .map(Into::into)
3998 .map_err(|error| VNextError::Serialization {
3999 context: "decode untrusted resolved model plan",
4000 message: error.to_string(),
4001 })
4002 }
4003
4004 pub fn from_json_validated(
4005 bytes: &[u8],
4006 context: &ResolvedPlanValidationContext<'_>,
4007 ) -> Result<Self, VNextError> {
4008 Self::decode_untrusted(bytes)?.revalidate(context)
4009 }
4010}