Skip to main content

animsmith_core/
engine_contract.rs

1//! Registry-independent engine profile facts and resolved settings.
2//!
3//! These wire values deliberately mirror the closed V1 vocabulary owned by
4//! `animsmith-engine` without making core depend on that crate. Their
5//! canonical encoders preserve the byte preimages introduced with the V1
6//! engine registry.
7
8use crate::bounded_deserialize::{
9    BudgetedCappedSequenceSeed, CappedSequence, RowBudget, consume_ignored_tail,
10    deserialize_capped_sequence,
11};
12use crate::{InputIdentity, SourceFormatV1};
13use serde::de::{DeserializeSeed, IgnoredAny, MapAccess, SeqAccess, Visitor};
14use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error as _};
15use std::collections::BTreeSet;
16use std::fmt;
17use std::marker::PhantomData;
18
19/// Semantic contract for a self-contained V1 engine profile record.
20pub const ENGINE_PROFILE_FACTS_V1_ID: &str = "urn:animsmith:engine-profile-facts:1";
21/// Semantic contract for the extended immutable engine-profile vocabulary.
22pub const ENGINE_PROFILE_FACTS_V2_ID: &str = "urn:animsmith:engine-profile-facts:2";
23/// Semantic contract for fully materialized V1 engine settings.
24pub const RESOLVED_ENGINE_SETTINGS_V1_ID: &str = "urn:animsmith:resolved-engine-settings:1";
25/// Semantic contract for bounded, explicitly partial V2 engine settings.
26pub const RESOLVED_ENGINE_SETTINGS_V2_ID: &str = "urn:animsmith:resolved-engine-settings:2";
27/// Semantic contract for origin-bearing resolved engine settings.
28pub const RESOLVED_ENGINE_SETTINGS_V3_ID: &str = "urn:animsmith:resolved-engine-settings:3";
29/// Maximum rows in any individual V1 profile or resolved-settings collection.
30pub const ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS: usize = 4_096;
31/// Maximum aggregate profile and materialized-setting rows retained by one lint file.
32pub const ENGINE_CONTRACT_V1_MAX_AGGREGATE_ROWS: usize = 65_536;
33/// Maximum UTF-8 bytes in one retained profile/settings string.
34pub const ENGINE_CONTRACT_V1_MAX_TEXT_BYTES: usize = 4_096;
35/// Maximum aggregate UTF-8 bytes retained by V1 provenance and predictions in one lint file.
36pub const ENGINE_CONTRACT_V1_MAX_TOTAL_TEXT_BYTES: usize = 8 * 1024 * 1024;
37
38const ENGINE_FACTS_PREIMAGE_DOMAIN: &str = "animsmith-engine-facts-v1";
39const ENGINE_SETTINGS_PREIMAGE_DOMAIN: &str = "animsmith-engine-settings-v1";
40const ENGINE_FACTS_V2_PREIMAGE_DOMAIN: &str = "animsmith-engine-facts-v2";
41const ENGINE_SETTINGS_V3_PREIMAGE_DOMAIN: &str = "animsmith-engine-settings-v3";
42
43fn deserialize_collection_rows<'de, D, T>(deserializer: D) -> Result<CappedSequence<T>, D::Error>
44where
45    D: Deserializer<'de>,
46    T: Deserialize<'de>,
47{
48    deserialize_capped_sequence(deserializer, ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS)
49}
50
51fn deserialize_collection_vec<'de, D, T>(deserializer: D) -> Result<Vec<T>, D::Error>
52where
53    D: Deserializer<'de>,
54    T: Deserialize<'de>,
55{
56    let values = deserialize_capped_sequence(deserializer, ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS)?;
57    if values.overflowed {
58        return Err(D::Error::custom(
59            "engine-contract collection exceeds 4096 rows",
60        ));
61    }
62    Ok(values.values)
63}
64
65#[derive(Debug)]
66struct ProfileRows {
67    local: RowBudget,
68    provenance: Option<RowBudget>,
69}
70
71impl ProfileRows {
72    fn new(provenance_limit: Option<usize>) -> Self {
73        Self {
74            local: RowBudget::new(ENGINE_CONTRACT_V1_MAX_AGGREGATE_ROWS),
75            provenance: provenance_limit.map(RowBudget::new),
76        }
77    }
78
79    fn admit_top_level(&mut self) -> bool {
80        if !self.local.admit() {
81            return false;
82        }
83        self.provenance.as_mut().is_none_or(RowBudget::admit)
84    }
85
86    fn provenance_overflowed(&self) -> bool {
87        self.provenance.as_ref().is_some_and(RowBudget::overflowed)
88    }
89}
90
91#[derive(Debug)]
92struct SettingsRows {
93    local: RowBudget,
94    provenance: Option<RowBudget>,
95}
96
97impl SettingsRows {
98    fn new(provenance_limit: Option<usize>) -> Self {
99        Self {
100            local: RowBudget::new(ENGINE_CONTRACT_V1_MAX_AGGREGATE_ROWS),
101            provenance: provenance_limit.map(RowBudget::new),
102        }
103    }
104
105    fn admit_clip(&mut self) -> bool {
106        self.local.admit()
107    }
108
109    fn admit_setting(&mut self) -> bool {
110        if !self.local.admit() {
111            return false;
112        }
113        self.provenance.as_mut().is_none_or(RowBudget::admit)
114    }
115
116    fn provenance_overflowed(&self) -> bool {
117        self.provenance.as_ref().is_some_and(RowBudget::overflowed)
118    }
119}
120
121/// Fixed-field-order token encoder shared by V1 prediction identities.
122///
123/// Each token is prefixed by its unsigned eight-byte big-endian UTF-8 byte
124/// length. Counts are decimal UTF-8 tokens.
125#[derive(Debug, Default)]
126pub(crate) struct CanonicalEncoder(Vec<u8>);
127
128impl CanonicalEncoder {
129    /// Start a composite with its domain token.
130    pub(crate) fn new(domain: &str) -> Self {
131        let mut encoder = Self::default();
132        encoder.token(domain);
133        encoder
134    }
135
136    /// Append one UTF-8 token.
137    pub(crate) fn token(&mut self, token: impl AsRef<str>) {
138        let bytes = token.as_ref().as_bytes();
139        self.0
140            .extend_from_slice(&(bytes.len() as u64).to_be_bytes());
141        self.0.extend_from_slice(bytes);
142    }
143
144    /// Append a field-name token.
145    pub(crate) fn field(&mut self, field: &'static str) {
146        self.token(field);
147    }
148
149    /// Append a collection count as a minimal decimal token.
150    pub(crate) fn count(&mut self, count: usize) {
151        self.token(count.to_string());
152    }
153
154    /// Hash the complete canonical preimage.
155    pub(crate) fn identity(self) -> InputIdentity {
156        InputIdentity::from_bytes(&self.0)
157    }
158
159    /// Return the complete canonical preimage bytes.
160    pub(crate) fn into_bytes(self) -> Vec<u8> {
161        self.0
162    }
163}
164
165/// Append the canonical four-token representation of an input identity.
166pub(crate) fn encode_input_identity(encoder: &mut CanonicalEncoder, identity: &InputIdentity) {
167    encoder.token("sha256");
168    encoder.token(identity.sha256());
169    encoder.token("bytes");
170    encoder.token(identity.bytes().to_string());
171}
172
173impl Serialize for SourceFormatV1 {
174    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
175    where
176        S: Serializer,
177    {
178        serializer.serialize_str(source_format_name(*self))
179    }
180}
181
182impl<'de> Deserialize<'de> for SourceFormatV1 {
183    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
184    where
185        D: Deserializer<'de>,
186    {
187        match String::deserialize(deserializer)?.as_str() {
188            "gltf_json" => Ok(Self::GltfJson),
189            "glb" => Ok(Self::Glb),
190            "fbx" => Ok(Self::Fbx),
191            other => Err(D::Error::custom(format!(
192                "unknown V1 source format {other:?}"
193            ))),
194        }
195    }
196}
197
198impl<'de> Deserialize<'de> for InputIdentity {
199    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
200    where
201        D: Deserializer<'de>,
202    {
203        #[derive(Deserialize)]
204        #[serde(deny_unknown_fields)]
205        struct WireIdentity {
206            sha256: String,
207            bytes: u64,
208        }
209
210        let wire = WireIdentity::deserialize(deserializer)?;
211        if wire.sha256.len() != 64
212            || !wire
213                .sha256
214                .bytes()
215                .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))
216        {
217            return Err(D::Error::custom(
218                "input identity sha256 must be exactly 64 lowercase hexadecimal digits",
219            ));
220        }
221        let mut digest = [0_u8; 32];
222        for (index, pair) in wire.sha256.as_bytes().as_chunks::<2>().0.iter().enumerate() {
223            digest[index] = (hex_nibble(pair[0]).expect("validated hexadecimal") << 4)
224                | hex_nibble(pair[1]).expect("validated hexadecimal");
225        }
226        Ok(InputIdentity::from_sha256_digest(digest, wire.bytes))
227    }
228}
229
230fn hex_nibble(byte: u8) -> Option<u8> {
231    match byte {
232        b'0'..=b'9' => Some(byte - b'0'),
233        b'a'..=b'f' => Some(byte - b'a' + 10),
234        _ => None,
235    }
236}
237
238/// Exact four-field key selecting one revisioned engine profile.
239#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
240#[serde(deny_unknown_fields)]
241pub struct EngineProfileSelectionV1 {
242    family: String,
243    profile_revision: u32,
244    engine_version: String,
245    importer: String,
246}
247
248impl EngineProfileSelectionV1 {
249    /// Construct an exact profile selection.
250    ///
251    /// # Errors
252    ///
253    /// Returns [`EngineContractError`] when a retained string is empty or
254    /// exceeds the V1 per-string limit.
255    pub fn new(
256        family: impl Into<String>,
257        profile_revision: u32,
258        engine_version: impl Into<String>,
259        importer: impl Into<String>,
260    ) -> Result<Self, EngineContractError> {
261        let selection = Self {
262            family: family.into(),
263            profile_revision,
264            engine_version: engine_version.into(),
265            importer: importer.into(),
266        };
267        selection.validate()?;
268        Ok(selection)
269    }
270
271    /// Stable engine-family id.
272    pub fn family(&self) -> &str {
273        &self.family
274    }
275
276    /// Exact immutable profile revision.
277    pub const fn profile_revision(&self) -> u32 {
278        self.profile_revision
279    }
280
281    /// Exact target engine version.
282    pub fn engine_version(&self) -> &str {
283        &self.engine_version
284    }
285
286    /// Exact importer id.
287    pub fn importer(&self) -> &str {
288        &self.importer
289    }
290
291    fn validate(&self) -> Result<(), EngineContractError> {
292        validate_required_text("selection.family", &self.family)?;
293        validate_required_text("selection.engine_version", &self.engine_version)?;
294        validate_required_text("selection.importer", &self.importer)
295    }
296
297    fn retained_text_bytes(&self) -> Result<usize, EngineContractError> {
298        checked_sum(
299            "profile retained text",
300            [
301                self.family.len(),
302                self.engine_version.len(),
303                self.importer.len(),
304            ],
305        )
306    }
307}
308
309/// Stable id for one immutable V1 profile fact.
310#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
311#[serde(rename_all = "snake_case")]
312pub enum EngineFactIdV1 {
313    /// Bounded input formats accepted by the profile.
314    AcceptedInputs,
315    /// How imported animation assets are addressed.
316    AnimationAddressability,
317    /// Target coordinate basis.
318    TargetCoordinateBasis,
319    /// Target linear unit.
320    TargetLinearUnit,
321    /// Source-to-target unit-conversion control.
322    UnitConversionControl,
323    /// Source-to-target axis-conversion control.
324    AxisConversionControl,
325    /// Exact axis-conversion transform.
326    ExactAxisConversion,
327    /// Resulting imported hierarchy scale.
328    ResultingHierarchyScale,
329    /// Whether clip boundaries require a whole end frame.
330    WholeEndFrameRequired,
331    /// Import handling of animation channels.
332    AnimationChannelHandling,
333    /// Import handling of source extensions.
334    ExtensionHandling,
335    /// Import handling of source constructs.
336    ConstructHandling,
337    /// How imported animation targets are addressed.
338    AnimationTargetAddressability,
339    /// How root-motion sources are addressed.
340    RootMotionAddressability,
341}
342
343impl EngineFactIdV1 {
344    /// Stable wire and canonical spelling.
345    pub const fn as_str(self) -> &'static str {
346        match self {
347            Self::AcceptedInputs => "accepted_inputs",
348            Self::AnimationAddressability => "animation_addressability",
349            Self::TargetCoordinateBasis => "target_coordinate_basis",
350            Self::TargetLinearUnit => "target_linear_unit",
351            Self::UnitConversionControl => "unit_conversion_control",
352            Self::AxisConversionControl => "axis_conversion_control",
353            Self::ExactAxisConversion => "exact_axis_conversion",
354            Self::ResultingHierarchyScale => "resulting_hierarchy_scale",
355            Self::WholeEndFrameRequired => "whole_end_frame_required",
356            Self::AnimationChannelHandling => "animation_channel_handling",
357            Self::ExtensionHandling => "extension_handling",
358            Self::ConstructHandling => "construct_handling",
359            Self::AnimationTargetAddressability => "animation_target_addressability",
360            Self::RootMotionAddressability => "root_motion_addressability",
361        }
362    }
363}
364
365const ALL_FACT_IDS: [EngineFactIdV1; 14] = [
366    EngineFactIdV1::AcceptedInputs,
367    EngineFactIdV1::AnimationAddressability,
368    EngineFactIdV1::AnimationChannelHandling,
369    EngineFactIdV1::AnimationTargetAddressability,
370    EngineFactIdV1::AxisConversionControl,
371    EngineFactIdV1::ConstructHandling,
372    EngineFactIdV1::ExactAxisConversion,
373    EngineFactIdV1::ExtensionHandling,
374    EngineFactIdV1::ResultingHierarchyScale,
375    EngineFactIdV1::RootMotionAddressability,
376    EngineFactIdV1::TargetCoordinateBasis,
377    EngineFactIdV1::TargetLinearUnit,
378    EngineFactIdV1::UnitConversionControl,
379    EngineFactIdV1::WholeEndFrameRequired,
380];
381
382/// Coordinate-system handedness.
383#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
384#[serde(rename_all = "snake_case")]
385pub enum EngineHandednessV1 {
386    /// Left-handed coordinates.
387    Left,
388    /// Right-handed coordinates.
389    Right,
390}
391
392/// Positive world axis used as up.
393#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
394#[serde(rename_all = "snake_case")]
395pub enum EngineUpAxisV1 {
396    /// Positive X is up.
397    X,
398    /// Positive Y is up.
399    Y,
400    /// Positive Z is up.
401    Z,
402}
403
404/// Signed world axis used as forward.
405#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
406#[serde(rename_all = "snake_case")]
407pub enum EngineForwardAxisV1 {
408    /// Positive X is forward.
409    PositiveX,
410    /// Negative X is forward.
411    NegativeX,
412    /// Positive Y is forward.
413    PositiveY,
414    /// Negative Y is forward.
415    NegativeY,
416    /// Positive Z is forward.
417    PositiveZ,
418    /// Negative Z is forward.
419    NegativeZ,
420}
421
422/// Known target coordinate basis.
423#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
424#[serde(deny_unknown_fields)]
425pub struct EngineCoordinateBasisV1 {
426    /// Coordinate-system handedness.
427    pub handedness: EngineHandednessV1,
428    /// Positive world up axis.
429    pub up_axis: EngineUpAxisV1,
430    /// Signed target forward axis.
431    pub forward_axis: EngineForwardAxisV1,
432}
433
434/// Known target linear unit.
435#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
436#[serde(rename_all = "snake_case")]
437pub enum EngineLinearUnitV1 {
438    /// Metre.
439    Metre,
440    /// Centimetre.
441    Centimetre,
442}
443
444/// Stable id in the closed V1 engine-setting vocabulary.
445#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
446#[serde(rename_all = "snake_case")]
447pub enum EngineSettingIdV1 {
448    /// Unity document-level unit-conversion toggle.
449    ConvertUnits,
450    /// Unity document-level axis-baking toggle.
451    BakeAxisConversion,
452    /// Unity Generic exact source-transform path.
453    RootMotionSource,
454    /// Unity per-clip root-rotation policy.
455    RootRotation,
456    /// Unity per-clip vertical root-position policy.
457    RootPositionY,
458    /// Unity per-clip horizontal root-position policy.
459    RootPositionXz,
460}
461
462impl EngineSettingIdV1 {
463    /// Stable wire and canonical spelling.
464    pub const fn as_str(self) -> &'static str {
465        match self {
466            Self::ConvertUnits => "convert_units",
467            Self::BakeAxisConversion => "bake_axis_conversion",
468            Self::RootMotionSource => "root_motion_source",
469            Self::RootRotation => "root_rotation",
470            Self::RootPositionY => "root_position_y",
471            Self::RootPositionXz => "root_position_xz",
472        }
473    }
474}
475
476/// Known importer control relevant to source-to-target conversion.
477#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
478#[serde(rename_all = "snake_case")]
479pub enum EngineConversionControlV1 {
480    /// Behavior is controlled by one declared profile setting.
481    ProfileSetting(EngineSettingIdV1),
482    /// Behavior is exposed by the importer but is not a V1 profile setting.
483    ImporterOption,
484}
485
486/// Known importer treatment for a fact domain.
487#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
488#[serde(rename_all = "snake_case")]
489pub enum EngineImportHandlingV1 {
490    /// The importer retains the domain.
491    Preserved,
492    /// The importer converts the domain.
493    Converted,
494    /// The importer discards the domain.
495    Discarded,
496    /// The importer does not support the domain.
497    Unsupported,
498}
499
500/// Known animation-target addressability behavior.
501#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
502#[serde(rename_all = "snake_case")]
503pub enum EngineTargetAddressabilityV1 {
504    /// Targets use a stable id derived from their name path.
505    NamePathDerivedId,
506}
507
508/// Known animation-asset addressability behavior.
509#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
510#[serde(rename_all = "snake_case")]
511pub enum EngineAnimationAddressabilityV1 {
512    /// Bevy addresses each glTF animation by its source-array index through
513    /// `GltfAssetLabel::Animation(index)`; animation names populate Bevy's
514    /// separate named-animation map rather than this typed label.
515    GltfAssetLabel,
516}
517
518/// Known root-motion addressability behavior.
519#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
520#[serde(rename_all = "snake_case")]
521pub enum EngineRootMotionAddressabilityV1 {
522    /// A bounded exact source-transform path selects the motion node.
523    ExactSourceTransformPath,
524    /// Humanoid Avatar/body semantics determine root motion.
525    HumanoidAvatarBody,
526}
527
528/// Typed value of one known immutable profile fact.
529#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize)]
530#[serde(rename_all = "snake_case")]
531pub enum EngineFactValueV1 {
532    /// Exact accepted input formats.
533    AcceptedFormats(#[serde(deserialize_with = "deserialize_collection_vec")] Vec<SourceFormatV1>),
534    /// Animation-asset addressability.
535    AnimationAddressability(EngineAnimationAddressabilityV1),
536    /// Target coordinate basis.
537    CoordinateBasis(EngineCoordinateBasisV1),
538    /// Target linear unit.
539    LinearUnit(EngineLinearUnitV1),
540    /// Source-to-target conversion control.
541    ConversionControl(EngineConversionControlV1),
542    /// Boolean predicate.
543    Boolean(bool),
544    /// Import handling of a domain.
545    ImportHandling(EngineImportHandlingV1),
546    /// Animation-target addressability.
547    TargetAddressability(EngineTargetAddressabilityV1),
548    /// Root-motion addressability.
549    RootMotionAddressability(EngineRootMotionAddressabilityV1),
550}
551
552#[derive(Deserialize)]
553#[serde(rename_all = "snake_case")]
554enum EngineFactValueWireV1 {
555    AcceptedFormats(
556        #[serde(deserialize_with = "deserialize_collection_rows")] CappedSequence<SourceFormatV1>,
557    ),
558    AnimationAddressability(EngineAnimationAddressabilityV1),
559    CoordinateBasis(EngineCoordinateBasisV1),
560    LinearUnit(EngineLinearUnitV1),
561    ConversionControl(EngineConversionControlV1),
562    Boolean(bool),
563    ImportHandling(EngineImportHandlingV1),
564    TargetAddressability(EngineTargetAddressabilityV1),
565    RootMotionAddressability(EngineRootMotionAddressabilityV1),
566}
567
568impl TryFrom<EngineFactValueWireV1> for EngineFactValueV1 {
569    type Error = EngineContractError;
570
571    fn try_from(wire: EngineFactValueWireV1) -> Result<Self, Self::Error> {
572        Ok(match wire {
573            EngineFactValueWireV1::AcceptedFormats(formats) => {
574                if formats.overflowed {
575                    return Err(EngineContractError::InvalidAcceptedInputs);
576                }
577                Self::AcceptedFormats(formats.values)
578            }
579            EngineFactValueWireV1::AnimationAddressability(value) => {
580                Self::AnimationAddressability(value)
581            }
582            EngineFactValueWireV1::CoordinateBasis(value) => Self::CoordinateBasis(value),
583            EngineFactValueWireV1::LinearUnit(value) => Self::LinearUnit(value),
584            EngineFactValueWireV1::ConversionControl(value) => Self::ConversionControl(value),
585            EngineFactValueWireV1::Boolean(value) => Self::Boolean(value),
586            EngineFactValueWireV1::ImportHandling(value) => Self::ImportHandling(value),
587            EngineFactValueWireV1::TargetAddressability(value) => Self::TargetAddressability(value),
588            EngineFactValueWireV1::RootMotionAddressability(value) => {
589                Self::RootMotionAddressability(value)
590            }
591        })
592    }
593}
594
595impl<'de> Deserialize<'de> for EngineFactValueV1 {
596    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
597    where
598        D: Deserializer<'de>,
599    {
600        EngineFactValueWireV1::deserialize(deserializer)?
601            .try_into()
602            .map_err(D::Error::custom)
603    }
604}
605
606/// Evidence state of one immutable profile fact.
607#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
608#[serde(rename_all = "snake_case")]
609pub enum EngineFactStateV1 {
610    /// Supported known value.
611    Known(EngineFactValueV1),
612    /// Primary evidence does not establish a value.
613    Unknown,
614    /// The fact domain genuinely does not apply.
615    NotApplicable,
616}
617
618/// One stable fact and its explicit evidence state.
619#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
620#[serde(deny_unknown_fields)]
621pub struct EngineProfileFactV1 {
622    id: EngineFactIdV1,
623    state: EngineFactStateV1,
624}
625
626impl EngineProfileFactV1 {
627    /// Construct one profile fact.
628    pub const fn new(id: EngineFactIdV1, state: EngineFactStateV1) -> Self {
629        Self { id, state }
630    }
631
632    /// Stable fact id.
633    pub const fn id(&self) -> EngineFactIdV1 {
634        self.id
635    }
636
637    /// Explicit evidence state.
638    pub const fn state(&self) -> &EngineFactStateV1 {
639        &self.state
640    }
641}
642
643/// Configuration scope of a setting.
644#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
645#[serde(rename_all = "snake_case")]
646pub enum EngineSettingScopeV1 {
647    /// One value governs the imported document and all clips.
648    Document,
649    /// One materialized value is required for each actual clip.
650    Clip,
651}
652
653/// Closed value domain of a setting.
654#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
655#[serde(rename_all = "snake_case")]
656pub enum EngineSettingDomainV1 {
657    /// Boolean value.
658    Boolean,
659    /// `bake` or `extract`.
660    BakeOrExtract,
661    /// Bounded exact source-transform path.
662    SourceTransformPath,
663}
664
665/// Whether a descriptor applies to a profile revision.
666#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
667#[serde(rename_all = "snake_case")]
668pub enum EngineSettingApplicabilityV1 {
669    /// The setting applies.
670    Applicable,
671    /// The setting genuinely does not apply.
672    NotApplicable,
673}
674
675/// Verified default status of a setting.
676#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
677#[serde(rename_all = "snake_case")]
678pub enum EngineDefaultStatusV1 {
679    /// The caller must declare a value because no default is verified.
680    RequiredWithoutDefault,
681    /// Default behavior is irrelevant because the setting does not apply.
682    NotApplicable,
683}
684
685/// Immutable descriptor for one stable setting id.
686#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
687#[serde(deny_unknown_fields)]
688pub struct EngineSettingDescriptorV1 {
689    id: EngineSettingIdV1,
690    scope: EngineSettingScopeV1,
691    domain: EngineSettingDomainV1,
692    applicability: EngineSettingApplicabilityV1,
693    default_status: EngineDefaultStatusV1,
694}
695
696impl EngineSettingDescriptorV1 {
697    /// Construct one immutable setting descriptor.
698    pub const fn new(
699        id: EngineSettingIdV1,
700        scope: EngineSettingScopeV1,
701        domain: EngineSettingDomainV1,
702        applicability: EngineSettingApplicabilityV1,
703        default_status: EngineDefaultStatusV1,
704    ) -> Self {
705        Self {
706            id,
707            scope,
708            domain,
709            applicability,
710            default_status,
711        }
712    }
713
714    /// Stable setting id.
715    pub const fn id(&self) -> EngineSettingIdV1 {
716        self.id
717    }
718
719    /// Required setting scope.
720    pub const fn scope(&self) -> EngineSettingScopeV1 {
721        self.scope
722    }
723
724    /// Closed value domain.
725    pub const fn domain(&self) -> EngineSettingDomainV1 {
726        self.domain
727    }
728
729    /// Applicability to this exact profile revision.
730    pub const fn applicability(&self) -> EngineSettingApplicabilityV1 {
731        self.applicability
732    }
733
734    /// Verified default status.
735    pub const fn default_status(&self) -> EngineDefaultStatusV1 {
736        self.default_status
737    }
738}
739
740/// One primary source retained by an immutable profile record.
741#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize)]
742#[serde(deny_unknown_fields)]
743pub struct EnginePrimarySourceV1 {
744    id: String,
745    target_version: String,
746    url: String,
747    verified_on: String,
748    supported_fact_ids: Vec<EngineFactIdV1>,
749    supported_setting_ids: Vec<EngineSettingIdV1>,
750}
751
752#[derive(Deserialize)]
753#[serde(deny_unknown_fields)]
754struct EnginePrimarySourceWireV1 {
755    id: String,
756    target_version: String,
757    url: String,
758    verified_on: String,
759    #[serde(deserialize_with = "deserialize_collection_rows")]
760    supported_fact_ids: CappedSequence<EngineFactIdV1>,
761    #[serde(deserialize_with = "deserialize_collection_rows")]
762    supported_setting_ids: CappedSequence<EngineSettingIdV1>,
763}
764
765struct EnginePrimarySourceSeed<'a> {
766    rows: &'a mut ProfileRows,
767}
768
769impl<'de> DeserializeSeed<'de> for EnginePrimarySourceSeed<'_> {
770    type Value = EnginePrimarySourceWireV1;
771
772    fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
773    where
774        D: Deserializer<'de>,
775    {
776        #[derive(Deserialize)]
777        #[serde(field_identifier, rename_all = "snake_case")]
778        enum Field {
779            Id,
780            TargetVersion,
781            Url,
782            VerifiedOn,
783            SupportedFactIds,
784            SupportedSettingIds,
785        }
786
787        struct PrimarySourceVisitor<'a> {
788            rows: &'a mut ProfileRows,
789        }
790
791        impl<'de> Visitor<'de> for PrimarySourceVisitor<'_> {
792            type Value = EnginePrimarySourceWireV1;
793
794            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
795                formatter.write_str("an engine primary-source record")
796            }
797
798            fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
799            where
800                A: MapAccess<'de>,
801            {
802                let mut id = None;
803                let mut target_version = None;
804                let mut url = None;
805                let mut verified_on = None;
806                let mut supported_fact_ids = None;
807                let mut supported_setting_ids = None;
808                while let Some(field) = map.next_key()? {
809                    match field {
810                        Field::Id => set_once(&mut id, map.next_value()?, "id")?,
811                        Field::TargetVersion => {
812                            set_once(&mut target_version, map.next_value()?, "target_version")?
813                        }
814                        Field::Url => set_once(&mut url, map.next_value()?, "url")?,
815                        Field::VerifiedOn => {
816                            set_once(&mut verified_on, map.next_value()?, "verified_on")?
817                        }
818                        Field::SupportedFactIds => {
819                            if supported_fact_ids.is_some() {
820                                return Err(A::Error::duplicate_field("supported_fact_ids"));
821                            }
822                            supported_fact_ids =
823                                Some(map.next_value_seed(BudgetedCappedSequenceSeed {
824                                    budget: &mut self.rows.local,
825                                    local_limit: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS,
826                                    element: PhantomData,
827                                })?);
828                        }
829                        Field::SupportedSettingIds => {
830                            if supported_setting_ids.is_some() {
831                                return Err(A::Error::duplicate_field("supported_setting_ids"));
832                            }
833                            supported_setting_ids =
834                                Some(map.next_value_seed(BudgetedCappedSequenceSeed {
835                                    budget: &mut self.rows.local,
836                                    local_limit: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS,
837                                    element: PhantomData,
838                                })?);
839                        }
840                    }
841                }
842                Ok(EnginePrimarySourceWireV1 {
843                    id: required(id, "id")?,
844                    target_version: required(target_version, "target_version")?,
845                    url: required(url, "url")?,
846                    verified_on: required(verified_on, "verified_on")?,
847                    supported_fact_ids: required(supported_fact_ids, "supported_fact_ids")?,
848                    supported_setting_ids: required(
849                        supported_setting_ids,
850                        "supported_setting_ids",
851                    )?,
852                })
853            }
854        }
855
856        deserializer.deserialize_struct(
857            "EnginePrimarySourceV1",
858            &[
859                "id",
860                "target_version",
861                "url",
862                "verified_on",
863                "supported_fact_ids",
864                "supported_setting_ids",
865            ],
866            PrimarySourceVisitor { rows: self.rows },
867        )
868    }
869}
870
871fn set_once<E, T>(slot: &mut Option<T>, value: T, field: &'static str) -> Result<(), E>
872where
873    E: serde::de::Error,
874{
875    if slot.replace(value).is_some() {
876        return Err(E::duplicate_field(field));
877    }
878    Ok(())
879}
880
881fn required<E, T>(value: Option<T>, field: &'static str) -> Result<T, E>
882where
883    E: serde::de::Error,
884{
885    value.ok_or_else(|| E::missing_field(field))
886}
887
888impl EnginePrimarySourceV1 {
889    fn from_wire(wire: EnginePrimarySourceWireV1) -> Result<Self, EngineContractError> {
890        validate_required_text("primary_sources.id", &wire.id)?;
891        validate_required_text("primary_sources.target_version", &wire.target_version)?;
892        validate_required_text("primary_sources.url", &wire.url)?;
893        validate_required_text("primary_sources.verified_on", &wire.verified_on)?;
894        if wire.supported_fact_ids.overflowed {
895            return Err(EngineContractError::TooManyRows {
896                field: "primary_sources.supported_fact_ids",
897                found: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS + 1,
898                max: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS,
899            });
900        }
901        if wire.supported_setting_ids.overflowed {
902            return Err(EngineContractError::TooManyRows {
903                field: "primary_sources.supported_setting_ids",
904                found: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS + 1,
905                max: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS,
906            });
907        }
908        let source = Self {
909            id: wire.id,
910            target_version: wire.target_version,
911            url: wire.url,
912            verified_on: wire.verified_on,
913            supported_fact_ids: wire.supported_fact_ids.values,
914            supported_setting_ids: wire.supported_setting_ids.values,
915        };
916        source.validate(true)?;
917        Ok(source)
918    }
919}
920
921impl<'de> Deserialize<'de> for EnginePrimarySourceV1 {
922    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
923    where
924        D: Deserializer<'de>,
925    {
926        Self::from_wire(EnginePrimarySourceWireV1::deserialize(deserializer)?)
927            .map_err(D::Error::custom)
928    }
929}
930
931impl EnginePrimarySourceV1 {
932    /// Construct one primary-source row, canonicalizing its supported-id sets.
933    ///
934    /// # Errors
935    ///
936    /// Returns [`EngineContractError`] for empty or oversized text, oversized
937    /// id sets, or duplicate supported ids.
938    pub fn new(
939        id: impl Into<String>,
940        target_version: impl Into<String>,
941        url: impl Into<String>,
942        verified_on: impl Into<String>,
943        mut supported_fact_ids: Vec<EngineFactIdV1>,
944        mut supported_setting_ids: Vec<EngineSettingIdV1>,
945    ) -> Result<Self, EngineContractError> {
946        supported_fact_ids.sort_by_key(|id| id.as_str());
947        supported_setting_ids.sort_by_key(|id| id.as_str());
948        let source = Self {
949            id: id.into(),
950            target_version: target_version.into(),
951            url: url.into(),
952            verified_on: verified_on.into(),
953            supported_fact_ids,
954            supported_setting_ids,
955        };
956        source.validate(true)?;
957        Ok(source)
958    }
959
960    /// Stable primary-source id.
961    pub fn id(&self) -> &str {
962        &self.id
963    }
964
965    /// Source target version.
966    pub fn target_version(&self) -> &str {
967        &self.target_version
968    }
969
970    /// Primary-source URL.
971    pub fn url(&self) -> &str {
972        &self.url
973    }
974
975    /// ISO verification date.
976    pub fn verified_on(&self) -> &str {
977        &self.verified_on
978    }
979
980    /// Stable fact ids supported by this source.
981    pub fn supported_fact_ids(&self) -> &[EngineFactIdV1] {
982        &self.supported_fact_ids
983    }
984
985    /// Stable setting ids supported by this source.
986    pub fn supported_setting_ids(&self) -> &[EngineSettingIdV1] {
987        &self.supported_setting_ids
988    }
989
990    fn validate(&self, require_order: bool) -> Result<(), EngineContractError> {
991        validate_required_text("primary_sources.id", &self.id)?;
992        validate_required_text("primary_sources.target_version", &self.target_version)?;
993        validate_required_text("primary_sources.url", &self.url)?;
994        validate_required_text("primary_sources.verified_on", &self.verified_on)?;
995        validate_collection_len(
996            "primary_sources.supported_fact_ids",
997            self.supported_fact_ids.len(),
998        )?;
999        validate_collection_len(
1000            "primary_sources.supported_setting_ids",
1001            self.supported_setting_ids.len(),
1002        )?;
1003        validate_unique_order(
1004            "primary_sources.supported_fact_ids",
1005            &self.supported_fact_ids,
1006            |id| id.as_str(),
1007            require_order,
1008        )?;
1009        validate_unique_order(
1010            "primary_sources.supported_setting_ids",
1011            &self.supported_setting_ids,
1012            |id| id.as_str(),
1013            require_order,
1014        )
1015    }
1016
1017    fn retained_text_bytes(&self) -> Result<usize, EngineContractError> {
1018        checked_sum(
1019            "profile retained text",
1020            [
1021                self.id.len(),
1022                self.target_version.len(),
1023                self.url.len(),
1024                self.verified_on.len(),
1025            ],
1026        )
1027    }
1028}
1029
1030/// Registry-independent, self-contained immutable engine profile record.
1031#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1032pub struct ResolvedEngineProfileV1 {
1033    schema: String,
1034    selection: EngineProfileSelectionV1,
1035    fact_bundle_urn: String,
1036    identity: InputIdentity,
1037    facts: Vec<EngineProfileFactV1>,
1038    setting_descriptors: Vec<EngineSettingDescriptorV1>,
1039    primary_sources: Vec<EnginePrimarySourceV1>,
1040}
1041
1042impl ResolvedEngineProfileV1 {
1043    /// Construct and canonically order one self-contained profile record.
1044    ///
1045    /// # Errors
1046    ///
1047    /// Returns [`EngineContractError`] when the profile is incomplete,
1048    /// internally inconsistent, duplicated, or exceeds a V1 bound.
1049    pub fn new(
1050        selection: EngineProfileSelectionV1,
1051        fact_bundle_urn: impl Into<String>,
1052        mut facts: Vec<EngineProfileFactV1>,
1053        mut setting_descriptors: Vec<EngineSettingDescriptorV1>,
1054        mut primary_sources: Vec<EnginePrimarySourceV1>,
1055    ) -> Result<Self, EngineContractError> {
1056        for fact in &mut facts {
1057            if let EngineFactStateV1::Known(EngineFactValueV1::AcceptedFormats(formats)) =
1058                &mut fact.state
1059            {
1060                formats.sort_by_key(|format| source_format_name(*format));
1061            }
1062        }
1063        facts.sort_by_key(|fact| fact.id.as_str());
1064        setting_descriptors.sort_by_key(|descriptor| descriptor.id.as_str());
1065        primary_sources.sort_by(|left, right| left.id.cmp(&right.id));
1066        let mut profile = Self {
1067            schema: ENGINE_PROFILE_FACTS_V1_ID.to_owned(),
1068            selection,
1069            fact_bundle_urn: fact_bundle_urn.into(),
1070            identity: InputIdentity::from_bytes(&[]),
1071            facts,
1072            setting_descriptors,
1073            primary_sources,
1074        };
1075        profile.validate_semantics(true, false)?;
1076        profile.identity = profile.computed_identity();
1077        Ok(profile)
1078    }
1079
1080    /// Contract id carried in the `schema` field.
1081    pub fn contract_id(&self) -> &str {
1082        &self.schema
1083    }
1084
1085    /// Exact four-field profile selection.
1086    pub const fn selection(&self) -> &EngineProfileSelectionV1 {
1087        &self.selection
1088    }
1089
1090    /// Selected immutable profile fact-bundle URN.
1091    pub fn fact_bundle_urn(&self) -> &str {
1092        &self.fact_bundle_urn
1093    }
1094
1095    /// SHA-256 plus byte count of the unchanged #464 facts preimage.
1096    pub const fn facts_identity(&self) -> &InputIdentity {
1097        &self.identity
1098    }
1099
1100    /// Complete typed fact inventory in stable-id order.
1101    pub fn facts(&self) -> &[EngineProfileFactV1] {
1102        &self.facts
1103    }
1104
1105    /// Complete descriptor inventory in stable-id order.
1106    pub fn setting_descriptors(&self) -> &[EngineSettingDescriptorV1] {
1107        &self.setting_descriptors
1108    }
1109
1110    /// Primary-source rows in stable-id order.
1111    pub fn primary_sources(&self) -> &[EnginePrimarySourceV1] {
1112        &self.primary_sources
1113    }
1114
1115    /// Look up one fact by stable id.
1116    pub fn fact(&self, id: EngineFactIdV1) -> Option<&EngineProfileFactV1> {
1117        self.facts.iter().find(|fact| fact.id == id)
1118    }
1119
1120    /// Whether the exact embedded `accepted_inputs` fact accepts `format`.
1121    pub fn accepts_format(&self, format: SourceFormatV1) -> bool {
1122        matches!(
1123            self.fact(EngineFactIdV1::AcceptedInputs)
1124                .map(EngineProfileFactV1::state),
1125            Some(EngineFactStateV1::Known(EngineFactValueV1::AcceptedFormats(formats)))
1126                if formats.contains(&format)
1127        )
1128    }
1129
1130    /// Look up one setting descriptor, including not-applicable descriptors.
1131    pub fn setting_descriptor(&self, id: EngineSettingIdV1) -> Option<&EngineSettingDescriptorV1> {
1132        self.setting_descriptors
1133            .iter()
1134            .find(|descriptor| descriptor.id == id)
1135    }
1136
1137    /// Look up one primary source by stable id.
1138    pub fn source(&self, id: &str) -> Option<&EnginePrimarySourceV1> {
1139        self.primary_sources.iter().find(|source| source.id == id)
1140    }
1141
1142    /// Revalidate all profile semantics and its identity.
1143    ///
1144    /// # Errors
1145    ///
1146    /// Returns [`EngineContractError`] for any invalid wire or cross-reference.
1147    pub fn validate(&self) -> Result<(), EngineContractError> {
1148        self.validate_semantics(true, true)
1149    }
1150
1151    /// Append the complete unchanged #464 facts preimage, including its domain.
1152    pub(crate) fn encode_preimage(&self, encoder: &mut CanonicalEncoder) {
1153        encoder.token(ENGINE_FACTS_PREIMAGE_DOMAIN);
1154        encode_profile_key(encoder, &self.selection);
1155        encoder.field("fact_bundle_urn");
1156        encoder.token(&self.fact_bundle_urn);
1157        encoder.field("facts");
1158        encoder.count(self.facts.len());
1159        for fact in &self.facts {
1160            encoder.token(fact.id.as_str());
1161            encode_fact_state(encoder, &fact.state);
1162        }
1163        encoder.field("setting_descriptors");
1164        encoder.count(self.setting_descriptors.len());
1165        for descriptor in &self.setting_descriptors {
1166            encoder.token(descriptor.id.as_str());
1167            encoder.token(setting_scope_name(descriptor.scope));
1168            encoder.token(setting_domain_name(descriptor.domain));
1169            encoder.token(match descriptor.applicability {
1170                EngineSettingApplicabilityV1::Applicable => "applicable",
1171                EngineSettingApplicabilityV1::NotApplicable => "not_applicable",
1172            });
1173            encoder.token(match descriptor.default_status {
1174                EngineDefaultStatusV1::RequiredWithoutDefault => "required_without_default",
1175                EngineDefaultStatusV1::NotApplicable => "not_applicable",
1176            });
1177        }
1178        encoder.field("sources");
1179        encoder.count(self.primary_sources.len());
1180        for source in &self.primary_sources {
1181            encoder.token(&source.id);
1182            encoder.token(&source.target_version);
1183            encoder.token(&source.url);
1184            encoder.token(&source.verified_on);
1185            encoder.count(source.supported_fact_ids.len());
1186            for id in &source.supported_fact_ids {
1187                encoder.token(id.as_str());
1188            }
1189            encoder.count(source.supported_setting_ids.len());
1190            for id in &source.supported_setting_ids {
1191                encoder.token(id.as_str());
1192            }
1193        }
1194    }
1195
1196    pub(crate) fn retained_rows(&self) -> Result<usize, EngineContractError> {
1197        let nested = self.primary_sources.iter().map(|source| {
1198            source
1199                .supported_fact_ids
1200                .len()
1201                .checked_add(source.supported_setting_ids.len())
1202                .ok_or(EngineContractError::ArithmeticOverflow {
1203                    field: "profile retained rows",
1204                })
1205        });
1206        checked_sum_results(
1207            "profile retained rows",
1208            [
1209                self.facts.len(),
1210                self.setting_descriptors.len(),
1211                self.primary_sources.len(),
1212            ],
1213            nested,
1214        )
1215    }
1216
1217    pub(crate) fn provenance_rows(&self) -> usize {
1218        self.facts
1219            .len()
1220            .saturating_add(self.setting_descriptors.len())
1221            .saturating_add(self.primary_sources.len())
1222    }
1223
1224    pub(crate) fn retained_text_bytes(&self) -> Result<usize, EngineContractError> {
1225        let base = self
1226            .selection
1227            .retained_text_bytes()?
1228            .checked_add(self.fact_bundle_urn.len())
1229            .ok_or(EngineContractError::ArithmeticOverflow {
1230                field: "profile retained text",
1231            })?;
1232        checked_sum_results(
1233            "profile retained text",
1234            [base],
1235            self.primary_sources
1236                .iter()
1237                .map(EnginePrimarySourceV1::retained_text_bytes),
1238        )
1239    }
1240
1241    fn computed_identity(&self) -> InputIdentity {
1242        let mut encoder = CanonicalEncoder::default();
1243        self.encode_preimage(&mut encoder);
1244        encoder.identity()
1245    }
1246
1247    fn validate_semantics(
1248        &self,
1249        require_order: bool,
1250        verify_identity: bool,
1251    ) -> Result<(), EngineContractError> {
1252        validate_schema("profile.schema", &self.schema, ENGINE_PROFILE_FACTS_V1_ID)?;
1253        self.selection.validate()?;
1254        validate_required_text("profile.fact_bundle_urn", &self.fact_bundle_urn)?;
1255        validate_collection_len("profile.facts", self.facts.len())?;
1256        validate_collection_len(
1257            "profile.setting_descriptors",
1258            self.setting_descriptors.len(),
1259        )?;
1260        validate_collection_len("profile.primary_sources", self.primary_sources.len())?;
1261        validate_unique_order(
1262            "profile.facts",
1263            &self.facts,
1264            |fact| fact.id.as_str(),
1265            require_order,
1266        )?;
1267        if self.facts.len() != ALL_FACT_IDS.len()
1268            || !self
1269                .facts
1270                .iter()
1271                .zip(ALL_FACT_IDS)
1272                .all(|(fact, expected)| fact.id == expected)
1273        {
1274            return Err(EngineContractError::InvalidFactInventory);
1275        }
1276        for fact in &self.facts {
1277            validate_fact_value(fact)?;
1278            if let EngineFactStateV1::Known(EngineFactValueV1::ConversionControl(
1279                EngineConversionControlV1::ProfileSetting(setting),
1280            )) = &fact.state
1281                && self.setting_descriptor(*setting).is_none()
1282            {
1283                return Err(EngineContractError::InvalidFactValue { fact: fact.id });
1284            }
1285        }
1286        if !matches!(
1287            self.fact(EngineFactIdV1::AcceptedInputs)
1288                .map(EngineProfileFactV1::state),
1289            Some(EngineFactStateV1::Known(
1290                EngineFactValueV1::AcceptedFormats(formats)
1291            )) if !formats.is_empty()
1292        ) {
1293            return Err(EngineContractError::InvalidAcceptedInputs);
1294        }
1295        validate_unique_order(
1296            "profile.setting_descriptors",
1297            &self.setting_descriptors,
1298            |descriptor| descriptor.id.as_str(),
1299            require_order,
1300        )?;
1301        for descriptor in &self.setting_descriptors {
1302            if !matches!(
1303                (descriptor.applicability, descriptor.default_status),
1304                (
1305                    EngineSettingApplicabilityV1::Applicable,
1306                    EngineDefaultStatusV1::RequiredWithoutDefault
1307                ) | (
1308                    EngineSettingApplicabilityV1::NotApplicable,
1309                    EngineDefaultStatusV1::NotApplicable
1310                )
1311            ) {
1312                return Err(EngineContractError::InvalidDescriptorDefault {
1313                    setting: descriptor.id,
1314                });
1315            }
1316        }
1317        validate_unique_order(
1318            "profile.primary_sources",
1319            &self.primary_sources,
1320            |source| source.id.as_str(),
1321            require_order,
1322        )?;
1323        for source in &self.primary_sources {
1324            source.validate(require_order)?;
1325            for fact in &source.supported_fact_ids {
1326                let Some(row) = self.fact(*fact) else {
1327                    return Err(EngineContractError::UnknownSourceFact {
1328                        source_id: source.id.clone(),
1329                        fact: *fact,
1330                    });
1331                };
1332                if !matches!(row.state, EngineFactStateV1::Known(_)) {
1333                    return Err(EngineContractError::SourceReferencesNonKnownFact {
1334                        source_id: source.id.clone(),
1335                        fact: *fact,
1336                    });
1337                }
1338            }
1339            for setting in &source.supported_setting_ids {
1340                if self.setting_descriptor(*setting).is_none() {
1341                    return Err(EngineContractError::UnknownSourceSetting {
1342                        source_id: source.id.clone(),
1343                        setting: *setting,
1344                    });
1345                }
1346            }
1347        }
1348        for fact in &self.facts {
1349            if matches!(fact.state, EngineFactStateV1::Known(_))
1350                && !self
1351                    .primary_sources
1352                    .iter()
1353                    .any(|source| source.supported_fact_ids.contains(&fact.id))
1354            {
1355                return Err(EngineContractError::UnreferencedKnownFact { fact: fact.id });
1356            }
1357        }
1358        for descriptor in &self.setting_descriptors {
1359            if !self
1360                .primary_sources
1361                .iter()
1362                .any(|source| source.supported_setting_ids.contains(&descriptor.id))
1363            {
1364                return Err(EngineContractError::UnreferencedSetting {
1365                    setting: descriptor.id,
1366                });
1367            }
1368        }
1369        let rows = self.retained_rows()?;
1370        if rows > ENGINE_CONTRACT_V1_MAX_AGGREGATE_ROWS {
1371            return Err(EngineContractError::TooManyAggregateRows {
1372                found: rows,
1373                max: ENGINE_CONTRACT_V1_MAX_AGGREGATE_ROWS,
1374            });
1375        }
1376        let text = self.retained_text_bytes()?;
1377        if text > ENGINE_CONTRACT_V1_MAX_TOTAL_TEXT_BYTES {
1378            return Err(EngineContractError::TooMuchAggregateText {
1379                found: text,
1380                max: ENGINE_CONTRACT_V1_MAX_TOTAL_TEXT_BYTES,
1381            });
1382        }
1383        if verify_identity && self.identity != self.computed_identity() {
1384            return Err(EngineContractError::IdentityMismatch {
1385                contract: ENGINE_PROFILE_FACTS_V1_ID,
1386            });
1387        }
1388        Ok(())
1389    }
1390}
1391
1392struct ResolvedEngineProfileWireV1 {
1393    schema: String,
1394    selection: EngineProfileSelectionV1,
1395    fact_bundle_urn: String,
1396    identity: InputIdentity,
1397    facts: CappedSequence<EngineProfileFactV1>,
1398    setting_descriptors: CappedSequence<EngineSettingDescriptorV1>,
1399    primary_sources: CappedSequence<EnginePrimarySourceWireV1>,
1400    aggregate_rows: RowBudget,
1401    provenance_rows_overflowed: bool,
1402}
1403
1404enum ProfileTopLevelElement<T> {
1405    Value(T),
1406    Skipped,
1407}
1408
1409struct ProfileTopLevelElementSeed<'a, T> {
1410    rows: &'a mut ProfileRows,
1411    element: PhantomData<fn() -> T>,
1412}
1413
1414impl<'de, T> DeserializeSeed<'de> for ProfileTopLevelElementSeed<'_, T>
1415where
1416    T: Deserialize<'de>,
1417{
1418    type Value = ProfileTopLevelElement<T>;
1419
1420    fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
1421    where
1422        D: Deserializer<'de>,
1423    {
1424        if self.rows.admit_top_level() {
1425            T::deserialize(deserializer).map(ProfileTopLevelElement::Value)
1426        } else {
1427            IgnoredAny::deserialize(deserializer).map(|_| ProfileTopLevelElement::Skipped)
1428        }
1429    }
1430}
1431
1432struct ProfileTopLevelSequenceSeed<'a, T> {
1433    rows: &'a mut ProfileRows,
1434    element: PhantomData<fn() -> T>,
1435}
1436
1437impl<'de, T> DeserializeSeed<'de> for ProfileTopLevelSequenceSeed<'_, T>
1438where
1439    T: Deserialize<'de>,
1440{
1441    type Value = CappedSequence<T>;
1442
1443    fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
1444    where
1445        D: Deserializer<'de>,
1446    {
1447        struct ProfileTopLevelSequenceVisitor<'a, T> {
1448            rows: &'a mut ProfileRows,
1449            element: PhantomData<fn() -> T>,
1450        }
1451
1452        impl<'de, T> Visitor<'de> for ProfileTopLevelSequenceVisitor<'_, T>
1453        where
1454            T: Deserialize<'de>,
1455        {
1456            type Value = CappedSequence<T>;
1457
1458            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1459                formatter.write_str("a bounded sequence of engine profile rows")
1460            }
1461
1462            fn visit_seq<A>(self, mut sequence: A) -> Result<Self::Value, A::Error>
1463            where
1464                A: SeqAccess<'de>,
1465            {
1466                let mut values = Vec::with_capacity(
1467                    sequence
1468                        .size_hint()
1469                        .unwrap_or(0)
1470                        .min(ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS),
1471                );
1472                let mut seen = 0usize;
1473                while seen < ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS {
1474                    let Some(element) = sequence.next_element_seed(ProfileTopLevelElementSeed {
1475                        rows: self.rows,
1476                        element: PhantomData,
1477                    })?
1478                    else {
1479                        return Ok(CappedSequence {
1480                            values,
1481                            overflowed: false,
1482                        });
1483                    };
1484                    seen += 1;
1485                    match element {
1486                        ProfileTopLevelElement::Value(value) => values.push(value),
1487                        ProfileTopLevelElement::Skipped => {
1488                            let overflowed = consume_ignored_tail(
1489                                &mut sequence,
1490                                seen,
1491                                ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS,
1492                            )?;
1493                            return Ok(CappedSequence { values, overflowed });
1494                        }
1495                    }
1496                }
1497                let overflowed = consume_ignored_tail(
1498                    &mut sequence,
1499                    seen,
1500                    ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS,
1501                )?;
1502                Ok(CappedSequence { values, overflowed })
1503            }
1504        }
1505
1506        deserializer.deserialize_seq(ProfileTopLevelSequenceVisitor {
1507            rows: self.rows,
1508            element: PhantomData,
1509        })
1510    }
1511}
1512
1513enum PrimarySourceElement {
1514    Value(EnginePrimarySourceWireV1),
1515    Skipped,
1516}
1517
1518struct PrimarySourceElementSeed<'a> {
1519    rows: &'a mut ProfileRows,
1520}
1521
1522impl<'de> DeserializeSeed<'de> for PrimarySourceElementSeed<'_> {
1523    type Value = PrimarySourceElement;
1524
1525    fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
1526    where
1527        D: Deserializer<'de>,
1528    {
1529        if self.rows.admit_top_level() {
1530            EnginePrimarySourceSeed { rows: self.rows }
1531                .deserialize(deserializer)
1532                .map(PrimarySourceElement::Value)
1533        } else {
1534            IgnoredAny::deserialize(deserializer).map(|_| PrimarySourceElement::Skipped)
1535        }
1536    }
1537}
1538
1539struct PrimarySourcesSeed<'a> {
1540    rows: &'a mut ProfileRows,
1541}
1542
1543impl<'de> DeserializeSeed<'de> for PrimarySourcesSeed<'_> {
1544    type Value = CappedSequence<EnginePrimarySourceWireV1>;
1545
1546    fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
1547    where
1548        D: Deserializer<'de>,
1549    {
1550        struct PrimarySourcesVisitor<'a> {
1551            rows: &'a mut ProfileRows,
1552        }
1553
1554        impl<'de> Visitor<'de> for PrimarySourcesVisitor<'_> {
1555            type Value = CappedSequence<EnginePrimarySourceWireV1>;
1556
1557            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1558                formatter.write_str("a bounded sequence of engine primary sources")
1559            }
1560
1561            fn visit_seq<A>(self, mut sequence: A) -> Result<Self::Value, A::Error>
1562            where
1563                A: SeqAccess<'de>,
1564            {
1565                let mut values = Vec::with_capacity(
1566                    sequence
1567                        .size_hint()
1568                        .unwrap_or(0)
1569                        .min(ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS),
1570                );
1571                let mut seen = 0usize;
1572                while seen < ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS {
1573                    let Some(element) =
1574                        sequence.next_element_seed(PrimarySourceElementSeed { rows: self.rows })?
1575                    else {
1576                        return Ok(CappedSequence {
1577                            values,
1578                            overflowed: false,
1579                        });
1580                    };
1581                    seen += 1;
1582                    match element {
1583                        PrimarySourceElement::Value(value) => values.push(value),
1584                        PrimarySourceElement::Skipped => {
1585                            let overflowed = consume_ignored_tail(
1586                                &mut sequence,
1587                                seen,
1588                                ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS,
1589                            )?;
1590                            return Ok(CappedSequence { values, overflowed });
1591                        }
1592                    }
1593                }
1594                let overflowed = consume_ignored_tail(
1595                    &mut sequence,
1596                    seen,
1597                    ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS,
1598                )?;
1599                Ok(CappedSequence { values, overflowed })
1600            }
1601        }
1602
1603        deserializer.deserialize_seq(PrimarySourcesVisitor { rows: self.rows })
1604    }
1605}
1606
1607struct ResolvedEngineProfileWireSeed {
1608    provenance_limit: Option<usize>,
1609}
1610
1611impl<'de> DeserializeSeed<'de> for ResolvedEngineProfileWireSeed {
1612    type Value = ResolvedEngineProfileWireV1;
1613
1614    fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
1615    where
1616        D: Deserializer<'de>,
1617    {
1618        #[derive(Deserialize)]
1619        #[serde(field_identifier, rename_all = "snake_case")]
1620        enum Field {
1621            Schema,
1622            Selection,
1623            FactBundleUrn,
1624            Identity,
1625            Facts,
1626            SettingDescriptors,
1627            PrimarySources,
1628        }
1629
1630        struct ProfileVisitor {
1631            provenance_limit: Option<usize>,
1632        }
1633
1634        impl<'de> Visitor<'de> for ProfileVisitor {
1635            type Value = ResolvedEngineProfileWireV1;
1636
1637            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1638                formatter.write_str("a resolved engine profile")
1639            }
1640
1641            fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
1642            where
1643                A: MapAccess<'de>,
1644            {
1645                let mut rows = ProfileRows::new(self.provenance_limit);
1646                let mut schema = None;
1647                let mut selection = None;
1648                let mut fact_bundle_urn = None;
1649                let mut identity = None;
1650                let mut facts = None;
1651                let mut setting_descriptors = None;
1652                let mut primary_sources = None;
1653                while let Some(field) = map.next_key()? {
1654                    match field {
1655                        Field::Schema => set_once(&mut schema, map.next_value()?, "schema")?,
1656                        Field::Selection => {
1657                            set_once(&mut selection, map.next_value()?, "selection")?
1658                        }
1659                        Field::FactBundleUrn => {
1660                            set_once(&mut fact_bundle_urn, map.next_value()?, "fact_bundle_urn")?
1661                        }
1662                        Field::Identity => set_once(&mut identity, map.next_value()?, "identity")?,
1663                        Field::Facts => {
1664                            if facts.is_some() {
1665                                return Err(A::Error::duplicate_field("facts"));
1666                            }
1667                            facts = Some(map.next_value_seed(ProfileTopLevelSequenceSeed {
1668                                rows: &mut rows,
1669                                element: PhantomData,
1670                            })?);
1671                        }
1672                        Field::SettingDescriptors => {
1673                            if setting_descriptors.is_some() {
1674                                return Err(A::Error::duplicate_field("setting_descriptors"));
1675                            }
1676                            setting_descriptors =
1677                                Some(map.next_value_seed(ProfileTopLevelSequenceSeed {
1678                                    rows: &mut rows,
1679                                    element: PhantomData,
1680                                })?);
1681                        }
1682                        Field::PrimarySources => {
1683                            if primary_sources.is_some() {
1684                                return Err(A::Error::duplicate_field("primary_sources"));
1685                            }
1686                            primary_sources =
1687                                Some(map.next_value_seed(PrimarySourcesSeed { rows: &mut rows })?);
1688                        }
1689                    }
1690                }
1691                Ok(ResolvedEngineProfileWireV1 {
1692                    schema: required(schema, "schema")?,
1693                    selection: required(selection, "selection")?,
1694                    fact_bundle_urn: required(fact_bundle_urn, "fact_bundle_urn")?,
1695                    identity: required(identity, "identity")?,
1696                    facts: required(facts, "facts")?,
1697                    setting_descriptors: required(setting_descriptors, "setting_descriptors")?,
1698                    primary_sources: required(primary_sources, "primary_sources")?,
1699                    provenance_rows_overflowed: rows.provenance_overflowed(),
1700                    aggregate_rows: rows.local,
1701                })
1702            }
1703        }
1704
1705        deserializer.deserialize_struct(
1706            "ResolvedEngineProfileV1",
1707            &[
1708                "schema",
1709                "selection",
1710                "fact_bundle_urn",
1711                "identity",
1712                "facts",
1713                "setting_descriptors",
1714                "primary_sources",
1715            ],
1716            ProfileVisitor {
1717                provenance_limit: self.provenance_limit,
1718            },
1719        )
1720    }
1721}
1722
1723impl<'de> Deserialize<'de> for ResolvedEngineProfileWireV1 {
1724    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1725    where
1726        D: Deserializer<'de>,
1727    {
1728        ResolvedEngineProfileWireSeed {
1729            provenance_limit: None,
1730        }
1731        .deserialize(deserializer)
1732    }
1733}
1734
1735#[derive(Debug)]
1736pub(crate) enum EngineContractDecodeError {
1737    Shape(serde_json::Error),
1738    Semantic(EngineContractError),
1739}
1740
1741impl ResolvedEngineProfileV1 {
1742    fn validate_wire_limits(wire: &ResolvedEngineProfileWireV1) -> Result<(), EngineContractError> {
1743        validate_schema("profile.schema", &wire.schema, ENGINE_PROFILE_FACTS_V1_ID)?;
1744        wire.selection.validate()?;
1745        validate_required_text("profile.fact_bundle_urn", &wire.fact_bundle_urn)?;
1746        for (field, overflowed) in [
1747            ("profile.facts", wire.facts.overflowed),
1748            (
1749                "profile.setting_descriptors",
1750                wire.setting_descriptors.overflowed,
1751            ),
1752            ("profile.primary_sources", wire.primary_sources.overflowed),
1753        ] {
1754            if overflowed {
1755                return Err(EngineContractError::TooManyRows {
1756                    field,
1757                    found: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS + 1,
1758                    max: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS,
1759                });
1760            }
1761        }
1762        for source in &wire.primary_sources.values {
1763            if source.supported_fact_ids.overflowed {
1764                return Err(EngineContractError::TooManyRows {
1765                    field: "primary_sources.supported_fact_ids",
1766                    found: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS + 1,
1767                    max: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS,
1768                });
1769            }
1770            if source.supported_setting_ids.overflowed {
1771                return Err(EngineContractError::TooManyRows {
1772                    field: "primary_sources.supported_setting_ids",
1773                    found: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS + 1,
1774                    max: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS,
1775                });
1776            }
1777        }
1778        if wire.aggregate_rows.overflowed() {
1779            return Err(EngineContractError::TooManyAggregateRows {
1780                found: wire.aggregate_rows.found(),
1781                max: ENGINE_CONTRACT_V1_MAX_AGGREGATE_ROWS,
1782            });
1783        }
1784        Ok(())
1785    }
1786
1787    fn from_wire(wire: ResolvedEngineProfileWireV1) -> Result<Self, EngineContractError> {
1788        Self::validate_wire_limits(&wire)?;
1789        let primary_sources = wire
1790            .primary_sources
1791            .values
1792            .into_iter()
1793            .map(EnginePrimarySourceV1::from_wire)
1794            .collect::<Result<Vec<_>, _>>()?;
1795        let profile = Self {
1796            schema: wire.schema,
1797            selection: wire.selection,
1798            fact_bundle_urn: wire.fact_bundle_urn,
1799            identity: wire.identity,
1800            facts: wire.facts.values,
1801            setting_descriptors: wire.setting_descriptors.values,
1802            primary_sources,
1803        };
1804        profile.validate()?;
1805        Ok(profile)
1806    }
1807}
1808
1809#[cfg(test)]
1810pub(crate) fn decode_resolved_engine_profile_v1(
1811    raw: &str,
1812) -> Result<ResolvedEngineProfileV1, EngineContractDecodeError> {
1813    let wire = serde_json::from_str(raw).map_err(|source| {
1814        if source
1815            .to_string()
1816            .starts_with(&EngineContractError::InvalidAcceptedInputs.to_string())
1817        {
1818            EngineContractDecodeError::Semantic(EngineContractError::InvalidAcceptedInputs)
1819        } else {
1820            EngineContractDecodeError::Shape(source)
1821        }
1822    })?;
1823    ResolvedEngineProfileV1::from_wire(wire).map_err(EngineContractDecodeError::Semantic)
1824}
1825
1826pub(crate) enum EngineProfileLimitedDecodeError {
1827    Contract(EngineContractDecodeError),
1828    ProvenanceRowsOverflow,
1829}
1830
1831pub(crate) fn decode_resolved_engine_profile_v1_with_provenance_limit(
1832    raw: &str,
1833    provenance_limit: usize,
1834) -> Result<ResolvedEngineProfileV1, EngineProfileLimitedDecodeError> {
1835    let mut deserializer = serde_json::Deserializer::from_str(raw);
1836    let wire = ResolvedEngineProfileWireSeed {
1837        provenance_limit: Some(provenance_limit),
1838    }
1839    .deserialize(&mut deserializer)
1840    .map_err(|source| {
1841        if source
1842            .to_string()
1843            .starts_with(&EngineContractError::InvalidAcceptedInputs.to_string())
1844        {
1845            EngineProfileLimitedDecodeError::Contract(EngineContractDecodeError::Semantic(
1846                EngineContractError::InvalidAcceptedInputs,
1847            ))
1848        } else {
1849            EngineProfileLimitedDecodeError::Contract(EngineContractDecodeError::Shape(source))
1850        }
1851    })?;
1852    deserializer.end().map_err(|source| {
1853        EngineProfileLimitedDecodeError::Contract(EngineContractDecodeError::Shape(source))
1854    })?;
1855    ResolvedEngineProfileV1::validate_wire_limits(&wire).map_err(|source| {
1856        EngineProfileLimitedDecodeError::Contract(EngineContractDecodeError::Semantic(source))
1857    })?;
1858    if wire.provenance_rows_overflowed {
1859        return Err(EngineProfileLimitedDecodeError::ProvenanceRowsOverflow);
1860    }
1861    ResolvedEngineProfileV1::from_wire(wire).map_err(|source| {
1862        EngineProfileLimitedDecodeError::Contract(EngineContractDecodeError::Semantic(source))
1863    })
1864}
1865
1866impl<'de> Deserialize<'de> for ResolvedEngineProfileV1 {
1867    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1868    where
1869        D: Deserializer<'de>,
1870    {
1871        Self::from_wire(ResolvedEngineProfileWireV1::deserialize(deserializer)?)
1872            .map_err(D::Error::custom)
1873    }
1874}
1875
1876/// Exact policy for a root-transform component.
1877#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
1878#[serde(rename_all = "snake_case")]
1879pub enum EngineBakeOrExtractV1 {
1880    /// Bake the component into the pose.
1881    Bake,
1882    /// Extract the component as root motion.
1883    Extract,
1884}
1885
1886/// Closed public value vocabulary for fully materialized engine settings.
1887#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
1888#[serde(rename_all = "snake_case")]
1889pub enum EngineSettingValueV1 {
1890    /// Boolean setting value.
1891    Boolean(bool),
1892    /// Root-component bake/extract policy.
1893    BakeOrExtract(EngineBakeOrExtractV1),
1894    /// Bounded exact source-transform path.
1895    SourceTransformPath(String),
1896}
1897
1898/// One stable-id-keyed materialized setting value.
1899#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
1900#[serde(deny_unknown_fields)]
1901pub struct EngineSettingRowV1 {
1902    id: EngineSettingIdV1,
1903    value: EngineSettingValueV1,
1904}
1905
1906impl EngineSettingRowV1 {
1907    /// Construct one materialized setting row.
1908    pub const fn new(id: EngineSettingIdV1, value: EngineSettingValueV1) -> Self {
1909        Self { id, value }
1910    }
1911
1912    /// Stable setting id.
1913    pub const fn id(&self) -> EngineSettingIdV1 {
1914        self.id
1915    }
1916
1917    /// Fully materialized value.
1918    pub const fn value(&self) -> &EngineSettingValueV1 {
1919        &self.value
1920    }
1921}
1922
1923/// Fully materialized settings for one actual clip.
1924#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1925#[serde(deny_unknown_fields)]
1926pub struct EngineClipSettingsV1 {
1927    clip_name: String,
1928    settings: Vec<EngineSettingRowV1>,
1929}
1930
1931#[derive(Deserialize)]
1932#[serde(deny_unknown_fields)]
1933struct EngineClipSettingsWireV1 {
1934    clip_name: String,
1935    #[serde(deserialize_with = "deserialize_collection_rows")]
1936    settings: CappedSequence<EngineSettingRowV1>,
1937}
1938
1939struct EngineClipSettingsSeed<'a> {
1940    rows: &'a mut SettingsRows,
1941}
1942
1943enum SettingsElement<T> {
1944    Value(T),
1945    Skipped,
1946}
1947
1948struct SettingsElementSeed<'a, T> {
1949    rows: &'a mut SettingsRows,
1950    element: PhantomData<fn() -> T>,
1951}
1952
1953impl<'de, T> DeserializeSeed<'de> for SettingsElementSeed<'_, T>
1954where
1955    T: Deserialize<'de>,
1956{
1957    type Value = SettingsElement<T>;
1958
1959    fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
1960    where
1961        D: Deserializer<'de>,
1962    {
1963        if self.rows.admit_setting() {
1964            T::deserialize(deserializer).map(SettingsElement::Value)
1965        } else {
1966            IgnoredAny::deserialize(deserializer).map(|_| SettingsElement::Skipped)
1967        }
1968    }
1969}
1970
1971struct SettingsSequenceSeed<'a, T> {
1972    rows: &'a mut SettingsRows,
1973    element: PhantomData<fn() -> T>,
1974}
1975
1976impl<'de, T> DeserializeSeed<'de> for SettingsSequenceSeed<'_, T>
1977where
1978    T: Deserialize<'de>,
1979{
1980    type Value = CappedSequence<T>;
1981
1982    fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
1983    where
1984        D: Deserializer<'de>,
1985    {
1986        struct SettingsSequenceVisitor<'a, T> {
1987            rows: &'a mut SettingsRows,
1988            element: PhantomData<fn() -> T>,
1989        }
1990
1991        impl<'de, T> Visitor<'de> for SettingsSequenceVisitor<'_, T>
1992        where
1993            T: Deserialize<'de>,
1994        {
1995            type Value = CappedSequence<T>;
1996
1997            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1998                formatter.write_str("a bounded sequence of engine setting rows")
1999            }
2000
2001            fn visit_seq<A>(self, mut sequence: A) -> Result<Self::Value, A::Error>
2002            where
2003                A: SeqAccess<'de>,
2004            {
2005                let mut values = Vec::with_capacity(
2006                    sequence
2007                        .size_hint()
2008                        .unwrap_or(0)
2009                        .min(ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS),
2010                );
2011                let mut seen = 0usize;
2012                while seen < ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS {
2013                    let Some(element) = sequence.next_element_seed(SettingsElementSeed {
2014                        rows: self.rows,
2015                        element: PhantomData,
2016                    })?
2017                    else {
2018                        return Ok(CappedSequence {
2019                            values,
2020                            overflowed: false,
2021                        });
2022                    };
2023                    seen += 1;
2024                    match element {
2025                        SettingsElement::Value(value) => values.push(value),
2026                        SettingsElement::Skipped => {
2027                            let overflowed = consume_ignored_tail(
2028                                &mut sequence,
2029                                seen,
2030                                ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS,
2031                            )?;
2032                            return Ok(CappedSequence { values, overflowed });
2033                        }
2034                    }
2035                }
2036                let overflowed = consume_ignored_tail(
2037                    &mut sequence,
2038                    seen,
2039                    ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS,
2040                )?;
2041                Ok(CappedSequence { values, overflowed })
2042            }
2043        }
2044
2045        deserializer.deserialize_seq(SettingsSequenceVisitor {
2046            rows: self.rows,
2047            element: PhantomData,
2048        })
2049    }
2050}
2051
2052impl<'de> DeserializeSeed<'de> for EngineClipSettingsSeed<'_> {
2053    type Value = EngineClipSettingsWireV1;
2054
2055    fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
2056    where
2057        D: Deserializer<'de>,
2058    {
2059        #[derive(Deserialize)]
2060        #[serde(field_identifier, rename_all = "snake_case")]
2061        enum Field {
2062            ClipName,
2063            Settings,
2064        }
2065
2066        struct ClipSettingsVisitor<'a> {
2067            rows: &'a mut SettingsRows,
2068        }
2069
2070        impl<'de> Visitor<'de> for ClipSettingsVisitor<'_> {
2071            type Value = EngineClipSettingsWireV1;
2072
2073            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
2074                formatter.write_str("an engine clip-settings record")
2075            }
2076
2077            fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
2078            where
2079                A: MapAccess<'de>,
2080            {
2081                let mut clip_name = None;
2082                let mut settings = None;
2083                while let Some(field) = map.next_key()? {
2084                    match field {
2085                        Field::ClipName => {
2086                            set_once(&mut clip_name, map.next_value()?, "clip_name")?
2087                        }
2088                        Field::Settings => {
2089                            if settings.is_some() {
2090                                return Err(A::Error::duplicate_field("settings"));
2091                            }
2092                            settings = Some(map.next_value_seed(SettingsSequenceSeed {
2093                                rows: self.rows,
2094                                element: PhantomData,
2095                            })?);
2096                        }
2097                    }
2098                }
2099                Ok(EngineClipSettingsWireV1 {
2100                    clip_name: required(clip_name, "clip_name")?,
2101                    settings: required(settings, "settings")?,
2102                })
2103            }
2104        }
2105
2106        deserializer.deserialize_struct(
2107            "EngineClipSettingsV1",
2108            &["clip_name", "settings"],
2109            ClipSettingsVisitor { rows: self.rows },
2110        )
2111    }
2112}
2113
2114impl EngineClipSettingsV1 {
2115    fn from_wire(wire: EngineClipSettingsWireV1) -> Result<Self, EngineContractError> {
2116        validate_text("settings.clips.clip_name", &wire.clip_name)?;
2117        if wire.settings.overflowed {
2118            return Err(EngineContractError::TooManyRows {
2119                field: "settings.clips.settings",
2120                found: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS + 1,
2121                max: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS,
2122            });
2123        }
2124        let clip = Self {
2125            clip_name: wire.clip_name,
2126            settings: wire.settings.values,
2127        };
2128        clip.validate(true)?;
2129        Ok(clip)
2130    }
2131}
2132
2133impl<'de> Deserialize<'de> for EngineClipSettingsV1 {
2134    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2135    where
2136        D: Deserializer<'de>,
2137    {
2138        Self::from_wire(EngineClipSettingsWireV1::deserialize(deserializer)?)
2139            .map_err(D::Error::custom)
2140    }
2141}
2142
2143impl EngineClipSettingsV1 {
2144    /// Construct one clip row and canonicalize its setting-id order.
2145    ///
2146    /// # Errors
2147    ///
2148    /// Returns [`EngineContractError`] for an oversized clip name or setting
2149    /// list, an invalid path value, or duplicate setting ids.
2150    pub fn new(
2151        clip_name: impl Into<String>,
2152        mut settings: Vec<EngineSettingRowV1>,
2153    ) -> Result<Self, EngineContractError> {
2154        settings.sort_by_key(|row| row.id.as_str());
2155        let row = Self {
2156            clip_name: clip_name.into(),
2157            settings,
2158        };
2159        row.validate(true)?;
2160        Ok(row)
2161    }
2162
2163    /// Actual clip name supplied during input resolution.
2164    pub fn clip_name(&self) -> &str {
2165        &self.clip_name
2166    }
2167
2168    /// Fully materialized values in stable-id order.
2169    pub fn settings(&self) -> &[EngineSettingRowV1] {
2170        &self.settings
2171    }
2172
2173    /// Look up one setting value within this clip row.
2174    pub fn setting(&self, id: EngineSettingIdV1) -> Option<&EngineSettingValueV1> {
2175        self.settings
2176            .iter()
2177            .find(|row| row.id == id)
2178            .map(|row| &row.value)
2179    }
2180
2181    fn validate(&self, require_order: bool) -> Result<(), EngineContractError> {
2182        validate_text("settings.clips.clip_name", &self.clip_name)?;
2183        validate_collection_len("settings.clips.settings", self.settings.len())?;
2184        validate_unique_order(
2185            "settings.clips.settings",
2186            &self.settings,
2187            |row| row.id.as_str(),
2188            require_order,
2189        )?;
2190        for row in &self.settings {
2191            validate_setting_value(&row.value)?;
2192        }
2193        Ok(())
2194    }
2195
2196    fn retained_text_bytes(&self) -> Result<usize, EngineContractError> {
2197        let paths = self.settings.iter().filter_map(|row| match &row.value {
2198            EngineSettingValueV1::SourceTransformPath(path) => Some(path.len()),
2199            _ => None,
2200        });
2201        checked_sum(
2202            "settings retained text",
2203            [self.clip_name.len()].into_iter().chain(paths),
2204        )
2205    }
2206}
2207
2208/// Fully materialized, registry-independent V1 engine settings.
2209#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
2210pub struct ResolvedEngineSettingsV1 {
2211    schema: String,
2212    identity: InputIdentity,
2213    document_settings: Vec<EngineSettingRowV1>,
2214    clips: Vec<EngineClipSettingsV1>,
2215}
2216
2217impl ResolvedEngineSettingsV1 {
2218    /// Construct fully materialized settings against one exact embedded profile.
2219    ///
2220    /// Repeated equal clip names are retained and identity-significant.
2221    ///
2222    /// # Errors
2223    ///
2224    /// Returns [`EngineContractError`] for a bound, ordering, domain, scope,
2225    /// applicability, or required-value violation.
2226    pub fn new(
2227        profile: &ResolvedEngineProfileV1,
2228        mut document_settings: Vec<EngineSettingRowV1>,
2229        mut clips: Vec<EngineClipSettingsV1>,
2230    ) -> Result<Self, EngineContractError> {
2231        profile.validate()?;
2232        document_settings.sort_by_key(|row| row.id.as_str());
2233        clips.sort_by(|left, right| left.clip_name.cmp(&right.clip_name));
2234        let mut settings = Self {
2235            schema: RESOLVED_ENGINE_SETTINGS_V1_ID.to_owned(),
2236            identity: InputIdentity::from_bytes(&[]),
2237            document_settings,
2238            clips,
2239        };
2240        settings.validate_structure(true)?;
2241        settings.validate_materialization(profile, false)?;
2242        settings.identity = settings.computed_identity(profile);
2243        Ok(settings)
2244    }
2245
2246    /// Contract id carried in the `schema` field.
2247    pub fn contract_id(&self) -> &str {
2248        &self.schema
2249    }
2250
2251    /// SHA-256 plus byte count of the unchanged #464 settings preimage.
2252    pub const fn settings_identity(&self) -> &InputIdentity {
2253        &self.identity
2254    }
2255
2256    /// Stable-id-ordered fully materialized document settings.
2257    pub fn document_settings(&self) -> &[EngineSettingRowV1] {
2258        &self.document_settings
2259    }
2260
2261    /// Lexical-name-ordered actual clip rows, retaining repeated equal names.
2262    pub fn clips(&self) -> &[EngineClipSettingsV1] {
2263        &self.clips
2264    }
2265
2266    /// Look up one document setting.
2267    pub fn document_setting(&self, id: EngineSettingIdV1) -> Option<&EngineSettingValueV1> {
2268        self.document_settings
2269            .iter()
2270            .find(|row| row.id == id)
2271            .map(|row| &row.value)
2272    }
2273
2274    /// Look up one clip row by its identity-significant ordinal and exact name.
2275    pub fn clip_row(&self, ordinal: usize, clip_name: &str) -> Option<&EngineClipSettingsV1> {
2276        self.clips
2277            .get(ordinal)
2278            .filter(|row| row.clip_name == clip_name)
2279    }
2280
2281    /// Validate structure, materialization, and identity against one profile.
2282    ///
2283    /// # Errors
2284    ///
2285    /// Returns [`EngineContractError`] for any invalid wire, cross-reference,
2286    /// or canonical identity.
2287    pub fn validate_against(
2288        &self,
2289        profile: &ResolvedEngineProfileV1,
2290    ) -> Result<(), EngineContractError> {
2291        profile.validate()?;
2292        self.validate_structure(true)?;
2293        self.validate_materialization(profile, true)
2294    }
2295
2296    /// Append the complete unchanged #464 settings preimage, including its domain.
2297    pub(crate) fn encode_preimage(
2298        &self,
2299        profile: &ResolvedEngineProfileV1,
2300        encoder: &mut CanonicalEncoder,
2301    ) {
2302        encoder.token(ENGINE_SETTINGS_PREIMAGE_DOMAIN);
2303        encode_profile_key(encoder, &profile.selection);
2304        encoder.field("fact_bundle_urn");
2305        encoder.token(&profile.fact_bundle_urn);
2306        encoder.field("document_settings");
2307        encoder.count(self.document_settings.len());
2308        for row in &self.document_settings {
2309            encoder.token(row.id.as_str());
2310            encode_setting_value(encoder, &row.value);
2311        }
2312        encoder.field("clips");
2313        encoder.count(self.clips.len());
2314        for clip in &self.clips {
2315            encoder.token(&clip.clip_name);
2316            encoder.count(clip.settings.len());
2317            for row in &clip.settings {
2318                encoder.token(row.id.as_str());
2319                encode_setting_value(encoder, &row.value);
2320            }
2321        }
2322    }
2323
2324    pub(crate) fn retained_rows(&self) -> Result<usize, EngineContractError> {
2325        checked_sum(
2326            "settings retained rows",
2327            [self.document_settings.len(), self.clips.len()]
2328                .into_iter()
2329                .chain(self.clips.iter().map(|clip| clip.settings.len())),
2330        )
2331    }
2332
2333    pub(crate) fn retained_text_bytes(&self) -> Result<usize, EngineContractError> {
2334        let document_paths = self
2335            .document_settings
2336            .iter()
2337            .filter_map(|row| match &row.value {
2338                EngineSettingValueV1::SourceTransformPath(path) => Some(path.len()),
2339                _ => None,
2340            });
2341        checked_sum_results(
2342            "settings retained text",
2343            document_paths,
2344            self.clips
2345                .iter()
2346                .map(EngineClipSettingsV1::retained_text_bytes),
2347        )
2348    }
2349
2350    fn computed_identity(&self, profile: &ResolvedEngineProfileV1) -> InputIdentity {
2351        let mut encoder = CanonicalEncoder::default();
2352        self.encode_preimage(profile, &mut encoder);
2353        encoder.identity()
2354    }
2355
2356    fn validate_structure(&self, require_order: bool) -> Result<(), EngineContractError> {
2357        validate_schema(
2358            "settings.schema",
2359            &self.schema,
2360            RESOLVED_ENGINE_SETTINGS_V1_ID,
2361        )?;
2362        validate_collection_len("settings.document_settings", self.document_settings.len())?;
2363        validate_collection_len("settings.clips", self.clips.len())?;
2364        validate_unique_order(
2365            "settings.document_settings",
2366            &self.document_settings,
2367            |row| row.id.as_str(),
2368            require_order,
2369        )?;
2370        for row in &self.document_settings {
2371            validate_setting_value(&row.value)?;
2372        }
2373        if require_order
2374            && !self
2375                .clips
2376                .windows(2)
2377                .all(|pair| pair[0].clip_name <= pair[1].clip_name)
2378        {
2379            return Err(EngineContractError::NonCanonicalOrder {
2380                field: "settings.clips",
2381            });
2382        }
2383        for clip in &self.clips {
2384            clip.validate(require_order)?;
2385        }
2386        let rows = self.retained_rows()?;
2387        if rows > ENGINE_CONTRACT_V1_MAX_AGGREGATE_ROWS {
2388            return Err(EngineContractError::TooManyAggregateRows {
2389                found: rows,
2390                max: ENGINE_CONTRACT_V1_MAX_AGGREGATE_ROWS,
2391            });
2392        }
2393        let text = self.retained_text_bytes()?;
2394        if text > ENGINE_CONTRACT_V1_MAX_TOTAL_TEXT_BYTES {
2395            return Err(EngineContractError::TooMuchAggregateText {
2396                found: text,
2397                max: ENGINE_CONTRACT_V1_MAX_TOTAL_TEXT_BYTES,
2398            });
2399        }
2400        Ok(())
2401    }
2402
2403    fn validate_materialization(
2404        &self,
2405        profile: &ResolvedEngineProfileV1,
2406        verify_identity: bool,
2407    ) -> Result<(), EngineContractError> {
2408        validate_rows_for_scope(
2409            profile,
2410            &self.document_settings,
2411            EngineSettingScopeV1::Document,
2412            "document",
2413        )?;
2414        for (ordinal, clip) in self.clips.iter().enumerate() {
2415            validate_rows_for_scope(
2416                profile,
2417                &clip.settings,
2418                EngineSettingScopeV1::Clip,
2419                &format!("clip[{ordinal}]"),
2420            )?;
2421        }
2422        if verify_identity && self.identity != self.computed_identity(profile) {
2423            return Err(EngineContractError::IdentityMismatch {
2424                contract: RESOLVED_ENGINE_SETTINGS_V1_ID,
2425            });
2426        }
2427        Ok(())
2428    }
2429}
2430
2431struct ResolvedEngineSettingsWireV1 {
2432    schema: String,
2433    identity: InputIdentity,
2434    document_settings: CappedSequence<EngineSettingRowV1>,
2435    clips: CappedSequence<EngineClipSettingsWireV1>,
2436    aggregate_rows: RowBudget,
2437    provenance_rows_overflowed: bool,
2438}
2439
2440enum ClipSettingsElement {
2441    Value(EngineClipSettingsWireV1),
2442    Skipped,
2443}
2444
2445struct ClipSettingsElementSeed<'a> {
2446    rows: &'a mut SettingsRows,
2447}
2448
2449impl<'de> DeserializeSeed<'de> for ClipSettingsElementSeed<'_> {
2450    type Value = ClipSettingsElement;
2451
2452    fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
2453    where
2454        D: Deserializer<'de>,
2455    {
2456        if self.rows.admit_clip() {
2457            EngineClipSettingsSeed { rows: self.rows }
2458                .deserialize(deserializer)
2459                .map(ClipSettingsElement::Value)
2460        } else {
2461            IgnoredAny::deserialize(deserializer).map(|_| ClipSettingsElement::Skipped)
2462        }
2463    }
2464}
2465
2466struct ClipSettingsSequenceSeed<'a> {
2467    rows: &'a mut SettingsRows,
2468}
2469
2470impl<'de> DeserializeSeed<'de> for ClipSettingsSequenceSeed<'_> {
2471    type Value = CappedSequence<EngineClipSettingsWireV1>;
2472
2473    fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
2474    where
2475        D: Deserializer<'de>,
2476    {
2477        struct ClipSettingsSequenceVisitor<'a> {
2478            rows: &'a mut SettingsRows,
2479        }
2480
2481        impl<'de> Visitor<'de> for ClipSettingsSequenceVisitor<'_> {
2482            type Value = CappedSequence<EngineClipSettingsWireV1>;
2483
2484            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
2485                formatter.write_str("a bounded sequence of engine clip settings")
2486            }
2487
2488            fn visit_seq<A>(self, mut sequence: A) -> Result<Self::Value, A::Error>
2489            where
2490                A: SeqAccess<'de>,
2491            {
2492                let mut values = Vec::with_capacity(
2493                    sequence
2494                        .size_hint()
2495                        .unwrap_or(0)
2496                        .min(ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS),
2497                );
2498                let mut seen = 0usize;
2499                while seen < ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS {
2500                    let Some(element) =
2501                        sequence.next_element_seed(ClipSettingsElementSeed { rows: self.rows })?
2502                    else {
2503                        return Ok(CappedSequence {
2504                            values,
2505                            overflowed: false,
2506                        });
2507                    };
2508                    seen += 1;
2509                    match element {
2510                        ClipSettingsElement::Value(value) => values.push(value),
2511                        ClipSettingsElement::Skipped => {
2512                            let overflowed = consume_ignored_tail(
2513                                &mut sequence,
2514                                seen,
2515                                ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS,
2516                            )?;
2517                            return Ok(CappedSequence { values, overflowed });
2518                        }
2519                    }
2520                }
2521                let overflowed = consume_ignored_tail(
2522                    &mut sequence,
2523                    seen,
2524                    ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS,
2525                )?;
2526                Ok(CappedSequence { values, overflowed })
2527            }
2528        }
2529
2530        deserializer.deserialize_seq(ClipSettingsSequenceVisitor { rows: self.rows })
2531    }
2532}
2533
2534struct ResolvedEngineSettingsWireSeed {
2535    provenance_limit: Option<usize>,
2536}
2537
2538impl<'de> DeserializeSeed<'de> for ResolvedEngineSettingsWireSeed {
2539    type Value = ResolvedEngineSettingsWireV1;
2540
2541    fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
2542    where
2543        D: Deserializer<'de>,
2544    {
2545        #[derive(Deserialize)]
2546        #[serde(field_identifier, rename_all = "snake_case")]
2547        enum Field {
2548            Schema,
2549            Identity,
2550            DocumentSettings,
2551            Clips,
2552        }
2553
2554        struct SettingsVisitor {
2555            provenance_limit: Option<usize>,
2556        }
2557
2558        impl<'de> Visitor<'de> for SettingsVisitor {
2559            type Value = ResolvedEngineSettingsWireV1;
2560
2561            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
2562                formatter.write_str("resolved engine settings")
2563            }
2564
2565            fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
2566            where
2567                A: MapAccess<'de>,
2568            {
2569                let mut rows = SettingsRows::new(self.provenance_limit);
2570                let mut schema = None;
2571                let mut identity = None;
2572                let mut document_settings = None;
2573                let mut clips = None;
2574                while let Some(field) = map.next_key()? {
2575                    match field {
2576                        Field::Schema => set_once(&mut schema, map.next_value()?, "schema")?,
2577                        Field::Identity => set_once(&mut identity, map.next_value()?, "identity")?,
2578                        Field::DocumentSettings => {
2579                            if document_settings.is_some() {
2580                                return Err(A::Error::duplicate_field("document_settings"));
2581                            }
2582                            document_settings =
2583                                Some(map.next_value_seed(SettingsSequenceSeed {
2584                                    rows: &mut rows,
2585                                    element: PhantomData,
2586                                })?);
2587                        }
2588                        Field::Clips => {
2589                            if clips.is_some() {
2590                                return Err(A::Error::duplicate_field("clips"));
2591                            }
2592                            clips =
2593                                Some(map.next_value_seed(ClipSettingsSequenceSeed {
2594                                    rows: &mut rows,
2595                                })?);
2596                        }
2597                    }
2598                }
2599                Ok(ResolvedEngineSettingsWireV1 {
2600                    schema: required(schema, "schema")?,
2601                    identity: required(identity, "identity")?,
2602                    document_settings: required(document_settings, "document_settings")?,
2603                    clips: required(clips, "clips")?,
2604                    provenance_rows_overflowed: rows.provenance_overflowed(),
2605                    aggregate_rows: rows.local,
2606                })
2607            }
2608        }
2609
2610        deserializer.deserialize_struct(
2611            "ResolvedEngineSettingsV1",
2612            &["schema", "identity", "document_settings", "clips"],
2613            SettingsVisitor {
2614                provenance_limit: self.provenance_limit,
2615            },
2616        )
2617    }
2618}
2619
2620impl<'de> Deserialize<'de> for ResolvedEngineSettingsWireV1 {
2621    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2622    where
2623        D: Deserializer<'de>,
2624    {
2625        ResolvedEngineSettingsWireSeed {
2626            provenance_limit: None,
2627        }
2628        .deserialize(deserializer)
2629    }
2630}
2631
2632impl ResolvedEngineSettingsV1 {
2633    fn validate_wire_limits(
2634        wire: &ResolvedEngineSettingsWireV1,
2635    ) -> Result<(), EngineContractError> {
2636        validate_schema(
2637            "settings.schema",
2638            &wire.schema,
2639            RESOLVED_ENGINE_SETTINGS_V1_ID,
2640        )?;
2641        for (field, overflowed) in [
2642            (
2643                "settings.document_settings",
2644                wire.document_settings.overflowed,
2645            ),
2646            ("settings.clips", wire.clips.overflowed),
2647        ] {
2648            if overflowed {
2649                return Err(EngineContractError::TooManyRows {
2650                    field,
2651                    found: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS + 1,
2652                    max: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS,
2653                });
2654            }
2655        }
2656        for clip in &wire.clips.values {
2657            if clip.settings.overflowed {
2658                return Err(EngineContractError::TooManyRows {
2659                    field: "settings.clips.settings",
2660                    found: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS + 1,
2661                    max: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS,
2662                });
2663            }
2664        }
2665        if wire.aggregate_rows.overflowed() {
2666            return Err(EngineContractError::TooManyAggregateRows {
2667                found: wire.aggregate_rows.found(),
2668                max: ENGINE_CONTRACT_V1_MAX_AGGREGATE_ROWS,
2669            });
2670        }
2671        Ok(())
2672    }
2673
2674    fn from_wire(wire: ResolvedEngineSettingsWireV1) -> Result<Self, EngineContractError> {
2675        Self::validate_wire_limits(&wire)?;
2676        let clips = wire
2677            .clips
2678            .values
2679            .into_iter()
2680            .map(EngineClipSettingsV1::from_wire)
2681            .collect::<Result<Vec<_>, _>>()?;
2682        let settings = Self {
2683            schema: wire.schema,
2684            identity: wire.identity,
2685            document_settings: wire.document_settings.values,
2686            clips,
2687        };
2688        settings.validate_structure(true)?;
2689        Ok(settings)
2690    }
2691}
2692
2693pub(crate) enum EngineSettingsLimitedDecodeError {
2694    Contract(EngineContractDecodeError),
2695    ProvenanceRowsOverflow,
2696}
2697
2698pub(crate) fn decode_resolved_engine_settings_v1_with_provenance_limit(
2699    raw: &str,
2700    provenance_limit: usize,
2701) -> Result<ResolvedEngineSettingsV1, EngineSettingsLimitedDecodeError> {
2702    let mut deserializer = serde_json::Deserializer::from_str(raw);
2703    let wire = ResolvedEngineSettingsWireSeed {
2704        provenance_limit: Some(provenance_limit),
2705    }
2706    .deserialize(&mut deserializer)
2707    .map_err(|source| {
2708        EngineSettingsLimitedDecodeError::Contract(EngineContractDecodeError::Shape(source))
2709    })?;
2710    deserializer.end().map_err(|source| {
2711        EngineSettingsLimitedDecodeError::Contract(EngineContractDecodeError::Shape(source))
2712    })?;
2713    ResolvedEngineSettingsV1::validate_wire_limits(&wire).map_err(|source| {
2714        EngineSettingsLimitedDecodeError::Contract(EngineContractDecodeError::Semantic(source))
2715    })?;
2716    if wire.provenance_rows_overflowed {
2717        return Err(EngineSettingsLimitedDecodeError::ProvenanceRowsOverflow);
2718    }
2719    ResolvedEngineSettingsV1::from_wire(wire).map_err(|source| {
2720        EngineSettingsLimitedDecodeError::Contract(EngineContractDecodeError::Semantic(source))
2721    })
2722}
2723
2724impl<'de> Deserialize<'de> for ResolvedEngineSettingsV1 {
2725    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2726    where
2727        D: Deserializer<'de>,
2728    {
2729        Self::from_wire(ResolvedEngineSettingsWireV1::deserialize(deserializer)?)
2730            .map_err(D::Error::custom)
2731    }
2732}
2733
2734/// Complete or bounded-partial coverage of actual clip settings in V2.
2735#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
2736#[serde(rename_all = "snake_case")]
2737pub enum ResolvedEngineSettingsCoverageStateV2 {
2738    /// Every actual clip has a retained materialized settings row.
2739    Complete,
2740    /// Only the bounded canonical prefix was retained.
2741    Partial,
2742}
2743
2744/// Stable reason for bounded-partial V2 settings coverage.
2745#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
2746#[serde(rename_all = "snake_case")]
2747pub enum ResolvedEngineSettingsCoverageReasonV2 {
2748    /// The actual source inventory exceeded the 4,096 retained-row limit.
2749    ActualClipRowsExceeded,
2750}
2751
2752/// Explicit actual-clip coverage carried by resolved-engine-settings V2.
2753#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
2754#[serde(deny_unknown_fields)]
2755pub struct ResolvedEngineSettingsCoverageV2 {
2756    state: ResolvedEngineSettingsCoverageStateV2,
2757    #[serde(skip_serializing_if = "Option::is_none")]
2758    reason: Option<ResolvedEngineSettingsCoverageReasonV2>,
2759}
2760
2761impl ResolvedEngineSettingsCoverageV2 {
2762    /// Construct complete coverage.
2763    pub const fn complete() -> Self {
2764        Self {
2765            state: ResolvedEngineSettingsCoverageStateV2::Complete,
2766            reason: None,
2767        }
2768    }
2769
2770    /// Construct N+1 bounded partial coverage.
2771    pub const fn actual_clip_rows_exceeded() -> Self {
2772        Self {
2773            state: ResolvedEngineSettingsCoverageStateV2::Partial,
2774            reason: Some(ResolvedEngineSettingsCoverageReasonV2::ActualClipRowsExceeded),
2775        }
2776    }
2777
2778    /// Coverage state.
2779    pub const fn state(&self) -> ResolvedEngineSettingsCoverageStateV2 {
2780        self.state
2781    }
2782
2783    /// Partial-coverage reason, when applicable.
2784    pub const fn reason(&self) -> Option<ResolvedEngineSettingsCoverageReasonV2> {
2785        self.reason
2786    }
2787}
2788
2789/// Bounded work accounting carried by resolved-engine-settings V2.
2790#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
2791#[serde(deny_unknown_fields)]
2792pub struct ResolvedEngineSettingsWorkV2 {
2793    actual_clip_rows_inspected: usize,
2794    materialized_clip_rows: usize,
2795    retained_clip_rows: usize,
2796}
2797
2798impl ResolvedEngineSettingsWorkV2 {
2799    /// Construct bounded work counters.
2800    pub const fn new(
2801        actual_clip_rows_inspected: usize,
2802        materialized_clip_rows: usize,
2803        retained_clip_rows: usize,
2804    ) -> Self {
2805        Self {
2806            actual_clip_rows_inspected,
2807            materialized_clip_rows,
2808            retained_clip_rows,
2809        }
2810    }
2811
2812    /// Actual source rows inspected, capped at 4,097.
2813    pub const fn actual_clip_rows_inspected(&self) -> usize {
2814        self.actual_clip_rows_inspected
2815    }
2816
2817    /// Rows whose settings were materialized.
2818    pub const fn materialized_clip_rows(&self) -> usize {
2819        self.materialized_clip_rows
2820    }
2821
2822    /// Rows retained after canonicalization.
2823    pub const fn retained_clip_rows(&self) -> usize {
2824        self.retained_clip_rows
2825    }
2826}
2827
2828/// Fully materialized bounded-prefix V2 engine settings.
2829///
2830/// V2 preserves the ordinary V1 row spelling while committing to coverage and
2831/// bounded work in a separate canonical identity. Thus an exact 4,096-row
2832/// input cannot collide with a larger input sharing that prefix.
2833#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
2834pub struct ResolvedEngineSettingsV2 {
2835    schema: String,
2836    identity: InputIdentity,
2837    document_settings: Vec<EngineSettingRowV1>,
2838    clips: Vec<EngineClipSettingsV1>,
2839    clip_coverage: ResolvedEngineSettingsCoverageV2,
2840    work: ResolvedEngineSettingsWorkV2,
2841}
2842
2843struct ResolvedEngineSettingsWireV2 {
2844    schema: String,
2845    identity: InputIdentity,
2846    document_settings: CappedSequence<EngineSettingRowV1>,
2847    clips: CappedSequence<EngineClipSettingsWireV1>,
2848    clip_coverage: ResolvedEngineSettingsCoverageV2,
2849    work: ResolvedEngineSettingsWorkV2,
2850    aggregate_rows: RowBudget,
2851    provenance_rows_overflowed: bool,
2852}
2853
2854struct ResolvedEngineSettingsWireSeedV2 {
2855    provenance_limit: Option<usize>,
2856}
2857
2858impl<'de> DeserializeSeed<'de> for ResolvedEngineSettingsWireSeedV2 {
2859    type Value = ResolvedEngineSettingsWireV2;
2860    fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
2861    where
2862        D: Deserializer<'de>,
2863    {
2864        #[derive(Deserialize)]
2865        #[serde(field_identifier, rename_all = "snake_case")]
2866        enum Field {
2867            Schema,
2868            Identity,
2869            DocumentSettings,
2870            Clips,
2871            ClipCoverage,
2872            Work,
2873        }
2874        struct VisitorV2 {
2875            provenance_limit: Option<usize>,
2876        }
2877        impl<'de> Visitor<'de> for VisitorV2 {
2878            type Value = ResolvedEngineSettingsWireV2;
2879            fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2880                f.write_str("resolved engine settings V2")
2881            }
2882            fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
2883            where
2884                A: MapAccess<'de>,
2885            {
2886                let mut rows = SettingsRows::new(self.provenance_limit);
2887                let mut schema = None;
2888                let mut identity = None;
2889                let mut document_settings = None;
2890                let mut clips = None;
2891                let mut clip_coverage = None;
2892                let mut work = None;
2893                while let Some(field) = map.next_key()? {
2894                    match field {
2895                        Field::Schema => set_once(&mut schema, map.next_value()?, "schema")?,
2896                        Field::Identity => set_once(&mut identity, map.next_value()?, "identity")?,
2897                        Field::DocumentSettings => {
2898                            if document_settings.is_some() {
2899                                return Err(A::Error::duplicate_field("document_settings"));
2900                            }
2901                            document_settings =
2902                                Some(map.next_value_seed(SettingsSequenceSeed {
2903                                    rows: &mut rows,
2904                                    element: PhantomData,
2905                                })?);
2906                        }
2907                        Field::Clips => {
2908                            if clips.is_some() {
2909                                return Err(A::Error::duplicate_field("clips"));
2910                            }
2911                            clips =
2912                                Some(map.next_value_seed(ClipSettingsSequenceSeed {
2913                                    rows: &mut rows,
2914                                })?);
2915                        }
2916                        Field::ClipCoverage => {
2917                            set_once(&mut clip_coverage, map.next_value()?, "clip_coverage")?
2918                        }
2919                        Field::Work => set_once(&mut work, map.next_value()?, "work")?,
2920                    }
2921                }
2922                Ok(ResolvedEngineSettingsWireV2 {
2923                    schema: required(schema, "schema")?,
2924                    identity: required(identity, "identity")?,
2925                    document_settings: required(document_settings, "document_settings")?,
2926                    clips: required(clips, "clips")?,
2927                    clip_coverage: required(clip_coverage, "clip_coverage")?,
2928                    work: required(work, "work")?,
2929                    provenance_rows_overflowed: rows.provenance_overflowed(),
2930                    aggregate_rows: rows.local,
2931                })
2932            }
2933        }
2934        deserializer.deserialize_struct(
2935            "ResolvedEngineSettingsV2",
2936            &[
2937                "schema",
2938                "identity",
2939                "document_settings",
2940                "clips",
2941                "clip_coverage",
2942                "work",
2943            ],
2944            VisitorV2 {
2945                provenance_limit: self.provenance_limit,
2946            },
2947        )
2948    }
2949}
2950
2951impl<'de> Deserialize<'de> for ResolvedEngineSettingsWireV2 {
2952    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2953    where
2954        D: Deserializer<'de>,
2955    {
2956        ResolvedEngineSettingsWireSeedV2 {
2957            provenance_limit: None,
2958        }
2959        .deserialize(deserializer)
2960    }
2961}
2962
2963impl ResolvedEngineSettingsV2 {
2964    /// Construct V2 settings from a fully materialized retained prefix.
2965    pub fn new(
2966        profile: &ResolvedEngineProfileV1,
2967        document_settings: Vec<EngineSettingRowV1>,
2968        clips: Vec<EngineClipSettingsV1>,
2969        clip_coverage: ResolvedEngineSettingsCoverageV2,
2970        work: ResolvedEngineSettingsWorkV2,
2971    ) -> Result<Self, EngineContractError> {
2972        let prefix = ResolvedEngineSettingsV1::new(profile, document_settings, clips)?;
2973        let mut settings = Self {
2974            schema: RESOLVED_ENGINE_SETTINGS_V2_ID.to_owned(),
2975            identity: InputIdentity::from_bytes(&[]),
2976            document_settings: prefix.document_settings,
2977            clips: prefix.clips,
2978            clip_coverage,
2979            work,
2980        };
2981        settings.validate_prefix_against(profile)?;
2982        settings.validate_coverage_work()?;
2983        settings.identity = settings.computed_identity(profile);
2984        Ok(settings)
2985    }
2986
2987    /// Contract id carried in `schema`.
2988    pub fn contract_id(&self) -> &str {
2989        &self.schema
2990    }
2991
2992    /// Identity committing to prefix, coverage, and work.
2993    pub const fn settings_identity(&self) -> &InputIdentity {
2994        &self.identity
2995    }
2996
2997    /// Stable-id-ordered document settings.
2998    pub fn document_settings(&self) -> &[EngineSettingRowV1] {
2999        &self.document_settings
3000    }
3001
3002    /// Canonically ordered retained clip rows.
3003    pub fn clips(&self) -> &[EngineClipSettingsV1] {
3004        &self.clips
3005    }
3006
3007    /// Look up one retained document setting.
3008    pub fn document_setting(&self, id: EngineSettingIdV1) -> Option<&EngineSettingValueV1> {
3009        self.document_settings
3010            .iter()
3011            .find(|row| row.id == id)
3012            .map(|row| &row.value)
3013    }
3014
3015    /// Look up one retained clip row by source ordinal and exact name.
3016    pub fn clip_row(&self, ordinal: usize, clip_name: &str) -> Option<&EngineClipSettingsV1> {
3017        self.clips
3018            .get(ordinal)
3019            .filter(|row| row.clip_name == clip_name)
3020    }
3021
3022    /// Aggregate retained text in the shared settings prefix.
3023    pub(crate) fn retained_text_bytes(&self) -> Result<usize, EngineContractError> {
3024        self.v1_prefix().retained_text_bytes()
3025    }
3026
3027    /// Explicit actual-clip coverage.
3028    pub const fn clip_coverage(&self) -> &ResolvedEngineSettingsCoverageV2 {
3029        &self.clip_coverage
3030    }
3031
3032    /// Bounded resolution work.
3033    pub const fn work(&self) -> &ResolvedEngineSettingsWorkV2 {
3034        &self.work
3035    }
3036
3037    /// Validate settings, coverage/work, and the V2 identity against profile facts.
3038    pub fn validate_against(
3039        &self,
3040        profile: &ResolvedEngineProfileV1,
3041    ) -> Result<(), EngineContractError> {
3042        self.validate_prefix_against(profile)?;
3043        self.validate_coverage_work()?;
3044        if self.identity != self.computed_identity(profile) {
3045            return Err(EngineContractError::IdentityMismatch {
3046                contract: RESOLVED_ENGINE_SETTINGS_V2_ID,
3047            });
3048        }
3049        Ok(())
3050    }
3051
3052    fn validate_prefix_against(
3053        &self,
3054        profile: &ResolvedEngineProfileV1,
3055    ) -> Result<(), EngineContractError> {
3056        if self.schema != RESOLVED_ENGINE_SETTINGS_V2_ID {
3057            return Err(EngineContractError::InvalidSchema {
3058                field: "settings.schema",
3059                expected: RESOLVED_ENGINE_SETTINGS_V2_ID,
3060                found: self.schema.clone(),
3061            });
3062        }
3063        let prefix = self.v1_prefix();
3064        prefix.validate_structure(true)?;
3065        prefix.validate_materialization(profile, false)
3066    }
3067
3068    fn validate_coverage_work(&self) -> Result<(), EngineContractError> {
3069        let retained = self.clips.len();
3070        let complete = matches!(
3071            (self.clip_coverage.state, self.clip_coverage.reason),
3072            (ResolvedEngineSettingsCoverageStateV2::Complete, None)
3073        );
3074        let partial = matches!(
3075            (self.clip_coverage.state, self.clip_coverage.reason),
3076            (
3077                ResolvedEngineSettingsCoverageStateV2::Partial,
3078                Some(ResolvedEngineSettingsCoverageReasonV2::ActualClipRowsExceeded)
3079            )
3080        );
3081        let expected_inspected = if partial {
3082            ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS + 1
3083        } else {
3084            retained
3085        };
3086        if !(complete || partial)
3087            || self.work.actual_clip_rows_inspected != expected_inspected
3088            || self.work.materialized_clip_rows != retained
3089            || self.work.retained_clip_rows != retained
3090            || (partial && retained != ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS)
3091        {
3092            return Err(EngineContractError::InvalidV2CoverageWork);
3093        }
3094        Ok(())
3095    }
3096
3097    fn v1_prefix(&self) -> ResolvedEngineSettingsV1 {
3098        ResolvedEngineSettingsV1 {
3099            schema: RESOLVED_ENGINE_SETTINGS_V1_ID.to_owned(),
3100            identity: InputIdentity::from_bytes(&[]),
3101            document_settings: self.document_settings.clone(),
3102            clips: self.clips.clone(),
3103        }
3104    }
3105
3106    /// Construct an internal V1-shaped prefix solely to reuse established
3107    /// profile/raw-source cross-link validation. It is never serialized or
3108    /// returned from a V2 public API.
3109    pub(crate) fn validation_only_prefix(
3110        &self,
3111        profile: &ResolvedEngineProfileV1,
3112    ) -> Result<ResolvedEngineSettingsV1, EngineContractError> {
3113        ResolvedEngineSettingsV1::new(profile, self.document_settings.clone(), self.clips.clone())
3114    }
3115
3116    fn computed_identity(&self, profile: &ResolvedEngineProfileV1) -> InputIdentity {
3117        let mut encoder = CanonicalEncoder::new("animsmith-engine-settings-v2");
3118        self.v1_prefix().encode_preimage(profile, &mut encoder);
3119        encoder.field("clip_coverage.state");
3120        encoder.token(match self.clip_coverage.state {
3121            ResolvedEngineSettingsCoverageStateV2::Complete => "complete",
3122            ResolvedEngineSettingsCoverageStateV2::Partial => "partial",
3123        });
3124        encoder.field("clip_coverage.reason");
3125        encoder.token(match self.clip_coverage.reason {
3126            None => "none",
3127            Some(ResolvedEngineSettingsCoverageReasonV2::ActualClipRowsExceeded) => {
3128                "actual_clip_rows_exceeded"
3129            }
3130        });
3131        for (field, value) in [
3132            (
3133                "actual_clip_rows_inspected",
3134                self.work.actual_clip_rows_inspected,
3135            ),
3136            ("materialized_clip_rows", self.work.materialized_clip_rows),
3137            ("retained_clip_rows", self.work.retained_clip_rows),
3138        ] {
3139            encoder.field(field);
3140            encoder.count(value);
3141        }
3142        encoder.identity()
3143    }
3144}
3145
3146impl<'de> Deserialize<'de> for ResolvedEngineSettingsV2 {
3147    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
3148    where
3149        D: Deserializer<'de>,
3150    {
3151        let wire = ResolvedEngineSettingsWireV2::deserialize(deserializer)?;
3152        if wire.document_settings.overflowed || wire.clips.overflowed {
3153            return Err(D::Error::custom(
3154                "resolved-engine-settings V2 collection exceeds 4096 rows",
3155            ));
3156        }
3157        if wire.aggregate_rows.overflowed() {
3158            return Err(D::Error::custom(
3159                EngineContractError::TooManyAggregateRows {
3160                    found: wire.aggregate_rows.found(),
3161                    max: ENGINE_CONTRACT_V1_MAX_AGGREGATE_ROWS,
3162                },
3163            ));
3164        }
3165        let settings = Self {
3166            schema: wire.schema,
3167            identity: wire.identity,
3168            document_settings: wire.document_settings.values,
3169            clips: wire
3170                .clips
3171                .values
3172                .into_iter()
3173                .map(EngineClipSettingsV1::from_wire)
3174                .collect::<Result<_, _>>()
3175                .map_err(D::Error::custom)?,
3176            clip_coverage: wire.clip_coverage,
3177            work: wire.work,
3178        };
3179        if settings.schema != RESOLVED_ENGINE_SETTINGS_V2_ID {
3180            return Err(D::Error::custom(
3181                "invalid resolved-engine-settings V2 schema",
3182            ));
3183        }
3184        settings
3185            .v1_prefix()
3186            .validate_structure(true)
3187            .map_err(D::Error::custom)?;
3188        settings
3189            .validate_coverage_work()
3190            .map_err(D::Error::custom)?;
3191        Ok(settings)
3192    }
3193}
3194
3195/// Decode V2 settings under the caller-owned remaining provenance-row budget.
3196/// The seed admits only rows that fit the shared budget and consumes the first
3197/// excess row as a sentinel, so no partial settings prefix is returned.
3198pub(crate) fn decode_resolved_engine_settings_v2_with_provenance_limit(
3199    raw: &str,
3200    provenance_limit: usize,
3201) -> Result<ResolvedEngineSettingsV2, EngineSettingsLimitedDecodeError> {
3202    let mut deserializer = serde_json::Deserializer::from_str(raw);
3203    let wire = ResolvedEngineSettingsWireSeedV2 {
3204        provenance_limit: Some(provenance_limit),
3205    }
3206    .deserialize(&mut deserializer)
3207    .map_err(|source| {
3208        EngineSettingsLimitedDecodeError::Contract(EngineContractDecodeError::Shape(source))
3209    })?;
3210    deserializer.end().map_err(|source| {
3211        EngineSettingsLimitedDecodeError::Contract(EngineContractDecodeError::Shape(source))
3212    })?;
3213    if wire.provenance_rows_overflowed {
3214        return Err(EngineSettingsLimitedDecodeError::ProvenanceRowsOverflow);
3215    }
3216    let settings = ResolvedEngineSettingsV2 {
3217        schema: wire.schema,
3218        identity: wire.identity,
3219        document_settings: wire.document_settings.values,
3220        clips: wire
3221            .clips
3222            .values
3223            .into_iter()
3224            .map(EngineClipSettingsV1::from_wire)
3225            .collect::<Result<_, _>>()
3226            .map_err(|source| {
3227                EngineSettingsLimitedDecodeError::Contract(EngineContractDecodeError::Semantic(
3228                    source,
3229                ))
3230            })?,
3231        clip_coverage: wire.clip_coverage,
3232        work: wire.work,
3233    };
3234    settings
3235        .v1_prefix()
3236        .validate_structure(true)
3237        .map_err(|source| {
3238            EngineSettingsLimitedDecodeError::Contract(EngineContractDecodeError::Semantic(source))
3239        })?;
3240    settings.validate_coverage_work().map_err(|source| {
3241        EngineSettingsLimitedDecodeError::Contract(EngineContractDecodeError::Semantic(source))
3242    })?;
3243    Ok(settings)
3244}
3245
3246/// Positive reduced rational used where binary floating point would change an
3247/// exact importer contract.
3248#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
3249#[serde(try_from = "ReducedRatioWireV1")]
3250pub struct ReducedRatioV1 {
3251    numerator: u64,
3252    denominator: u64,
3253}
3254
3255#[derive(Deserialize)]
3256#[serde(deny_unknown_fields)]
3257struct ReducedRatioWireV1 {
3258    numerator: u64,
3259    denominator: u64,
3260}
3261
3262impl ReducedRatioV1 {
3263    /// Construct a positive ratio in lowest terms.
3264    pub fn new(numerator: u64, denominator: u64) -> Result<Self, EngineContractError> {
3265        let ratio = Self {
3266            numerator,
3267            denominator,
3268        };
3269        ratio.validate()?;
3270        Ok(ratio)
3271    }
3272
3273    /// Reduced numerator.
3274    pub const fn numerator(self) -> u64 {
3275        self.numerator
3276    }
3277
3278    /// Reduced nonzero denominator.
3279    pub const fn denominator(self) -> u64 {
3280        self.denominator
3281    }
3282
3283    fn validate(self) -> Result<(), EngineContractError> {
3284        if self.numerator == 0
3285            || self.denominator == 0
3286            || greatest_common_divisor(self.numerator, self.denominator) != 1
3287        {
3288            return Err(EngineContractError::InvalidReducedRatio {
3289                numerator: self.numerator,
3290                denominator: self.denominator,
3291            });
3292        }
3293        Ok(())
3294    }
3295}
3296
3297impl TryFrom<ReducedRatioWireV1> for ReducedRatioV1 {
3298    type Error = EngineContractError;
3299
3300    fn try_from(wire: ReducedRatioWireV1) -> Result<Self, Self::Error> {
3301        Self::new(wire.numerator, wire.denominator)
3302    }
3303}
3304
3305const fn greatest_common_divisor(mut left: u64, mut right: u64) -> u64 {
3306    while right != 0 {
3307        let remainder = left % right;
3308        left = right;
3309        right = remainder;
3310    }
3311    left
3312}
3313
3314/// Closed V2 fact vocabulary shared by prediction and advice profiles.
3315#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
3316#[serde(rename_all = "snake_case")]
3317pub enum EngineFactIdV2 {
3318    /// Accepted source containers.
3319    AcceptedInputs,
3320    /// Target world-length convention, without claiming application enforcement.
3321    TargetLinearUnit,
3322    /// Exact source numeric-unit mapping.
3323    SourceToTargetUnitMapping,
3324    /// Whether numeric physical dimensions survive importer conversion.
3325    PhysicalDimensionsPreserved,
3326    /// Importer scale-conversion behavior.
3327    ImporterScaleConversion,
3328    /// Whether the engine enforces an application-wide world-unit policy.
3329    ApplicationWorldUnitPolicy,
3330    /// Effective static/default-rest transform behavior.
3331    ResultingTransformScale,
3332    /// Root-motion source/addressability semantics.
3333    RootMotionAddressability,
3334    /// Import handling for source animation/channel subjects.
3335    SourceImportDisposition,
3336    /// Exact import-setting projection semantics.
3337    ImportSettingProjection,
3338}
3339
3340impl EngineFactIdV2 {
3341    /// Stable wire spelling.
3342    pub const fn as_str(self) -> &'static str {
3343        match self {
3344            Self::AcceptedInputs => "accepted_inputs",
3345            Self::TargetLinearUnit => "target_linear_unit",
3346            Self::SourceToTargetUnitMapping => "source_to_target_unit_mapping",
3347            Self::PhysicalDimensionsPreserved => "physical_dimensions_preserved",
3348            Self::ImporterScaleConversion => "importer_scale_conversion",
3349            Self::ApplicationWorldUnitPolicy => "application_world_unit_policy",
3350            Self::ResultingTransformScale => "resulting_transform_scale",
3351            Self::RootMotionAddressability => "root_motion_addressability",
3352            Self::SourceImportDisposition => "source_import_disposition",
3353            Self::ImportSettingProjection => "import_setting_projection",
3354        }
3355    }
3356}
3357
3358const ALL_FACT_IDS_V2: [EngineFactIdV2; 10] = [
3359    EngineFactIdV2::AcceptedInputs,
3360    EngineFactIdV2::ApplicationWorldUnitPolicy,
3361    EngineFactIdV2::ImportSettingProjection,
3362    EngineFactIdV2::ImporterScaleConversion,
3363    EngineFactIdV2::PhysicalDimensionsPreserved,
3364    EngineFactIdV2::ResultingTransformScale,
3365    EngineFactIdV2::RootMotionAddressability,
3366    EngineFactIdV2::SourceImportDisposition,
3367    EngineFactIdV2::SourceToTargetUnitMapping,
3368    EngineFactIdV2::TargetLinearUnit,
3369];
3370
3371/// Closed unit vocabulary for exact importer prediction.
3372#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
3373#[serde(rename_all = "snake_case")]
3374pub enum EngineLinearUnitV2 {
3375    /// glTF metre-per-unit semantics.
3376    Metre,
3377    /// Centimetre-authored numeric units.
3378    Centimetre,
3379    /// One engine world-space length unit, without a physical-unit guarantee.
3380    EngineWorldLengthUnit,
3381}
3382
3383/// Closed V2 fact value vocabulary.
3384#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
3385#[serde(rename_all = "snake_case")]
3386pub enum EngineFactValueV2 {
3387    /// Exact accepted formats.
3388    AcceptedFormats(Vec<SourceFormatV1>),
3389    /// A unit convention.
3390    LinearUnit(EngineLinearUnitV2),
3391    /// Exact target units per source unit.
3392    UnitRatio(ReducedRatioV1),
3393    /// Boolean fact.
3394    Boolean(bool),
3395    /// A closed semantic token whose vocabulary is owned by the fact id.
3396    Token(String),
3397    /// Root-motion addressability from the historical closed vocabulary.
3398    RootMotionAddressability(EngineRootMotionAddressabilityV1),
3399}
3400
3401/// Evidence state of one V2 profile fact.
3402#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
3403#[serde(rename_all = "snake_case")]
3404pub enum EngineFactStateV2 {
3405    /// Supported exact value.
3406    Known(EngineFactValueV2),
3407    /// Primary evidence does not establish a value.
3408    Unknown,
3409    /// The domain genuinely does not apply.
3410    NotApplicable,
3411}
3412
3413/// One immutable V2 fact row.
3414#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
3415#[serde(deny_unknown_fields)]
3416pub struct EngineProfileFactV2 {
3417    id: EngineFactIdV2,
3418    state: EngineFactStateV2,
3419}
3420
3421impl EngineProfileFactV2 {
3422    /// Construct one fact row.
3423    pub const fn new(id: EngineFactIdV2, state: EngineFactStateV2) -> Self {
3424        Self { id, state }
3425    }
3426
3427    /// Stable fact id.
3428    pub const fn id(&self) -> EngineFactIdV2 {
3429        self.id
3430    }
3431
3432    /// Explicit evidence state.
3433    pub const fn state(&self) -> &EngineFactStateV2 {
3434        &self.state
3435    }
3436}
3437
3438/// Closed setting ids required by the coordinated V2 profile migration.
3439#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
3440#[serde(rename_all = "snake_case")]
3441pub enum EngineSettingIdV2 {
3442    /// Historical unit conversion toggle.
3443    ConvertUnits,
3444    /// Historical axis conversion toggle.
3445    BakeAxisConversion,
3446    /// Exact root-motion source path.
3447    RootMotionSource,
3448    /// Yaw bake/extract policy.
3449    RootRotation,
3450    /// Vertical bake/extract policy.
3451    RootPositionY,
3452    /// Horizontal bake/extract policy.
3453    RootPositionXz,
3454    /// Unity animation type.
3455    AnimationType,
3456    /// Unity avatar construction policy.
3457    AvatarSetup,
3458    /// Unity animation import gate.
3459    ImportAnimation,
3460    /// Bevy scene-entity coordinate rotation.
3461    RotateSceneEntity,
3462    /// Bevy mesh coordinate rotation.
3463    RotateMeshes,
3464    /// Bevy mesh-name load filter.
3465    LoadMeshes,
3466    /// Exact Bevy extension-handler environment.
3467    ExtensionHandlerEnvironment,
3468    /// Compile-time Bevy animation feature.
3469    BevyAnimationFeature,
3470    /// Per-load Bevy animation gate.
3471    LoadAnimations,
3472    /// Godot animation bake frequency.
3473    AnimationFps,
3474    /// Godot animation trimming toggle.
3475    AnimationTrimming,
3476    /// Unreal sample-rate policy.
3477    SampleRate,
3478}
3479
3480impl EngineSettingIdV2 {
3481    /// Stable wire spelling.
3482    pub const fn as_str(self) -> &'static str {
3483        match self {
3484            Self::ConvertUnits => "convert_units",
3485            Self::BakeAxisConversion => "bake_axis_conversion",
3486            Self::RootMotionSource => "root_motion_source",
3487            Self::RootRotation => "root_rotation",
3488            Self::RootPositionY => "root_position_y",
3489            Self::RootPositionXz => "root_position_xz",
3490            Self::AnimationType => "animation_type",
3491            Self::AvatarSetup => "avatar_setup",
3492            Self::ImportAnimation => "import_animation",
3493            Self::RotateSceneEntity => "rotate_scene_entity",
3494            Self::RotateMeshes => "rotate_meshes",
3495            Self::LoadMeshes => "load_meshes",
3496            Self::ExtensionHandlerEnvironment => "extension_handler_environment",
3497            Self::BevyAnimationFeature => "bevy_animation_feature",
3498            Self::LoadAnimations => "load_animations",
3499            Self::AnimationFps => "animation_fps",
3500            Self::AnimationTrimming => "animation_trimming",
3501            Self::SampleRate => "sample_rate",
3502        }
3503    }
3504}
3505
3506/// Closed V2 setting value domains.
3507#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
3508#[serde(rename_all = "snake_case")]
3509pub enum EngineSettingDomainV2 {
3510    /// Boolean.
3511    Boolean,
3512    /// Positive bounded integer.
3513    PositiveInteger,
3514    /// Bake or extract.
3515    BakeOrExtract,
3516    /// Exact source-transform path.
3517    SourceTransformPath,
3518    /// Bounded ordered text list.
3519    TextList,
3520    /// Closed semantic token.
3521    Token,
3522    /// Unreal sample-rate selection.
3523    SampleRate,
3524}
3525
3526/// Unreal sample-rate selection without hidden compound values.
3527#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
3528#[serde(rename_all = "snake_case")]
3529pub enum EngineSampleRateV2 {
3530    /// `bUseDefaultSampleRate=true` and no hidden custom rate.
3531    Default30,
3532    /// `bUseDefaultSampleRate=false, CustomSampleRate=0`.
3533    SourceDetermined,
3534    /// Explicit custom rate in hertz.
3535    CustomHz(u32),
3536}
3537
3538/// Closed materialized setting values.
3539#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
3540#[serde(rename_all = "snake_case")]
3541pub enum EngineSettingValueV2 {
3542    /// Boolean value.
3543    Boolean(bool),
3544    /// Positive integer value.
3545    PositiveInteger(u32),
3546    /// Bake/extract value.
3547    BakeOrExtract(EngineBakeOrExtractV1),
3548    /// Exact case-sensitive source path.
3549    SourceTransformPath(String),
3550    /// Ordered bounded text list.
3551    TextList(#[serde(deserialize_with = "deserialize_collection_vec")] Vec<String>),
3552    /// Closed semantic token.
3553    Token(String),
3554    /// Unreal sample-rate policy.
3555    SampleRate(EngineSampleRateV2),
3556}
3557
3558impl EngineSettingValueV2 {
3559    pub(crate) fn retained_text_bytes(&self) -> Result<usize, EngineContractError> {
3560        match self {
3561            Self::SourceTransformPath(value) | Self::Token(value) => Ok(value.len()),
3562            Self::TextList(values) => checked_sum(
3563                "V2 setting value retained text",
3564                values.iter().map(String::len),
3565            ),
3566            Self::Boolean(_)
3567            | Self::PositiveInteger(_)
3568            | Self::BakeOrExtract(_)
3569            | Self::SampleRate(_) => Ok(0),
3570        }
3571    }
3572}
3573
3574/// Immutable V2 setting descriptor with exact source applicability and default.
3575#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
3576#[serde(deny_unknown_fields)]
3577pub struct EngineSettingDescriptorV2 {
3578    id: EngineSettingIdV2,
3579    scope: EngineSettingScopeV1,
3580    domain: EngineSettingDomainV2,
3581    #[serde(deserialize_with = "deserialize_collection_vec")]
3582    applicable_source_formats: Vec<SourceFormatV1>,
3583    default_value: Option<EngineSettingValueV2>,
3584}
3585
3586impl EngineSettingDescriptorV2 {
3587    /// Construct one descriptor. An empty format set means not applicable.
3588    pub fn new(
3589        id: EngineSettingIdV2,
3590        scope: EngineSettingScopeV1,
3591        domain: EngineSettingDomainV2,
3592        mut applicable_source_formats: Vec<SourceFormatV1>,
3593        default_value: Option<EngineSettingValueV2>,
3594    ) -> Result<Self, EngineContractError> {
3595        applicable_source_formats.sort_by_key(|format| source_format_name(*format));
3596        applicable_source_formats.dedup();
3597        let descriptor = Self {
3598            id,
3599            scope,
3600            domain,
3601            applicable_source_formats,
3602            default_value,
3603        };
3604        descriptor.validate()?;
3605        Ok(descriptor)
3606    }
3607
3608    /// Stable setting id.
3609    pub const fn id(&self) -> EngineSettingIdV2 {
3610        self.id
3611    }
3612
3613    /// Materialization scope.
3614    pub const fn scope(&self) -> EngineSettingScopeV1 {
3615        self.scope
3616    }
3617
3618    /// Closed value domain.
3619    pub const fn domain(&self) -> EngineSettingDomainV2 {
3620        self.domain
3621    }
3622
3623    /// Exact source-format applicability.
3624    pub fn applicable_source_formats(&self) -> &[SourceFormatV1] {
3625        &self.applicable_source_formats
3626    }
3627
3628    /// Verified profile default, if one exists.
3629    pub const fn default_value(&self) -> Option<&EngineSettingValueV2> {
3630        self.default_value.as_ref()
3631    }
3632
3633    fn validate(&self) -> Result<(), EngineContractError> {
3634        validate_collection_len(
3635            "V2 descriptor.applicable_source_formats",
3636            self.applicable_source_formats.len(),
3637        )?;
3638        validate_unique_order(
3639            "V2 descriptor.applicable_source_formats",
3640            &self.applicable_source_formats,
3641            |format| source_format_name(*format),
3642            true,
3643        )?;
3644        if self.applicable_source_formats.is_empty() && self.default_value.is_some() {
3645            return Err(EngineContractError::InvalidV2DescriptorDefault { setting: self.id });
3646        }
3647        if let Some(value) = &self.default_value {
3648            validate_setting_value_v2(self.id, self.domain, value)?;
3649        }
3650        Ok(())
3651    }
3652}
3653
3654/// One primary source for V2 facts and settings.
3655#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
3656#[serde(deny_unknown_fields)]
3657pub struct EnginePrimarySourceV2 {
3658    id: String,
3659    target_version: String,
3660    url: String,
3661    verified_on: String,
3662    #[serde(deserialize_with = "deserialize_collection_vec")]
3663    supported_fact_ids: Vec<EngineFactIdV2>,
3664    #[serde(deserialize_with = "deserialize_collection_vec")]
3665    supported_setting_ids: Vec<EngineSettingIdV2>,
3666}
3667
3668impl EnginePrimarySourceV2 {
3669    /// Construct one primary-source support row.
3670    pub fn new(
3671        id: impl Into<String>,
3672        target_version: impl Into<String>,
3673        url: impl Into<String>,
3674        verified_on: impl Into<String>,
3675        mut supported_fact_ids: Vec<EngineFactIdV2>,
3676        mut supported_setting_ids: Vec<EngineSettingIdV2>,
3677    ) -> Result<Self, EngineContractError> {
3678        supported_fact_ids.sort_by_key(|id| id.as_str());
3679        supported_fact_ids.dedup();
3680        supported_setting_ids.sort_by_key(|id| id.as_str());
3681        supported_setting_ids.dedup();
3682        let source = Self {
3683            id: id.into(),
3684            target_version: target_version.into(),
3685            url: url.into(),
3686            verified_on: verified_on.into(),
3687            supported_fact_ids,
3688            supported_setting_ids,
3689        };
3690        source.validate()?;
3691        Ok(source)
3692    }
3693
3694    /// Stable source id.
3695    pub fn id(&self) -> &str {
3696        &self.id
3697    }
3698
3699    /// Fact ids supported by this source.
3700    pub fn supported_fact_ids(&self) -> &[EngineFactIdV2] {
3701        &self.supported_fact_ids
3702    }
3703
3704    /// Setting ids supported by this source.
3705    pub fn supported_setting_ids(&self) -> &[EngineSettingIdV2] {
3706        &self.supported_setting_ids
3707    }
3708
3709    fn validate(&self) -> Result<(), EngineContractError> {
3710        for (field, value) in [
3711            ("V2 primary source.id", self.id.as_str()),
3712            (
3713                "V2 primary source.target_version",
3714                self.target_version.as_str(),
3715            ),
3716            ("V2 primary source.url", self.url.as_str()),
3717            ("V2 primary source.verified_on", self.verified_on.as_str()),
3718        ] {
3719            validate_required_text(field, value)?;
3720        }
3721        validate_collection_len(
3722            "V2 primary source.supported_fact_ids",
3723            self.supported_fact_ids.len(),
3724        )?;
3725        validate_collection_len(
3726            "V2 primary source.supported_setting_ids",
3727            self.supported_setting_ids.len(),
3728        )?;
3729        validate_unique_order(
3730            "V2 primary source.supported_fact_ids",
3731            &self.supported_fact_ids,
3732            |id| id.as_str(),
3733            true,
3734        )?;
3735        validate_unique_order(
3736            "V2 primary source.supported_setting_ids",
3737            &self.supported_setting_ids,
3738            |id| id.as_str(),
3739            true,
3740        )
3741    }
3742}
3743
3744/// Self-contained immutable V2 engine profile.
3745#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
3746pub struct ResolvedEngineProfileV2 {
3747    schema: &'static str,
3748    selection: EngineProfileSelectionV1,
3749    fact_bundle_urn: String,
3750    identity: InputIdentity,
3751    facts: Vec<EngineProfileFactV2>,
3752    setting_descriptors: Vec<EngineSettingDescriptorV2>,
3753    primary_sources: Vec<EnginePrimarySourceV2>,
3754}
3755
3756#[derive(Deserialize)]
3757#[serde(deny_unknown_fields)]
3758struct ResolvedEngineProfileWireV2 {
3759    schema: String,
3760    selection: EngineProfileSelectionV1,
3761    fact_bundle_urn: String,
3762    identity: InputIdentity,
3763    #[serde(deserialize_with = "deserialize_collection_rows")]
3764    facts: CappedSequence<EngineProfileFactV2>,
3765    #[serde(deserialize_with = "deserialize_collection_rows")]
3766    setting_descriptors: CappedSequence<EngineSettingDescriptorV2>,
3767    #[serde(deserialize_with = "deserialize_collection_rows")]
3768    primary_sources: CappedSequence<EnginePrimarySourceV2>,
3769}
3770
3771impl ResolvedEngineProfileV2 {
3772    /// Construct and canonically order one V2 profile.
3773    pub fn new(
3774        selection: EngineProfileSelectionV1,
3775        fact_bundle_urn: impl Into<String>,
3776        mut facts: Vec<EngineProfileFactV2>,
3777        mut setting_descriptors: Vec<EngineSettingDescriptorV2>,
3778        mut primary_sources: Vec<EnginePrimarySourceV2>,
3779    ) -> Result<Self, EngineContractError> {
3780        for fact in &mut facts {
3781            if let EngineFactStateV2::Known(EngineFactValueV2::AcceptedFormats(formats)) =
3782                &mut fact.state
3783            {
3784                formats.sort_by_key(|format| source_format_name(*format));
3785                formats.dedup();
3786            }
3787        }
3788        facts.sort_by_key(|fact| fact.id.as_str());
3789        setting_descriptors.sort_by_key(|descriptor| descriptor.id.as_str());
3790        primary_sources.sort_by(|left, right| left.id.cmp(&right.id));
3791        let mut profile = Self {
3792            schema: ENGINE_PROFILE_FACTS_V2_ID,
3793            selection,
3794            fact_bundle_urn: fact_bundle_urn.into(),
3795            identity: InputIdentity::from_bytes(&[]),
3796            facts,
3797            setting_descriptors,
3798            primary_sources,
3799        };
3800        profile.validate_semantics(false)?;
3801        profile.identity = profile.computed_identity();
3802        Ok(profile)
3803    }
3804
3805    /// Immutable contract id.
3806    pub const fn contract_id(&self) -> &'static str {
3807        self.schema
3808    }
3809
3810    /// Exact selected tuple.
3811    pub const fn selection(&self) -> &EngineProfileSelectionV1 {
3812        &self.selection
3813    }
3814
3815    /// Fact bundle URN.
3816    pub fn fact_bundle_urn(&self) -> &str {
3817        &self.fact_bundle_urn
3818    }
3819
3820    /// Canonical profile identity.
3821    pub const fn facts_identity(&self) -> &InputIdentity {
3822        &self.identity
3823    }
3824
3825    /// Canonically ordered complete fact inventory.
3826    pub fn facts(&self) -> &[EngineProfileFactV2] {
3827        &self.facts
3828    }
3829
3830    /// Canonically ordered setting descriptors.
3831    pub fn setting_descriptors(&self) -> &[EngineSettingDescriptorV2] {
3832        &self.setting_descriptors
3833    }
3834
3835    /// Canonically ordered primary sources.
3836    pub fn primary_sources(&self) -> &[EnginePrimarySourceV2] {
3837        &self.primary_sources
3838    }
3839
3840    /// Look up one fact.
3841    pub fn fact(&self, id: EngineFactIdV2) -> Option<&EngineProfileFactV2> {
3842        self.facts.iter().find(|fact| fact.id == id)
3843    }
3844
3845    /// Look up one descriptor.
3846    pub fn setting_descriptor(&self, id: EngineSettingIdV2) -> Option<&EngineSettingDescriptorV2> {
3847        self.setting_descriptors.iter().find(|row| row.id == id)
3848    }
3849
3850    /// Look up one primary source.
3851    pub fn source(&self, id: &str) -> Option<&EnginePrimarySourceV2> {
3852        self.primary_sources.iter().find(|source| source.id == id)
3853    }
3854
3855    /// Whether the exact accepted-inputs fact admits a format.
3856    pub fn accepts_format(&self, format: SourceFormatV1) -> bool {
3857        matches!(
3858            self.fact(EngineFactIdV2::AcceptedInputs).map(|fact| fact.state()),
3859            Some(EngineFactStateV2::Known(EngineFactValueV2::AcceptedFormats(formats)))
3860                if formats.contains(&format)
3861        )
3862    }
3863
3864    /// Revalidate canonical form and identity.
3865    pub fn validate(&self) -> Result<(), EngineContractError> {
3866        self.validate_semantics(true)
3867    }
3868
3869    pub(crate) fn retained_text_bytes(&self) -> Result<usize, EngineContractError> {
3870        let fact_text = self.facts.iter().map(|fact| match fact.state() {
3871            EngineFactStateV2::Known(EngineFactValueV2::Token(value)) => value.len(),
3872            _ => 0,
3873        });
3874        let descriptor_text = self.setting_descriptors.iter().map(|descriptor| {
3875            descriptor
3876                .default_value()
3877                .map_or(Ok(0), EngineSettingValueV2::retained_text_bytes)
3878        });
3879        let source_text = self.primary_sources.iter().map(|source| {
3880            checked_sum(
3881                "V2 primary-source retained text",
3882                [
3883                    source.id.len(),
3884                    source.target_version.len(),
3885                    source.url.len(),
3886                    source.verified_on.len(),
3887                ],
3888            )
3889        });
3890        checked_sum(
3891            "V2 profile retained text",
3892            [
3893                self.selection.retained_text_bytes()?,
3894                self.fact_bundle_urn.len(),
3895                checked_sum("V2 fact retained text", fact_text)?,
3896                checked_sum(
3897                    "V2 descriptor retained text",
3898                    descriptor_text.collect::<Result<Vec<_>, _>>()?,
3899                )?,
3900                checked_sum(
3901                    "V2 source retained text",
3902                    source_text.collect::<Result<Vec<_>, _>>()?,
3903                )?,
3904            ],
3905        )
3906    }
3907
3908    pub(crate) fn encode_preimage(&self, encoder: &mut CanonicalEncoder) {
3909        encoder.token(ENGINE_FACTS_V2_PREIMAGE_DOMAIN);
3910        encode_profile_key(encoder, &self.selection);
3911        encoder.field("fact_bundle_urn");
3912        encoder.token(&self.fact_bundle_urn);
3913        encode_json_rows(encoder, "facts", &self.facts);
3914        encode_json_rows(encoder, "setting_descriptors", &self.setting_descriptors);
3915        encode_json_rows(encoder, "primary_sources", &self.primary_sources);
3916    }
3917
3918    fn computed_identity(&self) -> InputIdentity {
3919        let mut encoder = CanonicalEncoder::default();
3920        self.encode_preimage(&mut encoder);
3921        encoder.identity()
3922    }
3923
3924    fn validate_semantics(&self, verify_identity: bool) -> Result<(), EngineContractError> {
3925        validate_schema("V2 profile.schema", self.schema, ENGINE_PROFILE_FACTS_V2_ID)?;
3926        self.selection.validate()?;
3927        validate_required_text("V2 profile.fact_bundle_urn", &self.fact_bundle_urn)?;
3928        for (field, len) in [
3929            ("V2 profile.facts", self.facts.len()),
3930            (
3931                "V2 profile.setting_descriptors",
3932                self.setting_descriptors.len(),
3933            ),
3934            ("V2 profile.primary_sources", self.primary_sources.len()),
3935        ] {
3936            validate_collection_len(field, len)?;
3937        }
3938        validate_unique_order("V2 profile.facts", &self.facts, |row| row.id.as_str(), true)?;
3939        if self.facts.len() != ALL_FACT_IDS_V2.len()
3940            || !self
3941                .facts
3942                .iter()
3943                .zip(ALL_FACT_IDS_V2)
3944                .all(|(row, expected)| row.id == expected)
3945        {
3946            return Err(EngineContractError::InvalidV2FactInventory);
3947        }
3948        for fact in &self.facts {
3949            validate_fact_value_v2(fact)?;
3950        }
3951        validate_unique_order(
3952            "V2 profile.setting_descriptors",
3953            &self.setting_descriptors,
3954            |row| row.id.as_str(),
3955            true,
3956        )?;
3957        for descriptor in &self.setting_descriptors {
3958            descriptor.validate()?;
3959        }
3960        validate_unique_order(
3961            "V2 profile.primary_sources",
3962            &self.primary_sources,
3963            |row| row.id.as_str(),
3964            true,
3965        )?;
3966        for source in &self.primary_sources {
3967            source.validate()?;
3968            for id in source.supported_fact_ids() {
3969                if !matches!(
3970                    self.fact(*id).map(|fact| fact.state()),
3971                    Some(EngineFactStateV2::Known(_))
3972                ) {
3973                    return Err(EngineContractError::InvalidV2SourceFact {
3974                        source_id: source.id.clone(),
3975                        fact: *id,
3976                    });
3977                }
3978            }
3979            for id in source.supported_setting_ids() {
3980                if self.setting_descriptor(*id).is_none() {
3981                    return Err(EngineContractError::InvalidV2SourceSetting {
3982                        source_id: source.id.clone(),
3983                        setting: *id,
3984                    });
3985                }
3986            }
3987        }
3988        for fact in &self.facts {
3989            if matches!(fact.state(), EngineFactStateV2::Known(_))
3990                && !self
3991                    .primary_sources
3992                    .iter()
3993                    .any(|source| source.supported_fact_ids.contains(&fact.id))
3994            {
3995                return Err(EngineContractError::UnreferencedV2Fact { fact: fact.id });
3996            }
3997        }
3998        for descriptor in &self.setting_descriptors {
3999            if !self
4000                .primary_sources
4001                .iter()
4002                .any(|source| source.supported_setting_ids.contains(&descriptor.id))
4003            {
4004                return Err(EngineContractError::UnreferencedV2Setting {
4005                    setting: descriptor.id,
4006                });
4007            }
4008        }
4009        if !matches!(
4010            self.fact(EngineFactIdV2::AcceptedInputs).map(|fact| fact.state()),
4011            Some(EngineFactStateV2::Known(EngineFactValueV2::AcceptedFormats(formats))) if !formats.is_empty()
4012        ) {
4013            return Err(EngineContractError::InvalidAcceptedInputs);
4014        }
4015        let rows = self
4016            .facts
4017            .len()
4018            .checked_add(self.setting_descriptors.len())
4019            .and_then(|rows| rows.checked_add(self.primary_sources.len()))
4020            .ok_or(EngineContractError::ArithmeticOverflow {
4021                field: "V2 profile rows",
4022            })?;
4023        if rows > ENGINE_CONTRACT_V1_MAX_AGGREGATE_ROWS {
4024            return Err(EngineContractError::TooManyAggregateRows {
4025                found: rows,
4026                max: ENGINE_CONTRACT_V1_MAX_AGGREGATE_ROWS,
4027            });
4028        }
4029        let json_bytes = serde_json::to_vec(&(
4030            &self.selection,
4031            &self.fact_bundle_urn,
4032            &self.facts,
4033            &self.setting_descriptors,
4034            &self.primary_sources,
4035        ))
4036        .expect("V2 profile has infallible JSON serialization")
4037        .len();
4038        if json_bytes > ENGINE_CONTRACT_V1_MAX_TOTAL_TEXT_BYTES {
4039            return Err(EngineContractError::TooMuchAggregateText {
4040                found: json_bytes,
4041                max: ENGINE_CONTRACT_V1_MAX_TOTAL_TEXT_BYTES,
4042            });
4043        }
4044        if verify_identity && self.identity != self.computed_identity() {
4045            return Err(EngineContractError::IdentityMismatch {
4046                contract: ENGINE_PROFILE_FACTS_V2_ID,
4047            });
4048        }
4049        Ok(())
4050    }
4051}
4052
4053impl TryFrom<ResolvedEngineProfileWireV2> for ResolvedEngineProfileV2 {
4054    type Error = EngineContractError;
4055
4056    fn try_from(wire: ResolvedEngineProfileWireV2) -> Result<Self, Self::Error> {
4057        if wire.facts.overflowed
4058            || wire.setting_descriptors.overflowed
4059            || wire.primary_sources.overflowed
4060        {
4061            return Err(EngineContractError::TooManyRows {
4062                field: "V2 profile collection",
4063                found: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS + 1,
4064                max: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS,
4065            });
4066        }
4067        let profile = Self {
4068            schema: if wire.schema == ENGINE_PROFILE_FACTS_V2_ID {
4069                ENGINE_PROFILE_FACTS_V2_ID
4070            } else {
4071                return Err(EngineContractError::InvalidSchema {
4072                    field: "V2 profile.schema",
4073                    expected: ENGINE_PROFILE_FACTS_V2_ID,
4074                    found: wire.schema,
4075                });
4076            },
4077            selection: wire.selection,
4078            fact_bundle_urn: wire.fact_bundle_urn,
4079            identity: wire.identity,
4080            facts: wire.facts.values,
4081            setting_descriptors: wire.setting_descriptors.values,
4082            primary_sources: wire.primary_sources.values,
4083        };
4084        profile.validate()?;
4085        Ok(profile)
4086    }
4087}
4088
4089impl<'de> Deserialize<'de> for ResolvedEngineProfileV2 {
4090    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
4091    where
4092        D: Deserializer<'de>,
4093    {
4094        ResolvedEngineProfileWireV2::deserialize(deserializer)?
4095            .try_into()
4096            .map_err(D::Error::custom)
4097    }
4098}
4099
4100/// Authority that supplied one resolved value.
4101#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
4102#[serde(rename_all = "snake_case")]
4103pub enum EngineSettingValueOriginV3 {
4104    /// Caller configuration declared the value.
4105    ExplicitConfig,
4106    /// The immutable profile supplied its verified default.
4107    ProfileDefault,
4108}
4109
4110/// One materialized V3 setting row.
4111#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
4112#[serde(deny_unknown_fields)]
4113pub struct EngineSettingRowV3 {
4114    id: EngineSettingIdV2,
4115    value: EngineSettingValueV2,
4116    value_origin: EngineSettingValueOriginV3,
4117}
4118
4119impl EngineSettingRowV3 {
4120    /// Construct one origin-bearing row.
4121    pub const fn new(
4122        id: EngineSettingIdV2,
4123        value: EngineSettingValueV2,
4124        value_origin: EngineSettingValueOriginV3,
4125    ) -> Self {
4126        Self {
4127            id,
4128            value,
4129            value_origin,
4130        }
4131    }
4132
4133    /// Stable setting id.
4134    pub const fn id(&self) -> EngineSettingIdV2 {
4135        self.id
4136    }
4137    /// Materialized value.
4138    pub const fn value(&self) -> &EngineSettingValueV2 {
4139        &self.value
4140    }
4141    /// Exact value authority.
4142    pub const fn value_origin(&self) -> EngineSettingValueOriginV3 {
4143        self.value_origin
4144    }
4145}
4146
4147/// Origin-bearing settings for one exact source clip ordinal/name pair.
4148#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
4149#[serde(deny_unknown_fields)]
4150pub struct EngineClipSettingsV3 {
4151    clip_ordinal: u64,
4152    clip_name: String,
4153    #[serde(deserialize_with = "deserialize_collection_vec")]
4154    settings: Vec<EngineSettingRowV3>,
4155}
4156
4157impl EngineClipSettingsV3 {
4158    /// Construct one clip row and canonicalize its settings.
4159    pub fn new(
4160        clip_ordinal: u64,
4161        clip_name: impl Into<String>,
4162        mut settings: Vec<EngineSettingRowV3>,
4163    ) -> Result<Self, EngineContractError> {
4164        settings.sort_by_key(|row| row.id.as_str());
4165        let row = Self {
4166            clip_ordinal,
4167            clip_name: clip_name.into(),
4168            settings,
4169        };
4170        validate_required_text("V3 settings.clip_name", &row.clip_name)?;
4171        validate_collection_len("V3 settings.clip.settings", row.settings.len())?;
4172        validate_unique_order(
4173            "V3 settings.clip.settings",
4174            &row.settings,
4175            |setting| setting.id.as_str(),
4176            true,
4177        )?;
4178        Ok(row)
4179    }
4180
4181    /// Original zero-based source clip ordinal.
4182    pub const fn clip_ordinal(&self) -> u64 {
4183        self.clip_ordinal
4184    }
4185    /// Exact source clip name.
4186    pub fn clip_name(&self) -> &str {
4187        &self.clip_name
4188    }
4189    /// Canonically ordered settings.
4190    pub fn settings(&self) -> &[EngineSettingRowV3] {
4191        &self.settings
4192    }
4193    /// Look up one setting.
4194    pub fn setting(&self, id: EngineSettingIdV2) -> Option<&EngineSettingRowV3> {
4195        self.settings.iter().find(|row| row.id == id)
4196    }
4197}
4198
4199/// Bounded, origin-bearing resolved settings used by profile facts V2.
4200#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
4201pub struct ResolvedEngineSettingsV3 {
4202    schema: &'static str,
4203    identity: InputIdentity,
4204    source_format: SourceFormatV1,
4205    document_settings: Vec<EngineSettingRowV3>,
4206    clips: Vec<EngineClipSettingsV3>,
4207    clip_coverage: ResolvedEngineSettingsCoverageV2,
4208    work: ResolvedEngineSettingsWorkV2,
4209}
4210
4211#[derive(Deserialize)]
4212#[serde(deny_unknown_fields)]
4213struct ResolvedEngineSettingsWireV3 {
4214    schema: String,
4215    identity: InputIdentity,
4216    source_format: SourceFormatV1,
4217    #[serde(deserialize_with = "deserialize_collection_rows")]
4218    document_settings: CappedSequence<EngineSettingRowV3>,
4219    #[serde(deserialize_with = "deserialize_collection_rows")]
4220    clips: CappedSequence<EngineClipSettingsV3>,
4221    clip_coverage: ResolvedEngineSettingsCoverageV2,
4222    work: ResolvedEngineSettingsWorkV2,
4223}
4224
4225impl ResolvedEngineSettingsV3 {
4226    /// Construct complete or explicit-partial settings and bind them to a profile.
4227    pub fn new(
4228        profile: &ResolvedEngineProfileV2,
4229        source_format: SourceFormatV1,
4230        mut document_settings: Vec<EngineSettingRowV3>,
4231        mut clips: Vec<EngineClipSettingsV3>,
4232        clip_coverage: ResolvedEngineSettingsCoverageV2,
4233        work: ResolvedEngineSettingsWorkV2,
4234    ) -> Result<Self, EngineContractError> {
4235        document_settings.sort_by_key(|row| row.id.as_str());
4236        clips.sort_by(|left, right| {
4237            (left.clip_ordinal, left.clip_name.as_str())
4238                .cmp(&(right.clip_ordinal, right.clip_name.as_str()))
4239        });
4240        let mut settings = Self {
4241            schema: RESOLVED_ENGINE_SETTINGS_V3_ID,
4242            identity: InputIdentity::from_bytes(&[]),
4243            source_format,
4244            document_settings,
4245            clips,
4246            clip_coverage,
4247            work,
4248        };
4249        settings.validate_semantics(profile, false)?;
4250        settings.identity = settings.computed_identity(profile);
4251        Ok(settings)
4252    }
4253
4254    /// Immutable contract id.
4255    pub const fn contract_id(&self) -> &'static str {
4256        self.schema
4257    }
4258    /// Canonical settings identity.
4259    pub const fn settings_identity(&self) -> &InputIdentity {
4260        &self.identity
4261    }
4262    /// Exact source format whose applicable descriptors were materialized.
4263    pub const fn source_format(&self) -> SourceFormatV1 {
4264        self.source_format
4265    }
4266    /// Document-scoped rows.
4267    pub fn document_settings(&self) -> &[EngineSettingRowV3] {
4268        &self.document_settings
4269    }
4270    /// Clip-scoped rows in source-ordinal order.
4271    pub fn clips(&self) -> &[EngineClipSettingsV3] {
4272        &self.clips
4273    }
4274    /// Exact/partial clip coverage.
4275    pub const fn clip_coverage(&self) -> &ResolvedEngineSettingsCoverageV2 {
4276        &self.clip_coverage
4277    }
4278    /// Bounded work counters.
4279    pub const fn work(&self) -> ResolvedEngineSettingsWorkV2 {
4280        self.work
4281    }
4282    /// Look up one document setting.
4283    pub fn document_setting(&self, id: EngineSettingIdV2) -> Option<&EngineSettingRowV3> {
4284        self.document_settings.iter().find(|row| row.id == id)
4285    }
4286    /// Look up one exact clip row.
4287    pub fn clip_row(&self, ordinal: u64, name: &str) -> Option<&EngineClipSettingsV3> {
4288        self.clips
4289            .iter()
4290            .find(|row| row.clip_ordinal == ordinal && row.clip_name == name)
4291    }
4292    /// Validate against the exact profile used by the identity.
4293    pub fn validate_against(
4294        &self,
4295        profile: &ResolvedEngineProfileV2,
4296    ) -> Result<(), EngineContractError> {
4297        self.validate_semantics(profile, true)
4298    }
4299
4300    pub(crate) fn retained_text_bytes(&self) -> Result<usize, EngineContractError> {
4301        let document_text = self
4302            .document_settings
4303            .iter()
4304            .map(|row| row.value.retained_text_bytes())
4305            .collect::<Result<Vec<_>, _>>()?;
4306        let clip_text = self
4307            .clips
4308            .iter()
4309            .map(|clip| {
4310                checked_sum(
4311                    "V3 clip retained text",
4312                    std::iter::once(clip.clip_name.len()).chain(
4313                        clip.settings
4314                            .iter()
4315                            .map(|row| row.value.retained_text_bytes())
4316                            .collect::<Result<Vec<_>, _>>()?,
4317                    ),
4318                )
4319            })
4320            .collect::<Result<Vec<_>, _>>()?;
4321        checked_sum(
4322            "V3 settings retained text",
4323            document_text.into_iter().chain(clip_text),
4324        )
4325    }
4326
4327    pub(crate) fn encode_preimage(
4328        &self,
4329        profile: &ResolvedEngineProfileV2,
4330        encoder: &mut CanonicalEncoder,
4331    ) {
4332        encoder.token(ENGINE_SETTINGS_V3_PREIMAGE_DOMAIN);
4333        encoder.field("profile_identity");
4334        encode_input_identity(encoder, profile.facts_identity());
4335        encoder.field("source_format");
4336        encoder.token(source_format_name(self.source_format));
4337        encode_json_rows(encoder, "document_settings", &self.document_settings);
4338        encode_json_rows(encoder, "clips", &self.clips);
4339        encoder.field("clip_coverage");
4340        encoder.token(serde_json::to_string(&self.clip_coverage).expect("coverage serializes"));
4341        encoder.field("work");
4342        encoder.token(serde_json::to_string(&self.work).expect("work serializes"));
4343    }
4344
4345    fn computed_identity(&self, profile: &ResolvedEngineProfileV2) -> InputIdentity {
4346        let mut encoder = CanonicalEncoder::default();
4347        self.encode_preimage(profile, &mut encoder);
4348        encoder.identity()
4349    }
4350
4351    fn validate_semantics(
4352        &self,
4353        profile: &ResolvedEngineProfileV2,
4354        verify_identity: bool,
4355    ) -> Result<(), EngineContractError> {
4356        profile.validate()?;
4357        if !profile.accepts_format(self.source_format) {
4358            return Err(EngineContractError::InvalidV3SettingsSourceFormat {
4359                format: self.source_format,
4360            });
4361        }
4362        validate_schema(
4363            "V3 settings.schema",
4364            self.schema,
4365            RESOLVED_ENGINE_SETTINGS_V3_ID,
4366        )?;
4367        for (field, len) in [
4368            (
4369                "V3 settings.document_settings",
4370                self.document_settings.len(),
4371            ),
4372            ("V3 settings.clips", self.clips.len()),
4373        ] {
4374            validate_collection_len(field, len)?;
4375        }
4376        validate_unique_order(
4377            "V3 settings.document_settings",
4378            &self.document_settings,
4379            |row| row.id.as_str(),
4380            true,
4381        )?;
4382        if self.clips.windows(2).any(|pair| {
4383            (pair[0].clip_ordinal, pair[0].clip_name.as_str())
4384                >= (pair[1].clip_ordinal, pair[1].clip_name.as_str())
4385        }) {
4386            return Err(EngineContractError::NonCanonicalOrder {
4387                field: "V3 settings.clips",
4388            });
4389        }
4390        for (expected_ordinal, clip) in self.clips.iter().enumerate() {
4391            if clip.clip_ordinal != expected_ordinal as u64 {
4392                return Err(EngineContractError::NonCanonicalOrder {
4393                    field: "V3 settings.clips source ordinals",
4394                });
4395            }
4396            validate_required_text("V3 settings.clip_name", &clip.clip_name)?;
4397            validate_collection_len("V3 settings.clip.settings", clip.settings.len())?;
4398            validate_unique_order(
4399                "V3 settings.clip.settings",
4400                &clip.settings,
4401                |row| row.id.as_str(),
4402                true,
4403            )?;
4404        }
4405        for row in self
4406            .document_settings
4407            .iter()
4408            .chain(self.clips.iter().flat_map(|clip| clip.settings.iter()))
4409        {
4410            let descriptor = profile
4411                .setting_descriptor(row.id)
4412                .ok_or(EngineContractError::UnknownV2MaterializedSetting { setting: row.id })?;
4413            let expected_scope = if self
4414                .document_settings
4415                .iter()
4416                .any(|candidate| std::ptr::eq(candidate, row))
4417            {
4418                EngineSettingScopeV1::Document
4419            } else {
4420                EngineSettingScopeV1::Clip
4421            };
4422            if descriptor.scope != expected_scope {
4423                return Err(EngineContractError::WrongV2SettingScope { setting: row.id });
4424            }
4425            if !descriptor
4426                .applicable_source_formats
4427                .contains(&self.source_format)
4428            {
4429                return Err(EngineContractError::InapplicableV2MaterializedSetting {
4430                    setting: row.id,
4431                    format: self.source_format,
4432                });
4433            }
4434            validate_setting_value_v2(row.id, descriptor.domain, &row.value)?;
4435            if row.value_origin == EngineSettingValueOriginV3::ProfileDefault
4436                && descriptor.default_value.as_ref() != Some(&row.value)
4437            {
4438                return Err(EngineContractError::InvalidProfileDefaultOrigin { setting: row.id });
4439            }
4440        }
4441        for descriptor in profile.setting_descriptors().iter().filter(|descriptor| {
4442            descriptor
4443                .applicable_source_formats
4444                .contains(&self.source_format)
4445        }) {
4446            match descriptor.scope {
4447                EngineSettingScopeV1::Document => {
4448                    if self.document_setting(descriptor.id).is_none() {
4449                        return Err(EngineContractError::MissingApplicableV2Setting {
4450                            setting: descriptor.id,
4451                            format: self.source_format,
4452                        });
4453                    }
4454                }
4455                EngineSettingScopeV1::Clip => {
4456                    if self
4457                        .clips
4458                        .iter()
4459                        .any(|clip| clip.setting(descriptor.id).is_none())
4460                    {
4461                        return Err(EngineContractError::MissingApplicableV2Setting {
4462                            setting: descriptor.id,
4463                            format: self.source_format,
4464                        });
4465                    }
4466                }
4467            }
4468        }
4469        let retained_clip_rows = self.clips.len();
4470        match self.clip_coverage.state() {
4471            ResolvedEngineSettingsCoverageStateV2::Complete
4472                if self.work.actual_clip_rows_inspected() != retained_clip_rows
4473                    || self.work.materialized_clip_rows() != retained_clip_rows
4474                    || self.work.retained_clip_rows() != retained_clip_rows =>
4475            {
4476                return Err(EngineContractError::InvalidV2CoverageWork);
4477            }
4478            ResolvedEngineSettingsCoverageStateV2::Partial
4479                if self.work.actual_clip_rows_inspected()
4480                    <= ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS
4481                    || self.work.materialized_clip_rows() != retained_clip_rows
4482                    || self.work.retained_clip_rows() != retained_clip_rows =>
4483            {
4484                return Err(EngineContractError::InvalidV2CoverageWork);
4485            }
4486            _ => {}
4487        }
4488        let rows = self
4489            .document_settings
4490            .len()
4491            .checked_add(self.clips.len())
4492            .and_then(|rows| {
4493                rows.checked_add(self.clips.iter().map(|clip| clip.settings.len()).sum())
4494            })
4495            .ok_or(EngineContractError::ArithmeticOverflow {
4496                field: "V3 settings rows",
4497            })?;
4498        if rows > ENGINE_CONTRACT_V1_MAX_AGGREGATE_ROWS {
4499            return Err(EngineContractError::TooManyAggregateRows {
4500                found: rows,
4501                max: ENGINE_CONTRACT_V1_MAX_AGGREGATE_ROWS,
4502            });
4503        }
4504        if verify_identity && self.identity != self.computed_identity(profile) {
4505            return Err(EngineContractError::IdentityMismatch {
4506                contract: RESOLVED_ENGINE_SETTINGS_V3_ID,
4507            });
4508        }
4509        Ok(())
4510    }
4511}
4512
4513impl TryFrom<ResolvedEngineSettingsWireV3> for ResolvedEngineSettingsV3 {
4514    type Error = EngineContractError;
4515
4516    fn try_from(wire: ResolvedEngineSettingsWireV3) -> Result<Self, Self::Error> {
4517        if wire.schema != RESOLVED_ENGINE_SETTINGS_V3_ID {
4518            return Err(EngineContractError::InvalidSchema {
4519                field: "V3 settings.schema",
4520                expected: RESOLVED_ENGINE_SETTINGS_V3_ID,
4521                found: wire.schema,
4522            });
4523        }
4524        if wire.document_settings.overflowed || wire.clips.overflowed {
4525            return Err(EngineContractError::TooManyRows {
4526                field: "V3 settings collection",
4527                found: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS + 1,
4528                max: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS,
4529            });
4530        }
4531        let settings = Self {
4532            schema: RESOLVED_ENGINE_SETTINGS_V3_ID,
4533            identity: wire.identity,
4534            source_format: wire.source_format,
4535            document_settings: wire.document_settings.values,
4536            clips: wire.clips.values,
4537            clip_coverage: wire.clip_coverage,
4538            work: wire.work,
4539        };
4540        // Profile-dependent identity and descriptor checks are deliberately
4541        // deferred to `validate_against`, as in historical settings readers.
4542        for (field, len) in [
4543            (
4544                "V3 settings.document_settings",
4545                settings.document_settings.len(),
4546            ),
4547            ("V3 settings.clips", settings.clips.len()),
4548        ] {
4549            validate_collection_len(field, len)?;
4550        }
4551        validate_unique_order(
4552            "V3 settings.document_settings",
4553            &settings.document_settings,
4554            |row| row.id.as_str(),
4555            true,
4556        )?;
4557        Ok(settings)
4558    }
4559}
4560
4561impl<'de> Deserialize<'de> for ResolvedEngineSettingsV3 {
4562    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
4563    where
4564        D: Deserializer<'de>,
4565    {
4566        ResolvedEngineSettingsWireV3::deserialize(deserializer)?
4567            .try_into()
4568            .map_err(D::Error::custom)
4569    }
4570}
4571
4572fn encode_json_rows<T: Serialize>(encoder: &mut CanonicalEncoder, field: &'static str, rows: &[T]) {
4573    encoder.field(field);
4574    encoder.count(rows.len());
4575    for row in rows {
4576        encoder.token(
4577            serde_json::to_string(row).expect("contract row has infallible JSON serialization"),
4578        );
4579    }
4580}
4581
4582fn validate_fact_value_v2(fact: &EngineProfileFactV2) -> Result<(), EngineContractError> {
4583    let valid = match (&fact.id, &fact.state) {
4584        (_, EngineFactStateV2::Unknown | EngineFactStateV2::NotApplicable) => true,
4585        (
4586            EngineFactIdV2::AcceptedInputs,
4587            EngineFactStateV2::Known(EngineFactValueV2::AcceptedFormats(formats)),
4588        ) => {
4589            !formats.is_empty()
4590                && formats.len() <= 3
4591                && formats
4592                    .windows(2)
4593                    .all(|pair| source_format_name(pair[0]) < source_format_name(pair[1]))
4594        }
4595        (
4596            EngineFactIdV2::TargetLinearUnit,
4597            EngineFactStateV2::Known(EngineFactValueV2::LinearUnit(_)),
4598        )
4599        | (
4600            EngineFactIdV2::SourceToTargetUnitMapping,
4601            EngineFactStateV2::Known(EngineFactValueV2::UnitRatio(_)),
4602        )
4603        | (
4604            EngineFactIdV2::PhysicalDimensionsPreserved
4605            | EngineFactIdV2::ApplicationWorldUnitPolicy,
4606            EngineFactStateV2::Known(EngineFactValueV2::Boolean(_)),
4607        )
4608        | (
4609            EngineFactIdV2::ImporterScaleConversion
4610            | EngineFactIdV2::ResultingTransformScale
4611            | EngineFactIdV2::SourceImportDisposition
4612            | EngineFactIdV2::ImportSettingProjection,
4613            EngineFactStateV2::Known(EngineFactValueV2::Token(_)),
4614        )
4615        | (
4616            EngineFactIdV2::RootMotionAddressability,
4617            EngineFactStateV2::Known(EngineFactValueV2::RootMotionAddressability(_)),
4618        ) => true,
4619        _ => false,
4620    };
4621    if !valid {
4622        return Err(EngineContractError::InvalidV2FactValue { fact: fact.id });
4623    }
4624    if let EngineFactStateV2::Known(EngineFactValueV2::Token(value)) = &fact.state {
4625        validate_required_text("V2 fact token", value)?;
4626        let allowed = match fact.id {
4627            EngineFactIdV2::ImporterScaleConversion => value == "none",
4628            EngineFactIdV2::ResultingTransformScale => {
4629                value
4630                    == "loader_entities_unit_orthonormal_trs_nodes_passthrough_matrix_nodes_decomposed"
4631            }
4632            EngineFactIdV2::SourceImportDisposition => value == "materialized_import_gates",
4633            EngineFactIdV2::ImportSettingProjection => {
4634                matches!(value.as_str(), "godot_params" | "unreal_fbx_import_data")
4635            }
4636            _ => false,
4637        };
4638        if !allowed {
4639            return Err(EngineContractError::InvalidV2FactValue { fact: fact.id });
4640        }
4641    }
4642    if let EngineFactStateV2::Known(EngineFactValueV2::UnitRatio(value)) = &fact.state {
4643        value.validate()?;
4644    }
4645    Ok(())
4646}
4647
4648fn validate_setting_value_v2(
4649    id: EngineSettingIdV2,
4650    domain: EngineSettingDomainV2,
4651    value: &EngineSettingValueV2,
4652) -> Result<(), EngineContractError> {
4653    let valid = match (domain, value) {
4654        (EngineSettingDomainV2::Boolean, EngineSettingValueV2::Boolean(_))
4655        | (EngineSettingDomainV2::BakeOrExtract, EngineSettingValueV2::BakeOrExtract(_))
4656        | (
4657            EngineSettingDomainV2::SampleRate,
4658            EngineSettingValueV2::SampleRate(
4659                EngineSampleRateV2::Default30 | EngineSampleRateV2::SourceDetermined,
4660            ),
4661        ) => true,
4662        (EngineSettingDomainV2::PositiveInteger, EngineSettingValueV2::PositiveInteger(value)) => {
4663            *value > 0
4664        }
4665        (
4666            EngineSettingDomainV2::SourceTransformPath,
4667            EngineSettingValueV2::SourceTransformPath(value),
4668        ) => validate_required_text("V2 setting value", value).is_ok(),
4669        (EngineSettingDomainV2::Token, EngineSettingValueV2::Token(value)) => {
4670            validate_required_text("V2 setting value", value).is_ok()
4671                && match id {
4672                    EngineSettingIdV2::LoadMeshes => {
4673                        matches!(value.as_str(), "empty" | "nonempty")
4674                    }
4675                    EngineSettingIdV2::ExtensionHandlerEnvironment => {
4676                        matches!(value.as_str(), "bare_empty" | "bevy_pbr_stock_0_19")
4677                    }
4678                    EngineSettingIdV2::AnimationType => {
4679                        matches!(value.as_str(), "generic" | "humanoid" | "legacy")
4680                    }
4681                    EngineSettingIdV2::AvatarSetup => matches!(
4682                        value.as_str(),
4683                        "create_from_this_model" | "copy_from_other_avatar"
4684                    ),
4685                    _ => false,
4686                }
4687        }
4688        (EngineSettingDomainV2::TextList, EngineSettingValueV2::TextList(values)) => {
4689            values.len() <= ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS
4690                && values
4691                    .iter()
4692                    .all(|value| validate_required_text("V2 setting list value", value).is_ok())
4693        }
4694        (
4695            EngineSettingDomainV2::SampleRate,
4696            EngineSettingValueV2::SampleRate(EngineSampleRateV2::CustomHz(value)),
4697        ) => (1..=48_000).contains(value),
4698        _ => false,
4699    };
4700    if !valid {
4701        return Err(EngineContractError::WrongV2SettingDomain { setting: id });
4702    }
4703    if id == EngineSettingIdV2::AnimationFps
4704        && !matches!(value, EngineSettingValueV2::PositiveInteger(1..=120))
4705    {
4706        return Err(EngineContractError::V2SettingValueOutOfRange { setting: id });
4707    }
4708    Ok(())
4709}
4710
4711/// Typed violation of the core-owned profile/settings contract.
4712#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
4713#[non_exhaustive]
4714pub enum EngineContractError {
4715    /// A contract schema id did not match its immutable V1 value.
4716    #[error("{field} must be {expected:?}, found {found:?}")]
4717    InvalidSchema {
4718        /// Field carrying the invalid schema id.
4719        field: &'static str,
4720        /// Required immutable schema id.
4721        expected: &'static str,
4722        /// Supplied schema id.
4723        found: String,
4724    },
4725    /// V2 clip coverage and bounded work counters disagreed with retained rows.
4726    #[error("resolved-engine-settings V2 coverage and work counters are inconsistent")]
4727    InvalidV2CoverageWork,
4728    /// A required retained string was empty.
4729    #[error("{field} must not be empty")]
4730    EmptyText {
4731        /// Invalid field.
4732        field: &'static str,
4733    },
4734    /// One retained string exceeded the per-value byte limit.
4735    #[error("{field} retains {found} UTF-8 bytes, exceeding {max}")]
4736    TextTooLong {
4737        /// Oversized field.
4738        field: &'static str,
4739        /// Observed UTF-8 byte count.
4740        found: usize,
4741        /// Maximum permitted byte count.
4742        max: usize,
4743    },
4744    /// One collection exceeded the V1 row limit.
4745    #[error("{field} contains {found} rows, exceeding {max}")]
4746    TooManyRows {
4747        /// Oversized collection.
4748        field: &'static str,
4749        /// Observed row count.
4750        found: usize,
4751        /// Maximum permitted row count.
4752        max: usize,
4753    },
4754    /// Aggregate profile/settings rows exceeded the V1 limit.
4755    #[error("profile/settings retain {found} aggregate rows, exceeding {max}")]
4756    TooManyAggregateRows {
4757        /// Observed aggregate row count.
4758        found: usize,
4759        /// Maximum permitted aggregate row count.
4760        max: usize,
4761    },
4762    /// Aggregate retained UTF-8 text exceeded the V1 limit.
4763    #[error("profile/settings retain {found} UTF-8 bytes, exceeding {max}")]
4764    TooMuchAggregateText {
4765        /// Observed aggregate UTF-8 byte count.
4766        found: usize,
4767        /// Maximum permitted aggregate byte count.
4768        max: usize,
4769    },
4770    /// Checked arithmetic overflowed while accounting bounded work.
4771    #[error("checked arithmetic overflow while accounting {field}")]
4772    ArithmeticOverflow {
4773        /// Counter that overflowed.
4774        field: &'static str,
4775    },
4776    /// A set/map-shaped collection contains a duplicate stable key.
4777    #[error("{field} contains duplicate key {key:?}")]
4778    DuplicateKey {
4779        /// Collection containing the duplicate.
4780        field: &'static str,
4781        /// Duplicate stable key.
4782        key: String,
4783    },
4784    /// A canonical collection is not in its required order.
4785    #[error("{field} is not in canonical order")]
4786    NonCanonicalOrder {
4787        /// Unordered collection.
4788        field: &'static str,
4789    },
4790    /// The complete closed fact-id inventory is absent or malformed.
4791    #[error("profile facts must contain every V1 fact id exactly once")]
4792    InvalidFactInventory,
4793    /// A known fact carries a value from another fact domain.
4794    #[error("profile fact {fact:?} carries an invalid known-value variant")]
4795    InvalidFactValue {
4796        /// Fact whose value is invalid.
4797        fact: EngineFactIdV1,
4798    },
4799    /// The accepted-input list is empty, duplicated, or noncanonical.
4800    #[error("profile accepted_inputs must be a nonempty canonical set")]
4801    InvalidAcceptedInputs,
4802    /// A descriptor's applicability and default state disagree.
4803    #[error("setting descriptor {setting:?} has inconsistent applicability/default status")]
4804    InvalidDescriptorDefault {
4805        /// Invalid setting descriptor.
4806        setting: EngineSettingIdV1,
4807    },
4808    /// A source references a fact absent from the profile.
4809    #[error("primary source {source_id:?} references absent fact {fact:?}")]
4810    UnknownSourceFact {
4811        /// Source id.
4812        source_id: String,
4813        /// Missing fact id.
4814        fact: EngineFactIdV1,
4815    },
4816    /// A source cites a fact whose state is not known.
4817    #[error("primary source {source_id:?} references non-known fact {fact:?}")]
4818    SourceReferencesNonKnownFact {
4819        /// Source id.
4820        source_id: String,
4821        /// Non-known fact id.
4822        fact: EngineFactIdV1,
4823    },
4824    /// A source references a descriptor absent from the profile.
4825    #[error("primary source {source_id:?} references absent setting {setting:?}")]
4826    UnknownSourceSetting {
4827        /// Source id.
4828        source_id: String,
4829        /// Missing setting id.
4830        setting: EngineSettingIdV1,
4831    },
4832    /// No primary source supports a known profile fact.
4833    #[error("known profile fact {fact:?} has no primary-source reference")]
4834    UnreferencedKnownFact {
4835        /// Unsupported known fact.
4836        fact: EngineFactIdV1,
4837    },
4838    /// No primary source supports a setting descriptor.
4839    #[error("setting descriptor {setting:?} has no primary-source reference")]
4840    UnreferencedSetting {
4841        /// Unsupported descriptor.
4842        setting: EngineSettingIdV1,
4843    },
4844    /// A source-transform path is malformed.
4845    #[error("source-transform path is invalid: {reason}")]
4846    InvalidSourceTransformPath {
4847        /// Stable explanation of the malformed path.
4848        reason: &'static str,
4849    },
4850    /// A materialized setting is absent from the profile.
4851    #[error("{location} contains unknown setting {setting:?}")]
4852    UnknownMaterializedSetting {
4853        /// Stable document or clip location.
4854        location: String,
4855        /// Unknown setting id.
4856        setting: EngineSettingIdV1,
4857    },
4858    /// A materialized setting is declared at the wrong scope.
4859    #[error("{location} contains {setting:?} at the wrong scope")]
4860    WrongSettingScope {
4861        /// Stable document or clip location.
4862        location: String,
4863        /// Wrong-scope setting id.
4864        setting: EngineSettingIdV1,
4865    },
4866    /// A materialized setting is not applicable to the profile.
4867    #[error("{location} contains non-applicable setting {setting:?}")]
4868    NonApplicableSetting {
4869        /// Stable document or clip location.
4870        location: String,
4871        /// Non-applicable setting id.
4872        setting: EngineSettingIdV1,
4873    },
4874    /// A materialized value does not match its descriptor domain.
4875    #[error("{location} contains {setting:?} with a value outside its domain")]
4876    WrongSettingDomain {
4877        /// Stable document or clip location.
4878        location: String,
4879        /// Invalid setting id.
4880        setting: EngineSettingIdV1,
4881    },
4882    /// A required-without-default setting is absent.
4883    #[error("{location} is missing required setting {setting:?}")]
4884    MissingRequiredSetting {
4885        /// Stable document or clip location.
4886        location: String,
4887        /// Missing setting id.
4888        setting: EngineSettingIdV1,
4889    },
4890    /// A rational was zero, had a zero denominator, or was not reduced.
4891    #[error("invalid reduced ratio {numerator}/{denominator}")]
4892    InvalidReducedRatio {
4893        /// Supplied numerator.
4894        numerator: u64,
4895        /// Supplied denominator.
4896        denominator: u64,
4897    },
4898    /// The complete closed V2 fact inventory was malformed.
4899    #[error("profile facts must contain every V2 fact id exactly once")]
4900    InvalidV2FactInventory,
4901    /// A V2 fact carried a value from another domain.
4902    #[error("V2 profile fact {fact:?} carries an invalid known-value variant")]
4903    InvalidV2FactValue {
4904        /// Invalid fact id.
4905        fact: EngineFactIdV2,
4906    },
4907    /// A V2 descriptor carried a default while not applicable.
4908    #[error("V2 setting descriptor {setting:?} has an invalid default")]
4909    InvalidV2DescriptorDefault {
4910        /// Invalid descriptor id.
4911        setting: EngineSettingIdV2,
4912    },
4913    /// A V2 primary source referenced a missing or non-known fact.
4914    #[error("V2 primary source {source_id:?} references unsupported fact {fact:?}")]
4915    InvalidV2SourceFact {
4916        /// Source id.
4917        source_id: String,
4918        /// Invalid fact id.
4919        fact: EngineFactIdV2,
4920    },
4921    /// A V2 primary source referenced an absent descriptor.
4922    #[error("V2 primary source {source_id:?} references absent setting {setting:?}")]
4923    InvalidV2SourceSetting {
4924        /// Source id.
4925        source_id: String,
4926        /// Missing setting id.
4927        setting: EngineSettingIdV2,
4928    },
4929    /// A known V2 fact had no supporting source.
4930    #[error("known V2 profile fact {fact:?} has no primary-source reference")]
4931    UnreferencedV2Fact {
4932        /// Unsupported fact.
4933        fact: EngineFactIdV2,
4934    },
4935    /// A V2 descriptor had no supporting source.
4936    #[error("V2 setting descriptor {setting:?} has no primary-source reference")]
4937    UnreferencedV2Setting {
4938        /// Unsupported setting.
4939        setting: EngineSettingIdV2,
4940    },
4941    /// A V3 materialized row named no profile descriptor.
4942    #[error("V3 settings contain unknown setting {setting:?}")]
4943    UnknownV2MaterializedSetting {
4944        /// Unknown setting id.
4945        setting: EngineSettingIdV2,
4946    },
4947    /// A V3 materialized row appeared at the wrong scope.
4948    #[error("V3 settings contain {setting:?} at the wrong scope")]
4949    WrongV2SettingScope {
4950        /// Wrong-scope setting id.
4951        setting: EngineSettingIdV2,
4952    },
4953    /// A V3 value did not match its descriptor domain.
4954    #[error("V3 setting {setting:?} has a value outside its domain")]
4955    WrongV2SettingDomain {
4956        /// Invalid setting id.
4957        setting: EngineSettingIdV2,
4958    },
4959    /// A bounded setting value was outside its setting-specific range.
4960    #[error("V3 setting {setting:?} is outside its allowed range")]
4961    V2SettingValueOutOfRange {
4962        /// Out-of-range setting id.
4963        setting: EngineSettingIdV2,
4964    },
4965    /// A row claimed a profile default that disagreed with its descriptor.
4966    #[error("V3 setting {setting:?} claims a mismatched profile default")]
4967    InvalidProfileDefaultOrigin {
4968        /// Mismatched setting id.
4969        setting: EngineSettingIdV2,
4970    },
4971    /// V3 settings were bound to a format not accepted by their profile.
4972    #[error("V3 settings source format {format:?} is not accepted by the profile")]
4973    InvalidV3SettingsSourceFormat {
4974        /// Rejected source format.
4975        format: SourceFormatV1,
4976    },
4977    /// A V3 row materialized a descriptor outside its source applicability.
4978    #[error("V3 setting {setting:?} is not applicable to source format {format:?}")]
4979    InapplicableV2MaterializedSetting {
4980        /// Inapplicable setting.
4981        setting: EngineSettingIdV2,
4982        /// Exact source format.
4983        format: SourceFormatV1,
4984    },
4985    /// An applicable V2 descriptor was omitted from V3 materialization.
4986    #[error("V3 settings omit applicable setting {setting:?} for source format {format:?}")]
4987    MissingApplicableV2Setting {
4988        /// Missing setting.
4989        setting: EngineSettingIdV2,
4990        /// Exact source format.
4991        format: SourceFormatV1,
4992    },
4993    /// A canonical identity does not match its semantic preimage.
4994    #[error("identity does not match canonical {contract}")]
4995    IdentityMismatch {
4996        /// Contract whose digest mismatched.
4997        contract: &'static str,
4998    },
4999}
5000
5001fn validate_schema(
5002    field: &'static str,
5003    found: &str,
5004    expected: &'static str,
5005) -> Result<(), EngineContractError> {
5006    if found == expected {
5007        Ok(())
5008    } else {
5009        Err(EngineContractError::InvalidSchema {
5010            field,
5011            expected,
5012            found: found.to_owned(),
5013        })
5014    }
5015}
5016
5017fn validate_required_text(field: &'static str, value: &str) -> Result<(), EngineContractError> {
5018    if value.is_empty() {
5019        return Err(EngineContractError::EmptyText { field });
5020    }
5021    validate_text(field, value)
5022}
5023
5024fn validate_text(field: &'static str, value: &str) -> Result<(), EngineContractError> {
5025    if value.len() > ENGINE_CONTRACT_V1_MAX_TEXT_BYTES {
5026        Err(EngineContractError::TextTooLong {
5027            field,
5028            found: value.len(),
5029            max: ENGINE_CONTRACT_V1_MAX_TEXT_BYTES,
5030        })
5031    } else {
5032        Ok(())
5033    }
5034}
5035
5036fn validate_collection_len(field: &'static str, found: usize) -> Result<(), EngineContractError> {
5037    if found > ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS {
5038        Err(EngineContractError::TooManyRows {
5039            field,
5040            found,
5041            max: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS,
5042        })
5043    } else {
5044        Ok(())
5045    }
5046}
5047
5048fn validate_unique_order<T>(
5049    field: &'static str,
5050    rows: &[T],
5051    key: impl Fn(&T) -> &str,
5052    require_order: bool,
5053) -> Result<(), EngineContractError> {
5054    let mut seen = BTreeSet::new();
5055    let mut previous = None;
5056    for row in rows {
5057        let current = key(row);
5058        if !seen.insert(current) {
5059            return Err(EngineContractError::DuplicateKey {
5060                field,
5061                key: current.to_owned(),
5062            });
5063        }
5064        if require_order && previous.is_some_and(|previous| previous >= current) {
5065            return Err(EngineContractError::NonCanonicalOrder { field });
5066        }
5067        previous = Some(current);
5068    }
5069    Ok(())
5070}
5071
5072fn validate_fact_value(fact: &EngineProfileFactV1) -> Result<(), EngineContractError> {
5073    let EngineFactStateV1::Known(value) = &fact.state else {
5074        return Ok(());
5075    };
5076    let valid = matches!(
5077        (fact.id, value),
5078        (
5079            EngineFactIdV1::AcceptedInputs,
5080            EngineFactValueV1::AcceptedFormats(_)
5081        ) | (
5082            EngineFactIdV1::AnimationAddressability,
5083            EngineFactValueV1::AnimationAddressability(_)
5084        ) | (
5085            EngineFactIdV1::TargetCoordinateBasis,
5086            EngineFactValueV1::CoordinateBasis(_)
5087        ) | (
5088            EngineFactIdV1::TargetLinearUnit,
5089            EngineFactValueV1::LinearUnit(_)
5090        ) | (
5091            EngineFactIdV1::UnitConversionControl | EngineFactIdV1::AxisConversionControl,
5092            EngineFactValueV1::ConversionControl(_)
5093        ) | (
5094            EngineFactIdV1::WholeEndFrameRequired,
5095            EngineFactValueV1::Boolean(_)
5096        ) | (
5097            EngineFactIdV1::AnimationChannelHandling
5098                | EngineFactIdV1::ExtensionHandling
5099                | EngineFactIdV1::ConstructHandling,
5100            EngineFactValueV1::ImportHandling(_)
5101        ) | (
5102            EngineFactIdV1::AnimationTargetAddressability,
5103            EngineFactValueV1::TargetAddressability(_)
5104        ) | (
5105            EngineFactIdV1::RootMotionAddressability,
5106            EngineFactValueV1::RootMotionAddressability(_)
5107        )
5108    );
5109    if !valid {
5110        return Err(EngineContractError::InvalidFactValue { fact: fact.id });
5111    }
5112    if let EngineFactValueV1::AcceptedFormats(formats) = value
5113        && (formats.is_empty()
5114            || formats.len() > ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS
5115            || !formats
5116                .windows(2)
5117                .all(|pair| source_format_name(pair[0]) < source_format_name(pair[1])))
5118    {
5119        return Err(EngineContractError::InvalidAcceptedInputs);
5120    }
5121    if let EngineFactValueV1::ConversionControl(EngineConversionControlV1::ProfileSetting(
5122        setting,
5123    )) = value
5124    {
5125        let expected = match fact.id {
5126            EngineFactIdV1::UnitConversionControl => EngineSettingIdV1::ConvertUnits,
5127            EngineFactIdV1::AxisConversionControl => EngineSettingIdV1::BakeAxisConversion,
5128            _ => return Err(EngineContractError::InvalidFactValue { fact: fact.id }),
5129        };
5130        if *setting != expected {
5131            return Err(EngineContractError::InvalidFactValue { fact: fact.id });
5132        }
5133    }
5134    Ok(())
5135}
5136
5137fn validate_setting_value(value: &EngineSettingValueV1) -> Result<(), EngineContractError> {
5138    let EngineSettingValueV1::SourceTransformPath(path) = value else {
5139        return Ok(());
5140    };
5141    validate_text("source_transform_path", path)?;
5142    let reason = if path.is_empty() {
5143        Some("empty path")
5144    } else if path.starts_with('/') {
5145        Some("absolute path")
5146    } else if path.chars().any(char::is_control) {
5147        Some("control character")
5148    } else if path.split('/').any(str::is_empty) {
5149        Some("empty path segment")
5150    } else if path.split('/').any(|segment| matches!(segment, "." | "..")) {
5151        Some("dot path segment")
5152    } else {
5153        None
5154    };
5155    if let Some(reason) = reason {
5156        Err(EngineContractError::InvalidSourceTransformPath { reason })
5157    } else {
5158        Ok(())
5159    }
5160}
5161
5162fn validate_rows_for_scope(
5163    profile: &ResolvedEngineProfileV1,
5164    rows: &[EngineSettingRowV1],
5165    scope: EngineSettingScopeV1,
5166    location: &str,
5167) -> Result<(), EngineContractError> {
5168    for row in rows {
5169        let Some(descriptor) = profile.setting_descriptor(row.id) else {
5170            return Err(EngineContractError::UnknownMaterializedSetting {
5171                location: location.to_owned(),
5172                setting: row.id,
5173            });
5174        };
5175        if descriptor.scope != scope {
5176            return Err(EngineContractError::WrongSettingScope {
5177                location: location.to_owned(),
5178                setting: row.id,
5179            });
5180        }
5181        if descriptor.applicability != EngineSettingApplicabilityV1::Applicable {
5182            return Err(EngineContractError::NonApplicableSetting {
5183                location: location.to_owned(),
5184                setting: row.id,
5185            });
5186        }
5187        let domain_matches = matches!(
5188            (descriptor.domain, &row.value),
5189            (
5190                EngineSettingDomainV1::Boolean,
5191                EngineSettingValueV1::Boolean(_)
5192            ) | (
5193                EngineSettingDomainV1::BakeOrExtract,
5194                EngineSettingValueV1::BakeOrExtract(_)
5195            ) | (
5196                EngineSettingDomainV1::SourceTransformPath,
5197                EngineSettingValueV1::SourceTransformPath(_)
5198            )
5199        );
5200        if !domain_matches {
5201            return Err(EngineContractError::WrongSettingDomain {
5202                location: location.to_owned(),
5203                setting: row.id,
5204            });
5205        }
5206    }
5207    for descriptor in &profile.setting_descriptors {
5208        if descriptor.scope == scope
5209            && descriptor.applicability == EngineSettingApplicabilityV1::Applicable
5210            && descriptor.default_status == EngineDefaultStatusV1::RequiredWithoutDefault
5211            && !rows.iter().any(|row| row.id == descriptor.id)
5212        {
5213            return Err(EngineContractError::MissingRequiredSetting {
5214                location: location.to_owned(),
5215                setting: descriptor.id,
5216            });
5217        }
5218    }
5219    Ok(())
5220}
5221
5222fn checked_sum(
5223    field: &'static str,
5224    values: impl IntoIterator<Item = usize>,
5225) -> Result<usize, EngineContractError> {
5226    values.into_iter().try_fold(0_usize, |total, value| {
5227        total
5228            .checked_add(value)
5229            .ok_or(EngineContractError::ArithmeticOverflow { field })
5230    })
5231}
5232
5233fn checked_sum_results(
5234    field: &'static str,
5235    initial: impl IntoIterator<Item = usize>,
5236    values: impl IntoIterator<Item = Result<usize, EngineContractError>>,
5237) -> Result<usize, EngineContractError> {
5238    let initial = checked_sum(field, initial)?;
5239    values.into_iter().try_fold(initial, |total, value| {
5240        total
5241            .checked_add(value?)
5242            .ok_or(EngineContractError::ArithmeticOverflow { field })
5243    })
5244}
5245
5246fn encode_profile_key(encoder: &mut CanonicalEncoder, selection: &EngineProfileSelectionV1) {
5247    encoder.field("selection");
5248    encoder.token(&selection.family);
5249    encoder.token(selection.profile_revision.to_string());
5250    encoder.token(&selection.engine_version);
5251    encoder.token(&selection.importer);
5252}
5253
5254fn encode_fact_state(encoder: &mut CanonicalEncoder, state: &EngineFactStateV1) {
5255    match state {
5256        EngineFactStateV1::Unknown => encoder.token("unknown"),
5257        EngineFactStateV1::NotApplicable => encoder.token("not_applicable"),
5258        EngineFactStateV1::Known(value) => {
5259            encoder.token("known");
5260            match value {
5261                EngineFactValueV1::AcceptedFormats(formats) => {
5262                    encoder.token("accepted_formats");
5263                    encoder.count(formats.len());
5264                    for format in formats {
5265                        encoder.token(source_format_name(*format));
5266                    }
5267                }
5268                EngineFactValueV1::AnimationAddressability(value) => {
5269                    encoder.token("animation_addressability");
5270                    encoder.token(match value {
5271                        EngineAnimationAddressabilityV1::GltfAssetLabel => "gltf_asset_label",
5272                    });
5273                }
5274                EngineFactValueV1::CoordinateBasis(value) => {
5275                    encoder.token("coordinate_basis");
5276                    encoder.token(match value.handedness {
5277                        EngineHandednessV1::Left => "left",
5278                        EngineHandednessV1::Right => "right",
5279                    });
5280                    encoder.token(match value.up_axis {
5281                        EngineUpAxisV1::X => "x",
5282                        EngineUpAxisV1::Y => "y",
5283                        EngineUpAxisV1::Z => "z",
5284                    });
5285                    encoder.token(match value.forward_axis {
5286                        EngineForwardAxisV1::PositiveX => "+x",
5287                        EngineForwardAxisV1::NegativeX => "-x",
5288                        EngineForwardAxisV1::PositiveY => "+y",
5289                        EngineForwardAxisV1::NegativeY => "-y",
5290                        EngineForwardAxisV1::PositiveZ => "+z",
5291                        EngineForwardAxisV1::NegativeZ => "-z",
5292                    });
5293                }
5294                EngineFactValueV1::LinearUnit(value) => {
5295                    encoder.token("linear_unit");
5296                    encoder.token(match value {
5297                        EngineLinearUnitV1::Metre => "metre",
5298                        EngineLinearUnitV1::Centimetre => "centimetre",
5299                    });
5300                }
5301                EngineFactValueV1::ConversionControl(value) => {
5302                    encoder.token("conversion_control");
5303                    match value {
5304                        EngineConversionControlV1::ProfileSetting(setting) => {
5305                            encoder.token("profile_setting");
5306                            encoder.token(setting.as_str());
5307                        }
5308                        EngineConversionControlV1::ImporterOption => {
5309                            encoder.token("importer_option");
5310                        }
5311                    }
5312                }
5313                EngineFactValueV1::Boolean(value) => {
5314                    encoder.token("boolean");
5315                    encoder.token(if *value { "true" } else { "false" });
5316                }
5317                EngineFactValueV1::ImportHandling(value) => {
5318                    encoder.token("import_handling");
5319                    encoder.token(match value {
5320                        EngineImportHandlingV1::Preserved => "preserved",
5321                        EngineImportHandlingV1::Converted => "converted",
5322                        EngineImportHandlingV1::Discarded => "discarded",
5323                        EngineImportHandlingV1::Unsupported => "unsupported",
5324                    });
5325                }
5326                EngineFactValueV1::TargetAddressability(value) => {
5327                    encoder.token("target_addressability");
5328                    encoder.token(match value {
5329                        EngineTargetAddressabilityV1::NamePathDerivedId => "name_path_derived_id",
5330                    });
5331                }
5332                EngineFactValueV1::RootMotionAddressability(value) => {
5333                    encoder.token("root_motion_addressability");
5334                    encoder.token(match value {
5335                        EngineRootMotionAddressabilityV1::ExactSourceTransformPath => {
5336                            "exact_source_transform_path"
5337                        }
5338                        EngineRootMotionAddressabilityV1::HumanoidAvatarBody => {
5339                            "humanoid_avatar_body"
5340                        }
5341                    });
5342                }
5343            }
5344        }
5345    }
5346}
5347
5348fn encode_setting_value(encoder: &mut CanonicalEncoder, value: &EngineSettingValueV1) {
5349    match value {
5350        EngineSettingValueV1::Boolean(value) => {
5351            encoder.token("boolean");
5352            encoder.token(if *value { "true" } else { "false" });
5353        }
5354        EngineSettingValueV1::BakeOrExtract(value) => {
5355            encoder.token("bake_or_extract");
5356            encoder.token(match value {
5357                EngineBakeOrExtractV1::Bake => "bake",
5358                EngineBakeOrExtractV1::Extract => "extract",
5359            });
5360        }
5361        EngineSettingValueV1::SourceTransformPath(value) => {
5362            encoder.token("source_transform_path");
5363            encoder.token(value);
5364        }
5365    }
5366}
5367
5368const fn source_format_name(format: SourceFormatV1) -> &'static str {
5369    match format {
5370        SourceFormatV1::GltfJson => "gltf_json",
5371        SourceFormatV1::Glb => "glb",
5372        SourceFormatV1::Fbx => "fbx",
5373    }
5374}
5375
5376const fn setting_scope_name(scope: EngineSettingScopeV1) -> &'static str {
5377    match scope {
5378        EngineSettingScopeV1::Document => "document",
5379        EngineSettingScopeV1::Clip => "clip",
5380    }
5381}
5382
5383const fn setting_domain_name(domain: EngineSettingDomainV1) -> &'static str {
5384    match domain {
5385        EngineSettingDomainV1::Boolean => "boolean",
5386        EngineSettingDomainV1::BakeOrExtract => "bake_or_extract",
5387        EngineSettingDomainV1::SourceTransformPath => "source_transform_path",
5388    }
5389}
5390
5391#[cfg(test)]
5392mod tests {
5393    use super::*;
5394    use serde_json::json;
5395
5396    fn fact_inventory(accepted: Vec<SourceFormatV1>) -> Vec<EngineProfileFactV1> {
5397        ALL_FACT_IDS
5398            .into_iter()
5399            .map(|id| {
5400                let state = if id == EngineFactIdV1::AcceptedInputs {
5401                    EngineFactStateV1::Known(EngineFactValueV1::AcceptedFormats(accepted.clone()))
5402                } else {
5403                    EngineFactStateV1::Unknown
5404                };
5405                EngineProfileFactV1::new(id, state)
5406            })
5407            .collect()
5408    }
5409
5410    fn godot_profile() -> ResolvedEngineProfileV1 {
5411        ResolvedEngineProfileV1::new(
5412            EngineProfileSelectionV1::new("godot", 1, "4.7", "resource-importer-scene").unwrap(),
5413            "urn:animsmith:engine-profile:godot:1",
5414            fact_inventory(vec![
5415                SourceFormatV1::GltfJson,
5416                SourceFormatV1::Glb,
5417                SourceFormatV1::Fbx,
5418            ]),
5419            vec![],
5420            vec![
5421                EnginePrimarySourceV1::new(
5422                    "godot-resource-importer-scene-4.7",
5423                    "4.7",
5424                    "https://docs.godotengine.org/en/4.7/classes/class_resourceimporterscene.html",
5425                    "2026-08-20",
5426                    vec![EngineFactIdV1::AcceptedInputs],
5427                    vec![],
5428                )
5429                .unwrap(),
5430            ],
5431        )
5432        .unwrap()
5433    }
5434
5435    fn settings_profile(family: &str) -> ResolvedEngineProfileV1 {
5436        let mut facts = fact_inventory(vec![SourceFormatV1::Fbx]);
5437        facts
5438            .iter_mut()
5439            .find(|fact| fact.id == EngineFactIdV1::UnitConversionControl)
5440            .unwrap()
5441            .state = EngineFactStateV1::Known(EngineFactValueV1::ConversionControl(
5442            EngineConversionControlV1::ProfileSetting(EngineSettingIdV1::ConvertUnits),
5443        ));
5444        facts
5445            .iter_mut()
5446            .find(|fact| fact.id == EngineFactIdV1::AxisConversionControl)
5447            .unwrap()
5448            .state = EngineFactStateV1::Known(EngineFactValueV1::ConversionControl(
5449            EngineConversionControlV1::ProfileSetting(EngineSettingIdV1::BakeAxisConversion),
5450        ));
5451        let descriptors = vec![
5452            EngineSettingDescriptorV1::new(
5453                EngineSettingIdV1::ConvertUnits,
5454                EngineSettingScopeV1::Document,
5455                EngineSettingDomainV1::Boolean,
5456                EngineSettingApplicabilityV1::Applicable,
5457                EngineDefaultStatusV1::RequiredWithoutDefault,
5458            ),
5459            EngineSettingDescriptorV1::new(
5460                EngineSettingIdV1::BakeAxisConversion,
5461                EngineSettingScopeV1::Document,
5462                EngineSettingDomainV1::Boolean,
5463                EngineSettingApplicabilityV1::Applicable,
5464                EngineDefaultStatusV1::RequiredWithoutDefault,
5465            ),
5466        ];
5467        let source = EnginePrimarySourceV1::new(
5468            "source",
5469            "1",
5470            "https://example.invalid/source",
5471            "2026-08-20",
5472            vec![
5473                EngineFactIdV1::AcceptedInputs,
5474                EngineFactIdV1::UnitConversionControl,
5475                EngineFactIdV1::AxisConversionControl,
5476            ],
5477            vec![
5478                EngineSettingIdV1::ConvertUnits,
5479                EngineSettingIdV1::BakeAxisConversion,
5480            ],
5481        )
5482        .unwrap();
5483        ResolvedEngineProfileV1::new(
5484            EngineProfileSelectionV1::new(family, 1, "1", "importer").unwrap(),
5485            format!("urn:animsmith:engine-profile:{family}:1"),
5486            facts,
5487            descriptors,
5488            vec![source],
5489        )
5490        .unwrap()
5491    }
5492
5493    fn document_settings() -> Vec<EngineSettingRowV1> {
5494        vec![
5495            EngineSettingRowV1::new(
5496                EngineSettingIdV1::ConvertUnits,
5497                EngineSettingValueV1::Boolean(true),
5498            ),
5499            EngineSettingRowV1::new(
5500                EngineSettingIdV1::BakeAxisConversion,
5501                EngineSettingValueV1::Boolean(false),
5502            ),
5503        ]
5504    }
5505
5506    #[test]
5507    fn profile_encoder_preserves_464_godot_golden() {
5508        let profile = godot_profile();
5509        assert_eq!(
5510            profile.facts_identity().sha256(),
5511            "e9c8316d1655c487b60dd35bbfc70289952c5fa12f4718f0be09c7e9a00fbe87"
5512        );
5513        assert_eq!(profile.facts_identity().bytes(), 1_166);
5514
5515        let mut encoder = CanonicalEncoder::default();
5516        profile.encode_preimage(&mut encoder);
5517        assert_eq!(encoder.into_bytes().len(), 1_166);
5518    }
5519
5520    #[test]
5521    fn settings_encoder_preserves_464_godot_golden() {
5522        let profile = godot_profile();
5523        let settings = ResolvedEngineSettingsV1::new(&profile, vec![], vec![]).unwrap();
5524        assert_eq!(
5525            settings.settings_identity().sha256(),
5526            "02032c315fa41ad65249efe1b6914456b3b98caf9b5374b168854cd357f85515"
5527        );
5528        assert_eq!(settings.settings_identity().bytes(), 240);
5529    }
5530
5531    #[test]
5532    fn constructors_canonicalize_sets_maps_and_retain_repeated_clips() {
5533        let profile = settings_profile("test");
5534        let first = ResolvedEngineSettingsV1::new(
5535            &profile,
5536            document_settings(),
5537            vec![
5538                EngineClipSettingsV1::new("walk", vec![]).unwrap(),
5539                EngineClipSettingsV1::new("idle", vec![]).unwrap(),
5540                EngineClipSettingsV1::new("walk", vec![]).unwrap(),
5541            ],
5542        )
5543        .unwrap();
5544        let mut reversed = document_settings();
5545        reversed.reverse();
5546        let second = ResolvedEngineSettingsV1::new(
5547            &profile,
5548            reversed,
5549            vec![
5550                EngineClipSettingsV1::new("walk", vec![]).unwrap(),
5551                EngineClipSettingsV1::new("walk", vec![]).unwrap(),
5552                EngineClipSettingsV1::new("idle", vec![]).unwrap(),
5553            ],
5554        )
5555        .unwrap();
5556
5557        assert_eq!(first, second);
5558        assert_eq!(
5559            first
5560                .clips()
5561                .iter()
5562                .map(EngineClipSettingsV1::clip_name)
5563                .collect::<Vec<_>>(),
5564            vec!["idle", "walk", "walk"]
5565        );
5566        assert!(first.clip_row(1, "walk").is_some());
5567        assert!(first.clip_row(1, "idle").is_none());
5568
5569        let deduplicated = ResolvedEngineSettingsV1::new(
5570            &profile,
5571            document_settings(),
5572            vec![
5573                EngineClipSettingsV1::new("idle", vec![]).unwrap(),
5574                EngineClipSettingsV1::new("walk", vec![]).unwrap(),
5575            ],
5576        )
5577        .unwrap();
5578        assert_ne!(first.settings_identity(), deduplicated.settings_identity());
5579    }
5580
5581    #[test]
5582    fn wire_round_trip_is_strict_and_revalidates_identities() {
5583        let profile = godot_profile();
5584        let value = serde_json::to_value(&profile).unwrap();
5585        assert_eq!(
5586            value["schema"],
5587            json!("urn:animsmith:engine-profile-facts:1")
5588        );
5589        assert!(value.get("primary_sources").is_some());
5590        let decoded: ResolvedEngineProfileV1 = serde_json::from_value(value.clone()).unwrap();
5591        assert_eq!(decoded, profile);
5592
5593        let mut unknown = value.clone();
5594        unknown["unexpected"] = json!(true);
5595        assert!(
5596            serde_json::from_value::<ResolvedEngineProfileV1>(unknown)
5597                .unwrap_err()
5598                .to_string()
5599                .contains("unknown field")
5600        );
5601
5602        let mut identity = value.clone();
5603        identity["identity"]["bytes"] = json!(0);
5604        assert!(
5605            serde_json::from_value::<ResolvedEngineProfileV1>(identity)
5606                .unwrap_err()
5607                .to_string()
5608                .contains("identity does not match")
5609        );
5610
5611        let mut reordered = value;
5612        reordered["facts"].as_array_mut().unwrap().swap(0, 1);
5613        assert!(
5614            serde_json::from_value::<ResolvedEngineProfileV1>(reordered)
5615                .unwrap_err()
5616                .to_string()
5617                .contains("canonical order")
5618        );
5619    }
5620
5621    #[test]
5622    fn profile_mutations_return_the_specific_first_contract_error() {
5623        let profile = godot_profile();
5624
5625        let mut changed = profile.clone();
5626        changed.schema = "urn:changed".into();
5627        assert!(matches!(
5628            changed.validate(),
5629            Err(EngineContractError::InvalidSchema {
5630                field: "profile.schema",
5631                ..
5632            })
5633        ));
5634
5635        let mut changed = profile.clone();
5636        changed.selection.family.push_str("-changed");
5637        assert_eq!(
5638            changed.validate(),
5639            Err(EngineContractError::IdentityMismatch {
5640                contract: ENGINE_PROFILE_FACTS_V1_ID,
5641            })
5642        );
5643
5644        let mut changed = profile.clone();
5645        changed.facts[0].state = EngineFactStateV1::Unknown;
5646        assert_eq!(
5647            changed.validate(),
5648            Err(EngineContractError::InvalidAcceptedInputs)
5649        );
5650
5651        let mut changed = profile;
5652        changed.primary_sources[0].url.push_str("/changed");
5653        assert_eq!(
5654            changed.validate(),
5655            Err(EngineContractError::IdentityMismatch {
5656                contract: ENGINE_PROFILE_FACTS_V1_ID,
5657            })
5658        );
5659    }
5660
5661    #[test]
5662    fn profile_acceptance_mutation_matrix_pins_tuple_facts_descriptors_and_sources() {
5663        let profile = settings_profile("matrix");
5664        let identity_mismatch = Err(EngineContractError::IdentityMismatch {
5665            contract: ENGINE_PROFILE_FACTS_V1_ID,
5666        });
5667
5668        let mut changed = profile.clone();
5669        changed.selection.family.push_str("-changed");
5670        assert_eq!(changed.validate(), identity_mismatch);
5671
5672        let mut changed = profile.clone();
5673        changed.selection.profile_revision += 1;
5674        assert_eq!(changed.validate(), identity_mismatch);
5675
5676        let mut changed = profile.clone();
5677        changed.selection.engine_version.push_str("-changed");
5678        assert_eq!(changed.validate(), identity_mismatch);
5679
5680        let mut changed = profile.clone();
5681        changed.selection.importer.push_str("-changed");
5682        assert_eq!(changed.validate(), identity_mismatch);
5683
5684        let mut changed = profile.clone();
5685        changed.fact_bundle_urn.push_str(":changed");
5686        assert_eq!(changed.validate(), identity_mismatch);
5687
5688        let mut changed = profile.clone();
5689        changed.facts.pop();
5690        assert_eq!(
5691            changed.validate(),
5692            Err(EngineContractError::InvalidFactInventory)
5693        );
5694
5695        let mut changed = profile.clone();
5696        changed
5697            .facts
5698            .iter_mut()
5699            .find(|fact| fact.id == EngineFactIdV1::AcceptedInputs)
5700            .unwrap()
5701            .state = EngineFactStateV1::Known(EngineFactValueV1::Boolean(true));
5702        assert_eq!(
5703            changed.validate(),
5704            Err(EngineContractError::InvalidFactValue {
5705                fact: EngineFactIdV1::AcceptedInputs,
5706            })
5707        );
5708
5709        let mut changed = profile.clone();
5710        changed.setting_descriptors[1].id = EngineSettingIdV1::BakeAxisConversion;
5711        assert_eq!(
5712            changed.validate(),
5713            Err(EngineContractError::InvalidFactValue {
5714                fact: EngineFactIdV1::UnitConversionControl,
5715            })
5716        );
5717
5718        let mut changed = profile.clone();
5719        changed.setting_descriptors[0].scope = EngineSettingScopeV1::Clip;
5720        assert_eq!(changed.validate(), identity_mismatch);
5721
5722        let mut changed = profile.clone();
5723        changed.setting_descriptors[0].domain = EngineSettingDomainV1::BakeOrExtract;
5724        assert_eq!(changed.validate(), identity_mismatch);
5725
5726        let mut changed = profile.clone();
5727        let descriptor_id = changed.setting_descriptors[0].id;
5728        changed.setting_descriptors[0].applicability = EngineSettingApplicabilityV1::NotApplicable;
5729        assert_eq!(
5730            changed.validate(),
5731            Err(EngineContractError::InvalidDescriptorDefault {
5732                setting: descriptor_id,
5733            })
5734        );
5735
5736        let mut changed = profile.clone();
5737        let descriptor_id = changed.setting_descriptors[0].id;
5738        changed.setting_descriptors[0].default_status = EngineDefaultStatusV1::NotApplicable;
5739        assert_eq!(
5740            changed.validate(),
5741            Err(EngineContractError::InvalidDescriptorDefault {
5742                setting: descriptor_id,
5743            })
5744        );
5745
5746        let mut changed = profile.clone();
5747        changed.primary_sources[0].id.clear();
5748        assert_eq!(
5749            changed.validate(),
5750            Err(EngineContractError::EmptyText {
5751                field: "primary_sources.id",
5752            })
5753        );
5754
5755        let mut changed = profile.clone();
5756        changed.primary_sources[0].url.push_str("/changed");
5757        assert_eq!(changed.validate(), identity_mismatch);
5758
5759        let mut changed = profile.clone();
5760        changed.primary_sources[0]
5761            .supported_fact_ids
5762            .push(EngineFactIdV1::AnimationAddressability);
5763        changed.primary_sources[0]
5764            .supported_fact_ids
5765            .sort_by_key(|id| id.as_str());
5766        assert_eq!(
5767            changed.validate(),
5768            Err(EngineContractError::SourceReferencesNonKnownFact {
5769                source_id: "source".to_owned(),
5770                fact: EngineFactIdV1::AnimationAddressability,
5771            })
5772        );
5773
5774        let mut changed = profile.clone();
5775        changed.schema = "urn:changed".to_owned();
5776        assert_eq!(
5777            changed.validate(),
5778            Err(EngineContractError::InvalidSchema {
5779                field: "profile.schema",
5780                expected: ENGINE_PROFILE_FACTS_V1_ID,
5781                found: "urn:changed".to_owned(),
5782            })
5783        );
5784
5785        let mut changed = profile;
5786        changed.identity = InputIdentity::from_bytes(b"changed");
5787        assert_eq!(changed.validate(), identity_mismatch);
5788    }
5789
5790    #[test]
5791    fn settings_wire_requires_profile_validation_after_structural_read() {
5792        let profile = settings_profile("wire");
5793        let settings =
5794            ResolvedEngineSettingsV1::new(&profile, document_settings(), vec![]).unwrap();
5795        let mut value = serde_json::to_value(&settings).unwrap();
5796        let decoded: ResolvedEngineSettingsV1 = serde_json::from_value(value.clone()).unwrap();
5797        decoded.validate_against(&profile).unwrap();
5798
5799        value["identity"]["bytes"] = json!(0);
5800        let decoded: ResolvedEngineSettingsV1 = serde_json::from_value(value).unwrap();
5801        assert_eq!(
5802            decoded.validate_against(&profile),
5803            Err(EngineContractError::IdentityMismatch {
5804                contract: RESOLVED_ENGINE_SETTINGS_V1_ID,
5805            })
5806        );
5807    }
5808
5809    #[test]
5810    fn settings_mutations_reject_noncanonical_order_before_identity() {
5811        let profile = settings_profile("order");
5812        let mut settings =
5813            ResolvedEngineSettingsV1::new(&profile, document_settings(), vec![]).unwrap();
5814        settings.document_settings.swap(0, 1);
5815        assert_eq!(
5816            settings.validate_against(&profile),
5817            Err(EngineContractError::NonCanonicalOrder {
5818                field: "settings.document_settings",
5819            })
5820        );
5821    }
5822
5823    #[test]
5824    fn v2_settings_identity_commits_to_complete_or_n_plus_one_coverage_and_work() {
5825        let profile = settings_profile("v2-settings");
5826        let clips: Vec<_> = (0..ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS)
5827            .map(|_| EngineClipSettingsV1::new("same", Vec::new()).unwrap())
5828            .collect();
5829        let complete = ResolvedEngineSettingsV2::new(
5830            &profile,
5831            document_settings(),
5832            clips.clone(),
5833            ResolvedEngineSettingsCoverageV2::complete(),
5834            ResolvedEngineSettingsWorkV2::new(
5835                ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS,
5836                ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS,
5837                ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS,
5838            ),
5839        )
5840        .unwrap();
5841        let partial = ResolvedEngineSettingsV2::new(
5842            &profile,
5843            document_settings(),
5844            clips,
5845            ResolvedEngineSettingsCoverageV2::actual_clip_rows_exceeded(),
5846            ResolvedEngineSettingsWorkV2::new(
5847                ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS + 1,
5848                ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS,
5849                ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS,
5850            ),
5851        )
5852        .unwrap();
5853
5854        assert_ne!(complete.settings_identity(), partial.settings_identity());
5855        for changed_work in [
5856            ResolvedEngineSettingsWorkV2::new(
5857                ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS,
5858                ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS - 1,
5859                ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS,
5860            ),
5861            ResolvedEngineSettingsWorkV2::new(
5862                ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS,
5863                ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS,
5864                ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS - 1,
5865            ),
5866        ] {
5867            let mut changed = complete.clone();
5868            changed.work = changed_work;
5869            assert_ne!(
5870                complete.settings_identity(),
5871                &changed.computed_identity(&profile),
5872                "every bounded work counter must change the V2 identity preimage"
5873            );
5874        }
5875        complete.validate_against(&profile).unwrap();
5876        partial.validate_against(&profile).unwrap();
5877
5878        let mut forged = serde_json::to_value(&complete).unwrap();
5879        forged["clip_coverage"] = serde_json::json!({
5880            "state": "partial",
5881            "reason": "actual_clip_rows_exceeded"
5882        });
5883        assert!(serde_json::from_value::<ResolvedEngineSettingsV2>(forged).is_err());
5884    }
5885
5886    #[test]
5887    fn materialized_settings_acceptance_mutation_matrix_pins_id_value_location_and_identity() {
5888        let profile = settings_profile("settings-matrix");
5889        let settings =
5890            ResolvedEngineSettingsV1::new(&profile, document_settings(), vec![]).unwrap();
5891
5892        let mut changed = settings.clone();
5893        changed.document_settings[1].id = EngineSettingIdV1::RootMotionSource;
5894        assert_eq!(
5895            changed.validate_against(&profile),
5896            Err(EngineContractError::UnknownMaterializedSetting {
5897                location: "document".to_owned(),
5898                setting: EngineSettingIdV1::RootMotionSource,
5899            })
5900        );
5901
5902        let mut changed = settings.clone();
5903        changed.document_settings[0].value =
5904            EngineSettingValueV1::BakeOrExtract(EngineBakeOrExtractV1::Bake);
5905        assert_eq!(
5906            changed.validate_against(&profile),
5907            Err(EngineContractError::WrongSettingDomain {
5908                location: "document".to_owned(),
5909                setting: EngineSettingIdV1::BakeAxisConversion,
5910            })
5911        );
5912
5913        let mut changed = settings.clone();
5914        changed.clips.push(
5915            EngineClipSettingsV1::new(
5916                "walk",
5917                vec![EngineSettingRowV1::new(
5918                    EngineSettingIdV1::ConvertUnits,
5919                    EngineSettingValueV1::Boolean(true),
5920                )],
5921            )
5922            .unwrap(),
5923        );
5924        assert_eq!(
5925            changed.validate_against(&profile),
5926            Err(EngineContractError::WrongSettingScope {
5927                location: "clip[0]".to_owned(),
5928                setting: EngineSettingIdV1::ConvertUnits,
5929            })
5930        );
5931
5932        let mut changed = settings.clone();
5933        changed.document_settings.swap(0, 1);
5934        assert_eq!(
5935            changed.validate_against(&profile),
5936            Err(EngineContractError::NonCanonicalOrder {
5937                field: "settings.document_settings",
5938            })
5939        );
5940
5941        let mut changed = settings.clone();
5942        changed.schema = "urn:changed".to_owned();
5943        assert_eq!(
5944            changed.validate_against(&profile),
5945            Err(EngineContractError::InvalidSchema {
5946                field: "settings.schema",
5947                expected: RESOLVED_ENGINE_SETTINGS_V1_ID,
5948                found: "urn:changed".to_owned(),
5949            })
5950        );
5951
5952        let mut changed = settings;
5953        changed.identity = InputIdentity::from_bytes(b"changed");
5954        assert_eq!(
5955            changed.validate_against(&profile),
5956            Err(EngineContractError::IdentityMismatch {
5957                contract: RESOLVED_ENGINE_SETTINGS_V1_ID,
5958            })
5959        );
5960    }
5961
5962    #[test]
5963    fn clip_collection_bound_accepts_exact_n_and_rejects_n_plus_one() {
5964        let profile = godot_profile();
5965        let clips = (0..ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS)
5966            .map(|_| EngineClipSettingsV1::new("same", vec![]).unwrap())
5967            .collect();
5968        let exact = ResolvedEngineSettingsV1::new(&profile, vec![], clips).unwrap();
5969        assert_eq!(exact.clips().len(), ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS);
5970
5971        let clips = (0..=ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS)
5972            .map(|_| EngineClipSettingsV1::new("same", vec![]).unwrap())
5973            .collect();
5974        assert_eq!(
5975            ResolvedEngineSettingsV1::new(&profile, vec![], clips),
5976            Err(EngineContractError::TooManyRows {
5977                field: "settings.clips",
5978                found: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS + 1,
5979                max: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS,
5980            })
5981        );
5982    }
5983
5984    #[test]
5985    fn text_bound_accepts_exact_n_and_rejects_n_plus_one() {
5986        let exact = "a".repeat(ENGINE_CONTRACT_V1_MAX_TEXT_BYTES);
5987        EngineClipSettingsV1::new(
5988            "clip",
5989            vec![EngineSettingRowV1::new(
5990                EngineSettingIdV1::RootMotionSource,
5991                EngineSettingValueV1::SourceTransformPath(exact),
5992            )],
5993        )
5994        .unwrap();
5995
5996        let oversized = "a".repeat(ENGINE_CONTRACT_V1_MAX_TEXT_BYTES + 1);
5997        assert_eq!(
5998            EngineClipSettingsV1::new(
5999                "clip",
6000                vec![EngineSettingRowV1::new(
6001                    EngineSettingIdV1::RootMotionSource,
6002                    EngineSettingValueV1::SourceTransformPath(oversized),
6003                )],
6004            ),
6005            Err(EngineContractError::TextTooLong {
6006                field: "source_transform_path",
6007                found: ENGINE_CONTRACT_V1_MAX_TEXT_BYTES + 1,
6008                max: ENGINE_CONTRACT_V1_MAX_TEXT_BYTES,
6009            })
6010        );
6011    }
6012
6013    #[test]
6014    fn materialized_setting_mutations_name_the_violated_contract() {
6015        let profile = settings_profile("mutations");
6016        assert_eq!(
6017            ResolvedEngineSettingsV1::new(
6018                &profile,
6019                vec![EngineSettingRowV1::new(
6020                    EngineSettingIdV1::ConvertUnits,
6021                    EngineSettingValueV1::Boolean(true),
6022                )],
6023                vec![],
6024            ),
6025            Err(EngineContractError::MissingRequiredSetting {
6026                location: "document".into(),
6027                setting: EngineSettingIdV1::BakeAxisConversion,
6028            })
6029        );
6030        assert_eq!(
6031            ResolvedEngineSettingsV1::new(
6032                &profile,
6033                vec![
6034                    EngineSettingRowV1::new(
6035                        EngineSettingIdV1::ConvertUnits,
6036                        EngineSettingValueV1::BakeOrExtract(EngineBakeOrExtractV1::Bake),
6037                    ),
6038                    EngineSettingRowV1::new(
6039                        EngineSettingIdV1::BakeAxisConversion,
6040                        EngineSettingValueV1::Boolean(true),
6041                    ),
6042                ],
6043                vec![],
6044            ),
6045            Err(EngineContractError::WrongSettingDomain {
6046                location: "document".into(),
6047                setting: EngineSettingIdV1::ConvertUnits,
6048            })
6049        );
6050    }
6051
6052    #[test]
6053    fn source_format_and_input_identity_deserialization_are_closed() {
6054        assert_eq!(
6055            serde_json::from_str::<SourceFormatV1>("\"glb\"").unwrap(),
6056            SourceFormatV1::Glb
6057        );
6058        assert!(serde_json::from_str::<SourceFormatV1>("\"obj\"").is_err());
6059
6060        let identity = InputIdentity::from_bytes(b"identity");
6061        let wire = serde_json::to_string(&identity).unwrap();
6062        assert_eq!(
6063            serde_json::from_str::<InputIdentity>(&wire).unwrap(),
6064            identity
6065        );
6066        let mut upper = serde_json::to_value(&identity).unwrap();
6067        upper["sha256"] = json!("A".repeat(64));
6068        assert!(serde_json::from_value::<InputIdentity>(upper).is_err());
6069    }
6070
6071    #[test]
6072    fn canonical_encoder_uses_length_prefixed_tokens() {
6073        let mut encoder = CanonicalEncoder::new("domain");
6074        encoder.field("field");
6075        encoder.count(12);
6076        encode_input_identity(&mut encoder, &InputIdentity::from_bytes(b"x"));
6077        let bytes = encoder.into_bytes();
6078        assert_eq!(&bytes[..8], &6_u64.to_be_bytes());
6079        assert_eq!(&bytes[8..14], b"domain");
6080    }
6081
6082    fn assert_profile_limit(value: &serde_json::Value, expected: EngineContractError) {
6083        match decode_resolved_engine_profile_v1(&serde_json::to_string(value).unwrap()) {
6084            Err(EngineContractDecodeError::Semantic(error)) => assert_eq!(error, expected),
6085            other => panic!("expected typed profile limit, got {other:?}"),
6086        }
6087    }
6088
6089    fn assert_settings_limit(value: &serde_json::Value, expected: EngineContractError) {
6090        match decode_resolved_engine_settings_v1_with_provenance_limit(
6091            &serde_json::to_string(value).unwrap(),
6092            ENGINE_CONTRACT_V1_MAX_AGGREGATE_ROWS,
6093        ) {
6094            Err(EngineSettingsLimitedDecodeError::Contract(
6095                EngineContractDecodeError::Semantic(error),
6096            )) => assert_eq!(error, expected),
6097            _ => panic!("expected typed settings limit"),
6098        }
6099    }
6100
6101    #[test]
6102    fn profile_sequences_reject_n_plus_one_before_decoding_null_sentinels() {
6103        let profile = settings_profile("stream-profile");
6104        let base = serde_json::to_value(&profile).unwrap();
6105        for (field, element) in [
6106            ("facts", base["facts"][0].clone()),
6107            (
6108                "setting_descriptors",
6109                base["setting_descriptors"][0].clone(),
6110            ),
6111            ("primary_sources", base["primary_sources"][0].clone()),
6112        ] {
6113            let mut rows = vec![element; ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS];
6114            rows.push(serde_json::Value::Null);
6115            let mut over = base.clone();
6116            over[field] = rows.into();
6117            assert_profile_limit(
6118                &over,
6119                EngineContractError::TooManyRows {
6120                    field: match field {
6121                        "facts" => "profile.facts",
6122                        "setting_descriptors" => "profile.setting_descriptors",
6123                        "primary_sources" => "profile.primary_sources",
6124                        _ => unreachable!(),
6125                    },
6126                    found: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS + 1,
6127                    max: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS,
6128                },
6129            );
6130        }
6131
6132        let mut accepted = vec![serde_json::json!("glb"); ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS];
6133        accepted.push(serde_json::Value::Null);
6134        let mut over = base.clone();
6135        over["facts"][0]["state"]["known"]["accepted_formats"] = accepted.into();
6136        assert_profile_limit(&over, EngineContractError::InvalidAcceptedInputs);
6137
6138        for (field, value) in [
6139            ("supported_fact_ids", serde_json::json!("accepted_inputs")),
6140            ("supported_setting_ids", serde_json::json!("convert_units")),
6141        ] {
6142            let mut rows = vec![value; ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS];
6143            rows.push(serde_json::Value::Null);
6144            let mut over = base.clone();
6145            over["primary_sources"][0][field] = rows.into();
6146            assert_profile_limit(
6147                &over,
6148                EngineContractError::TooManyRows {
6149                    field: if field == "supported_fact_ids" {
6150                        "primary_sources.supported_fact_ids"
6151                    } else {
6152                        "primary_sources.supported_setting_ids"
6153                    },
6154                    found: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS + 1,
6155                    max: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS,
6156                },
6157            );
6158        }
6159    }
6160
6161    #[test]
6162    fn settings_sequences_reject_n_plus_one_before_decoding_null_sentinels() {
6163        let profile = godot_profile();
6164        let settings = ResolvedEngineSettingsV1::new(&profile, vec![], vec![]).unwrap();
6165        let base = serde_json::to_value(settings).unwrap();
6166        let row = serde_json::json!({"id": "convert_units", "value": {"boolean": true}});
6167        let clip = serde_json::json!({"clip_name": "clip", "settings": []});
6168
6169        for (field, element, error_field) in [
6170            (
6171                "document_settings",
6172                row.clone(),
6173                "settings.document_settings",
6174            ),
6175            ("clips", clip.clone(), "settings.clips"),
6176        ] {
6177            let mut rows = vec![element; ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS];
6178            rows.push(serde_json::Value::Null);
6179            let mut over = base.clone();
6180            over[field] = rows.into();
6181            assert_settings_limit(
6182                &over,
6183                EngineContractError::TooManyRows {
6184                    field: error_field,
6185                    found: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS + 1,
6186                    max: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS,
6187                },
6188            );
6189        }
6190
6191        let mut rows = vec![row; ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS];
6192        rows.push(serde_json::Value::Null);
6193        let mut over = base;
6194        over["clips"] = serde_json::json!([{"clip_name": "clip", "settings": rows}]);
6195        assert_settings_limit(
6196            &over,
6197            EngineContractError::TooManyRows {
6198                field: "settings.clips.settings",
6199                found: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS + 1,
6200                max: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS,
6201            },
6202        );
6203    }
6204
6205    #[test]
6206    fn nested_profile_and_settings_aggregate_budgets_stop_at_global_n_plus_one() {
6207        let profile = settings_profile("aggregate-profile");
6208        let mut profile_wire = serde_json::to_value(profile).unwrap();
6209        profile_wire["facts"] = serde_json::json!([]);
6210        profile_wire["setting_descriptors"] = serde_json::json!([]);
6211        let source_template = serde_json::json!({
6212            "id": "source",
6213            "target_version": "1",
6214            "url": "https://example.invalid",
6215            "verified_on": "2026-08-20",
6216            "supported_fact_ids": vec![
6217                "accepted_inputs";
6218                ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS
6219            ],
6220            "supported_setting_ids": vec![
6221                "convert_units";
6222                ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS
6223            ]
6224        });
6225        let mut sources = vec![source_template.clone(); 7];
6226        let mut last = source_template;
6227        last["supported_setting_ids"] = serde_json::json!(vec!["convert_units"; 4_088]);
6228        last["supported_setting_ids"]
6229            .as_array_mut()
6230            .unwrap()
6231            .push(serde_json::Value::Null);
6232        sources.push(last);
6233        profile_wire["primary_sources"] = sources.into();
6234        assert_profile_limit(
6235            &profile_wire,
6236            EngineContractError::TooManyAggregateRows {
6237                found: ENGINE_CONTRACT_V1_MAX_AGGREGATE_ROWS + 1,
6238                max: ENGINE_CONTRACT_V1_MAX_AGGREGATE_ROWS,
6239            },
6240        );
6241        let mut locally_oversized =
6242            vec![serde_json::json!("convert_units"); ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS];
6243        locally_oversized.push(serde_json::Value::Null);
6244        profile_wire["primary_sources"][7]["supported_setting_ids"] = locally_oversized.into();
6245        assert_profile_limit(
6246            &profile_wire,
6247            EngineContractError::TooManyRows {
6248                field: "primary_sources.supported_setting_ids",
6249                found: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS + 1,
6250                max: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS,
6251            },
6252        );
6253
6254        let profile = godot_profile();
6255        let settings = ResolvedEngineSettingsV1::new(&profile, vec![], vec![]).unwrap();
6256        let mut settings_wire = serde_json::to_value(&settings).unwrap();
6257        let setting = serde_json::json!({"id": "convert_units", "value": {"boolean": true}});
6258        let full_clip = serde_json::json!({
6259            "clip_name": "clip",
6260            "settings": vec![setting.clone(); 4_095]
6261        });
6262        let mut clips = vec![full_clip; 15];
6263        let mut last = serde_json::json!({
6264            "clip_name": "clip",
6265            "settings": vec![setting; 4_095]
6266        });
6267        last["settings"]
6268            .as_array_mut()
6269            .unwrap()
6270            .push(serde_json::Value::Null);
6271        clips.push(last);
6272        settings_wire["clips"] = clips.into();
6273        assert_settings_limit(
6274            &settings_wire,
6275            EngineContractError::TooManyAggregateRows {
6276                found: ENGINE_CONTRACT_V1_MAX_AGGREGATE_ROWS + 1,
6277                max: ENGINE_CONTRACT_V1_MAX_AGGREGATE_ROWS,
6278            },
6279        );
6280        let mut locally_oversized = vec![
6281            serde_json::json!({"id": "convert_units", "value": {"boolean": true}});
6282            ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS
6283        ];
6284        locally_oversized.push(serde_json::Value::Null);
6285        settings_wire["clips"][15]["settings"] = locally_oversized.into();
6286        assert_settings_limit(
6287            &settings_wire,
6288            EngineContractError::TooManyRows {
6289                field: "settings.clips.settings",
6290                found: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS + 1,
6291                max: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS,
6292            },
6293        );
6294    }
6295
6296    fn profile_v2_for_origin_tests() -> ResolvedEngineProfileV2 {
6297        let mut facts = ALL_FACT_IDS_V2
6298            .into_iter()
6299            .map(|id| EngineProfileFactV2::new(id, EngineFactStateV2::Unknown))
6300            .collect::<Vec<_>>();
6301        facts
6302            .iter_mut()
6303            .find(|fact| fact.id() == EngineFactIdV2::AcceptedInputs)
6304            .unwrap()
6305            .state = EngineFactStateV2::Known(EngineFactValueV2::AcceptedFormats(vec![
6306            SourceFormatV1::Glb,
6307        ]));
6308        let descriptor = EngineSettingDescriptorV2::new(
6309            EngineSettingIdV2::LoadMeshes,
6310            EngineSettingScopeV1::Document,
6311            EngineSettingDomainV2::Token,
6312            vec![SourceFormatV1::Glb],
6313            Some(EngineSettingValueV2::Token("nonempty".into())),
6314        )
6315        .unwrap();
6316        let source = EnginePrimarySourceV2::new(
6317            "bevy-doc",
6318            "0.19",
6319            "https://example.invalid/bevy",
6320            "2026-08-25",
6321            vec![EngineFactIdV2::AcceptedInputs],
6322            vec![EngineSettingIdV2::LoadMeshes],
6323        )
6324        .unwrap();
6325        ResolvedEngineProfileV2::new(
6326            EngineProfileSelectionV1::new("bevy", 2, "0.19", "bevy_gltf").unwrap(),
6327            "urn:animsmith:engine-profile:bevy:0.19:2",
6328            facts,
6329            vec![descriptor],
6330            vec![source],
6331        )
6332        .unwrap()
6333    }
6334
6335    #[test]
6336    fn v3_value_origin_is_identity_bearing_and_applicable_rows_are_required() {
6337        let profile = profile_v2_for_origin_tests();
6338        let row = |origin| {
6339            EngineSettingRowV3::new(
6340                EngineSettingIdV2::LoadMeshes,
6341                EngineSettingValueV2::Token("nonempty".into()),
6342                origin,
6343            )
6344        };
6345        let default = ResolvedEngineSettingsV3::new(
6346            &profile,
6347            SourceFormatV1::Glb,
6348            vec![row(EngineSettingValueOriginV3::ProfileDefault)],
6349            vec![],
6350            ResolvedEngineSettingsCoverageV2::complete(),
6351            ResolvedEngineSettingsWorkV2::new(0, 0, 0),
6352        )
6353        .unwrap();
6354        let explicit = ResolvedEngineSettingsV3::new(
6355            &profile,
6356            SourceFormatV1::Glb,
6357            vec![row(EngineSettingValueOriginV3::ExplicitConfig)],
6358            vec![],
6359            ResolvedEngineSettingsCoverageV2::complete(),
6360            ResolvedEngineSettingsWorkV2::new(0, 0, 0),
6361        )
6362        .unwrap();
6363        assert_ne!(default.settings_identity(), explicit.settings_identity());
6364        assert!(matches!(
6365            ResolvedEngineSettingsV3::new(
6366                &profile,
6367                SourceFormatV1::Glb,
6368                vec![],
6369                vec![],
6370                ResolvedEngineSettingsCoverageV2::complete(),
6371                ResolvedEngineSettingsWorkV2::new(0, 0, 0),
6372            ),
6373            Err(EngineContractError::MissingApplicableV2Setting {
6374                setting: EngineSettingIdV2::LoadMeshes,
6375                ..
6376            })
6377        ));
6378    }
6379
6380    #[test]
6381    fn v2_profile_and_v3_settings_readers_stop_at_each_nested_n_plus_one() {
6382        let profile = profile_v2_for_origin_tests();
6383        let mut profile_wire = serde_json::to_value(&profile).unwrap();
6384        for field in ["facts", "setting_descriptors", "primary_sources"] {
6385            let element = profile_wire[field][0].clone();
6386            profile_wire[field] = vec![element; ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS].into();
6387            profile_wire[field]
6388                .as_array_mut()
6389                .unwrap()
6390                .push(serde_json::Value::Null);
6391            assert!(
6392                serde_json::from_value::<ResolvedEngineProfileV2>(profile_wire.clone()).is_err()
6393            );
6394            profile_wire = serde_json::to_value(&profile).unwrap();
6395        }
6396
6397        let settings = ResolvedEngineSettingsV3::new(
6398            &profile,
6399            SourceFormatV1::Glb,
6400            vec![EngineSettingRowV3::new(
6401                EngineSettingIdV2::LoadMeshes,
6402                EngineSettingValueV2::Token("nonempty".into()),
6403                EngineSettingValueOriginV3::ProfileDefault,
6404            )],
6405            vec![],
6406            ResolvedEngineSettingsCoverageV2::complete(),
6407            ResolvedEngineSettingsWorkV2::new(0, 0, 0),
6408        )
6409        .unwrap();
6410        let mut settings_wire = serde_json::to_value(&settings).unwrap();
6411        let row = settings_wire["document_settings"][0].clone();
6412        settings_wire["document_settings"] =
6413            vec![row; ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS].into();
6414        settings_wire["document_settings"]
6415            .as_array_mut()
6416            .unwrap()
6417            .push(serde_json::Value::Null);
6418        assert!(serde_json::from_value::<ResolvedEngineSettingsV3>(settings_wire).is_err());
6419
6420        let mut nested = serde_json::to_value(settings).unwrap();
6421        let mut rows = vec![
6422            serde_json::json!({
6423                "id": "load_meshes",
6424                "value": {"token": "nonempty"},
6425                "value_origin": "profile_default"
6426            });
6427            ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS
6428        ];
6429        rows.push(serde_json::Value::Null);
6430        nested["clips"] = serde_json::json!([{
6431            "clip_ordinal": 0,
6432            "clip_name": "clip",
6433            "settings": rows
6434        }]);
6435        assert!(serde_json::from_value::<ResolvedEngineSettingsV3>(nested).is_err());
6436    }
6437}