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::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    UserImportant,
24    UserAgentImportant,
25    Transition,
26}
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
29#[serde(rename_all = "camelCase")]
30pub struct LayerRank(pub i32);
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
33#[serde(rename_all = "camelCase")]
34pub struct Specificity {
35    pub ids: u32,
36    pub classes: u32,
37    pub elements: u32,
38}
39
40impl Specificity {
41    pub const ZERO: Self = Self {
42        ids: 0,
43        classes: 0,
44        elements: 0,
45    };
46
47    pub const fn new(ids: u32, classes: u32, elements: u32) -> Self {
48        Self {
49            ids,
50            classes,
51            elements,
52        }
53    }
54}
55
56impl Ord for Specificity {
57    fn cmp(&self, other: &Self) -> Ordering {
58        (self.ids, self.classes, self.elements).cmp(&(other.ids, other.classes, other.elements))
59    }
60}
61
62impl PartialOrd for Specificity {
63    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
64        Some(self.cmp(other))
65    }
66}
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
69#[serde(rename_all = "camelCase")]
70pub struct CascadeKey {
71    pub level: CascadeLevel,
72    pub layer_rank: LayerRank,
73    pub scope_proximity: u32,
74    pub specificity: Specificity,
75    pub source_order: u32,
76}
77
78impl CascadeKey {
79    pub const fn new(
80        level: CascadeLevel,
81        layer_rank: LayerRank,
82        scope_proximity: u32,
83        specificity: Specificity,
84        source_order: u32,
85    ) -> Self {
86        Self {
87            level,
88            layer_rank,
89            scope_proximity,
90            specificity,
91            source_order,
92        }
93    }
94}
95
96impl Ord for CascadeKey {
97    fn cmp(&self, other: &Self) -> Ordering {
98        self.level
99            .cmp(&other.level)
100            .then_with(|| self.layer_rank.cmp(&other.layer_rank))
101            .then_with(|| other.scope_proximity.cmp(&self.scope_proximity))
102            .then_with(|| self.specificity.cmp(&other.specificity))
103            .then_with(|| self.source_order.cmp(&other.source_order))
104    }
105}
106
107impl PartialOrd for CascadeKey {
108    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
109        Some(self.cmp(other))
110    }
111}
112
113#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
114#[serde(rename_all = "camelCase")]
115pub struct CascadeDeclaration {
116    pub id: String,
117    pub property: String,
118    pub value: CascadeValue,
119    pub key: CascadeKey,
120}
121
122#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
123#[serde(rename_all = "camelCase")]
124pub struct CascadeProof {
125    pub declaration_id: String,
126    pub property: String,
127    pub level: CascadeLevel,
128    pub layer_rank: LayerRank,
129    pub scope_proximity: u32,
130    pub specificity: Specificity,
131    pub source_order: u32,
132}
133
134impl CascadeProof {
135    pub fn from_declaration(declaration: &CascadeDeclaration) -> Self {
136        Self {
137            declaration_id: declaration.id.clone(),
138            property: declaration.property.clone(),
139            level: declaration.key.level,
140            layer_rank: declaration.key.layer_rank,
141            scope_proximity: declaration.key.scope_proximity,
142            specificity: declaration.key.specificity,
143            source_order: declaration.key.source_order,
144        }
145    }
146}
147
148#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
149#[serde(rename_all = "camelCase")]
150pub enum CascadeOutcome {
151    Definite {
152        winner: CascadeDeclaration,
153        proof: CascadeProof,
154        also_considered: Vec<CascadeDeclaration>,
155    },
156    RankedSet(Vec<CascadeDeclaration>),
157    Inherit,
158    Top,
159}
160
161#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
162#[serde(rename_all = "camelCase")]
163pub enum CascadeValue {
164    Literal(String),
165    Composite(Vec<CascadeValue>),
166    Var {
167        name: String,
168        fallback: Option<Box<CascadeValue>>,
169    },
170    Initial,
171    Inherit,
172    GuaranteedInvalid,
173    Unset,
174}
175
176#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
177#[serde(rename_all = "camelCase")]
178pub enum ComputedCascadeValueStatusV0 {
179    Resolved,
180    Inherited,
181    Initial,
182    InvalidAtComputedValueTime,
183}
184
185#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
186#[serde(rename_all = "camelCase")]
187pub struct CascadeComputedValueInputV0 {
188    pub property: String,
189    pub declarations: Vec<CascadeDeclaration>,
190    pub custom_property_env: CustomPropertyEnv,
191    pub parent_computed_value: Option<CascadeValue>,
192}
193
194#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
195#[serde(rename_all = "camelCase")]
196pub struct CascadeComputedValueResultV0 {
197    pub schema_version: &'static str,
198    pub product: &'static str,
199    pub property: String,
200    pub status: ComputedCascadeValueStatusV0,
201    pub value: CascadeValue,
202    pub winner_declaration_id: Option<String>,
203    pub inherited: bool,
204    pub used_initial_value: bool,
205    pub invalid_at_computed_value_time: bool,
206    pub derivation_steps: Vec<&'static str>,
207}
208
209#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
210#[serde(rename_all = "camelCase")]
211pub enum SelectorContextMatchKind {
212    NoMatch,
213    Global,
214    Root,
215    Exact,
216    ContainsSelector,
217}
218
219#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
220#[serde(rename_all = "camelCase")]
221pub struct SelectorContextWitness {
222    pub kind: SelectorContextMatchKind,
223    pub matched: bool,
224    pub rank: usize,
225    pub declaration_selector: Option<String>,
226    pub reference_selector: Option<String>,
227}
228
229impl SelectorContextWitness {
230    pub fn no_match() -> Self {
231        Self {
232            kind: SelectorContextMatchKind::NoMatch,
233            matched: false,
234            rank: 0,
235            declaration_selector: None,
236            reference_selector: None,
237        }
238    }
239}
240
241#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
242#[serde(rename_all = "camelCase")]
243pub struct ElementSignature {
244    pub tag: Option<String>,
245    pub id: Option<String>,
246    pub classes: BTreeSet<String>,
247    pub attributes: BTreeSet<String>,
248    pub pseudo_states: BTreeSet<String>,
249    pub classes_are_exact: bool,
250    pub attributes_are_exact: bool,
251    pub pseudo_states_are_exact: bool,
252    pub tag_is_exact: bool,
253    pub id_is_exact: bool,
254}
255
256impl ElementSignature {
257    pub fn concrete(
258        tag: Option<impl Into<String>>,
259        id: Option<impl Into<String>>,
260        classes: impl IntoIterator<Item = impl Into<String>>,
261    ) -> Self {
262        Self {
263            tag: tag.map(Into::into),
264            id: id.map(Into::into),
265            classes: classes.into_iter().map(Into::into).collect(),
266            attributes: BTreeSet::new(),
267            pseudo_states: BTreeSet::new(),
268            classes_are_exact: true,
269            attributes_are_exact: true,
270            pseudo_states_are_exact: true,
271            tag_is_exact: true,
272            id_is_exact: true,
273        }
274    }
275
276    pub fn at_least_classes(classes: impl IntoIterator<Item = impl Into<String>>) -> Self {
277        Self {
278            classes_are_exact: false,
279            ..Self::concrete(None::<String>, None::<String>, classes)
280        }
281    }
282}
283
284#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
285#[serde(rename_all = "camelCase")]
286pub struct SelectorSignature {
287    pub selector: String,
288    pub required_tag: Option<String>,
289    pub required_id: Option<String>,
290    pub required_classes: BTreeSet<String>,
291    pub required_attributes: BTreeSet<String>,
292    pub required_pseudo_states: BTreeSet<String>,
293    pub specificity: Specificity,
294}
295
296#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
297#[serde(rename_all = "camelCase")]
298pub enum SelectorMatchVerdict {
299    No,
300    Maybe,
301    Yes,
302}
303
304#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
305#[serde(rename_all = "camelCase")]
306pub enum SelectorMatchReason {
307    Universal,
308    SimpleCompound,
309    SelectorList,
310    MissingTag,
311    MissingId,
312    MissingClass,
313    MissingAttribute,
314    MissingPseudoState,
315    UnsupportedSelector,
316}
317
318#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
319#[serde(rename_all = "camelCase")]
320pub struct SelectorMatchWitness {
321    pub selector: String,
322    pub matched_branch: Option<String>,
323    pub verdict: SelectorMatchVerdict,
324    pub reason: SelectorMatchReason,
325    pub specificity: Specificity,
326    pub missing_tag: Option<String>,
327    pub missing_id: Option<String>,
328    pub missing_classes: BTreeSet<String>,
329    pub missing_attributes: BTreeSet<String>,
330    pub missing_pseudo_states: BTreeSet<String>,
331    pub unsupported_branches: Vec<String>,
332}
333
334impl SelectorMatchWitness {
335    pub(crate) fn unsupported(selector: &str) -> Self {
336        Self {
337            selector: selector.to_string(),
338            matched_branch: Some(selector.to_string()),
339            verdict: SelectorMatchVerdict::Maybe,
340            reason: SelectorMatchReason::UnsupportedSelector,
341            specificity: Specificity::ZERO,
342            missing_tag: None,
343            missing_id: None,
344            missing_classes: BTreeSet::new(),
345            missing_attributes: BTreeSet::new(),
346            missing_pseudo_states: BTreeSet::new(),
347            unsupported_branches: vec![selector.to_string()],
348        }
349    }
350}
351
352#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
353#[serde(rename_all = "camelCase")]
354pub struct CascadeBoundarySummary {
355    pub product: &'static str,
356    pub ordering_model: &'static str,
357    pub substitution_model: &'static str,
358    pub least_fixed_point_proof_model: &'static str,
359    pub ready_surfaces: Vec<&'static str>,
360    pub not_ready_surfaces: Vec<&'static str>,
361}
362
363#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
364#[serde(rename_all = "camelCase")]
365pub struct CascadeConformanceSeedCase {
366    pub name: String,
367    pub property: &'static str,
368    pub declarations: Vec<CascadeDeclaration>,
369    pub expected_outcome: &'static str,
370    pub expected_winner_id: Option<String>,
371}
372
373#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
374#[serde(rename_all = "camelCase")]
375pub struct CascadeConformanceSeedResult {
376    pub name: String,
377    pub passed: bool,
378    pub expected_outcome: &'static str,
379    pub actual_outcome: &'static str,
380    pub expected_winner_id: Option<String>,
381    pub actual_winner_id: Option<String>,
382}
383
384#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
385#[serde(rename_all = "camelCase")]
386pub struct CascadeConformanceSeedReport {
387    pub schema_version: &'static str,
388    pub product: &'static str,
389    pub case_count: usize,
390    pub passed_count: usize,
391    pub failed_count: usize,
392    pub results: Vec<CascadeConformanceSeedResult>,
393}
394
395#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
396#[serde(rename_all = "camelCase")]
397pub struct CascadeEvaluationFuzzCaseV0 {
398    pub seed: u64,
399    pub declaration_count: usize,
400}
401
402#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
403#[serde(rename_all = "camelCase")]
404pub struct CascadeEvaluationFuzzResultV0 {
405    pub seed: u64,
406    pub declaration_count: usize,
407    pub actual_winner_id: Option<String>,
408    pub expected_winner_id: Option<String>,
409    pub ranked_count: usize,
410    pub passed: bool,
411}
412
413#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
414#[serde(rename_all = "camelCase")]
415pub struct VarSubstitutionFuzzCaseV0 {
416    pub seed: u64,
417    pub chain_len: usize,
418    pub cycle: bool,
419}
420
421#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
422#[serde(rename_all = "camelCase")]
423pub struct VarSubstitutionFuzzResultV0 {
424    pub seed: u64,
425    pub chain_len: usize,
426    pub cycle: bool,
427    pub result: CascadeValue,
428    pub expected: CascadeValue,
429    pub passed: bool,
430}
431
432#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
433#[serde(rename_all = "camelCase")]
434pub struct CustomPropertyLeastFixedPointSummaryV0 {
435    pub schema_version: &'static str,
436    pub product: &'static str,
437    pub input_count: usize,
438    pub resolved_count: usize,
439    pub guaranteed_invalid_count: usize,
440    pub iteration_count: usize,
441    pub iteration_bound: usize,
442    pub reached_fixed_point: bool,
443    pub monotone_witness_valid: bool,
444    pub proof: CustomPropertyLeastFixedPointProofV0,
445    pub iteration_trace: Vec<CustomPropertyLeastFixedPointIterationV0>,
446    pub entries: Vec<CustomPropertyLeastFixedPointEntryV0>,
447    pub ready_surfaces: Vec<&'static str>,
448}
449
450#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
451#[serde(rename_all = "camelCase")]
452pub struct CustomPropertyLeastFixedPointProofV0 {
453    pub finite_domain: &'static str,
454    pub transfer_function: &'static str,
455    pub monotone_witness: &'static str,
456    pub iteration_bound_formula: &'static str,
457    pub cycle_policy: &'static str,
458    pub proof_obligations: Vec<&'static str>,
459}
460
461#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
462#[serde(rename_all = "camelCase")]
463pub struct CustomPropertyLeastFixedPointIterationV0 {
464    pub iteration: usize,
465    pub changed_count: usize,
466    pub settled_count: usize,
467    pub guaranteed_invalid_count: usize,
468}
469
470#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
471#[serde(rename_all = "camelCase")]
472pub struct CustomPropertyLeastFixedPointEntryV0 {
473    pub name: String,
474    pub input: CascadeValue,
475    pub resolved: CascadeValue,
476    pub changed: bool,
477    pub guaranteed_invalid: bool,
478}
479
480#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
481#[serde(rename_all = "camelCase")]
482pub struct CascadeFuzzSeedReportV0 {
483    pub schema_version: &'static str,
484    pub product: &'static str,
485    pub case_count: usize,
486    pub passed_count: usize,
487    pub failed_count: usize,
488    pub cascade_results: Vec<CascadeEvaluationFuzzResultV0>,
489    pub var_results: Vec<VarSubstitutionFuzzResultV0>,
490}
491
492#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
493#[serde(rename_all = "camelCase")]
494pub struct BoxLonghandInputV0 {
495    pub property: String,
496    pub value: String,
497    pub important: bool,
498    pub source_order: u32,
499}
500
501#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
502#[serde(rename_all = "camelCase")]
503pub struct ShorthandCombinationProofV0 {
504    pub schema_version: &'static str,
505    pub product: &'static str,
506    pub shorthand_property: String,
507    pub accepted: bool,
508    pub blocked_reason: Option<&'static str>,
509    pub ordered_longhand_properties: Vec<String>,
510    pub provenance_preserved: bool,
511    pub cascade_safe_witness: String,
512}
513
514#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
515#[serde(rename_all = "camelCase")]
516pub enum StaticSupportsAssumptionV0 {
517    ModernBrowser,
518}
519
520#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
521#[serde(rename_all = "camelCase")]
522pub enum StaticSupportsEvalVerdictV0 {
523    AlwaysTrue,
524    AlwaysFalse,
525    Unknown,
526}
527
528#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
529#[serde(rename_all = "camelCase")]
530pub struct StaticSupportsEvalWitnessV0 {
531    pub schema_version: &'static str,
532    pub product: &'static str,
533    pub condition: String,
534    pub assumption: StaticSupportsAssumptionV0,
535    pub verdict: StaticSupportsEvalVerdictV0,
536    pub reason: &'static str,
537    pub provenance_preserved: bool,
538}
539
540#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
541#[serde(rename_all = "camelCase")]
542pub struct ScopeFlattenInputV0 {
543    pub root_selector: String,
544    pub limit_selector: Option<String>,
545    pub scoped_rule_count: usize,
546    pub peer_scope_count: usize,
547    pub competing_unscoped_rule_count: usize,
548    pub inside_layer: bool,
549}
550
551#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
552#[serde(rename_all = "camelCase")]
553pub struct ScopeFlattenProofV0 {
554    pub schema_version: &'static str,
555    pub product: &'static str,
556    pub accepted: bool,
557    pub blocked_reason: Option<&'static str>,
558    pub root_selector: String,
559    pub provenance_preserved: bool,
560    pub cascade_safe_witness: String,
561}
562
563#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
564#[serde(rename_all = "camelCase")]
565pub struct LayerFlattenInputV0 {
566    pub layer_name: Option<String>,
567    pub layer_rule_count: usize,
568    pub peer_layer_count: usize,
569    pub unlayered_rule_count: usize,
570    pub important_declaration_count: usize,
571    pub closed_bundle: bool,
572}
573
574#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
575#[serde(rename_all = "camelCase")]
576pub struct LayerFlattenProofV0 {
577    pub schema_version: &'static str,
578    pub product: &'static str,
579    pub accepted: bool,
580    pub blocked_reason: Option<&'static str>,
581    pub layer_name: Option<String>,
582    pub provenance_preserved: bool,
583    pub cascade_safe_witness: String,
584}
585
586#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
587#[serde(tag = "witnessKind", content = "witness", rename_all = "camelCase")]
588pub enum ModalCheckWitnessSourceV0 {
589    ShorthandCombination(ShorthandCombinationProofV0),
590    StaticSupportsEval(StaticSupportsEvalWitnessV0),
591    ScopeFlatten(ScopeFlattenProofV0),
592    LayerFlatten(LayerFlattenProofV0),
593}
594
595#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
596#[serde(rename_all = "camelCase")]
597/// V0 freeze-candidate witness aggregation over existing cascade proof outputs.
598///
599/// This is a staged strict-superset surface for release evidence. It does not
600/// claim a completed modal theorem, paper-grade proof system, or Cargo 1.0 API.
601pub struct ModalCheckWitnessV0 {
602    pub schema_version: &'static str,
603    pub product: &'static str,
604    pub modal_family: &'static str,
605    pub substrate: &'static str,
606    pub obligation_count: usize,
607    pub accepted_count: usize,
608    pub blocked_count: usize,
609    pub all_provenance_preserved: bool,
610    pub source_products: Vec<&'static str>,
611    pub witnesses: Vec<ModalCheckWitnessSourceV0>,
612}
613
614#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
615#[serde(rename_all = "camelCase")]
616pub struct CascadeMarginSchemaV0 {
617    pub schema_version: &'static str,
618    pub product: &'static str,
619    pub margin_kind: &'static str,
620    pub axis_order: Vec<&'static str>,
621    pub calibration_stage: &'static str,
622    pub public_safety_claim_ready: bool,
623}
624
625#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
626#[serde(rename_all = "camelCase")]
627pub struct CascadeMarginV0 {
628    pub schema_version: &'static str,
629    pub product: &'static str,
630    pub margin_kind: &'static str,
631    pub winner_declaration_id: String,
632    pub challenger_declaration_id: Option<String>,
633    pub dominant_axis: &'static str,
634    pub signed_distance: i64,
635    pub winner_key: CascadeKey,
636    pub challenger_key: Option<CascadeKey>,
637    pub calibration_stage: &'static str,
638    pub public_safety_claim_ready: bool,
639}
640
641pub type CustomPropertyEnv = BTreeMap<String, CascadeValue>;