Skip to main content

ferrum_interfaces/vnext/
model.rs

1use serde::de::DeserializeOwned;
2use serde::{Deserialize, Deserializer, Serialize};
3use sha2::{Digest, Sha256};
4use std::collections::{BTreeMap, BTreeSet};
5
6use super::{
7    checked_elements, physical_component_ids, validate_physical_layout_budget, AttributeId,
8    AxisWeightComponent, BlockQuantizationSpec, CanonicalRational, CompositeWeightPart,
9    ContractVersion, ElementType, ExternalModelMetadataId, ModelFamilyId, NodeId, OperationId,
10    PhysicalStorageLayout, PhysicalWeightComponentBinding, PhysicalWeightLayout,
11    PhysicalWeightPadding, ProgramValueId, QuantizationGrouping, QuantizationPacking,
12    QuantizationSpec, ResolvedTensorLayout, ResolvedWeightBinding, ResolvedWeightComponentLayout,
13    ResolvedWeightLogicalValidation, SemanticValue, StateId, StateInitialization, TokenizerId,
14    VNextError, WeightComponentRole, WeightEncoding, WeightFormatId, WeightId, WeightLayoutId,
15    MAX_PHYSICAL_WEIGHT_LAYOUT_DEPTH, MAX_PHYSICAL_WEIGHT_LAYOUT_NODES,
16};
17
18#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
19pub struct WeightComponentSpec {
20    pub id: WeightId,
21    pub role: WeightComponentRole,
22    /// Ordered checkpoint tensors forming this physical component. Dense
23    /// multi-source components use `[source_count, source_shape...]` and stack
24    /// tensors in this exact order. Format adapters may instead define a typed
25    /// source recipe (for example values plus quantization sidecars), but must
26    /// validate every source identity, order, shape, and resulting byte count.
27    /// Aliases are resolved before the model family constructs this schema.
28    pub external_names: Vec<String>,
29    pub dimensions: Vec<u64>,
30    pub encoding: WeightEncoding,
31    pub required: bool,
32}
33
34impl WeightComponentSpec {
35    /// Exact bytes occupied by this physical component. Separate-component
36    /// quantized dimensions are byte dimensions. Block-quantized dimensions
37    /// are block-grid dimensions and are multiplied by the block ABI size.
38    /// Logical element counts live on `WeightTensorSpec` and are checked
39    /// against the corresponding physical layout contract.
40    pub fn physical_bytes(&self) -> Result<u64, VNextError> {
41        self.encoding.physical_bytes(&self.dimensions, &self.id)
42    }
43
44    pub fn dense_element_type(&self) -> Option<ElementType> {
45        self.encoding.dense_element_type()
46    }
47
48    pub fn physical_element_type(&self) -> ElementType {
49        self.dense_element_type().unwrap_or(ElementType::U8)
50    }
51}
52
53#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
54pub struct PhysicalWeightComponentRef {
55    pub component_id: WeightId,
56    pub physical_dimensions: Vec<u64>,
57    pub resource_bytes: u64,
58    pub element_type: ElementType,
59}
60
61#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
62pub struct WeightTensorSpec {
63    pub id: WeightId,
64    pub dimensions: Vec<u64>,
65    /// Dtype observed by the semantic program after decoding any physical
66    /// packing, indexing, or quantization components.
67    pub logical_element_type: ElementType,
68    pub physical_layout: PhysicalWeightLayout,
69    pub required: bool,
70}
71
72impl WeightTensorSpec {
73    pub fn logical_elements(&self) -> Result<u64, VNextError> {
74        checked_elements(&self.dimensions).ok_or_else(|| VNextError::InvalidExecutionPlan {
75            reason: format!("logical weight `{}` element count overflows u64", self.id),
76        })
77    }
78
79    pub fn logical_bytes(&self) -> Result<u64, VNextError> {
80        self.logical_elements()?
81            .checked_mul(self.logical_element_type.size_bytes())
82            .ok_or_else(|| VNextError::InvalidExecutionPlan {
83                reason: format!("logical weight `{}` byte size overflows u64", self.id),
84            })
85    }
86}
87
88#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
89pub struct WeightSchema {
90    pub format_id: WeightFormatId,
91    pub layout_id: WeightLayoutId,
92    pub version: ContractVersion,
93    pub components: Vec<WeightComponentSpec>,
94    pub tensors: Vec<WeightTensorSpec>,
95}
96
97impl WeightSchema {
98    pub(crate) fn normalize(&mut self) {
99        self.components
100            .sort_by(|left, right| left.id.cmp(&right.id));
101        for tensor in &mut self.tensors {
102            tensor.physical_layout.normalize();
103        }
104        self.tensors.sort_by(|left, right| left.id.cmp(&right.id));
105    }
106
107    pub fn validate(&self, family_id: &ModelFamilyId) -> Result<(), VNextError> {
108        if self.version.major == 0 || self.components.is_empty() || self.tensors.is_empty() {
109            return Err(VNextError::UnknownWeightLayout {
110                family_id: family_id.to_string(),
111                layout_id: self.layout_id.to_string(),
112            });
113        }
114        for tensor in &self.tensors {
115            validate_physical_layout_budget(&tensor.physical_layout).map_err(|reason| {
116                VNextError::InvalidModelConfig {
117                    family_id: family_id.to_string(),
118                    field: format!("weight_schema.tensors.{}.physical_layout", tensor.id),
119                    reason,
120                }
121            })?;
122        }
123        let mut component_ids = BTreeSet::new();
124        let mut names = BTreeSet::new();
125        let mut components = BTreeMap::new();
126        let mut quantization_abis = BTreeMap::new();
127        for component in &self.components {
128            if !component_ids.insert(component.id.clone())
129                || component.external_names.is_empty()
130                || component.dimensions.is_empty()
131                || component
132                    .external_names
133                    .iter()
134                    .any(|name| name.trim().is_empty() || !names.insert(name.clone()))
135                || component.dimensions.iter().any(|extent| *extent == 0)
136            {
137                return Err(VNextError::InvalidModelConfig {
138                    family_id: family_id.to_string(),
139                    field: "weight_schema.components".to_owned(),
140                    reason: "component identities, names, and dimensions must be valid and unique"
141                        .to_owned(),
142                });
143            }
144            if let WeightEncoding::Quantized(quantization) = &component.encoding {
145                quantization.validate()?;
146            }
147            if let WeightEncoding::BlockQuantized(quantization) = &component.encoding {
148                quantization.validate()?;
149            }
150            let quantization_format = match &component.encoding {
151                WeightEncoding::Quantized(spec) => Some(&spec.format_id),
152                WeightEncoding::BlockQuantized(spec) => Some(&spec.format_id),
153                WeightEncoding::Dense { .. } | WeightEncoding::DenseAffine { .. } => None,
154            };
155            if let Some(format_id) = quantization_format {
156                if let Some(existing) = quantization_abis.get(format_id) {
157                    if existing != &component.encoding {
158                        return Err(VNextError::InvalidModelConfig {
159                            family_id: family_id.to_string(),
160                            field: "weight_schema.components.encoding".to_owned(),
161                            reason: format!(
162                                "quantization format `{format_id}` maps to conflicting physical ABIs"
163                            ),
164                        });
165                    }
166                } else {
167                    quantization_abis.insert(format_id.clone(), component.encoding.clone());
168                }
169            }
170            if let WeightEncoding::DenseAffine { element_type, .. } = &component.encoding {
171                if component.role != WeightComponentRole::Values
172                    || !matches!(
173                        element_type,
174                        ElementType::F16 | ElementType::Bf16 | ElementType::F32
175                    )
176                {
177                    return Err(VNextError::InvalidModelConfig {
178                        family_id: family_id.to_string(),
179                        field: "weight_schema.components.encoding".to_owned(),
180                        reason: format!(
181                            "affine dense component `{}` must be a floating-point value component",
182                            component.id
183                        ),
184                    });
185                }
186            }
187            component
188                .physical_bytes()
189                .map_err(|error| VNextError::InvalidModelConfig {
190                    family_id: family_id.to_string(),
191                    field: "weight_schema.components.dimensions".to_owned(),
192                    reason: error.to_string(),
193                })?;
194            let role_encoding_valid = match component.role {
195                WeightComponentRole::Scales => matches!(
196                    component.encoding,
197                    WeightEncoding::Dense {
198                        element_type: ElementType::F16 | ElementType::Bf16 | ElementType::F32
199                    }
200                ),
201                WeightComponentRole::ZeroPoints
202                | WeightComponentRole::Indices
203                | WeightComponentRole::Permutation => matches!(
204                    component.encoding,
205                    WeightEncoding::Dense {
206                        element_type: ElementType::U8
207                            | ElementType::U32
208                            | ElementType::I8
209                            | ElementType::I32
210                    }
211                ),
212                WeightComponentRole::PackedValues => {
213                    matches!(
214                        component.encoding,
215                        WeightEncoding::Quantized(_) | WeightEncoding::BlockQuantized(_)
216                    )
217                }
218                _ => true,
219            };
220            if !role_encoding_valid {
221                return Err(VNextError::InvalidModelConfig {
222                    family_id: family_id.to_string(),
223                    field: "weight_schema.components.encoding".to_owned(),
224                    reason: format!(
225                        "component `{}` encoding is incompatible with its structural role",
226                        component.id
227                    ),
228                });
229            }
230            components.insert(component.id.clone(), component);
231        }
232
233        let mut tensor_ids = BTreeSet::new();
234        let mut referenced_components = BTreeSet::new();
235        for tensor in &self.tensors {
236            if !tensor_ids.insert(tensor.id.clone())
237                || tensor.dimensions.is_empty()
238                || tensor.dimensions.iter().any(|extent| *extent == 0)
239            {
240                return Err(VNextError::InvalidModelConfig {
241                    family_id: family_id.to_string(),
242                    field: "weight_schema.tensors".to_owned(),
243                    reason: "logical weight identities and dimensions must be valid and unique"
244                        .to_owned(),
245                });
246            }
247            self.validate_physical_layout(
248                family_id,
249                tensor,
250                &components,
251                &mut referenced_components,
252            )?;
253        }
254        if let Some(component) = self
255            .components
256            .iter()
257            .find(|component| component.required && !referenced_components.contains(&component.id))
258        {
259            return Err(VNextError::InvalidModelConfig {
260                family_id: family_id.to_string(),
261                field: "weight_schema.components".to_owned(),
262                reason: format!(
263                    "required component `{}` is not referenced by a logical weight",
264                    component.id
265                ),
266            });
267        }
268        Ok(())
269    }
270
271    pub fn fingerprint(&self) -> Result<String, VNextError> {
272        let bytes = serde_json::to_vec(self).map_err(|error| VNextError::Serialization {
273            context: "fingerprint weight schema",
274            message: error.to_string(),
275        })?;
276        Ok(format!("{:x}", Sha256::digest(bytes)))
277    }
278
279    pub fn quantization_formats(&self) -> BTreeSet<super::QuantizationFormatId> {
280        self.components
281            .iter()
282            .filter_map(|component| match &component.encoding {
283                WeightEncoding::Quantized(spec) => Some(spec.format_id.clone()),
284                WeightEncoding::BlockQuantized(spec) => Some(spec.format_id.clone()),
285                WeightEncoding::Dense { .. } | WeightEncoding::DenseAffine { .. } => None,
286            })
287            .collect()
288    }
289
290    fn validate_physical_layout(
291        &self,
292        family_id: &ModelFamilyId,
293        tensor: &WeightTensorSpec,
294        components: &BTreeMap<WeightId, &WeightComponentSpec>,
295        referenced: &mut BTreeSet<WeightId>,
296    ) -> Result<(), VNextError> {
297        let mut validator = PhysicalLayoutValidator {
298            family_id,
299            tensor_id: &tensor.id,
300            components,
301            referenced,
302            visited_nodes: 0,
303        };
304        validator.validate_layout(
305            &tensor.physical_layout,
306            &tensor.dimensions,
307            tensor.logical_element_type,
308            1,
309        )
310    }
311
312    pub fn tensor(&self, weight_id: &WeightId) -> Option<&WeightTensorSpec> {
313        self.tensors.iter().find(|tensor| &tensor.id == weight_id)
314    }
315
316    /// Ordered physical components needed to materialize one logical value.
317    /// The order is the schema's component order and is therefore stable for
318    /// fingerprints and resource-plan evidence.
319    pub fn physical_component_refs(
320        &self,
321        weight_id: &WeightId,
322    ) -> Result<Vec<&WeightComponentSpec>, VNextError> {
323        let tensor = self
324            .tensor(weight_id)
325            .ok_or_else(|| VNextError::InvalidExecutionPlan {
326                reason: format!("unknown logical weight `{weight_id}`"),
327            })?;
328        let required = physical_component_ids(&tensor.physical_layout).map_err(|reason| {
329            VNextError::InvalidExecutionPlan {
330                reason: format!(
331                    "logical weight `{weight_id}` has invalid physical layout: {reason}"
332                ),
333            }
334        })?;
335        let result = self
336            .components
337            .iter()
338            .filter(|component| required.contains(&component.id))
339            .collect::<Vec<_>>();
340        if result.len() != required.len() {
341            return Err(VNextError::InvalidExecutionPlan {
342                reason: format!("logical weight `{weight_id}` references an unknown component"),
343            });
344        }
345        Ok(result)
346    }
347
348    pub fn physical_bytes(&self, weight_id: &WeightId) -> Result<u64, VNextError> {
349        self.physical_component_refs(weight_id)?
350            .into_iter()
351            .try_fold(0_u64, |total, component| {
352                total
353                    .checked_add(component.physical_bytes()?)
354                    .ok_or_else(|| VNextError::InvalidExecutionPlan {
355                        reason: format!("logical weight `{weight_id}` physical bytes overflow u64"),
356                    })
357            })
358    }
359
360    pub fn physical_resource_requirements(
361        &self,
362        weight_id: &WeightId,
363    ) -> Result<Vec<PhysicalWeightComponentRef>, VNextError> {
364        self.physical_component_refs(weight_id)?
365            .into_iter()
366            .map(|component| {
367                Ok(PhysicalWeightComponentRef {
368                    component_id: component.id.clone(),
369                    physical_dimensions: component.dimensions.clone(),
370                    resource_bytes: component.physical_bytes()?,
371                    element_type: component.physical_element_type(),
372                })
373            })
374            .collect()
375    }
376}
377
378impl ResolvedWeightBinding {
379    pub fn from_schema(schema: &WeightSchema, weight_id: &WeightId) -> Result<Self, VNextError> {
380        let tensor = schema
381            .tensor(weight_id)
382            .ok_or_else(|| VNextError::InvalidExecutionPlan {
383                reason: format!("unknown logical weight `{weight_id}`"),
384            })?;
385        let mut components = schema
386            .physical_component_refs(weight_id)?
387            .into_iter()
388            .map(|component| {
389                ResolvedWeightComponentLayout::from_parts(
390                    component.id.clone(),
391                    component.role,
392                    component.dimensions.clone(),
393                    component.encoding.clone(),
394                )
395            })
396            .collect::<Vec<_>>();
397        components.sort_by(|left, right| left.component_id().cmp(right.component_id()));
398        let binding = Self::from_parts(
399            weight_id.clone(),
400            schema.format_id.clone(),
401            schema.layout_id.clone(),
402            schema.version,
403            tensor.physical_layout.clone(),
404            components,
405        )?;
406        binding.validate_logical(&tensor.dimensions, tensor.logical_element_type)?;
407        Ok(binding)
408    }
409
410    pub fn validate_logical(
411        &self,
412        logical_dimensions: &[u64],
413        logical_element_type: ElementType,
414    ) -> Result<(), VNextError> {
415        ResolvedWeightLogicalValidation::validate_logical_contract(
416            self,
417            logical_dimensions,
418            logical_element_type,
419        )
420    }
421}
422
423impl ResolvedWeightLogicalValidation for ResolvedWeightBinding {
424    fn validate_logical_contract(
425        &self,
426        logical_dimensions: &[u64],
427        logical_element_type: ElementType,
428    ) -> Result<(), VNextError> {
429        self.validate_structure()?;
430        let schema = WeightSchema {
431            format_id: self.schema_format_id().clone(),
432            layout_id: self.layout_id().clone(),
433            version: self.schema_version(),
434            components: self
435                .components()
436                .iter()
437                .map(|component| WeightComponentSpec {
438                    id: component.component_id().clone(),
439                    role: component.role(),
440                    external_names: vec![format!("resolved.{}", component.component_id())],
441                    dimensions: component.physical_dimensions().to_vec(),
442                    encoding: component.encoding().clone(),
443                    required: true,
444                })
445                .collect(),
446            tensors: vec![WeightTensorSpec {
447                id: self.weight_id().clone(),
448                dimensions: logical_dimensions.to_vec(),
449                logical_element_type,
450                physical_layout: self.physical_layout().clone(),
451                required: true,
452            }],
453        };
454        schema.validate(&ModelFamilyId::new("family.resolved-weight-binding")?)
455    }
456}
457
458struct PhysicalLayoutValidator<'schema, 'references> {
459    family_id: &'schema ModelFamilyId,
460    tensor_id: &'schema WeightId,
461    components: &'schema BTreeMap<WeightId, &'schema WeightComponentSpec>,
462    referenced: &'references mut BTreeSet<WeightId>,
463    visited_nodes: usize,
464}
465
466impl<'schema, 'references> PhysicalLayoutValidator<'schema, 'references> {
467    fn invalid(&self, reason: impl Into<String>) -> VNextError {
468        VNextError::InvalidModelConfig {
469            family_id: self.family_id.to_string(),
470            field: format!("weight_schema.tensors.{}.physical_layout", self.tensor_id),
471            reason: reason.into(),
472        }
473    }
474
475    fn visit_node(&mut self, depth: usize) -> Result<(), VNextError> {
476        if depth > MAX_PHYSICAL_WEIGHT_LAYOUT_DEPTH {
477            return Err(self.invalid(format!(
478                "physical layout depth exceeds {MAX_PHYSICAL_WEIGHT_LAYOUT_DEPTH}"
479            )));
480        }
481        self.visited_nodes = self
482            .visited_nodes
483            .checked_add(1)
484            .ok_or_else(|| self.invalid("physical layout node count overflows usize"))?;
485        if self.visited_nodes > MAX_PHYSICAL_WEIGHT_LAYOUT_NODES {
486            return Err(self.invalid(format!(
487                "physical layout node count exceeds {MAX_PHYSICAL_WEIGHT_LAYOUT_NODES}"
488            )));
489        }
490        Ok(())
491    }
492
493    fn component(
494        &self,
495        component_id: &WeightId,
496    ) -> Result<&'schema WeightComponentSpec, VNextError> {
497        self.components
498            .get(component_id)
499            .copied()
500            .ok_or_else(|| self.invalid(format!("unknown component `{component_id}`")))
501    }
502
503    fn bind_component(
504        &mut self,
505        binding: &PhysicalWeightComponentBinding,
506        semantic_dimensions: &[u64],
507        role: WeightComponentRole,
508        depth: usize,
509    ) -> Result<&'schema WeightComponentSpec, VNextError> {
510        self.visit_node(depth)?;
511        let component = self.component(&binding.component_id)?;
512        if component.role != role {
513            return Err(self.invalid(format!(
514                "component `{}` has role {:?}, expected {:?}",
515                component.id, component.role, role
516            )));
517        }
518        self.validate_storage(component, semantic_dimensions, &binding.storage)?;
519        if !self.referenced.insert(component.id.clone()) {
520            return Err(self.invalid(format!(
521                "component `{}` is referenced more than once in the physical layout tree",
522                component.id
523            )));
524        }
525        Ok(component)
526    }
527
528    fn validate_storage(
529        &self,
530        component: &WeightComponentSpec,
531        semantic_dimensions: &[u64],
532        storage: &PhysicalStorageLayout,
533    ) -> Result<(), VNextError> {
534        if semantic_dimensions.is_empty()
535            || semantic_dimensions.iter().any(|extent| *extent == 0)
536            || checked_elements(semantic_dimensions).is_none()
537        {
538            return Err(self.invalid(format!(
539                "component `{}` has an invalid or overflowing semantic shape",
540                component.id
541            )));
542        }
543        let raw_elements = checked_elements(&component.dimensions).ok_or_else(|| {
544            self.invalid(format!(
545                "component `{}` raw storage shape overflows u64",
546                component.id
547            ))
548        })?;
549        match storage {
550            PhysicalStorageLayout::Contiguous { padding } => {
551                let padded = self.resolve_padding(semantic_dimensions, padding)?;
552                if component.dimensions != padded {
553                    return Err(self.invalid(format!(
554                        "component `{}` contiguous shape {:?} differs from its explicit physical shape {:?}",
555                        component.id, padded, component.dimensions
556                    )));
557                }
558            }
559            PhysicalStorageLayout::Strided {
560                strides_in_elements,
561                padding,
562            } => {
563                let padded = self.resolve_padding(semantic_dimensions, padding)?;
564                let span = self.checked_strided_span(&padded, strides_in_elements, 1)?;
565                if span != raw_elements {
566                    return Err(self.invalid(format!(
567                        "component `{}` strided span {span} differs from its raw storage element count {raw_elements}",
568                        component.id
569                    )));
570                }
571            }
572            PhysicalStorageLayout::Tiled {
573                tile_shape,
574                axis_order,
575                tile_strides_in_elements,
576                padding,
577            } => {
578                let rank = semantic_dimensions.len();
579                if tile_shape.len() != rank
580                    || tile_shape.iter().any(|extent| *extent == 0)
581                    || !is_axis_permutation(axis_order, rank)
582                    || tile_strides_in_elements.len() != rank
583                {
584                    return Err(self.invalid(format!(
585                        "component `{}` tile shape, axis order, or strides do not match rank",
586                        component.id
587                    )));
588                }
589                let padded = self.resolve_padding(semantic_dimensions, padding)?;
590                let minimal_padded = semantic_dimensions
591                    .iter()
592                    .zip(tile_shape)
593                    .map(|(extent, tile)| checked_round_up(*extent, *tile))
594                    .collect::<Option<Vec<_>>>()
595                    .ok_or_else(|| {
596                        self.invalid(format!(
597                            "component `{}` tile padding overflows u64",
598                            component.id
599                        ))
600                    })?;
601                match padding {
602                    PhysicalWeightPadding::Exact if minimal_padded != semantic_dimensions => {
603                        return Err(self.invalid(format!(
604                            "component `{}` needs tile padding but declares exact storage",
605                            component.id
606                        )));
607                    }
608                    PhysicalWeightPadding::ZeroFill { .. } if padded != minimal_padded => {
609                        return Err(self.invalid(format!(
610                            "component `{}` tiled zero-fill shape is not the unique minimal padded shape",
611                            component.id
612                        )));
613                    }
614                    _ => {}
615                }
616                let semantic_grid = padded
617                    .iter()
618                    .zip(tile_shape)
619                    .map(|(extent, tile)| extent / tile)
620                    .collect::<Vec<_>>();
621                let physical_grid = axis_order
622                    .iter()
623                    .map(|axis| semantic_grid[*axis as usize])
624                    .collect::<Vec<_>>();
625                let tile_elements = checked_elements(tile_shape).ok_or_else(|| {
626                    self.invalid(format!(
627                        "component `{}` tile size overflows u64",
628                        component.id
629                    ))
630                })?;
631                let span = self.checked_strided_span(
632                    &physical_grid,
633                    tile_strides_in_elements,
634                    tile_elements,
635                )?;
636                if span != raw_elements {
637                    return Err(self.invalid(format!(
638                        "component `{}` tiled span {span} differs from its raw storage element count {raw_elements}",
639                        component.id
640                    )));
641                }
642            }
643        }
644        Ok(())
645    }
646
647    fn resolve_padding(
648        &self,
649        semantic_dimensions: &[u64],
650        padding: &PhysicalWeightPadding,
651    ) -> Result<Vec<u64>, VNextError> {
652        match padding {
653            PhysicalWeightPadding::Exact => Ok(semantic_dimensions.to_vec()),
654            PhysicalWeightPadding::ZeroFill { padded_dimensions } => {
655                if padded_dimensions.len() != semantic_dimensions.len()
656                    || padded_dimensions.iter().any(|extent| *extent == 0)
657                    || padded_dimensions
658                        .iter()
659                        .zip(semantic_dimensions)
660                        .any(|(padded, semantic)| padded < semantic)
661                    || padded_dimensions == semantic_dimensions
662                    || checked_elements(padded_dimensions).is_none()
663                {
664                    return Err(self.invalid(
665                        "zero-fill padding must explicitly enlarge a valid shape without shrinking any axis",
666                    ));
667                }
668                Ok(padded_dimensions.clone())
669            }
670        }
671    }
672
673    fn checked_strided_span(
674        &self,
675        dimensions: &[u64],
676        strides: &[u64],
677        base_span: u64,
678    ) -> Result<u64, VNextError> {
679        if dimensions.is_empty()
680            || dimensions.len() != strides.len()
681            || dimensions.iter().any(|extent| *extent == 0)
682            || strides.iter().any(|stride| *stride == 0)
683            || base_span == 0
684        {
685            return Err(self.invalid("strided storage dimensions and strides are invalid"));
686        }
687        let mut axes = dimensions
688            .iter()
689            .copied()
690            .zip(strides.iter().copied())
691            .filter(|(extent, _)| *extent > 1)
692            .collect::<Vec<_>>();
693        axes.sort_by_key(|(_, stride)| *stride);
694        let mut span = base_span;
695        for (extent, stride) in axes {
696            if stride < span {
697                return Err(
698                    self.invalid("strided storage aliases coordinates or overlaps physical tiles")
699                );
700            }
701            span = extent
702                .checked_sub(1)
703                .and_then(|count| count.checked_mul(stride))
704                .and_then(|addition| span.checked_add(addition))
705                .ok_or_else(|| self.invalid("strided storage span overflows u64"))?;
706        }
707        Ok(span)
708    }
709
710    fn grouped_dimensions(
711        &self,
712        semantic_dimensions: &[u64],
713        padding: &PhysicalWeightPadding,
714        group_axis: usize,
715        group_size: u64,
716    ) -> Result<Vec<u64>, VNextError> {
717        let axis_extent = semantic_dimensions[group_axis];
718        let minimal_axis = checked_round_up(axis_extent, group_size)
719            .ok_or_else(|| self.invalid("quantization group padding overflows u64"))?;
720        match padding {
721            PhysicalWeightPadding::Exact => {
722                if minimal_axis != axis_extent {
723                    return Err(self.invalid(
724                        "quantization groups require padding but exact storage was declared",
725                    ));
726                }
727                Ok(semantic_dimensions.to_vec())
728            }
729            PhysicalWeightPadding::ZeroFill { padded_dimensions } => {
730                if minimal_axis == axis_extent
731                    || padded_dimensions.len() != semantic_dimensions.len()
732                    || padded_dimensions.iter().enumerate().any(|(axis, extent)| {
733                        if axis == group_axis {
734                            *extent != minimal_axis
735                        } else {
736                            *extent != semantic_dimensions[axis]
737                        }
738                    })
739                {
740                    return Err(self.invalid(
741                        "quantization zero-fill must pad only the group axis to its unique minimal extent",
742                    ));
743                }
744                checked_elements(padded_dimensions)
745                    .is_some()
746                    .then(|| padded_dimensions.clone())
747                    .ok_or_else(|| self.invalid("quantization padded shape overflows u64"))
748            }
749        }
750    }
751
752    fn validate_dense_values(
753        &mut self,
754        binding: &PhysicalWeightComponentBinding,
755        semantic_dimensions: &[u64],
756        logical_element_type: ElementType,
757        depth: usize,
758    ) -> Result<(), VNextError> {
759        let component = self.bind_component(
760            binding,
761            semantic_dimensions,
762            WeightComponentRole::Values,
763            depth,
764        )?;
765        if component.dense_element_type() != Some(logical_element_type) {
766            return Err(self.invalid(format!(
767                "values component `{}` dtype differs from the logical tensor",
768                component.id
769            )));
770        }
771        Ok(())
772    }
773
774    fn validate_axis_component(
775        &mut self,
776        axis_component: &AxisWeightComponent,
777        semantic_dimensions: &[u64],
778        expected_axis: usize,
779        role: WeightComponentRole,
780        allow_narrow_integer: bool,
781        depth: usize,
782    ) -> Result<(), VNextError> {
783        if axis_component.axis as usize != expected_axis {
784            return Err(self.invalid(format!(
785                "axis component `{}` targets axis {}, expected {expected_axis}",
786                axis_component.component.component_id, axis_component.axis
787            )));
788        }
789        let axis_shape = [semantic_dimensions[expected_axis]];
790        let component = self.bind_component(&axis_component.component, &axis_shape, role, depth)?;
791        let integer_type_valid = component.dense_element_type().is_some_and(|element_type| {
792            if allow_narrow_integer {
793                matches!(
794                    element_type,
795                    ElementType::U8 | ElementType::U32 | ElementType::I8 | ElementType::I32
796                )
797            } else {
798                matches!(element_type, ElementType::U32 | ElementType::I32)
799            }
800        });
801        if !integer_type_valid {
802            return Err(self.invalid(format!(
803                "axis component `{}` must use an integer encoding valid for {:?}",
804                component.id, role
805            )));
806        }
807        Ok(())
808    }
809
810    fn validate_layout(
811        &mut self,
812        layout: &PhysicalWeightLayout,
813        semantic_dimensions: &[u64],
814        logical_element_type: ElementType,
815        depth: usize,
816    ) -> Result<(), VNextError> {
817        self.visit_node(depth)?;
818        if semantic_dimensions.is_empty()
819            || semantic_dimensions.iter().any(|extent| *extent == 0)
820            || checked_elements(semantic_dimensions).is_none()
821        {
822            return Err(self.invalid("logical layout shape is empty, zero, or overflowing"));
823        }
824        match layout {
825            PhysicalWeightLayout::Dense { component_id } => {
826                let binding =
827                    PhysicalWeightComponentBinding::exact_contiguous(component_id.clone());
828                self.validate_dense_values(
829                    &binding,
830                    semantic_dimensions,
831                    logical_element_type,
832                    depth,
833                )?;
834            }
835            PhysicalWeightLayout::Stored { component } => {
836                self.validate_dense_values(
837                    component,
838                    semantic_dimensions,
839                    logical_element_type,
840                    depth,
841                )?;
842            }
843            PhysicalWeightLayout::Composite { parts } => {
844                if parts.is_empty() {
845                    return Err(self.invalid("composite layout has no parts"));
846                }
847                let rank = semantic_dimensions.len();
848                let mut covered_elements = 0_u64;
849                for (index, part) in parts.iter().enumerate() {
850                    if part.logical_offsets.len() != rank
851                        || part.extents.len() != rank
852                        || part.extents.iter().any(|extent| *extent == 0)
853                        || part
854                            .logical_offsets
855                            .iter()
856                            .zip(&part.extents)
857                            .zip(semantic_dimensions)
858                            .any(|((offset, extent), logical)| {
859                                offset.checked_add(*extent).is_none_or(|end| end > *logical)
860                            })
861                    {
862                        return Err(self.invalid(format!(
863                            "composite part {index} has invalid semantic offsets or extents"
864                        )));
865                    }
866                    for previous in &parts[..index] {
867                        let overlaps = part
868                            .logical_offsets
869                            .iter()
870                            .zip(&part.extents)
871                            .zip(previous.logical_offsets.iter().zip(&previous.extents))
872                            .all(|((offset, extent), (other_offset, other_extent))| {
873                                offset.checked_add(*extent).is_some_and(|end| {
874                                    other_offset.checked_add(*other_extent).is_some_and(
875                                        |other_end| *offset < other_end && *other_offset < end,
876                                    )
877                                })
878                            });
879                        if overlaps {
880                            return Err(self.invalid("composite semantic placements overlap"));
881                        }
882                    }
883                    let part_elements = checked_elements(&part.extents)
884                        .ok_or_else(|| self.invalid("composite part size overflows u64"))?;
885                    covered_elements = covered_elements
886                        .checked_add(part_elements)
887                        .ok_or_else(|| self.invalid("composite coverage overflows u64"))?;
888                    self.validate_layout(
889                        &part.layout,
890                        &part.extents,
891                        logical_element_type,
892                        depth + 1,
893                    )?;
894                }
895                if covered_elements != checked_elements(semantic_dimensions).unwrap() {
896                    return Err(self.invalid(
897                        "composite semantic placements do not cover the logical tensor exactly",
898                    ));
899                }
900            }
901            PhysicalWeightLayout::Quantized {
902                packed_values,
903                packed_dimensions,
904                scales,
905                zero_points,
906                zero_point_packed_dimensions,
907                axis_indices,
908                permutation,
909                codebook,
910                group_axis,
911                group_padding,
912            } => {
913                if !matches!(
914                    logical_element_type,
915                    ElementType::F16 | ElementType::Bf16 | ElementType::F32
916                ) {
917                    return Err(
918                        self.invalid("quantized logical weight dtype must be floating point")
919                    );
920                }
921                let axis = *group_axis as usize;
922                if axis >= semantic_dimensions.len() {
923                    return Err(self.invalid("quantization group axis is out of range"));
924                }
925                let quantization = {
926                    let component = self.component(&packed_values.component_id)?;
927                    let WeightEncoding::Quantized(spec) = &component.encoding else {
928                        return Err(self.invalid(
929                            "packed-values component does not carry a quantization spec",
930                        ));
931                    };
932                    spec.clone()
933                };
934                let group_size = quantization
935                    .grouping
936                    .resolved_size(semantic_dimensions[axis]);
937                let grouped_dimensions =
938                    self.grouped_dimensions(semantic_dimensions, group_padding, axis, group_size)?;
939                let packed_bytes = checked_elements(&grouped_dimensions)
940                    .and_then(|elements| {
941                        elements.checked_mul(u64::from(quantization.bits_per_weight))
942                    })
943                    .and_then(|bits| bits.checked_add(7))
944                    .map(|bits| bits / 8)
945                    .ok_or_else(|| self.invalid("packed-values size overflows u64"))?;
946                if checked_elements(packed_dimensions) != Some(packed_bytes) {
947                    return Err(self.invalid(format!(
948                        "packed-values semantic shape contains {} storage bytes, expected {packed_bytes}",
949                        checked_elements(packed_dimensions)
950                            .map_or_else(|| "an overflowing number of".to_owned(), |value| value.to_string())
951                    )));
952                }
953                let packed = self.bind_component(
954                    packed_values,
955                    packed_dimensions,
956                    WeightComponentRole::PackedValues,
957                    depth,
958                )?;
959                if packed.encoding != WeightEncoding::Quantized(quantization.clone()) {
960                    return Err(self.invalid(
961                        "packed-values encoding changed while validating the quantized tree",
962                    ));
963                }
964
965                let mut group_shape = grouped_dimensions;
966                group_shape[axis] /= group_size;
967                let scales_component =
968                    self.bind_component(scales, &group_shape, WeightComponentRole::Scales, depth)?;
969                if scales_component.dense_element_type() != Some(quantization.scale_type) {
970                    return Err(
971                        self.invalid("scale component dtype differs from the quantization spec")
972                    );
973                }
974                match (
975                    quantization.zero_point_type,
976                    zero_points,
977                    zero_point_packed_dimensions,
978                ) {
979                    (Some(expected_type), Some(binding), Some(packed_dimensions)) => {
980                        let expected_bytes = checked_elements(&group_shape)
981                            .and_then(|elements| {
982                                elements.checked_mul(u64::from(quantization.bits_per_weight))
983                            })
984                            .and_then(|bits| bits.checked_add(7))
985                            .map(|bits| bits / 8)
986                            .ok_or_else(|| self.invalid("packed zero-point size overflows u64"))?;
987                        let component = self.bind_component(
988                            binding,
989                            packed_dimensions,
990                            WeightComponentRole::ZeroPoints,
991                            depth,
992                        )?;
993                        if component.dense_element_type() != Some(expected_type)
994                            || component.physical_bytes()? != expected_bytes
995                        {
996                            return Err(self.invalid(
997                                "packed zero-point component differs from its quantization contract",
998                            ));
999                        }
1000                    }
1001                    (Some(expected_type), Some(binding), None) => {
1002                        let component = self.bind_component(
1003                            binding,
1004                            &group_shape,
1005                            WeightComponentRole::ZeroPoints,
1006                            depth,
1007                        )?;
1008                        if component.dense_element_type() != Some(expected_type) {
1009                            return Err(self.invalid(
1010                                "zero-point component dtype differs from the quantization spec",
1011                            ));
1012                        }
1013                    }
1014                    (None, None, None) => {}
1015                    _ => {
1016                        return Err(self.invalid(
1017                            "zero-point component presence differs from the quantization spec",
1018                        ));
1019                    }
1020                }
1021                if let Some(axis_indices) = axis_indices {
1022                    self.validate_axis_component(
1023                        axis_indices,
1024                        semantic_dimensions,
1025                        axis,
1026                        WeightComponentRole::Indices,
1027                        true,
1028                        depth,
1029                    )?;
1030                }
1031                if let Some(permutation) = permutation {
1032                    self.validate_axis_component(
1033                        permutation,
1034                        semantic_dimensions,
1035                        axis,
1036                        WeightComponentRole::Permutation,
1037                        false,
1038                        depth,
1039                    )?;
1040                }
1041                if let Some(codebook) = codebook {
1042                    let entries = 1_u64
1043                        .checked_shl(u32::from(quantization.bits_per_weight))
1044                        .ok_or_else(|| self.invalid("codebook size overflows u64"))?;
1045                    let component = self.bind_component(
1046                        codebook,
1047                        &[entries],
1048                        WeightComponentRole::Codebook,
1049                        depth,
1050                    )?;
1051                    if component.dense_element_type() != Some(logical_element_type) {
1052                        return Err(
1053                            self.invalid("codebook dtype differs from the logical tensor dtype")
1054                        );
1055                    }
1056                }
1057            }
1058            PhysicalWeightLayout::BlockQuantized {
1059                blocks,
1060                block_axis,
1061                block_padding,
1062            } => {
1063                if !matches!(
1064                    logical_element_type,
1065                    ElementType::F16 | ElementType::Bf16 | ElementType::F32
1066                ) {
1067                    return Err(
1068                        self.invalid("block-quantized logical weight dtype must be floating point")
1069                    );
1070                }
1071                let axis = *block_axis as usize;
1072                if axis >= semantic_dimensions.len() {
1073                    return Err(self.invalid("block quantization axis is out of range"));
1074                }
1075                let quantization = {
1076                    let component = self.component(&blocks.component_id)?;
1077                    let WeightEncoding::BlockQuantized(spec) = &component.encoding else {
1078                        return Err(self
1079                            .invalid("block component does not carry a block quantization spec"));
1080                    };
1081                    spec.clone()
1082                };
1083                let mut block_dimensions = self.grouped_dimensions(
1084                    semantic_dimensions,
1085                    block_padding,
1086                    axis,
1087                    u64::from(quantization.logical_values_per_block),
1088                )?;
1089                block_dimensions[axis] /= u64::from(quantization.logical_values_per_block);
1090                let component = self.bind_component(
1091                    blocks,
1092                    &block_dimensions,
1093                    WeightComponentRole::PackedValues,
1094                    depth,
1095                )?;
1096                if component.encoding != WeightEncoding::BlockQuantized(quantization) {
1097                    return Err(
1098                        self.invalid("block encoding changed while validating the physical layout")
1099                    );
1100                }
1101            }
1102            PhysicalWeightLayout::AxisReshapePermutation {
1103                values,
1104                axis,
1105                logical_offset,
1106                extent,
1107                reshape,
1108                stored_axis_order,
1109            } => {
1110                let axis = *axis as usize;
1111                let end = logical_offset.checked_add(*extent);
1112                let reshape_rank = reshape.len();
1113                let order_is_permutation = stored_axis_order.len() == reshape_rank
1114                    && stored_axis_order
1115                        .iter()
1116                        .all(|axis| (*axis as usize) < reshape_rank)
1117                    && stored_axis_order
1118                        .iter()
1119                        .copied()
1120                        .collect::<BTreeSet<_>>()
1121                        .len()
1122                        == reshape_rank;
1123                let order_is_identity = stored_axis_order
1124                    .iter()
1125                    .filter(|stored| reshape[**stored as usize] > 1)
1126                    .copied()
1127                    .eq(reshape
1128                        .iter()
1129                        .enumerate()
1130                        .filter_map(|(axis, extent)| (*extent > 1).then_some(axis as u32)));
1131                if axis >= semantic_dimensions.len()
1132                    || *extent == 0
1133                    || end.is_none_or(|end| end > semantic_dimensions[axis])
1134                    || reshape_rank < 2
1135                    || reshape.iter().any(|dimension| *dimension == 0)
1136                    || checked_elements(reshape) != Some(*extent)
1137                    || !order_is_permutation
1138                    || order_is_identity
1139                {
1140                    return Err(self.invalid(
1141                        "axis reshape permutation has invalid range, shape, or stored axis order",
1142                    ));
1143                }
1144                self.validate_layout(values, semantic_dimensions, logical_element_type, depth + 1)?;
1145            }
1146            PhysicalWeightLayout::Indexed {
1147                indices,
1148                values,
1149                source_axis_extent,
1150            } => {
1151                let axis = indices.axis as usize;
1152                if axis >= semantic_dimensions.len() || *source_axis_extent == 0 {
1153                    return Err(self.invalid("indexed layout axis or source extent is invalid"));
1154                }
1155                self.validate_axis_component(
1156                    indices,
1157                    semantic_dimensions,
1158                    axis,
1159                    WeightComponentRole::Indices,
1160                    true,
1161                    depth,
1162                )?;
1163                let mut source_dimensions = semantic_dimensions.to_vec();
1164                source_dimensions[axis] = *source_axis_extent;
1165                checked_elements(&source_dimensions)
1166                    .ok_or_else(|| self.invalid("indexed source semantic shape overflows u64"))?;
1167                self.validate_layout(values, &source_dimensions, logical_element_type, depth + 1)?;
1168            }
1169            PhysicalWeightLayout::ExpertStack {
1170                experts,
1171                expert_axis,
1172            } => {
1173                let axis = *expert_axis as usize;
1174                if axis >= semantic_dimensions.len() {
1175                    return Err(self.invalid("expert stack axis is out of range"));
1176                }
1177                let expected_count = usize::try_from(semantic_dimensions[axis]).map_err(|_| {
1178                    self.invalid("expert stack count does not fit the platform usize")
1179                })?;
1180                if experts.is_empty() || experts.len() != expected_count {
1181                    return Err(self
1182                        .invalid("expert stack child count differs from its logical expert axis"));
1183                }
1184                let mut expert_dimensions = semantic_dimensions.to_vec();
1185                expert_dimensions.remove(axis);
1186                if expert_dimensions.is_empty() {
1187                    return Err(self.invalid(
1188                        "expert stack children must retain at least one tensor dimension",
1189                    ));
1190                }
1191                for expert in experts {
1192                    self.validate_layout(
1193                        expert,
1194                        &expert_dimensions,
1195                        logical_element_type,
1196                        depth + 1,
1197                    )?;
1198                }
1199            }
1200        }
1201        Ok(())
1202    }
1203}
1204
1205fn is_axis_permutation(axis_order: &[u32], rank: usize) -> bool {
1206    axis_order.len() == rank
1207        && axis_order.iter().all(|axis| (*axis as usize) < rank)
1208        && axis_order.iter().copied().collect::<BTreeSet<_>>().len() == rank
1209}
1210
1211fn checked_round_up(extent: u64, multiple: u64) -> Option<u64> {
1212    if extent == 0 || multiple == 0 {
1213        return None;
1214    }
1215    extent
1216        .checked_add(multiple.checked_sub(1)?)
1217        .map(|rounded| rounded / multiple * multiple)
1218}
1219
1220#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1221pub struct ProgramTensorSpec {
1222    pub dimensions: Vec<u64>,
1223    pub element_type: ElementType,
1224    pub layout: ResolvedTensorLayout,
1225}
1226
1227impl ProgramTensorSpec {
1228    pub fn validate(&self, field: &str) -> Result<(), VNextError> {
1229        super::ResolvedTensorSpec::new(
1230            self.dimensions.clone(),
1231            self.element_type,
1232            self.layout.clone(),
1233        )
1234        .map(|_| ())
1235        .map_err(|error| VNextError::InvalidExecutionPlan {
1236            reason: format!("{field} is invalid: {error}"),
1237        })
1238    }
1239
1240    pub fn byte_len(&self) -> Result<u64, VNextError> {
1241        checked_elements(&self.dimensions)
1242            .and_then(|elements| elements.checked_mul(self.element_type.size_bytes()))
1243            .ok_or_else(|| VNextError::InvalidExecutionPlan {
1244                reason: "program tensor byte size overflows u64".to_owned(),
1245            })
1246    }
1247}
1248
1249#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1250pub struct WeightReference {
1251    pub weight_id: WeightId,
1252    pub value_id: ProgramValueId,
1253    pub tensor: ProgramTensorSpec,
1254}
1255
1256#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1257pub struct StateSpec {
1258    pub id: StateId,
1259    pub value_id: ProgramValueId,
1260    /// Logical tensor view consumed by one operation. It does not select a
1261    /// physical allocator, addressability profile, or backing pool.
1262    pub tensor: ProgramTensorSpec,
1263    pub lifetime: StateLifetime,
1264    pub capacity_demand: StateCapacityDemand,
1265    /// Initial contents required when a new logical state scope acquires
1266    /// physical backing. This is semantic model state, not an allocator hint.
1267    pub initialization: StateInitialization,
1268}
1269
1270#[derive(Deserialize)]
1271#[serde(deny_unknown_fields)]
1272struct StateSpecWire {
1273    id: StateId,
1274    value_id: ProgramValueId,
1275    tensor: ProgramTensorSpec,
1276    lifetime: StateLifetime,
1277    capacity_demand: StateCapacityDemand,
1278    initialization: StateInitialization,
1279}
1280
1281impl<'de> Deserialize<'de> for StateSpec {
1282    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1283    where
1284        D: Deserializer<'de>,
1285    {
1286        let wire = StateSpecWire::deserialize(deserializer)?;
1287        wire.tensor
1288            .validate("state_spec.tensor")
1289            .and_then(|()| wire.capacity_demand.validate(wire.tensor.byte_len()?))
1290            .map_err(serde::de::Error::custom)?;
1291        Ok(Self {
1292            id: wire.id,
1293            value_id: wire.value_id,
1294            tensor: wire.tensor,
1295            lifetime: wire.lifetime,
1296            capacity_demand: wire.capacity_demand,
1297            initialization: wire.initialization,
1298        })
1299    }
1300}
1301
1302#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1303#[serde(rename_all = "snake_case")]
1304pub enum StateLifetime {
1305    Request,
1306    Sequence,
1307    Step,
1308}
1309
1310/// Backend-neutral capacity formula for semantic state. This deliberately says
1311/// nothing about pages, blocks, allocator kind, or provider-visible regions.
1312/// Concrete physical storage is selected only while building an execution
1313/// plan from provider requirements, runtime offers, and typed policy.
1314#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
1315#[serde(rename_all = "snake_case")]
1316pub enum StateCapacityDemand {
1317    FixedPerScope,
1318    TokenScaled {
1319        bytes_per_token: u64,
1320        maximum_tokens: u64,
1321    },
1322}
1323
1324#[derive(Deserialize)]
1325#[serde(rename_all = "snake_case", deny_unknown_fields)]
1326enum StateCapacityDemandWire {
1327    FixedPerScope,
1328    TokenScaled {
1329        bytes_per_token: u64,
1330        maximum_tokens: u64,
1331    },
1332}
1333
1334impl<'de> Deserialize<'de> for StateCapacityDemand {
1335    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1336    where
1337        D: Deserializer<'de>,
1338    {
1339        let demand = match StateCapacityDemandWire::deserialize(deserializer)? {
1340            StateCapacityDemandWire::FixedPerScope => Self::FixedPerScope,
1341            StateCapacityDemandWire::TokenScaled {
1342                bytes_per_token,
1343                maximum_tokens,
1344            } => Self::TokenScaled {
1345                bytes_per_token,
1346                maximum_tokens,
1347            },
1348        };
1349        demand.validate(1).map_err(serde::de::Error::custom)?;
1350        Ok(demand)
1351    }
1352}
1353
1354impl StateCapacityDemand {
1355    pub fn validate(self, tensor_minimum_bytes: u64) -> Result<(), VNextError> {
1356        let valid = match self {
1357            Self::FixedPerScope => tensor_minimum_bytes > 0,
1358            Self::TokenScaled {
1359                bytes_per_token,
1360                maximum_tokens,
1361            } => {
1362                bytes_per_token >= tensor_minimum_bytes
1363                    && maximum_tokens > 0
1364                    && bytes_per_token.checked_mul(maximum_tokens).is_some()
1365            }
1366        };
1367        if !valid {
1368            return Err(VNextError::InvalidExecutionPlan {
1369                reason: "state resource demand is zero, smaller than its tensor, or overflows u64"
1370                    .to_owned(),
1371            });
1372        }
1373        Ok(())
1374    }
1375
1376    pub fn minimum_bytes(self, tensor_minimum_bytes: u64) -> Result<u64, VNextError> {
1377        self.validate(tensor_minimum_bytes)?;
1378        Ok(match self {
1379            Self::FixedPerScope => tensor_minimum_bytes,
1380            Self::TokenScaled {
1381                bytes_per_token, ..
1382            } => bytes_per_token,
1383        })
1384    }
1385
1386    pub fn theoretical_bytes(self, tensor_minimum_bytes: u64) -> Result<u64, VNextError> {
1387        self.validate(tensor_minimum_bytes)?;
1388        match self {
1389            Self::FixedPerScope => Ok(tensor_minimum_bytes),
1390            Self::TokenScaled {
1391                bytes_per_token,
1392                maximum_tokens,
1393            } => bytes_per_token.checked_mul(maximum_tokens).ok_or_else(|| {
1394                VNextError::InvalidExecutionPlan {
1395                    reason: "token-scaled state demand overflows u64".to_owned(),
1396                }
1397            }),
1398        }
1399    }
1400}
1401
1402#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1403#[serde(rename_all = "snake_case")]
1404pub enum ProgramNodeWorkSpec {
1405    Fixed,
1406    Tokens { value_id: ProgramValueId, axis: u32 },
1407}
1408
1409impl ProgramNodeWorkSpec {
1410    pub fn tokens(value_id: ProgramValueId, axis: u32) -> Self {
1411        Self::Tokens { value_id, axis }
1412    }
1413}
1414
1415#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1416pub struct ProgramNode {
1417    pub id: NodeId,
1418    pub operation_id: OperationId,
1419    pub required_version: ContractVersion,
1420    pub work: ProgramNodeWorkSpec,
1421    pub inputs: Vec<ProgramValueId>,
1422    pub outputs: Vec<ProgramValueId>,
1423    pub attributes: BTreeMap<AttributeId, SemanticValue>,
1424}
1425
1426#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1427pub struct ProgramBlock {
1428    pub id: String,
1429    pub nodes: Vec<ProgramNode>,
1430}
1431
1432/// Backend-free semantic program for a model family.
1433#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1434pub struct ModelProgram {
1435    family_id: ModelFamilyId,
1436    inputs: Vec<ProgramValueId>,
1437    blocks: Vec<ProgramBlock>,
1438    states: Vec<StateSpec>,
1439    weights: Vec<WeightReference>,
1440    outputs: Vec<ProgramValueId>,
1441}
1442
1443#[derive(Deserialize)]
1444#[serde(deny_unknown_fields)]
1445struct ModelProgramWire {
1446    family_id: ModelFamilyId,
1447    inputs: Vec<ProgramValueId>,
1448    blocks: Vec<ProgramBlock>,
1449    states: Vec<StateSpec>,
1450    weights: Vec<WeightReference>,
1451    outputs: Vec<ProgramValueId>,
1452}
1453
1454impl<'de> Deserialize<'de> for ModelProgram {
1455    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1456    where
1457        D: Deserializer<'de>,
1458    {
1459        let wire = ModelProgramWire::deserialize(deserializer)?;
1460        Self::new(
1461            wire.family_id,
1462            wire.inputs,
1463            wire.blocks,
1464            wire.states,
1465            wire.weights,
1466            wire.outputs,
1467        )
1468        .map_err(serde::de::Error::custom)
1469    }
1470}
1471
1472impl ModelProgram {
1473    pub fn new(
1474        family_id: ModelFamilyId,
1475        inputs: Vec<ProgramValueId>,
1476        blocks: Vec<ProgramBlock>,
1477        mut states: Vec<StateSpec>,
1478        mut weights: Vec<WeightReference>,
1479        outputs: Vec<ProgramValueId>,
1480    ) -> Result<Self, VNextError> {
1481        if blocks.is_empty() {
1482            return Err(VNextError::InvalidModelConfig {
1483                family_id: family_id.to_string(),
1484                field: "program.blocks".to_owned(),
1485                reason: "at least one block is required".to_owned(),
1486            });
1487        }
1488        let mut known_values = BTreeSet::new();
1489        if inputs.is_empty()
1490            || inputs
1491                .iter()
1492                .any(|input| !known_values.insert(input.clone()))
1493        {
1494            return Err(VNextError::InvalidModelConfig {
1495                family_id: family_id.to_string(),
1496                field: "program.inputs".to_owned(),
1497                reason: "input identities must be non-empty and unique".to_owned(),
1498            });
1499        }
1500        let mut block_ids = BTreeSet::new();
1501        let mut node_ids = BTreeSet::new();
1502        for state in &states {
1503            let tensor_valid = state
1504                .tensor
1505                .validate(&format!("program.states.{}.tensor", state.id))
1506                .and_then(|()| state.capacity_demand.validate(state.tensor.byte_len()?));
1507            if tensor_valid.is_err() || !known_values.insert(state.value_id.clone()) {
1508                return Err(VNextError::InvalidModelConfig {
1509                    family_id: family_id.to_string(),
1510                    field: "program.states.value_id".to_owned(),
1511                    reason: format!("duplicate value `{}`", state.value_id),
1512                });
1513            }
1514        }
1515        let mut weight_ids = BTreeSet::new();
1516        for weight in &weights {
1517            if !weight_ids.insert(weight.weight_id.clone())
1518                || !known_values.insert(weight.value_id.clone())
1519            {
1520                return Err(VNextError::InvalidModelConfig {
1521                    family_id: family_id.to_string(),
1522                    field: "program.weights".to_owned(),
1523                    reason: format!(
1524                        "duplicate weight `{}` or value `{}`",
1525                        weight.weight_id, weight.value_id
1526                    ),
1527                });
1528            }
1529            weight
1530                .tensor
1531                .validate(&format!("program.weights.{}.tensor", weight.weight_id))?;
1532        }
1533        for block in &blocks {
1534            if block.id.is_empty() || block.nodes.is_empty() || !block_ids.insert(block.id.clone())
1535            {
1536                return Err(VNextError::InvalidModelConfig {
1537                    family_id: family_id.to_string(),
1538                    field: "program.blocks.id".to_owned(),
1539                    reason: "block identities must be non-empty and unique".to_owned(),
1540                });
1541            }
1542            for node in &block.nodes {
1543                if node.required_version.major == 0 || node.outputs.is_empty() {
1544                    return Err(VNextError::InvalidModelConfig {
1545                        family_id: family_id.to_string(),
1546                        field: "program.nodes.contract".to_owned(),
1547                        reason: format!("node `{}` has an invalid version or no outputs", node.id),
1548                    });
1549                }
1550                for value in node.attributes.values() {
1551                    value.validate(&format!("program node `{}` attributes", node.id))?;
1552                }
1553                if !node_ids.insert(node.id.clone()) {
1554                    return Err(VNextError::InvalidModelConfig {
1555                        family_id: family_id.to_string(),
1556                        field: "program.nodes.id".to_owned(),
1557                        reason: format!("duplicate node `{}`", node.id),
1558                    });
1559                }
1560                if let ProgramNodeWorkSpec::Tokens { value_id, .. } = &node.work {
1561                    let source_count = node
1562                        .inputs
1563                        .iter()
1564                        .chain(&node.outputs)
1565                        .filter(|candidate| *candidate == value_id)
1566                        .count();
1567                    let is_state_or_weight = states.iter().any(|state| state.value_id == *value_id)
1568                        || weights.iter().any(|weight| weight.value_id == *value_id);
1569                    if source_count != 1 || is_state_or_weight {
1570                        return Err(VNextError::InvalidModelConfig {
1571                            family_id: family_id.to_string(),
1572                            field: "program.nodes.work".to_owned(),
1573                            reason: format!(
1574                                "node `{}` token work source must identify one activation binding",
1575                                node.id
1576                            ),
1577                        });
1578                    }
1579                }
1580                if node
1581                    .inputs
1582                    .iter()
1583                    .any(|input| !known_values.contains(input))
1584                {
1585                    return Err(VNextError::InvalidModelConfig {
1586                        family_id: family_id.to_string(),
1587                        field: "program.nodes.inputs".to_owned(),
1588                        reason: format!("node `{}` references an unknown input", node.id),
1589                    });
1590                }
1591                for output in &node.outputs {
1592                    if !known_values.insert(output.clone()) {
1593                        return Err(VNextError::InvalidModelConfig {
1594                            family_id: family_id.to_string(),
1595                            field: "program.nodes.outputs".to_owned(),
1596                            reason: format!("value `{output}` has multiple producers"),
1597                        });
1598                    }
1599                }
1600            }
1601        }
1602        let mut state_ids = BTreeSet::new();
1603        if states
1604            .iter()
1605            .any(|state| !state_ids.insert(state.id.clone()))
1606        {
1607            return Err(VNextError::InvalidModelConfig {
1608                family_id: family_id.to_string(),
1609                field: "program.states.id".to_owned(),
1610                reason: "state identities must be unique".to_owned(),
1611            });
1612        }
1613        let mut output_ids = BTreeSet::new();
1614        if outputs.is_empty()
1615            || outputs
1616                .iter()
1617                .any(|output| !known_values.contains(output) || !output_ids.insert(output.clone()))
1618        {
1619            return Err(VNextError::InvalidModelConfig {
1620                family_id: family_id.to_string(),
1621                field: "program.outputs".to_owned(),
1622                reason: "program outputs must be non-empty, known, and unique".to_owned(),
1623            });
1624        }
1625        states.sort_by(|left, right| left.id.cmp(&right.id));
1626        weights.sort_by(|left, right| left.weight_id.cmp(&right.weight_id));
1627        Ok(Self {
1628            family_id,
1629            inputs,
1630            blocks,
1631            states,
1632            weights,
1633            outputs,
1634        })
1635    }
1636
1637    pub fn family_id(&self) -> &ModelFamilyId {
1638        &self.family_id
1639    }
1640
1641    pub fn inputs(&self) -> &[ProgramValueId] {
1642        &self.inputs
1643    }
1644
1645    pub fn blocks(&self) -> &[ProgramBlock] {
1646        &self.blocks
1647    }
1648
1649    pub fn states(&self) -> &[StateSpec] {
1650        &self.states
1651    }
1652
1653    pub fn weights(&self) -> &[WeightReference] {
1654        &self.weights
1655    }
1656
1657    pub fn outputs(&self) -> &[ProgramValueId] {
1658        &self.outputs
1659    }
1660
1661    pub fn fingerprint(&self) -> Result<String, VNextError> {
1662        let bytes = serde_json::to_vec(self).map_err(|error| VNextError::Serialization {
1663            context: "serialize model program",
1664            message: error.to_string(),
1665        })?;
1666        Ok(format!("{:x}", Sha256::digest(bytes)))
1667    }
1668}
1669
1670impl WeightSchema {
1671    pub fn validate_program_references(
1672        &self,
1673        family_id: &ModelFamilyId,
1674        program: &ModelProgram,
1675    ) -> Result<(), VNextError> {
1676        if program.family_id() != family_id {
1677            return Err(VNextError::InvalidModelConfig {
1678                family_id: family_id.to_string(),
1679                field: "program.family_id".to_owned(),
1680                reason: "program family does not match the weight schema owner".to_owned(),
1681            });
1682        }
1683        let schema_weights = self
1684            .tensors
1685            .iter()
1686            .map(|tensor| (&tensor.id, tensor.required))
1687            .collect::<BTreeMap<_, _>>();
1688        let referenced_weights = program
1689            .weights()
1690            .iter()
1691            .map(|reference| &reference.weight_id)
1692            .collect::<BTreeSet<_>>();
1693        if let Some(weight_id) = referenced_weights
1694            .iter()
1695            .find(|weight_id| !schema_weights.contains_key(**weight_id))
1696        {
1697            return Err(VNextError::InvalidModelConfig {
1698                family_id: family_id.to_string(),
1699                field: "program.weights".to_owned(),
1700                reason: format!("program references unknown weight `{weight_id}`"),
1701            });
1702        }
1703        if let Some(weight_id) = schema_weights.iter().find_map(|(weight_id, required)| {
1704            (*required && !referenced_weights.contains(weight_id)).then_some(*weight_id)
1705        }) {
1706            return Err(VNextError::InvalidModelConfig {
1707                family_id: family_id.to_string(),
1708                field: "program.weights".to_owned(),
1709                reason: format!("program does not reference required weight `{weight_id}`"),
1710            });
1711        }
1712        for reference in program.weights() {
1713            let tensor = self.tensor(&reference.weight_id).ok_or_else(|| {
1714                VNextError::InvalidModelConfig {
1715                    family_id: family_id.to_string(),
1716                    field: "program.weights".to_owned(),
1717                    reason: format!(
1718                        "program references unknown weight `{}`",
1719                        reference.weight_id
1720                    ),
1721                }
1722            })?;
1723            if reference.tensor.dimensions != tensor.dimensions
1724                || reference.tensor.element_type != tensor.logical_element_type
1725            {
1726                return Err(VNextError::InvalidModelConfig {
1727                    family_id: family_id.to_string(),
1728                    field: format!("program.weights.{}.tensor", reference.weight_id),
1729                    reason: "program value shape or dtype differs from the logical weight schema"
1730                        .to_owned(),
1731                });
1732            }
1733        }
1734        Ok(())
1735    }
1736}
1737
1738#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1739pub struct TemplateMetadata {
1740    pub template: String,
1741    pub source_file: String,
1742    pub sha256: String,
1743}
1744
1745#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
1746#[serde(rename_all = "snake_case")]
1747pub enum SpecialTokenRole {
1748    Bos,
1749    Eos,
1750    Pad,
1751    Stop,
1752}
1753
1754#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize)]
1755pub struct SpecialTokenCollision {
1756    first: SpecialTokenRole,
1757    second: SpecialTokenRole,
1758}
1759
1760#[derive(Deserialize)]
1761#[serde(deny_unknown_fields)]
1762struct SpecialTokenCollisionWire {
1763    first: SpecialTokenRole,
1764    second: SpecialTokenRole,
1765}
1766
1767impl<'de> Deserialize<'de> for SpecialTokenCollision {
1768    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1769    where
1770        D: Deserializer<'de>,
1771    {
1772        let wire = SpecialTokenCollisionWire::deserialize(deserializer)?;
1773        Self::new(wire.first, wire.second).map_err(serde::de::Error::custom)
1774    }
1775}
1776
1777impl SpecialTokenCollision {
1778    pub fn new(first: SpecialTokenRole, second: SpecialTokenRole) -> Result<Self, VNextError> {
1779        if first == second {
1780            return Err(VNextError::InvalidExecutionPlan {
1781                reason: "a special-token collision must name two different roles".to_owned(),
1782            });
1783        }
1784        let (first, second) = if first < second {
1785            (first, second)
1786        } else {
1787            (second, first)
1788        };
1789        Ok(Self { first, second })
1790    }
1791
1792    pub const fn first(&self) -> SpecialTokenRole {
1793        self.first
1794    }
1795
1796    pub const fn second(&self) -> SpecialTokenRole {
1797        self.second
1798    }
1799}
1800
1801#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1802pub struct SpecialTokenCollisionPolicy {
1803    allowed: BTreeSet<SpecialTokenCollision>,
1804}
1805
1806#[derive(Deserialize)]
1807#[serde(deny_unknown_fields)]
1808struct SpecialTokenCollisionPolicyWire {
1809    allowed: BTreeSet<SpecialTokenCollision>,
1810}
1811
1812impl<'de> Deserialize<'de> for SpecialTokenCollisionPolicy {
1813    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1814    where
1815        D: Deserializer<'de>,
1816    {
1817        let wire = SpecialTokenCollisionPolicyWire::deserialize(deserializer)?;
1818        Ok(Self::new(wire.allowed))
1819    }
1820}
1821
1822impl SpecialTokenCollisionPolicy {
1823    pub fn new(allowed: BTreeSet<SpecialTokenCollision>) -> Self {
1824        Self { allowed }
1825    }
1826
1827    pub fn require_distinct() -> Self {
1828        Self {
1829            allowed: BTreeSet::new(),
1830        }
1831    }
1832
1833    pub fn allows(&self, left: SpecialTokenRole, right: SpecialTokenRole) -> bool {
1834        SpecialTokenCollision::new(left, right)
1835            .is_ok_and(|collision| self.allowed.contains(&collision))
1836    }
1837
1838    pub fn allowed(&self) -> &BTreeSet<SpecialTokenCollision> {
1839        &self.allowed
1840    }
1841}
1842
1843#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1844pub struct SpecialTokenMetadata {
1845    pub bos_token_id: Option<u32>,
1846    pub eos_token_ids: BTreeSet<u32>,
1847    pub pad_token_id: Option<u32>,
1848    pub collision_policy: SpecialTokenCollisionPolicy,
1849}
1850
1851#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1852pub struct ModelSemanticMetadata {
1853    pub template: TemplateMetadata,
1854    pub special_tokens: SpecialTokenMetadata,
1855}
1856
1857/// Compile-time model family provider with a typed, validated configuration.
1858pub trait ModelFamilyProvider: Send + Sync {
1859    type Config: Clone + Send + Sync + Serialize + DeserializeOwned + 'static;
1860
1861    fn family_id(&self) -> &ModelFamilyId;
1862
1863    fn external_metadata_ids(&self) -> BTreeSet<ExternalModelMetadataId>;
1864
1865    fn validate_config_identity(
1866        &self,
1867        raw: &serde_json::Value,
1868        config: &Self::Config,
1869    ) -> Result<(), VNextError>;
1870
1871    /// Returns the exact external metadata identity represented by `config`.
1872    /// Every provider must make this selection explicit; core never assumes a
1873    /// singleton catalog row is the intended typed identity.
1874    fn validated_external_metadata_id(
1875        &self,
1876        raw: &serde_json::Value,
1877        config: &Self::Config,
1878    ) -> Result<ExternalModelMetadataId, VNextError>;
1879
1880    fn parse_config(&self, raw: &serde_json::Value) -> Result<Self::Config, VNextError>;
1881
1882    fn weight_schema(&self, config: &Self::Config) -> Result<WeightSchema, VNextError>;
1883
1884    fn semantic_program(&self, config: &Self::Config) -> Result<ModelProgram, VNextError>;
1885
1886    fn semantic_metadata(&self, config: &Self::Config)
1887        -> Result<ModelSemanticMetadata, VNextError>;
1888}
1889
1890/// Maximum raw JSON bytes accepted before decoding a prepared family package.
1891pub const MAX_PREPARED_MODEL_FAMILY_WIRE_BYTES: usize = 16 * 1024 * 1024;
1892
1893#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1894pub struct PreparedModelFamily {
1895    family_id: ModelFamilyId,
1896    external_metadata_id: ExternalModelMetadataId,
1897    canonical_config: serde_json::Value,
1898    config_fingerprint: String,
1899    weight_schema: WeightSchema,
1900    program: ModelProgram,
1901    metadata: ModelSemanticMetadata,
1902}
1903
1904/// Serialized prepared packages are evidence, not trusted runtime objects.
1905/// Rehydration must resolve the typed provider again and reproduce every field.
1906#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1907pub struct UnvalidatedPreparedModelFamily {
1908    family_id: ModelFamilyId,
1909    external_metadata_id: ExternalModelMetadataId,
1910    canonical_config: serde_json::Value,
1911    config_fingerprint: String,
1912    weight_schema: WeightSchema,
1913    program: ModelProgram,
1914    metadata: ModelSemanticMetadata,
1915}
1916
1917/// Crate-private serde shape used by both the top-level decoder and nested
1918/// resolved-plan wire. Public code can only obtain the explicit unvalidated
1919/// package through a byte-bounded decoder.
1920#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1921pub(crate) struct PreparedModelFamilyWire {
1922    family_id: ModelFamilyId,
1923    external_metadata_id: ExternalModelMetadataId,
1924    canonical_config: serde_json::Value,
1925    config_fingerprint: String,
1926    weight_schema: WeightSchema,
1927    program: ModelProgram,
1928    metadata: ModelSemanticMetadata,
1929}
1930
1931#[derive(Deserialize, Serialize)]
1932#[serde(deny_unknown_fields)]
1933struct PreparedModelFamilyWireFields {
1934    family_id: ModelFamilyId,
1935    external_metadata_id: ExternalModelMetadataId,
1936    canonical_config: serde_json::Value,
1937    config_fingerprint: String,
1938    weight_schema: WeightSchema,
1939    program: ModelProgram,
1940    metadata: ModelSemanticMetadata,
1941}
1942
1943impl<'de> Deserialize<'de> for PreparedModelFamilyWire {
1944    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1945    where
1946        D: Deserializer<'de>,
1947    {
1948        let raw = serde_json::Value::deserialize(deserializer)?;
1949        let fields =
1950            PreparedModelFamilyWireFields::deserialize(&raw).map_err(serde::de::Error::custom)?;
1951        let canonical = serde_json::to_value(&fields).map_err(serde::de::Error::custom)?;
1952        if canonical != raw {
1953            return Err(serde::de::Error::custom(
1954                "prepared model family wire contains unknown or non-canonical nested fields",
1955            ));
1956        }
1957        Ok(Self {
1958            family_id: fields.family_id,
1959            external_metadata_id: fields.external_metadata_id,
1960            canonical_config: fields.canonical_config,
1961            config_fingerprint: fields.config_fingerprint,
1962            weight_schema: fields.weight_schema,
1963            program: fields.program,
1964            metadata: fields.metadata,
1965        })
1966    }
1967}
1968
1969impl From<PreparedModelFamilyWire> for UnvalidatedPreparedModelFamily {
1970    fn from(wire: PreparedModelFamilyWire) -> Self {
1971        Self {
1972            family_id: wire.family_id,
1973            external_metadata_id: wire.external_metadata_id,
1974            canonical_config: wire.canonical_config,
1975            config_fingerprint: wire.config_fingerprint,
1976            weight_schema: wire.weight_schema,
1977            program: wire.program,
1978            metadata: wire.metadata,
1979        }
1980    }
1981}
1982
1983impl UnvalidatedPreparedModelFamily {
1984    pub fn revalidate(
1985        self,
1986        registry: &dyn ModelFamilyRegistry,
1987    ) -> Result<PreparedModelFamily, VNextError> {
1988        let registration = registry.resolve(&self.family_id)?;
1989        if registration.family_id() != &self.family_id {
1990            return Err(VNextError::InvalidModelConfig {
1991                family_id: self.family_id.to_string(),
1992                field: "registration.family_id".to_owned(),
1993                reason: "registry returned a registration for a different family".to_owned(),
1994            });
1995        }
1996        let metadata_registration = registry.resolve_external(&self.external_metadata_id)?;
1997        if !std::ptr::eq(registration, metadata_registration) {
1998            return Err(VNextError::InvalidModelConfig {
1999                family_id: self.family_id.to_string(),
2000                field: "external_metadata_id".to_owned(),
2001                reason: "external metadata identity resolves to a different family registration"
2002                    .to_owned(),
2003            });
2004        }
2005        let rebuilt = registration.prepare(&self.canonical_config)?;
2006        let exact_match = rebuilt.family_id == self.family_id
2007            && rebuilt.external_metadata_id == self.external_metadata_id
2008            && rebuilt.canonical_config == self.canonical_config
2009            && rebuilt.config_fingerprint == self.config_fingerprint
2010            && rebuilt.weight_schema == self.weight_schema
2011            && rebuilt.program == self.program
2012            && rebuilt.metadata == self.metadata;
2013        if !exact_match {
2014            return Err(VNextError::InvalidModelConfig {
2015                family_id: self.family_id.to_string(),
2016                field: "prepared_package".to_owned(),
2017                reason: "serialized package differs from the typed provider reconstruction"
2018                    .to_owned(),
2019            });
2020        }
2021        Ok(rebuilt)
2022    }
2023}
2024
2025impl PreparedModelFamily {
2026    fn from_canonical_config(
2027        family_id: ModelFamilyId,
2028        external_metadata_id: ExternalModelMetadataId,
2029        canonical_config: serde_json::Value,
2030        mut weight_schema: WeightSchema,
2031        program: ModelProgram,
2032        metadata: ModelSemanticMetadata,
2033    ) -> Result<Self, VNextError> {
2034        if !canonical_config.is_object()
2035            || canonicalize_json(canonical_config.clone()) != canonical_config
2036        {
2037            return Err(VNextError::InvalidModelConfig {
2038                family_id: family_id.to_string(),
2039                field: "config".to_owned(),
2040                reason: "prepared config must be a canonical JSON object".to_owned(),
2041            });
2042        }
2043        let config_bytes =
2044            serde_json::to_vec(&canonical_config).map_err(|error| VNextError::Serialization {
2045                context: "serialize canonical model family config",
2046                message: error.to_string(),
2047            })?;
2048        let config_fingerprint = format!("{:x}", Sha256::digest(config_bytes));
2049        weight_schema.validate(&family_id)?;
2050        weight_schema.normalize();
2051        weight_schema.validate(&family_id)?;
2052        if program.family_id() != &family_id {
2053            return Err(VNextError::InvalidModelConfig {
2054                family_id: family_id.to_string(),
2055                field: "program.family_id".to_owned(),
2056                reason: "program family does not match prepared family".to_owned(),
2057            });
2058        }
2059        weight_schema.validate_program_references(&family_id, &program)?;
2060        Self::validate_metadata(&family_id, &metadata)?;
2061        Ok(Self {
2062            family_id,
2063            external_metadata_id,
2064            canonical_config,
2065            config_fingerprint,
2066            weight_schema,
2067            program,
2068            metadata,
2069        })
2070    }
2071
2072    fn validate_metadata(
2073        family_id: &ModelFamilyId,
2074        metadata: &ModelSemanticMetadata,
2075    ) -> Result<(), VNextError> {
2076        let source = metadata.template.source_file.as_str();
2077        let valid_source = !source.is_empty()
2078            && !source.starts_with('/')
2079            && !source.contains('\\')
2080            && source
2081                .split('/')
2082                .all(|component| !matches!(component, "" | "." | ".."));
2083        if metadata.template.template.is_empty()
2084            || !valid_source
2085            || !is_canonical_sha256(&metadata.template.sha256)
2086            || metadata.special_tokens.eos_token_ids.is_empty()
2087        {
2088            return Err(VNextError::InvalidModelConfig {
2089                family_id: family_id.to_string(),
2090                field: "semantic_metadata".to_owned(),
2091                reason: "template, source, checksum, and end tokens must be explicit and valid"
2092                    .to_owned(),
2093            });
2094        }
2095        Ok(())
2096    }
2097
2098    pub fn family_id(&self) -> &ModelFamilyId {
2099        &self.family_id
2100    }
2101
2102    pub fn external_metadata_id(&self) -> &ExternalModelMetadataId {
2103        &self.external_metadata_id
2104    }
2105
2106    pub fn canonical_config(&self) -> &serde_json::Value {
2107        &self.canonical_config
2108    }
2109
2110    pub fn config_fingerprint(&self) -> &str {
2111        &self.config_fingerprint
2112    }
2113
2114    pub fn weight_schema(&self) -> &WeightSchema {
2115        &self.weight_schema
2116    }
2117
2118    pub fn program(&self) -> &ModelProgram {
2119        &self.program
2120    }
2121
2122    pub fn metadata(&self) -> &ModelSemanticMetadata {
2123        &self.metadata
2124    }
2125
2126    pub fn fingerprint(&self) -> Result<String, VNextError> {
2127        let bytes = serde_json::to_vec(self).map_err(|error| VNextError::Serialization {
2128            context: "serialize prepared model family",
2129            message: error.to_string(),
2130        })?;
2131        Ok(format!("{:x}", Sha256::digest(bytes)))
2132    }
2133
2134    pub fn decode_untrusted(bytes: &[u8]) -> Result<UnvalidatedPreparedModelFamily, VNextError> {
2135        if bytes.len() > MAX_PREPARED_MODEL_FAMILY_WIRE_BYTES {
2136            return Err(VNextError::Serialization {
2137                context: "decode untrusted prepared model family",
2138                message: format!(
2139                    "payload has {} bytes; maximum is {MAX_PREPARED_MODEL_FAMILY_WIRE_BYTES}",
2140                    bytes.len()
2141                ),
2142            });
2143        }
2144        serde_json::from_slice::<PreparedModelFamilyWire>(bytes)
2145            .map(Into::into)
2146            .map_err(|error| VNextError::Serialization {
2147                context: "decode untrusted prepared model family",
2148                message: error.to_string(),
2149            })
2150    }
2151
2152    pub fn from_json_validated(
2153        bytes: &[u8],
2154        registry: &dyn ModelFamilyRegistry,
2155    ) -> Result<Self, VNextError> {
2156        Self::decode_untrusted(bytes)?.revalidate(registry)
2157    }
2158}
2159
2160fn canonicalize_json(value: serde_json::Value) -> serde_json::Value {
2161    match value {
2162        serde_json::Value::Array(values) => {
2163            serde_json::Value::Array(values.into_iter().map(canonicalize_json).collect())
2164        }
2165        serde_json::Value::Object(values) => {
2166            let sorted = values
2167                .into_iter()
2168                .map(|(key, value)| (key, canonicalize_json(value)))
2169                .collect::<BTreeMap<_, _>>();
2170            serde_json::Value::Object(sorted.into_iter().collect())
2171        }
2172        other => other,
2173    }
2174}
2175
2176fn validate_raw_config_consumed(
2177    family_id: &ModelFamilyId,
2178    raw: &serde_json::Value,
2179    typed: &serde_json::Value,
2180) -> Result<(), VNextError> {
2181    // Typed serialization may add explicit provider defaults. Every caller-
2182    // supplied value must still survive at the same path with the same JSON
2183    // value; serde's default unknown-field behavior cannot erase input here.
2184    fn walk(raw: &serde_json::Value, typed: &serde_json::Value, path: &str) -> Option<String> {
2185        match (raw, typed) {
2186            (serde_json::Value::Object(raw), serde_json::Value::Object(typed)) => {
2187                for (key, raw_value) in raw {
2188                    let next = if path.is_empty() {
2189                        format!("/{key}")
2190                    } else {
2191                        format!("{path}/{key}")
2192                    };
2193                    let Some(typed_value) = typed.get(key) else {
2194                        return Some(next);
2195                    };
2196                    if let Some(rejected) = walk(raw_value, typed_value, &next) {
2197                        return Some(rejected);
2198                    }
2199                }
2200                None
2201            }
2202            (serde_json::Value::Array(raw), serde_json::Value::Array(typed))
2203                if raw.len() == typed.len() =>
2204            {
2205                raw.iter()
2206                    .zip(typed)
2207                    .enumerate()
2208                    .find_map(|(index, (raw, typed))| walk(raw, typed, &format!("{path}/{index}")))
2209            }
2210            _ if raw == typed => None,
2211            _ => Some(path.to_owned()),
2212        }
2213    }
2214
2215    if !raw.is_object() || !typed.is_object() {
2216        return Err(VNextError::InvalidModelConfig {
2217            family_id: family_id.to_string(),
2218            field: "config".to_owned(),
2219            reason: "raw and typed model configurations must be JSON objects".to_owned(),
2220        });
2221    }
2222    if let Some(path) = walk(raw, typed, "") {
2223        return Err(VNextError::InvalidModelConfig {
2224            family_id: family_id.to_string(),
2225            field: if path.is_empty() {
2226                "config".to_owned()
2227            } else {
2228                path
2229            },
2230            reason: "raw configuration field was ignored or changed by typed parsing".to_owned(),
2231        });
2232    }
2233    Ok(())
2234}
2235
2236fn is_canonical_sha256(value: &str) -> bool {
2237    value.len() == 64
2238        && value
2239            .bytes()
2240            .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))
2241}
2242
2243/// Object-safe loading-time trampoline for a heterogeneous family catalog.
2244/// The raw JSON exists only at the configuration boundary; a typed provider
2245/// validates it before producing the backend-free package.
2246pub trait ModelFamilyRegistration: Send + Sync {
2247    fn family_id(&self) -> &ModelFamilyId;
2248
2249    fn external_metadata_ids(&self) -> BTreeSet<ExternalModelMetadataId>;
2250
2251    fn prepare(&self, raw_config: &serde_json::Value) -> Result<PreparedModelFamily, VNextError>;
2252}
2253
2254pub struct TypedFamilyRegistration<P> {
2255    provider: P,
2256}
2257
2258impl<P> TypedFamilyRegistration<P> {
2259    pub fn new(provider: P) -> Self {
2260        Self { provider }
2261    }
2262}
2263
2264impl<P: ModelFamilyProvider> ModelFamilyRegistration for TypedFamilyRegistration<P> {
2265    fn family_id(&self) -> &ModelFamilyId {
2266        self.provider.family_id()
2267    }
2268
2269    fn external_metadata_ids(&self) -> BTreeSet<ExternalModelMetadataId> {
2270        self.provider.external_metadata_ids()
2271    }
2272
2273    fn prepare(&self, raw_config: &serde_json::Value) -> Result<PreparedModelFamily, VNextError> {
2274        let external_metadata_ids = self.provider.external_metadata_ids();
2275        if external_metadata_ids.is_empty() {
2276            return Err(VNextError::InvalidModelConfig {
2277                family_id: self.provider.family_id().to_string(),
2278                field: "external_metadata_ids".to_owned(),
2279                reason: "model family must declare at least one external metadata identity"
2280                    .to_owned(),
2281            });
2282        }
2283        let config = self.provider.parse_config(raw_config)?;
2284        let external_metadata_id = self
2285            .provider
2286            .validated_external_metadata_id(raw_config, &config)?;
2287        if !external_metadata_ids.contains(&external_metadata_id) {
2288            return Err(VNextError::InvalidModelConfig {
2289                family_id: self.provider.family_id().to_string(),
2290                field: "external_metadata_id".to_owned(),
2291                reason: format!(
2292                    "provider selected undeclared external metadata identity `{external_metadata_id}`"
2293                ),
2294            });
2295        }
2296        let typed_config = canonicalize_json(serde_json::to_value(&config).map_err(|error| {
2297            VNextError::Serialization {
2298                context: "serialize typed model configuration",
2299                message: error.to_string(),
2300            }
2301        })?);
2302        validate_raw_config_consumed(self.provider.family_id(), raw_config, &typed_config)?;
2303        let weight_schema = self.provider.weight_schema(&config)?;
2304        let program = self.provider.semantic_program(&config)?;
2305        let metadata = self.provider.semantic_metadata(&config)?;
2306        PreparedModelFamily::from_canonical_config(
2307            self.provider.family_id().clone(),
2308            external_metadata_id,
2309            typed_config,
2310            weight_schema,
2311            program,
2312            metadata,
2313        )
2314    }
2315}
2316
2317pub trait ModelFamilyRegistry: Send + Sync {
2318    /// Returns the complete trusted catalog. Core owns all identity lookup so a
2319    /// registry cannot silently fall back or hide ambiguous registrations.
2320    fn registrations(&self) -> Vec<&dyn ModelFamilyRegistration>;
2321}
2322
2323impl dyn ModelFamilyRegistry + '_ {
2324    pub fn resolve(
2325        &self,
2326        family_id: &ModelFamilyId,
2327    ) -> Result<&dyn ModelFamilyRegistration, VNextError> {
2328        let matches = self
2329            .registrations()
2330            .into_iter()
2331            .filter(|registration| registration.family_id() == family_id)
2332            .collect::<Vec<_>>();
2333        match matches.as_slice() {
2334            [] => Err(VNextError::UnknownModelFamily {
2335                family_id: family_id.to_string(),
2336            }),
2337            [registration] => Ok(*registration),
2338            _ => Err(VNextError::AmbiguousModelFamilyRegistration {
2339                identity_kind: "internal family",
2340                identity: family_id.to_string(),
2341                matches: matches.len(),
2342            }),
2343        }
2344    }
2345
2346    pub fn resolve_external(
2347        &self,
2348        metadata_id: &ExternalModelMetadataId,
2349    ) -> Result<&dyn ModelFamilyRegistration, VNextError> {
2350        let matches = self
2351            .registrations()
2352            .into_iter()
2353            .filter(|registration| registration.external_metadata_ids().contains(metadata_id))
2354            .collect::<Vec<_>>();
2355        match matches.as_slice() {
2356            [] => Err(VNextError::UnknownExternalModelMetadata {
2357                metadata_id: metadata_id.to_string(),
2358            }),
2359            [registration] => Ok(*registration),
2360            _ => Err(VNextError::AmbiguousModelFamilyRegistration {
2361                identity_kind: "external metadata",
2362                identity: metadata_id.to_string(),
2363                matches: matches.len(),
2364            }),
2365        }
2366    }
2367}
2368
2369#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
2370pub struct TokenizerDescriptor {
2371    pub tokenizer_id: TokenizerId,
2372    pub source_file: String,
2373    pub sha256: String,
2374    pub vocabulary_size: u64,
2375}