Skip to main content

omena_cascade/
model.rs

1//! Public data model for cascade ordering, selector witnesses, and proof reports.
2//!
3//! These serializable types are the stable boundary consumed by query,
4//! transform, conformance, fuzz, and LSP surfaces. They intentionally expose
5//! evidence fields instead of opaque booleans so later passes can explain why a
6//! cascade-sensitive rewrite was accepted or blocked.
7
8use omena_syntax::ident::{
9    AuthoredPropertyTextV0, CanonicalClassKeyV0, CanonicalCustomPropertyNameV0, CanonicalIdKeyV0,
10    CanonicalPropertyKeyV0, CanonicalTypeSelectorKeyV0, ClassNameV0,
11};
12use serde::{Deserialize, Serialize};
13use std::{
14    cmp::Ordering,
15    collections::{BTreeMap, BTreeSet},
16};
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
19#[serde(rename_all = "camelCase")]
20pub enum CascadeLevel {
21    UserAgentNormal,
22    UserNormal,
23    AuthorNormal,
24    InlineNormal,
25    Animation,
26    AuthorImportant,
27    InlineImportant,
28    UserImportant,
29    UserAgentImportant,
30    Transition,
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
34#[serde(rename_all = "camelCase")]
35pub struct LayerRank(i32);
36
37impl LayerRank {
38    /// Returns the opaque scalar used by the cascade key ordering.
39    pub const fn get(self) -> i32 {
40        self.0
41    }
42}
43
44/// Position in a flattened cascade-layer order before importance normalization.
45///
46/// The sentinel-safe domain is `0 <= ordinal < i32::MAX`; `None` represents an
47/// unlayered declaration at the normalization boundary.
48#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
49#[serde(transparent)]
50pub struct LayerOrdinal(i32);
51
52impl LayerOrdinal {
53    /// Rejects ordinals that would collide with the unlayered sentinels.
54    pub const fn new(ordinal: i32) -> Option<Self> {
55        if 0 <= ordinal && ordinal < i32::MAX {
56            Some(Self(ordinal))
57        } else {
58            None
59        }
60    }
61
62    pub const fn get(self) -> i32 {
63        self.0
64    }
65}
66
67/// Maps a layer ordinal into the comparison domain used by `CascadeKey`.
68///
69/// Unlayered declarations form an implicit final layer, and the whole layer
70/// order is reversed for important declarations. This scalar encoding is sound
71/// because `CascadeKey` compares `level` before `layer_rank`, so normal and
72/// important declarations never rely on their shared zero value.
73pub const fn normalized_layer_rank(important: bool, ordinal: Option<LayerOrdinal>) -> LayerRank {
74    match (important, ordinal) {
75        (false, Some(ordinal)) => LayerRank(ordinal.get()),
76        (false, None) => LayerRank(i32::MAX),
77        (true, Some(ordinal)) => LayerRank(-ordinal.get()),
78        (true, None) => LayerRank(i32::MIN),
79    }
80}
81
82#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
83#[serde(rename_all = "camelCase")]
84pub struct Specificity {
85    pub ids: u32,
86    pub classes: u32,
87    pub elements: u32,
88}
89
90impl Specificity {
91    pub const ZERO: Self = Self {
92        ids: 0,
93        classes: 0,
94        elements: 0,
95    };
96
97    pub const fn new(ids: u32, classes: u32, elements: u32) -> Self {
98        Self {
99            ids,
100            classes,
101            elements,
102        }
103    }
104}
105
106#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
107#[serde(rename_all = "camelCase")]
108/// Whether a specificity estimate is complete enough for exact cascade ordering.
109pub enum SpecificityExactnessV0 {
110    /// Every selector component that contributes specificity was modeled.
111    Exact,
112    /// The numeric specificity is only a lower bound because some syntax was unmodeled.
113    Inexact,
114}
115
116impl Ord for Specificity {
117    fn cmp(&self, other: &Self) -> Ordering {
118        crate::axis_order::compare_specificity_axes_v0(self, other)
119    }
120}
121
122impl PartialOrd for Specificity {
123    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
124        Some(self.cmp(other))
125    }
126}
127
128#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
129#[serde(rename_all = "camelCase")]
130pub struct ModuleRank {
131    pub distance_priority: u32,
132    pub import_order_priority: u32,
133    pub file_order_priority: u32,
134}
135
136impl ModuleRank {
137    pub const ZERO: Self = Self {
138        distance_priority: 0,
139        import_order_priority: 0,
140        file_order_priority: 0,
141    };
142
143    pub const fn new(
144        distance_priority: u32,
145        import_order_priority: u32,
146        file_order_priority: u32,
147    ) -> Self {
148        Self {
149            distance_priority,
150            import_order_priority,
151            file_order_priority,
152        }
153    }
154}
155
156impl Ord for ModuleRank {
157    fn cmp(&self, other: &Self) -> Ordering {
158        (
159            self.distance_priority,
160            self.import_order_priority,
161            self.file_order_priority,
162        )
163            .cmp(&(
164                other.distance_priority,
165                other.import_order_priority,
166                other.file_order_priority,
167            ))
168    }
169}
170
171impl PartialOrd for ModuleRank {
172    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
173        Some(self.cmp(other))
174    }
175}
176
177/// Provenance evidence used only to make open-world ties deterministic.
178///
179/// This evidence is deliberately separate from [`CascadeKey`]: it is not a
180/// spec-defined cascade axis and cannot make an otherwise ambiguous cascade
181/// outcome definite.
182#[non_exhaustive]
183#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
184#[serde(rename_all = "camelCase")]
185pub struct OpenWorldTieEvidence {
186    pub module_rank: ModuleRank,
187}
188
189impl OpenWorldTieEvidence {
190    /// No provenance preference is available.
191    pub const NONE: Self = Self {
192        module_rank: ModuleRank::ZERO,
193    };
194
195    /// Numeric zero form retained for callers that model evidence as a rank.
196    pub const ZERO: Self = Self::NONE;
197
198    pub const fn new(module_rank: ModuleRank) -> Self {
199        Self { module_rank }
200    }
201}
202
203#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
204#[serde(rename_all = "camelCase")]
205pub struct CascadeKey {
206    pub level: CascadeLevel,
207    pub layer_rank: LayerRank,
208    pub scope_proximity: u32,
209    pub specificity: Specificity,
210    pub source_order: u32,
211}
212
213impl CascadeKey {
214    pub const fn new(
215        level: CascadeLevel,
216        layer_rank: LayerRank,
217        scope_proximity: u32,
218        specificity: Specificity,
219        source_order: u32,
220    ) -> Self {
221        Self {
222            level,
223            layer_rank,
224            scope_proximity,
225            specificity,
226            source_order,
227        }
228    }
229}
230
231impl Ord for CascadeKey {
232    fn cmp(&self, other: &Self) -> Ordering {
233        crate::axis_order::compare_cascade_key_axes_v0(self, other)
234    }
235}
236
237impl PartialOrd for CascadeKey {
238    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
239        Some(self.cmp(other))
240    }
241}
242
243#[derive(Debug, Clone, Serialize)]
244#[serde(rename_all = "camelCase")]
245pub struct CascadeDeclaration {
246    pub id: String,
247    pub property: AuthoredPropertyTextV0,
248    pub property_key: CanonicalPropertyKeyV0,
249    pub value: CascadeValue,
250    pub key: CascadeKey,
251    /// Non-spec evidence for deterministic ordering of open-world ties.
252    pub open_world_tie_evidence: OpenWorldTieEvidence,
253    /// Trust boundary for using `key.specificity` to mint an exact winner.
254    pub specificity_exactness: SpecificityExactnessV0,
255}
256
257impl PartialEq for CascadeDeclaration {
258    fn eq(&self, other: &Self) -> bool {
259        self.id == other.id
260            && self.property_key == other.property_key
261            && self.value == other.value
262            && self.key == other.key
263            && self.open_world_tie_evidence == other.open_world_tie_evidence
264            && self.specificity_exactness == other.specificity_exactness
265    }
266}
267
268impl Eq for CascadeDeclaration {}
269
270#[derive(Debug, Clone, Serialize)]
271#[serde(rename_all = "camelCase")]
272pub struct CascadeProof {
273    pub declaration_id: String,
274    pub property: AuthoredPropertyTextV0,
275    pub property_key: CanonicalPropertyKeyV0,
276    pub level: CascadeLevel,
277    pub layer_rank: LayerRank,
278    pub scope_proximity: u32,
279    pub specificity: Specificity,
280    pub module_rank: ModuleRank,
281    pub source_order: u32,
282}
283
284impl PartialEq for CascadeProof {
285    fn eq(&self, other: &Self) -> bool {
286        self.declaration_id == other.declaration_id
287            && self.property_key == other.property_key
288            && self.level == other.level
289            && self.layer_rank == other.layer_rank
290            && self.scope_proximity == other.scope_proximity
291            && self.specificity == other.specificity
292            && self.module_rank == other.module_rank
293            && self.source_order == other.source_order
294    }
295}
296
297impl Eq for CascadeProof {}
298
299impl CascadeProof {
300    pub fn from_declaration(declaration: &CascadeDeclaration) -> Self {
301        assert_eq!(
302            declaration.specificity_exactness,
303            SpecificityExactnessV0::Exact,
304            "cascade proofs require exact specificity"
305        );
306        Self {
307            declaration_id: declaration.id.clone(),
308            property: declaration.property.clone(),
309            property_key: declaration.property_key.clone(),
310            level: declaration.key.level,
311            layer_rank: declaration.key.layer_rank,
312            scope_proximity: declaration.key.scope_proximity,
313            specificity: declaration.key.specificity,
314            module_rank: declaration.open_world_tie_evidence.module_rank,
315            source_order: declaration.key.source_order,
316        }
317    }
318}
319
320#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
321#[serde(rename_all = "camelCase")]
322pub enum CascadeOutcome {
323    Definite {
324        winner: CascadeDeclaration,
325        proof: Box<CascadeProof>,
326        also_considered: Vec<CascadeDeclaration>,
327    },
328    RankedSet(Vec<CascadeDeclaration>),
329    Inherit,
330    Top,
331}
332
333#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
334#[serde(rename_all = "camelCase")]
335pub enum CascadeValue {
336    Literal(String),
337    Composite(Vec<CascadeValue>),
338    Var {
339        name: CanonicalCustomPropertyNameV0,
340        fallback: Option<Box<CascadeValue>>,
341    },
342    Initial,
343    Inherit,
344    Indeterminate,
345    GuaranteedInvalid,
346    Unset,
347}
348
349#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
350#[serde(rename_all = "camelCase")]
351pub enum ComputedCascadeValueStatusV0 {
352    Resolved,
353    Inherited,
354    Initial,
355    Indeterminate,
356    InvalidAtComputedValueTime,
357}
358
359macro_rules! define_computed_cascade_indeterminate_reasons {
360    ($($variant:ident => $wire_name:literal),+ $(,)?) => {
361        #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
362        #[serde(rename_all = "camelCase")]
363        pub enum ComputedCascadeIndeterminateReasonV0 {
364            $($variant),+
365        }
366
367        impl ComputedCascadeIndeterminateReasonV0 {
368            #[cfg(test)]
369            pub(crate) const ALL: &'static [Self] = &[$(Self::$variant),+];
370
371            pub const fn wire_name(self) -> &'static str {
372                match self {
373                    $(Self::$variant => $wire_name),+
374                }
375            }
376        }
377    };
378}
379
380define_computed_cascade_indeterminate_reasons! {
381    CascadeOutcomeIndeterminate => "cascadeOutcomeIndeterminate",
382    PropertyInheritanceMetadataUnavailable => "propertyInheritanceMetadataUnavailable",
383    PropertyInitialValueMetadataUnavailable => "propertyInitialValueMetadataUnavailable",
384    RegisteredPropertySyntaxIndeterminate => "registeredPropertySyntaxIndeterminate",
385    StandardPropertySyntaxIndeterminate => "standardPropertySyntaxIndeterminate",
386    InheritedFromIndeterminateParent => "inheritedFromIndeterminateParent",
387}
388
389#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
390#[serde(rename_all = "camelCase")]
391pub enum CascadeRegisteredValueVerdictV0 {
392    Matched,
393    Unmatched,
394    Unknown,
395}
396
397#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
398#[serde(rename_all = "camelCase")]
399pub enum CascadeStandardValueVerdictV0 {
400    Matched,
401    Unmatched,
402    Unknown,
403}
404
405#[derive(Debug, Clone, Serialize)]
406#[serde(rename_all = "camelCase")]
407pub struct CascadeRegisteredCustomPropertyV0 {
408    pub name: AuthoredPropertyTextV0,
409    pub inherits: bool,
410    pub initial_value: CascadeValue,
411    pub declaration_value_verdicts: BTreeMap<String, CascadeRegisteredValueVerdictV0>,
412}
413
414impl PartialEq for CascadeRegisteredCustomPropertyV0 {
415    fn eq(&self, other: &Self) -> bool {
416        self.name.to_custom_key() == other.name.to_custom_key()
417            && self.inherits == other.inherits
418            && self.initial_value == other.initial_value
419            && self.declaration_value_verdicts == other.declaration_value_verdicts
420    }
421}
422
423impl Eq for CascadeRegisteredCustomPropertyV0 {}
424
425#[derive(Debug, Clone, Serialize)]
426#[serde(rename_all = "camelCase")]
427pub struct CascadeComputedValueInputV0 {
428    pub property: AuthoredPropertyTextV0,
429    pub declarations: Vec<CascadeDeclaration>,
430    pub custom_property_env: CustomPropertyEnv,
431    pub parent_computed_value: Option<CascadeValue>,
432    #[serde(skip_serializing_if = "Option::is_none")]
433    pub registered_custom_property: Option<CascadeRegisteredCustomPropertyV0>,
434    /// Caller-supplied grammar verdicts for standard (non-custom) properties,
435    /// keyed by declaration id. `omena-cascade` does not own a property grammar;
436    /// the authority is `omena-abstract-value::validate_standard_property_value_v0`,
437    /// consulted by the caller. Absence means no verdict is available, not that
438    /// the value is valid.
439    pub standard_property_value_verdicts: BTreeMap<String, CascadeStandardValueVerdictV0>,
440}
441
442impl PartialEq for CascadeComputedValueInputV0 {
443    fn eq(&self, other: &Self) -> bool {
444        self.property
445            .to_property_name()
446            .same_as(&other.property.to_property_name())
447            && self.declarations == other.declarations
448            && self.custom_property_env == other.custom_property_env
449            && self.parent_computed_value == other.parent_computed_value
450            && self.registered_custom_property == other.registered_custom_property
451            && self.standard_property_value_verdicts == other.standard_property_value_verdicts
452    }
453}
454
455impl Eq for CascadeComputedValueInputV0 {}
456
457#[derive(Debug, Clone, Serialize)]
458#[serde(rename_all = "camelCase")]
459pub struct CascadeComputedValueResultV0 {
460    pub schema_version: &'static str,
461    pub product: &'static str,
462    pub property: AuthoredPropertyTextV0,
463    pub status: ComputedCascadeValueStatusV0,
464    pub value: CascadeValue,
465    pub winner_declaration_id: Option<String>,
466    pub inherited: bool,
467    pub used_initial_value: bool,
468    pub invalid_at_computed_value_time: bool,
469    #[serde(skip_serializing_if = "Option::is_none")]
470    pub indeterminate_reason: Option<ComputedCascadeIndeterminateReasonV0>,
471    /// Why the value the declaration falls back to could not be determined when
472    /// the declaration itself became invalid at computed-value time. This is
473    /// orthogonal to `indeterminate_reason`, which remains absent unless the
474    /// result status is `Indeterminate`.
475    #[serde(skip_serializing_if = "Option::is_none")]
476    pub fallback_indeterminate_reason: Option<ComputedCascadeIndeterminateReasonV0>,
477    pub derivation_steps: Vec<&'static str>,
478}
479
480impl PartialEq for CascadeComputedValueResultV0 {
481    fn eq(&self, other: &Self) -> bool {
482        self.schema_version == other.schema_version
483            && self.product == other.product
484            && self
485                .property
486                .to_property_name()
487                .same_as(&other.property.to_property_name())
488            && self.status == other.status
489            && self.value == other.value
490            && self.winner_declaration_id == other.winner_declaration_id
491            && self.inherited == other.inherited
492            && self.used_initial_value == other.used_initial_value
493            && self.invalid_at_computed_value_time == other.invalid_at_computed_value_time
494            && self.indeterminate_reason == other.indeterminate_reason
495            && self.fallback_indeterminate_reason == other.fallback_indeterminate_reason
496            && self.derivation_steps == other.derivation_steps
497    }
498}
499
500impl Eq for CascadeComputedValueResultV0 {}
501
502#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
503#[serde(rename_all = "camelCase")]
504pub enum SelectorContextMatchKind {
505    NoMatch,
506    Global,
507    Root,
508    Exact,
509    ContainsSelector,
510    ApproximateSelector,
511}
512
513#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
514#[serde(rename_all = "camelCase")]
515pub struct SelectorContextWitness {
516    pub kind: SelectorContextMatchKind,
517    pub verdict: SelectorMatchVerdict,
518    pub matched: bool,
519    pub rank: usize,
520    pub declaration_selector: Option<String>,
521    pub reference_selector: Option<String>,
522}
523
524impl SelectorContextWitness {
525    pub fn no_match() -> Self {
526        Self {
527            kind: SelectorContextMatchKind::NoMatch,
528            verdict: SelectorMatchVerdict::No,
529            matched: false,
530            rank: 0,
531            declaration_selector: None,
532            reference_selector: None,
533        }
534    }
535}
536
537#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
538#[serde(rename_all = "camelCase")]
539pub struct ElementSignature {
540    pub tag: Option<CanonicalTypeSelectorKeyV0>,
541    pub id: Option<CanonicalIdKeyV0>,
542    pub classes: BTreeSet<CanonicalClassKeyV0>,
543    pub attributes: BTreeSet<String>,
544    pub pseudo_states: BTreeSet<String>,
545    pub classes_are_exact: bool,
546    pub attributes_are_exact: bool,
547    pub pseudo_states_are_exact: bool,
548    pub tag_is_exact: bool,
549    pub id_is_exact: bool,
550}
551
552#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
553#[serde(rename_all = "camelCase")]
554pub struct ElementIdentityV0 {
555    pub source_path: String,
556    pub byte_start: usize,
557    pub byte_end: usize,
558}
559
560#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
561#[serde(rename_all = "camelCase")]
562pub struct ElementSignatureWithParentsV0 {
563    pub identity: ElementIdentityV0,
564    pub signature: ElementSignature,
565    pub parent_chain: Vec<ElementIdentityV0>,
566    pub parent_chain_complete: bool,
567}
568
569#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
570#[serde(rename_all = "camelCase")]
571pub enum ElementParentChainStatusV0 {
572    Complete,
573    MissingSource,
574    MissingElement,
575    AmbiguousParent,
576    Cycle,
577}
578
579#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
580#[serde(rename_all = "camelCase")]
581pub struct ElementParentChainV0 {
582    pub target: ElementIdentityV0,
583    pub ancestors: Vec<ElementIdentityV0>,
584    pub status: ElementParentChainStatusV0,
585}
586
587#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
588#[serde(rename_all = "camelCase")]
589pub enum ScopeProximityStatusV0 {
590    Known,
591    IncompleteParentChain,
592    MissingElementSignature,
593    UnsupportedRootSelector,
594    NoMatchingRoot,
595}
596
597#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
598#[serde(rename_all = "camelCase")]
599pub struct ScopeProximityV0 {
600    pub status: ScopeProximityStatusV0,
601    pub distance: Option<u32>,
602    pub matched_root: Option<ElementIdentityV0>,
603    pub examined_element_count: usize,
604}
605
606impl ScopeProximityV0 {
607    pub const fn unknown(status: ScopeProximityStatusV0) -> Self {
608        Self {
609            status,
610            distance: None,
611            matched_root: None,
612            examined_element_count: 0,
613        }
614    }
615}
616
617impl ElementParentChainV0 {
618    pub fn is_complete(&self) -> bool {
619        self.status == ElementParentChainStatusV0::Complete
620    }
621}
622
623impl ElementSignature {
624    pub fn concrete(
625        tag: Option<impl Into<String>>,
626        id: Option<impl Into<String>>,
627        classes: impl IntoIterator<Item = impl Into<String>>,
628    ) -> Self {
629        Self {
630            tag: tag.map(|tag| CanonicalTypeSelectorKeyV0::from_authored(&tag.into())),
631            id: id.map(|id| CanonicalIdKeyV0::from_authored(&id.into())),
632            classes: classes
633                .into_iter()
634                .map(|class| ClassNameV0::new(class.into()).canonical_key())
635                .collect(),
636            attributes: BTreeSet::new(),
637            pseudo_states: BTreeSet::new(),
638            classes_are_exact: true,
639            attributes_are_exact: true,
640            pseudo_states_are_exact: true,
641            tag_is_exact: true,
642            id_is_exact: true,
643        }
644    }
645
646    pub fn at_least_classes(classes: impl IntoIterator<Item = impl Into<String>>) -> Self {
647        Self {
648            classes_are_exact: false,
649            ..Self::concrete(None::<String>, None::<String>, classes)
650        }
651    }
652}
653
654#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
655#[serde(rename_all = "camelCase")]
656pub struct SelectorFunctionalPseudoConstraintV0 {
657    pub name: String,
658    pub arguments: String,
659}
660
661#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
662#[serde(rename_all = "camelCase")]
663pub struct SelectorSignature {
664    pub selector: String,
665    pub required_tag: Option<CanonicalTypeSelectorKeyV0>,
666    pub required_id: Option<CanonicalIdKeyV0>,
667    pub required_classes: BTreeSet<CanonicalClassKeyV0>,
668    pub required_attributes: BTreeSet<String>,
669    pub required_pseudo_states: BTreeSet<String>,
670    pub functional_pseudo_constraints: Vec<SelectorFunctionalPseudoConstraintV0>,
671    pub specificity: Specificity,
672    pub specificity_exactness: SpecificityExactnessV0,
673}
674
675impl SelectorSignature {
676    pub fn requires_class(&self, authored: &str) -> bool {
677        self.required_classes
678            .contains(&ClassNameV0::new(authored).canonical_key())
679    }
680}
681
682#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
683#[serde(rename_all = "camelCase")]
684pub enum SelectorMatchVerdict {
685    No,
686    Maybe,
687    Yes,
688}
689
690#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
691#[serde(rename_all = "camelCase")]
692pub enum SelectorMatchReason {
693    Universal,
694    SimpleCompound,
695    SelectorList,
696    MissingTag,
697    MissingId,
698    MissingClass,
699    MissingAttribute,
700    MissingPseudoState,
701    UnsupportedSelector,
702}
703
704#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
705#[serde(rename_all = "camelCase")]
706pub struct SelectorMatchWitness {
707    pub selector: String,
708    pub matched_branch: Option<String>,
709    pub verdict: SelectorMatchVerdict,
710    pub reason: SelectorMatchReason,
711    pub specificity: Specificity,
712    pub specificity_exactness: SpecificityExactnessV0,
713    pub missing_tag: Option<String>,
714    pub missing_id: Option<String>,
715    pub missing_classes: BTreeSet<String>,
716    pub missing_attributes: BTreeSet<String>,
717    pub missing_pseudo_states: BTreeSet<String>,
718    pub unsupported_branches: Vec<String>,
719}
720
721impl SelectorMatchWitness {
722    pub(crate) fn unsupported(selector: &str) -> Self {
723        Self {
724            selector: selector.to_string(),
725            matched_branch: Some(selector.to_string()),
726            verdict: SelectorMatchVerdict::Maybe,
727            reason: SelectorMatchReason::UnsupportedSelector,
728            specificity: Specificity::ZERO,
729            specificity_exactness: SpecificityExactnessV0::Inexact,
730            missing_tag: None,
731            missing_id: None,
732            missing_classes: BTreeSet::new(),
733            missing_attributes: BTreeSet::new(),
734            missing_pseudo_states: BTreeSet::new(),
735            unsupported_branches: vec![selector.to_string()],
736        }
737    }
738}
739
740#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
741#[serde(rename_all = "camelCase")]
742pub struct CascadeBoundarySummary {
743    pub product: &'static str,
744    pub ordering_model: &'static str,
745    pub substitution_model: &'static str,
746    pub least_fixed_point_proof_model: &'static str,
747    pub ready_surfaces: Vec<&'static str>,
748    pub not_ready_surfaces: Vec<&'static str>,
749}
750
751#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
752#[serde(rename_all = "camelCase")]
753pub struct CascadeConformanceSeedCase {
754    pub name: String,
755    pub property: &'static str,
756    pub declarations: Vec<CascadeDeclaration>,
757    pub expected_outcome: &'static str,
758    pub expected_winner_id: Option<String>,
759}
760
761#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
762#[serde(rename_all = "camelCase")]
763pub struct CascadeConformanceSeedResult {
764    pub name: String,
765    pub passed: bool,
766    pub expected_outcome: &'static str,
767    pub actual_outcome: &'static str,
768    pub expected_winner_id: Option<String>,
769    pub actual_winner_id: Option<String>,
770}
771
772#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
773#[serde(rename_all = "camelCase")]
774pub struct CascadeConformanceSeedReport {
775    pub schema_version: &'static str,
776    pub product: &'static str,
777    pub case_count: usize,
778    pub passed_count: usize,
779    pub failed_count: usize,
780    pub results: Vec<CascadeConformanceSeedResult>,
781}
782
783#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
784#[serde(rename_all = "camelCase")]
785pub struct CascadeEvaluationFuzzCaseV0 {
786    pub seed: u64,
787    pub declaration_count: usize,
788}
789
790#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
791#[serde(rename_all = "camelCase")]
792pub struct CascadeEvaluationFuzzResultV0 {
793    pub seed: u64,
794    pub declaration_count: usize,
795    pub actual_winner_id: Option<String>,
796    pub expected_winner_id: Option<String>,
797    pub ranked_count: usize,
798    pub passed: bool,
799}
800
801#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
802#[serde(rename_all = "camelCase")]
803pub struct VarSubstitutionFuzzCaseV0 {
804    pub seed: u64,
805    pub chain_len: usize,
806    pub cycle: bool,
807}
808
809#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
810#[serde(rename_all = "camelCase")]
811pub struct VarSubstitutionFuzzResultV0 {
812    pub seed: u64,
813    pub chain_len: usize,
814    pub cycle: bool,
815    pub result: CascadeValue,
816    pub expected: CascadeValue,
817    pub passed: bool,
818}
819
820#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
821#[serde(rename_all = "camelCase")]
822pub struct CustomPropertyLeastFixedPointSummaryV0 {
823    pub schema_version: &'static str,
824    pub product: &'static str,
825    pub input_count: usize,
826    pub resolved_count: usize,
827    pub guaranteed_invalid_count: usize,
828    pub iteration_count: usize,
829    pub iteration_bound: usize,
830    pub reached_fixed_point: bool,
831    pub monotone_witness_valid: bool,
832    pub proof: CustomPropertyLeastFixedPointProofV0,
833    pub iteration_trace: Vec<CustomPropertyLeastFixedPointIterationV0>,
834    pub entries: Vec<CustomPropertyLeastFixedPointEntryV0>,
835    pub ready_surfaces: Vec<&'static str>,
836}
837
838/// Historical compatibility shape for the custom-property structural computation witness.
839///
840/// New code should prefer [`CustomPropertyBoundedFixedPointComputationWitnessV0`].
841/// The proof-oriented name and fields remain available for 0.x consumers.
842#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
843#[serde(rename_all = "camelCase")]
844pub struct CustomPropertyLeastFixedPointProofV0 {
845    pub finite_domain: &'static str,
846    pub transfer_function: &'static str,
847    #[serde(skip_serializing)]
848    pub bounded_fixed_point_computation_witness: &'static str,
849    /// Compatibility wording; prefer [`Self::monotonic_progress_witness`].
850    pub monotone_witness: &'static str,
851    #[serde(skip_serializing)]
852    pub monotonic_progress_witness: &'static str,
853    pub iteration_bound_formula: &'static str,
854    pub cycle_policy: &'static str,
855    /// Compatibility wording retained alongside the computation-witness fields.
856    pub proof_obligations: Vec<&'static str>,
857}
858
859/// Compatibility alias retained for the structural custom-property computation witness.
860pub type CustomPropertyBoundedFixedPointComputationWitnessV0 = CustomPropertyLeastFixedPointProofV0;
861
862#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
863#[serde(rename_all = "camelCase")]
864pub struct CustomPropertyLeastFixedPointIterationV0 {
865    pub iteration: usize,
866    pub changed_count: usize,
867    pub settled_count: usize,
868    pub guaranteed_invalid_count: usize,
869}
870
871/// The structural reason a custom-property binding became guaranteed-invalid.
872#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
873#[serde(rename_all = "camelCase")]
874pub enum CustomPropertyGuaranteedInvalidReasonV0 {
875    CycleMember,
876    MissingReference,
877    InvalidDependencyWithoutFallback,
878}
879
880#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
881#[serde(rename_all = "camelCase")]
882pub struct CustomPropertyLeastFixedPointEntryV0 {
883    pub name: CanonicalCustomPropertyNameV0,
884    pub input: CascadeValue,
885    pub resolved: CascadeValue,
886    pub changed: bool,
887    pub guaranteed_invalid: bool,
888    pub guaranteed_invalid_reason: Option<CustomPropertyGuaranteedInvalidReasonV0>,
889}
890
891#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
892#[serde(rename_all = "camelCase")]
893pub struct CascadeFuzzSeedReportV0 {
894    pub schema_version: &'static str,
895    pub product: &'static str,
896    pub case_count: usize,
897    pub passed_count: usize,
898    pub failed_count: usize,
899    pub cascade_results: Vec<CascadeEvaluationFuzzResultV0>,
900    pub var_results: Vec<VarSubstitutionFuzzResultV0>,
901}
902
903#[derive(Debug, Clone, Serialize)]
904#[serde(rename_all = "camelCase")]
905pub struct BoxLonghandInputV0 {
906    pub property: AuthoredPropertyTextV0,
907    pub value: String,
908    pub important: bool,
909    pub source_order: u32,
910}
911
912impl PartialEq for BoxLonghandInputV0 {
913    fn eq(&self, other: &Self) -> bool {
914        self.property.to_standard_key() == other.property.to_standard_key()
915            && self.value == other.value
916            && self.important == other.important
917            && self.source_order == other.source_order
918    }
919}
920
921impl Eq for BoxLonghandInputV0 {}
922
923pub type LonghandMergeInputV0 = BoxLonghandInputV0;
924
925#[derive(Debug, Clone, Serialize)]
926#[serde(rename_all = "camelCase")]
927pub struct ShorthandCombinationProofV0 {
928    pub schema_version: &'static str,
929    pub product: &'static str,
930    pub shorthand_property: AuthoredPropertyTextV0,
931    pub accepted: bool,
932    pub blocked_reason: Option<&'static str>,
933    pub ordered_longhand_properties: Vec<AuthoredPropertyTextV0>,
934    pub provenance_preserved: bool,
935    pub cascade_safe_witness: String,
936}
937
938impl PartialEq for ShorthandCombinationProofV0 {
939    fn eq(&self, other: &Self) -> bool {
940        self.schema_version == other.schema_version
941            && self.product == other.product
942            && self.shorthand_property.to_standard_key()
943                == other.shorthand_property.to_standard_key()
944            && self.accepted == other.accepted
945            && self.blocked_reason == other.blocked_reason
946            && self.ordered_longhand_properties.len() == other.ordered_longhand_properties.len()
947            && self
948                .ordered_longhand_properties
949                .iter()
950                .zip(other.ordered_longhand_properties.iter())
951                .all(|(left, right)| left.to_standard_key() == right.to_standard_key())
952            && self.provenance_preserved == other.provenance_preserved
953            && self.cascade_safe_witness == other.cascade_safe_witness
954    }
955}
956
957impl Eq for ShorthandCombinationProofV0 {}
958
959pub type LonghandMergeProofV0 = ShorthandCombinationProofV0;
960
961#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
962#[serde(default, rename_all = "camelCase")]
963pub struct SupportsTargetCapabilityV0 {
964    pub supports_light_dark: bool,
965    pub supports_color_mix: bool,
966    pub supports_oklch_oklab: bool,
967    pub supports_color_function: bool,
968    pub supports_relative_color: bool,
969    pub supports_logical_properties: bool,
970    pub supports_css_nesting: bool,
971    pub supports_css_scope: bool,
972    pub supports_cascade_layers: bool,
973}
974
975impl SupportsTargetCapabilityV0 {
976    pub const fn all_supported() -> Self {
977        Self {
978            supports_light_dark: true,
979            supports_color_mix: true,
980            supports_oklch_oklab: true,
981            supports_color_function: true,
982            supports_relative_color: true,
983            supports_logical_properties: true,
984            supports_css_nesting: true,
985            supports_css_scope: true,
986            supports_cascade_layers: true,
987        }
988    }
989
990    pub const fn none_supported() -> Self {
991        Self {
992            supports_light_dark: false,
993            supports_color_mix: false,
994            supports_oklch_oklab: false,
995            supports_color_function: false,
996            supports_relative_color: false,
997            supports_logical_properties: false,
998            supports_css_nesting: false,
999            supports_css_scope: false,
1000            supports_cascade_layers: false,
1001        }
1002    }
1003}
1004
1005impl Default for SupportsTargetCapabilityV0 {
1006    fn default() -> Self {
1007        Self::none_supported()
1008    }
1009}
1010
1011#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
1012#[serde(rename_all = "camelCase")]
1013pub enum StaticSupportsAssumptionV0 {
1014    ModernBrowser,
1015    TargetCapability(SupportsTargetCapabilityV0),
1016}
1017
1018#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
1019#[serde(rename_all = "camelCase")]
1020pub enum StaticSupportsEvalVerdictV0 {
1021    AlwaysTrue,
1022    AlwaysFalse,
1023    Unknown,
1024}
1025
1026#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1027#[serde(rename_all = "camelCase")]
1028pub struct StaticSupportsEvalWitnessV0 {
1029    pub schema_version: &'static str,
1030    pub product: &'static str,
1031    pub condition: String,
1032    pub assumption: StaticSupportsAssumptionV0,
1033    pub verdict: StaticSupportsEvalVerdictV0,
1034    pub reason: &'static str,
1035    pub provenance_preserved: bool,
1036}
1037
1038#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1039#[serde(rename_all = "camelCase")]
1040pub struct ScopeFlattenInputV0 {
1041    pub root_selector: String,
1042    pub limit_selector: Option<String>,
1043    pub scoped_rule_count: usize,
1044    pub peer_scope_count: usize,
1045    pub competing_unscoped_rule_count: usize,
1046    pub inside_layer: bool,
1047}
1048
1049#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1050#[serde(rename_all = "camelCase")]
1051pub struct ScopeFlattenProofV0 {
1052    pub schema_version: &'static str,
1053    pub product: &'static str,
1054    pub accepted: bool,
1055    pub blocked_reason: Option<&'static str>,
1056    pub root_selector: String,
1057    pub provenance_preserved: bool,
1058    pub cascade_safe_witness: String,
1059}
1060
1061#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1062#[serde(rename_all = "camelCase")]
1063pub struct LayerFlattenInputV0 {
1064    pub layer_name: Option<String>,
1065    pub layer_rule_count: usize,
1066    pub peer_layer_count: usize,
1067    pub unlayered_rule_count: usize,
1068    pub important_declaration_count: usize,
1069    pub closed_bundle: bool,
1070}
1071
1072#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1073#[serde(rename_all = "camelCase")]
1074pub struct LayerFlattenProofV0 {
1075    pub schema_version: &'static str,
1076    pub product: &'static str,
1077    pub accepted: bool,
1078    pub blocked_reason: Option<&'static str>,
1079    pub layer_name: Option<String>,
1080    pub provenance_preserved: bool,
1081    pub cascade_safe_witness: String,
1082}
1083
1084#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1085#[serde(tag = "witnessKind", content = "witness", rename_all = "camelCase")]
1086pub enum ModalCheckWitnessSourceV0 {
1087    ShorthandCombination(ShorthandCombinationProofV0),
1088    StaticSupportsEval(StaticSupportsEvalWitnessV0),
1089    ScopeFlatten(ScopeFlattenProofV0),
1090    LayerFlatten(LayerFlattenProofV0),
1091}
1092
1093#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1094#[serde(rename_all = "camelCase")]
1095/// V0 freeze-candidate witness aggregation over existing cascade proof outputs.
1096///
1097/// This is a staged strict-superset surface for release evidence. It does not
1098/// claim a completed modal theorem, paper-grade proof system, or Cargo 1.0 API.
1099pub struct ModalCheckWitnessV0 {
1100    pub schema_version: &'static str,
1101    pub product: &'static str,
1102    pub modal_family: &'static str,
1103    pub substrate: &'static str,
1104    pub obligation_count: usize,
1105    pub accepted_count: usize,
1106    pub blocked_count: usize,
1107    pub all_provenance_preserved: bool,
1108    pub source_products: Vec<&'static str>,
1109    pub witnesses: Vec<ModalCheckWitnessSourceV0>,
1110}
1111
1112#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1113#[serde(rename_all = "camelCase")]
1114pub struct CascadeMarginSchemaV0 {
1115    pub schema_version: &'static str,
1116    pub product: &'static str,
1117    pub margin_kind: &'static str,
1118    pub axis_order: Vec<&'static str>,
1119    pub calibration_stage: &'static str,
1120    pub public_safety_claim_ready: bool,
1121}
1122
1123#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1124#[serde(rename_all = "camelCase")]
1125pub struct CascadeMarginV0 {
1126    pub schema_version: &'static str,
1127    pub product: &'static str,
1128    pub margin_kind: &'static str,
1129    pub winner_declaration_id: String,
1130    pub challenger_declaration_id: Option<String>,
1131    pub dominant_axis: &'static str,
1132    pub signed_distance: i64,
1133    pub winner_key: CascadeKey,
1134    pub challenger_key: Option<CascadeKey>,
1135    pub calibration_stage: &'static str,
1136    pub public_safety_claim_ready: bool,
1137}
1138
1139pub type CustomPropertyEnv = BTreeMap<CanonicalCustomPropertyNameV0, CascadeValue>;