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