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