Skip to main content

omena_refinement/
lib.rs

1//! Refinement type system contracts for cascade analysis.
2//!
3//! The crate keeps legacy abstract property values wire-compatible by adding a
4//! strict-superset wrapper and delegating cascade checks to the byte-stable
5//! `omena-cascade` proof primitives.
6//!
7//! claim_level: m6DimensionalRefinementBridgeSubstrate, not Liquid-Haskell
8//! inference or SMT completeness.
9
10use std::{collections::BTreeSet, marker::PhantomData};
11
12use omena_abstract_value::{AbstractPropertyValueV0, CascadeValueFamilyV0};
13use omena_cascade::{
14    CascadeDeclaration, CascadeRefinementContextV0,
15    refine_declaration_in_context as refine_cascade_declaration_in_context,
16};
17use omena_refinement_trait::{
18    PropertyIndexV0, REFINEMENT_FEATURE_GATE_V0, REFINEMENT_LAYER_MARKER_V0,
19    REFINEMENT_SCHEMA_VERSION_V0, RefinementPredicateV0, RefinementVerdictV0, RefinementWitnessV0,
20    refinement_provenance_v0, refinement_witness_v0,
21};
22use serde::Serialize;
23
24pub const REFINEMENT_BRIDGE_CLAIM_LEVEL_V0: &str = "m6DimensionalRefinementBridgeSubstrate";
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
27#[serde(rename_all = "camelCase")]
28pub enum AbstractValueShapeV0 {
29    Bottom,
30    Exact,
31    FiniteSet,
32    CustomPropertyReference,
33    Top,
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
37#[serde(rename_all = "camelCase")]
38pub struct TopPredicateV0 {
39    pub schema_version: &'static str,
40    pub product: &'static str,
41    pub layer_marker: &'static str,
42    pub feature_gate: &'static str,
43}
44
45impl Default for TopPredicateV0 {
46    fn default() -> Self {
47        Self {
48            schema_version: REFINEMENT_SCHEMA_VERSION_V0,
49            product: "omena-refinement.top-predicate",
50            layer_marker: REFINEMENT_LAYER_MARKER_V0,
51            feature_gate: REFINEMENT_FEATURE_GATE_V0,
52        }
53    }
54}
55
56impl RefinementPredicateV0 for TopPredicateV0 {
57    const PREDICATE_ID: &'static str = "top";
58}
59
60#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
61#[serde(rename_all = "camelCase")]
62pub struct AnyPropertyIndexV0 {
63    pub schema_version: &'static str,
64    pub product: &'static str,
65    pub layer_marker: &'static str,
66    pub feature_gate: &'static str,
67}
68
69impl Default for AnyPropertyIndexV0 {
70    fn default() -> Self {
71        Self {
72            schema_version: REFINEMENT_SCHEMA_VERSION_V0,
73            product: "omena-refinement.any-property-index",
74            layer_marker: REFINEMENT_LAYER_MARKER_V0,
75            feature_gate: REFINEMENT_FEATURE_GATE_V0,
76        }
77    }
78}
79
80impl PropertyIndexV0 for AnyPropertyIndexV0 {
81    const PROPERTY_NAME: &'static str = "*";
82}
83
84#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
85#[serde(rename_all = "camelCase", bound = "")]
86pub struct RefinedAbstractPropertyValueV0<P: PropertyIndexV0, R: RefinementPredicateV0> {
87    pub schema_version: &'static str,
88    pub product: &'static str,
89    pub layer_marker: &'static str,
90    pub feature_gate: &'static str,
91    pub property_name: &'static str,
92    pub predicate_id: &'static str,
93    pub value_shape: AbstractValueShapeV0,
94    pub legacy_value: AbstractPropertyValueV0,
95    pub strict_superset_of_legacy_v0: bool,
96    #[serde(skip)]
97    marker: PhantomData<(P, R)>,
98}
99
100#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
101#[serde(rename_all = "camelCase")]
102pub enum RefinementPropertyPredicateV0 {
103    Any,
104    ExactValue {
105        property_name: String,
106        value: String,
107    },
108    OneOfValues {
109        property_name: String,
110        values: Vec<String>,
111    },
112    CustomPropertyReference {
113        property_name: String,
114        custom_property_name: String,
115    },
116    NumericRange {
117        property_name: String,
118        min_inclusive: Option<i64>,
119        max_inclusive: Option<i64>,
120        unit: Option<String>,
121    },
122    HasPseudoState {
123        property_name: String,
124        pseudo_state: String,
125    },
126    And {
127        predicates: Vec<RefinementPropertyPredicateV0>,
128    },
129    Or {
130        predicates: Vec<RefinementPropertyPredicateV0>,
131    },
132    Not {
133        predicate: Box<RefinementPropertyPredicateV0>,
134    },
135}
136
137#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
138#[serde(rename_all = "camelCase")]
139pub struct RefinementPredicateEvaluationV0 {
140    pub schema_version: &'static str,
141    pub product: &'static str,
142    pub layer_marker: &'static str,
143    pub feature_gate: &'static str,
144    pub predicate_expression_id: String,
145    pub value_shape: AbstractValueShapeV0,
146    pub verdict: RefinementVerdictV0,
147    pub matched_clause_count: usize,
148    pub witness: RefinementWitnessV0,
149}
150
151#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
152#[serde(rename_all = "camelCase")]
153pub struct RefinementContextSummaryV0 {
154    pub schema_version: &'static str,
155    pub product: &'static str,
156    pub layer_marker: &'static str,
157    pub feature_gate: &'static str,
158    pub predicate_count: usize,
159    pub context_digest: u64,
160    pub witness_provenance_count: usize,
161    pub downstream_invalidation_required: bool,
162}
163
164/// Bridge between context-indexed property values and refinement facts.
165///
166/// This is a research-staged substrate: it evaluates the existing cascade
167/// family through the existing refinement predicate evaluator. It does not
168/// claim Liquid-Haskell-style inference, SMT completeness, or a theorem.
169#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
170#[serde(rename_all = "camelCase")]
171pub struct CascadeDimensionalRefinementBridgeV0 {
172    pub schema_version: &'static str,
173    pub product: &'static str,
174    pub layer_marker: &'static str,
175    pub feature_gate: &'static str,
176    pub claim_level: &'static str,
177    pub property_name: String,
178    pub cascade_family_product: &'static str,
179    pub predicate_count: usize,
180    pub context_value_count: usize,
181    pub restriction_map_count: usize,
182    pub context_evaluation_count: usize,
183    pub satisfied_all_context_count: usize,
184    pub satisfied_some_context_count: usize,
185    pub unknown_context_count: usize,
186    pub unsatisfiable_context_count: usize,
187    pub witness_provenance_count: usize,
188    pub property_consistent: bool,
189    pub uses_existing_abstract_property_value_substrate: bool,
190    pub uses_existing_cascade_family_substrate: bool,
191    pub uses_existing_refinement_predicate_substrate: bool,
192    pub forks_unit_system: bool,
193    pub liquid_haskell_complete: bool,
194    pub smt_backend_available: bool,
195    pub smt_complete: bool,
196    pub theorem_claimed: bool,
197    pub product_path_evidence_ready: bool,
198    pub stronger_type_safety_claim_ready: bool,
199    pub dimension_vector_domain_ready: bool,
200    pub calc_dimension_diagnostics_ready: bool,
201    pub dimension_vector_domain: DimensionVectorDomainSummaryV0,
202    pub calc_dimension_diagnostics: CalcDimensionDiagnosticSummaryV0,
203    pub evaluations: Vec<CascadeDimensionalRefinementContextEvaluationV0>,
204}
205
206#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize)]
207#[serde(rename_all = "camelCase")]
208pub struct DimensionVectorV0 {
209    pub length: i8,
210    pub angle: i8,
211    pub time: i8,
212    pub percentage: i8,
213    pub number: i8,
214}
215
216#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
217#[serde(rename_all = "camelCase")]
218pub struct DimensionVectorValueV0 {
219    pub source_value: String,
220    pub unit: String,
221    pub unit_family: &'static str,
222    pub vector: DimensionVectorV0,
223}
224
225#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
226#[serde(rename_all = "camelCase")]
227pub struct DimensionVectorDomainSummaryV0 {
228    pub schema_version: &'static str,
229    pub product: &'static str,
230    pub feature_gate: &'static str,
231    pub claim_level: &'static str,
232    pub theorem_claimed: bool,
233    pub forks_unit_system: bool,
234    pub value_count: usize,
235    pub vector_count: usize,
236    pub values: Vec<DimensionVectorValueV0>,
237}
238
239#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
240#[serde(rename_all = "camelCase")]
241pub enum CalcDimensionDiagnosticKindV0 {
242    DimensionalMismatch,
243    MixedDimension,
244    ContextDependentDimension,
245}
246
247#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
248#[serde(rename_all = "camelCase")]
249pub struct CalcDimensionDiagnosticV0 {
250    pub schema_version: &'static str,
251    pub product: &'static str,
252    pub feature_gate: &'static str,
253    pub claim_level: &'static str,
254    pub theorem_claimed: bool,
255    pub public_safety_claim_ready: bool,
256    pub kind: CalcDimensionDiagnosticKindV0,
257    pub property_name: String,
258    pub expression: String,
259    pub observed_units: Vec<String>,
260    pub observed_vectors: Vec<DimensionVectorV0>,
261}
262
263#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
264#[serde(rename_all = "camelCase")]
265pub struct CalcDimensionDiagnosticSummaryV0 {
266    pub schema_version: &'static str,
267    pub product: &'static str,
268    pub feature_gate: &'static str,
269    pub claim_level: &'static str,
270    pub theorem_claimed: bool,
271    pub forks_unit_system: bool,
272    pub smt_complete: bool,
273    pub liquid_haskell_complete: bool,
274    pub stronger_type_safety_claim_ready: bool,
275    pub diagnostic_count: usize,
276    pub diagnostics: Vec<CalcDimensionDiagnosticV0>,
277}
278
279#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
280#[serde(rename_all = "camelCase")]
281pub struct CascadeDimensionalRefinementContextEvaluationV0 {
282    pub schema_version: &'static str,
283    pub product: &'static str,
284    pub layer_marker: &'static str,
285    pub feature_gate: &'static str,
286    pub context_id: String,
287    pub selector_count: usize,
288    pub condition_count: usize,
289    pub layer_count: usize,
290    pub value_shape: AbstractValueShapeV0,
291    pub combined_verdict: RefinementVerdictV0,
292    pub predicate_evaluation_count: usize,
293    pub matched_clause_count: usize,
294    pub witness_provenance_count: usize,
295    pub predicate_expression_ids: Vec<String>,
296}
297
298pub fn project_legacy_to_refined_v0<P, R>(
299    legacy_value: AbstractPropertyValueV0,
300) -> RefinedAbstractPropertyValueV0<P, R>
301where
302    P: PropertyIndexV0,
303    R: RefinementPredicateV0,
304{
305    let mut refined = RefinedAbstractPropertyValueV0 {
306        schema_version: REFINEMENT_SCHEMA_VERSION_V0,
307        product: "omena-refinement.refined-abstract-property-value",
308        layer_marker: REFINEMENT_LAYER_MARKER_V0,
309        feature_gate: REFINEMENT_FEATURE_GATE_V0,
310        property_name: P::PROPERTY_NAME,
311        predicate_id: R::PREDICATE_ID,
312        value_shape: abstract_property_value_shape_v0(&legacy_value),
313        legacy_value,
314        strict_superset_of_legacy_v0: false,
315        marker: PhantomData,
316    };
317    refined.strict_superset_of_legacy_v0 =
318        refined_projection_preserves_legacy_value_v0::<P, R>(&refined);
319    refined
320}
321
322pub fn project_refined_to_legacy_v0<P, R>(
323    refined: &RefinedAbstractPropertyValueV0<P, R>,
324) -> AbstractPropertyValueV0
325where
326    P: PropertyIndexV0,
327    R: RefinementPredicateV0,
328{
329    refined.legacy_value.clone()
330}
331
332pub fn refined_projection_preserves_legacy_value_v0<P, R>(
333    refined: &RefinedAbstractPropertyValueV0<P, R>,
334) -> bool
335where
336    P: PropertyIndexV0,
337    R: RefinementPredicateV0,
338{
339    refined.schema_version == REFINEMENT_SCHEMA_VERSION_V0
340        && refined.layer_marker == REFINEMENT_LAYER_MARKER_V0
341        && refined.feature_gate == REFINEMENT_FEATURE_GATE_V0
342        && refined.property_name == P::PROPERTY_NAME
343        && refined.predicate_id == R::PREDICATE_ID
344        && abstract_property_value_shape_v0(&project_refined_to_legacy_v0(refined))
345            == refined.value_shape
346}
347
348pub fn evaluate_refinement_property_predicate_v0(
349    predicate: &RefinementPropertyPredicateV0,
350    value: &AbstractPropertyValueV0,
351) -> RefinementPredicateEvaluationV0 {
352    let verdict = evaluate_refinement_predicate_verdict_v0(predicate, value);
353    let matched_clause_count = count_satisfied_refinement_clauses_v0(predicate, value);
354    let predicate_expression_id = refinement_predicate_expression_id_v0(predicate);
355    let witness = refinement_witness_v0(
356        "property-grammar",
357        verdict,
358        refinement_predicate_provenance_v0(predicate),
359    );
360
361    RefinementPredicateEvaluationV0 {
362        schema_version: REFINEMENT_SCHEMA_VERSION_V0,
363        product: "omena-refinement.property-predicate-evaluation",
364        layer_marker: REFINEMENT_LAYER_MARKER_V0,
365        feature_gate: REFINEMENT_FEATURE_GATE_V0,
366        predicate_expression_id,
367        value_shape: abstract_property_value_shape_v0(value),
368        verdict,
369        matched_clause_count,
370        witness,
371    }
372}
373
374pub fn refine_declaration_in_context(
375    declaration: &CascadeDeclaration,
376    context: &CascadeRefinementContextV0,
377) -> RefinementWitnessV0 {
378    refine_cascade_declaration_in_context(declaration, context)
379}
380
381pub fn summarize_refinement_context_v0(
382    predicates: &[RefinementPropertyPredicateV0],
383) -> RefinementContextSummaryV0 {
384    let mut expression_ids = predicates
385        .iter()
386        .map(refinement_predicate_expression_id_v0)
387        .collect::<Vec<_>>();
388    expression_ids.sort();
389
390    let witness_provenance_count = predicates
391        .iter()
392        .flat_map(refinement_predicate_provenance_v0)
393        .map(|provenance| provenance.source)
394        .collect::<std::collections::BTreeSet<_>>()
395        .len();
396    let context_digest = deterministic_refinement_digest_v0(expression_ids.join("\n").as_bytes());
397
398    RefinementContextSummaryV0 {
399        schema_version: REFINEMENT_SCHEMA_VERSION_V0,
400        product: "omena-refinement.context-summary",
401        layer_marker: REFINEMENT_LAYER_MARKER_V0,
402        feature_gate: REFINEMENT_FEATURE_GATE_V0,
403        predicate_count: predicates.len(),
404        context_digest,
405        witness_provenance_count,
406        downstream_invalidation_required: !predicates.is_empty(),
407    }
408}
409
410pub fn summarize_dimension_vector_domain_v0(values: &[String]) -> DimensionVectorDomainSummaryV0 {
411    let mut entries = values
412        .iter()
413        .flat_map(|value| {
414            extract_calc_expression_v0(value)
415                .map(parse_dimension_vector_values_from_expression_v0)
416                .unwrap_or_else(|| parse_dimension_vector_value_v0(value).into_iter().collect())
417        })
418        .collect::<Vec<_>>();
419    entries.sort_by(|left, right| {
420        left.source_value
421            .cmp(&right.source_value)
422            .then_with(|| left.unit.cmp(&right.unit))
423    });
424    entries.dedup_by(|left, right| {
425        left.source_value == right.source_value
426            && left.unit == right.unit
427            && left.unit_family == right.unit_family
428            && left.vector == right.vector
429    });
430    let vector_count = entries
431        .iter()
432        .map(|entry| entry.vector)
433        .collect::<BTreeSet<_>>()
434        .len();
435
436    DimensionVectorDomainSummaryV0 {
437        schema_version: REFINEMENT_SCHEMA_VERSION_V0,
438        product: "omena-refinement.dimension-vector-domain",
439        feature_gate: "dimension-vector-domain-v0",
440        claim_level: "fixtureWitnessDimensionVectorDomain",
441        theorem_claimed: false,
442        forks_unit_system: false,
443        value_count: entries.len(),
444        vector_count,
445        values: entries,
446    }
447}
448
449pub fn summarize_calc_dimension_diagnostics_v0(
450    property_name: &str,
451    values: &[String],
452) -> CalcDimensionDiagnosticSummaryV0 {
453    let mut diagnostics = values
454        .iter()
455        .filter_map(|value| calc_dimension_diagnostic_for_value_v0(property_name, value))
456        .collect::<Vec<_>>();
457    diagnostics.sort_by(|left, right| {
458        left.property_name
459            .cmp(&right.property_name)
460            .then_with(|| left.expression.cmp(&right.expression))
461    });
462
463    CalcDimensionDiagnosticSummaryV0 {
464        schema_version: REFINEMENT_SCHEMA_VERSION_V0,
465        product: "omena-refinement.calc-dimension-diagnostics",
466        feature_gate: "calc-dimension-diagnostics-v0",
467        claim_level: "researchGradeHint",
468        theorem_claimed: false,
469        forks_unit_system: false,
470        smt_complete: false,
471        liquid_haskell_complete: false,
472        stronger_type_safety_claim_ready: false,
473        diagnostic_count: diagnostics.len(),
474        diagnostics,
475    }
476}
477
478pub fn summarize_cascade_dimensional_refinement_bridge_v0(
479    family: &CascadeValueFamilyV0,
480    predicates: &[RefinementPropertyPredicateV0],
481) -> CascadeDimensionalRefinementBridgeV0 {
482    let exact_values = family
483        .members
484        .iter()
485        .filter_map(|member| match &member.value {
486            AbstractPropertyValueV0::Exact { value, .. } => Some(value.clone()),
487            _ => None,
488        })
489        .collect::<Vec<_>>();
490    let dimension_vector_domain = summarize_dimension_vector_domain_v0(&exact_values);
491    let calc_dimension_diagnostics =
492        summarize_calc_dimension_diagnostics_v0(&family.property_name, &exact_values);
493    let mut global_provenance_sources = BTreeSet::new();
494    let mut evaluations = family
495        .members
496        .iter()
497        .map(|member| {
498            let predicate_evaluations = predicates
499                .iter()
500                .map(|predicate| {
501                    evaluate_refinement_property_predicate_v0(predicate, &member.value)
502                })
503                .collect::<Vec<_>>();
504            let verdicts = predicate_evaluations
505                .iter()
506                .map(|evaluation| evaluation.verdict)
507                .collect::<Vec<_>>();
508            let combined_verdict = combine_and_refinement_verdicts_v0(&verdicts);
509            let mut context_provenance_sources = BTreeSet::new();
510            for evaluation in &predicate_evaluations {
511                for provenance in &evaluation.witness.provenance {
512                    context_provenance_sources.insert(provenance.source);
513                    global_provenance_sources.insert(provenance.source);
514                }
515            }
516
517            CascadeDimensionalRefinementContextEvaluationV0 {
518                schema_version: REFINEMENT_SCHEMA_VERSION_V0,
519                product: "omena-refinement.cascade-dimensional-refinement-context-evaluation",
520                layer_marker: REFINEMENT_LAYER_MARKER_V0,
521                feature_gate: REFINEMENT_FEATURE_GATE_V0,
522                context_id: member.context.id.clone(),
523                selector_count: member.context.selectors.len(),
524                condition_count: member.context.conditions.len(),
525                layer_count: member.context.layers.len(),
526                value_shape: abstract_property_value_shape_v0(&member.value),
527                combined_verdict,
528                predicate_evaluation_count: predicate_evaluations.len(),
529                matched_clause_count: predicate_evaluations
530                    .iter()
531                    .map(|evaluation| evaluation.matched_clause_count)
532                    .sum(),
533                witness_provenance_count: context_provenance_sources.len(),
534                predicate_expression_ids: predicate_evaluations
535                    .into_iter()
536                    .map(|evaluation| evaluation.predicate_expression_id)
537                    .collect(),
538            }
539        })
540        .collect::<Vec<_>>();
541    evaluations.sort_by(|left, right| left.context_id.cmp(&right.context_id));
542
543    CascadeDimensionalRefinementBridgeV0 {
544        schema_version: REFINEMENT_SCHEMA_VERSION_V0,
545        product: "omena-refinement.cascade-dimensional-refinement-bridge",
546        layer_marker: REFINEMENT_LAYER_MARKER_V0,
547        feature_gate: REFINEMENT_FEATURE_GATE_V0,
548        claim_level: REFINEMENT_BRIDGE_CLAIM_LEVEL_V0,
549        property_name: family.property_name.clone(),
550        cascade_family_product: family.product,
551        predicate_count: predicates.len(),
552        context_value_count: family.context_value_count,
553        restriction_map_count: family.restriction_map_count,
554        context_evaluation_count: evaluations.len(),
555        satisfied_all_context_count: count_context_verdicts_v0(
556            &evaluations,
557            RefinementVerdictV0::SatisfiedAll,
558        ),
559        satisfied_some_context_count: count_context_verdicts_v0(
560            &evaluations,
561            RefinementVerdictV0::SatisfiedSome,
562        ),
563        unknown_context_count: count_context_verdicts_v0(
564            &evaluations,
565            RefinementVerdictV0::Unknown,
566        ),
567        unsatisfiable_context_count: count_context_verdicts_v0(
568            &evaluations,
569            RefinementVerdictV0::Unsatisfiable,
570        ),
571        witness_provenance_count: global_provenance_sources.len(),
572        property_consistent: family.property_consistent,
573        uses_existing_abstract_property_value_substrate: true,
574        uses_existing_cascade_family_substrate: true,
575        uses_existing_refinement_predicate_substrate: true,
576        forks_unit_system: false,
577        liquid_haskell_complete: false,
578        smt_backend_available: refinement_smt_backend_available_v0(),
579        smt_complete: false,
580        theorem_claimed: false,
581        product_path_evidence_ready: true,
582        stronger_type_safety_claim_ready: false,
583        dimension_vector_domain_ready: dimension_vector_domain.value_count > 0
584            && !dimension_vector_domain.forks_unit_system
585            && !dimension_vector_domain.theorem_claimed,
586        calc_dimension_diagnostics_ready: !calc_dimension_diagnostics.forks_unit_system
587            && !calc_dimension_diagnostics.smt_complete
588            && !calc_dimension_diagnostics.liquid_haskell_complete
589            && !calc_dimension_diagnostics.stronger_type_safety_claim_ready
590            && !calc_dimension_diagnostics.theorem_claimed,
591        dimension_vector_domain,
592        calc_dimension_diagnostics,
593        evaluations,
594    }
595}
596
597pub fn abstract_property_value_shape_v0(value: &AbstractPropertyValueV0) -> AbstractValueShapeV0 {
598    match value {
599        AbstractPropertyValueV0::Bottom { .. } => AbstractValueShapeV0::Bottom,
600        AbstractPropertyValueV0::Exact { .. } => AbstractValueShapeV0::Exact,
601        AbstractPropertyValueV0::FiniteSet { .. } => AbstractValueShapeV0::FiniteSet,
602        AbstractPropertyValueV0::CustomPropertyReference { .. } => {
603            AbstractValueShapeV0::CustomPropertyReference
604        }
605        AbstractPropertyValueV0::Top { .. } => AbstractValueShapeV0::Top,
606    }
607}
608
609fn count_context_verdicts_v0(
610    evaluations: &[CascadeDimensionalRefinementContextEvaluationV0],
611    verdict: RefinementVerdictV0,
612) -> usize {
613    evaluations
614        .iter()
615        .filter(|evaluation| evaluation.combined_verdict == verdict)
616        .count()
617}
618
619fn evaluate_refinement_predicate_verdict_v0(
620    predicate: &RefinementPropertyPredicateV0,
621    value: &AbstractPropertyValueV0,
622) -> RefinementVerdictV0 {
623    match predicate {
624        RefinementPropertyPredicateV0::Any => RefinementVerdictV0::SatisfiedAll,
625        RefinementPropertyPredicateV0::ExactValue {
626            property_name,
627            value: expected,
628        } => evaluate_exact_value_predicate_v0(property_name, expected, value),
629        RefinementPropertyPredicateV0::OneOfValues {
630            property_name,
631            values,
632        } => evaluate_one_of_values_predicate_v0(property_name, values, value),
633        RefinementPropertyPredicateV0::CustomPropertyReference {
634            property_name,
635            custom_property_name,
636        } => evaluate_custom_property_reference_predicate_v0(
637            property_name,
638            custom_property_name,
639            value,
640        ),
641        RefinementPropertyPredicateV0::NumericRange {
642            property_name,
643            min_inclusive,
644            max_inclusive,
645            unit,
646        } => evaluate_numeric_range_predicate_v0(
647            property_name,
648            *min_inclusive,
649            *max_inclusive,
650            unit.as_deref(),
651            value,
652        ),
653        RefinementPropertyPredicateV0::HasPseudoState {
654            property_name,
655            pseudo_state,
656        } => evaluate_pseudo_state_predicate_v0(property_name, pseudo_state, value),
657        RefinementPropertyPredicateV0::And { predicates } => combine_and_refinement_verdicts_v0(
658            &predicates
659                .iter()
660                .map(|predicate| evaluate_refinement_predicate_verdict_v0(predicate, value))
661                .collect::<Vec<_>>(),
662        ),
663        RefinementPropertyPredicateV0::Or { predicates } => combine_or_refinement_verdicts_v0(
664            &predicates
665                .iter()
666                .map(|predicate| evaluate_refinement_predicate_verdict_v0(predicate, value))
667                .collect::<Vec<_>>(),
668        ),
669        RefinementPropertyPredicateV0::Not { predicate } => {
670            match evaluate_refinement_predicate_verdict_v0(predicate, value) {
671                RefinementVerdictV0::SatisfiedAll => RefinementVerdictV0::Unsatisfiable,
672                RefinementVerdictV0::Unsatisfiable => RefinementVerdictV0::SatisfiedAll,
673                RefinementVerdictV0::SatisfiedSome | RefinementVerdictV0::Unknown => {
674                    RefinementVerdictV0::Unknown
675                }
676            }
677        }
678    }
679}
680
681fn evaluate_numeric_range_predicate_v0(
682    property_name: &str,
683    min_inclusive: Option<i64>,
684    max_inclusive: Option<i64>,
685    unit: Option<&str>,
686    value: &AbstractPropertyValueV0,
687) -> RefinementVerdictV0 {
688    match value {
689        AbstractPropertyValueV0::Exact {
690            property_name: actual_property,
691            value: actual_value,
692            ..
693        } if actual_property == property_name => {
694            if numeric_range_contains_value_v0(actual_value, min_inclusive, max_inclusive, unit) {
695                RefinementVerdictV0::SatisfiedAll
696            } else {
697                RefinementVerdictV0::Unsatisfiable
698            }
699        }
700        AbstractPropertyValueV0::FiniteSet {
701            property_name: actual_property,
702            values,
703            ..
704        } if actual_property == property_name => {
705            let matched = values
706                .iter()
707                .filter(|candidate| {
708                    numeric_range_contains_value_v0(candidate, min_inclusive, max_inclusive, unit)
709                })
710                .count();
711            if matched == values.len() {
712                RefinementVerdictV0::SatisfiedAll
713            } else if matched > 0 {
714                RefinementVerdictV0::SatisfiedSome
715            } else {
716                RefinementVerdictV0::Unsatisfiable
717            }
718        }
719        AbstractPropertyValueV0::Top {
720            property_name: actual_property,
721        }
722        | AbstractPropertyValueV0::CustomPropertyReference {
723            property_name: actual_property,
724            ..
725        } if actual_property == property_name => RefinementVerdictV0::Unknown,
726        _ => RefinementVerdictV0::Unsatisfiable,
727    }
728}
729
730fn evaluate_pseudo_state_predicate_v0(
731    property_name: &str,
732    expected_pseudo_state: &str,
733    value: &AbstractPropertyValueV0,
734) -> RefinementVerdictV0 {
735    match value {
736        AbstractPropertyValueV0::Exact {
737            property_name: actual_property,
738            pseudo_state,
739            ..
740        }
741        | AbstractPropertyValueV0::CustomPropertyReference {
742            property_name: actual_property,
743            pseudo_state,
744            ..
745        } if actual_property == property_name => {
746            if pseudo_state.as_deref() == Some(expected_pseudo_state) {
747                RefinementVerdictV0::SatisfiedAll
748            } else {
749                RefinementVerdictV0::Unsatisfiable
750            }
751        }
752        AbstractPropertyValueV0::FiniteSet {
753            property_name: actual_property,
754            pseudo_states,
755            ..
756        } if actual_property == property_name => {
757            if pseudo_states.len() == 1
758                && pseudo_states
759                    .iter()
760                    .any(|pseudo_state| pseudo_state == expected_pseudo_state)
761            {
762                RefinementVerdictV0::SatisfiedAll
763            } else if pseudo_states
764                .iter()
765                .any(|pseudo_state| pseudo_state == expected_pseudo_state)
766            {
767                RefinementVerdictV0::SatisfiedSome
768            } else {
769                RefinementVerdictV0::Unsatisfiable
770            }
771        }
772        AbstractPropertyValueV0::Top {
773            property_name: actual_property,
774        } if actual_property == property_name => RefinementVerdictV0::Unknown,
775        _ => RefinementVerdictV0::Unsatisfiable,
776    }
777}
778
779fn evaluate_exact_value_predicate_v0(
780    property_name: &str,
781    expected: &str,
782    value: &AbstractPropertyValueV0,
783) -> RefinementVerdictV0 {
784    match value {
785        AbstractPropertyValueV0::Exact {
786            property_name: actual_property,
787            value: actual_value,
788            ..
789        } if actual_property == property_name && actual_value == expected => {
790            RefinementVerdictV0::SatisfiedAll
791        }
792        AbstractPropertyValueV0::FiniteSet {
793            property_name: actual_property,
794            values,
795            ..
796        } if actual_property == property_name && values.iter().any(|value| value == expected) => {
797            if values.len() == 1 {
798                RefinementVerdictV0::SatisfiedAll
799            } else {
800                RefinementVerdictV0::SatisfiedSome
801            }
802        }
803        AbstractPropertyValueV0::Top {
804            property_name: actual_property,
805        }
806        | AbstractPropertyValueV0::CustomPropertyReference {
807            property_name: actual_property,
808            ..
809        } if actual_property == property_name => RefinementVerdictV0::Unknown,
810        _ => RefinementVerdictV0::Unsatisfiable,
811    }
812}
813
814fn evaluate_one_of_values_predicate_v0(
815    property_name: &str,
816    expected_values: &[String],
817    value: &AbstractPropertyValueV0,
818) -> RefinementVerdictV0 {
819    match value {
820        AbstractPropertyValueV0::Exact {
821            property_name: actual_property,
822            value: actual_value,
823            ..
824        } if actual_property == property_name => {
825            if expected_values.contains(actual_value) {
826                RefinementVerdictV0::SatisfiedAll
827            } else {
828                RefinementVerdictV0::Unsatisfiable
829            }
830        }
831        AbstractPropertyValueV0::FiniteSet {
832            property_name: actual_property,
833            values,
834            ..
835        } if actual_property == property_name => {
836            let matched = values
837                .iter()
838                .filter(|value| expected_values.contains(*value))
839                .count();
840            if matched == values.len() {
841                RefinementVerdictV0::SatisfiedAll
842            } else if matched > 0 {
843                RefinementVerdictV0::SatisfiedSome
844            } else {
845                RefinementVerdictV0::Unsatisfiable
846            }
847        }
848        AbstractPropertyValueV0::Top {
849            property_name: actual_property,
850        }
851        | AbstractPropertyValueV0::CustomPropertyReference {
852            property_name: actual_property,
853            ..
854        } if actual_property == property_name => RefinementVerdictV0::Unknown,
855        _ => RefinementVerdictV0::Unsatisfiable,
856    }
857}
858
859fn evaluate_custom_property_reference_predicate_v0(
860    property_name: &str,
861    expected_custom_property: &str,
862    value: &AbstractPropertyValueV0,
863) -> RefinementVerdictV0 {
864    match value {
865        AbstractPropertyValueV0::CustomPropertyReference {
866            property_name: actual_property,
867            custom_property_name,
868            ..
869        } if actual_property == property_name
870            && custom_property_name == expected_custom_property =>
871        {
872            RefinementVerdictV0::SatisfiedAll
873        }
874        AbstractPropertyValueV0::Top {
875            property_name: actual_property,
876        } if actual_property == property_name => RefinementVerdictV0::Unknown,
877        _ => RefinementVerdictV0::Unsatisfiable,
878    }
879}
880
881fn combine_and_refinement_verdicts_v0(verdicts: &[RefinementVerdictV0]) -> RefinementVerdictV0 {
882    if verdicts.is_empty()
883        || verdicts
884            .iter()
885            .all(|verdict| *verdict == RefinementVerdictV0::SatisfiedAll)
886    {
887        RefinementVerdictV0::SatisfiedAll
888    } else if verdicts.contains(&RefinementVerdictV0::Unsatisfiable) {
889        RefinementVerdictV0::Unsatisfiable
890    } else if verdicts.contains(&RefinementVerdictV0::SatisfiedAll)
891        || verdicts.contains(&RefinementVerdictV0::SatisfiedSome)
892    {
893        RefinementVerdictV0::SatisfiedSome
894    } else {
895        RefinementVerdictV0::Unknown
896    }
897}
898
899fn combine_or_refinement_verdicts_v0(verdicts: &[RefinementVerdictV0]) -> RefinementVerdictV0 {
900    if verdicts.is_empty() || verdicts.contains(&RefinementVerdictV0::SatisfiedAll) {
901        RefinementVerdictV0::SatisfiedAll
902    } else if verdicts.contains(&RefinementVerdictV0::SatisfiedSome) {
903        RefinementVerdictV0::SatisfiedSome
904    } else if verdicts
905        .iter()
906        .all(|verdict| *verdict == RefinementVerdictV0::Unsatisfiable)
907    {
908        RefinementVerdictV0::Unsatisfiable
909    } else {
910        RefinementVerdictV0::Unknown
911    }
912}
913
914fn count_satisfied_refinement_clauses_v0(
915    predicate: &RefinementPropertyPredicateV0,
916    value: &AbstractPropertyValueV0,
917) -> usize {
918    match predicate {
919        RefinementPropertyPredicateV0::And { predicates }
920        | RefinementPropertyPredicateV0::Or { predicates } => predicates
921            .iter()
922            .map(|predicate| count_satisfied_refinement_clauses_v0(predicate, value))
923            .sum(),
924        RefinementPropertyPredicateV0::Not { predicate } => usize::from(matches!(
925            evaluate_refinement_predicate_verdict_v0(predicate, value),
926            RefinementVerdictV0::Unsatisfiable
927        )),
928        _ => usize::from(matches!(
929            evaluate_refinement_predicate_verdict_v0(predicate, value),
930            RefinementVerdictV0::SatisfiedAll | RefinementVerdictV0::SatisfiedSome
931        )),
932    }
933}
934
935fn refinement_predicate_expression_id_v0(predicate: &RefinementPropertyPredicateV0) -> String {
936    match predicate {
937        RefinementPropertyPredicateV0::Any => "any".to_string(),
938        RefinementPropertyPredicateV0::ExactValue {
939            property_name,
940            value,
941        } => format!("exact:{property_name}:{value}"),
942        RefinementPropertyPredicateV0::OneOfValues {
943            property_name,
944            values,
945        } => format!("one-of:{property_name}:{}", values.join("|")),
946        RefinementPropertyPredicateV0::CustomPropertyReference {
947            property_name,
948            custom_property_name,
949        } => format!("custom-ref:{property_name}:{custom_property_name}"),
950        RefinementPropertyPredicateV0::NumericRange {
951            property_name,
952            min_inclusive,
953            max_inclusive,
954            unit,
955        } => format!(
956            "numeric-range:{property_name}:{}..{}:{}",
957            min_inclusive
958                .map(|value| value.to_string())
959                .unwrap_or_else(|| "-inf".to_string()),
960            max_inclusive
961                .map(|value| value.to_string())
962                .unwrap_or_else(|| "inf".to_string()),
963            unit.as_deref().unwrap_or("*")
964        ),
965        RefinementPropertyPredicateV0::HasPseudoState {
966            property_name,
967            pseudo_state,
968        } => format!("pseudo-state:{property_name}:{pseudo_state}"),
969        RefinementPropertyPredicateV0::And { predicates } => format!(
970            "and({})",
971            predicates
972                .iter()
973                .map(refinement_predicate_expression_id_v0)
974                .collect::<Vec<_>>()
975                .join(",")
976        ),
977        RefinementPropertyPredicateV0::Or { predicates } => format!(
978            "or({})",
979            predicates
980                .iter()
981                .map(refinement_predicate_expression_id_v0)
982                .collect::<Vec<_>>()
983                .join(",")
984        ),
985        RefinementPropertyPredicateV0::Not { predicate } => {
986            format!("not({})", refinement_predicate_expression_id_v0(predicate))
987        }
988    }
989}
990
991fn refinement_predicate_provenance_v0(
992    predicate: &RefinementPropertyPredicateV0,
993) -> Vec<omena_refinement_trait::RefinementProvenanceV0> {
994    let mut provenance = Vec::new();
995    collect_refinement_predicate_provenance_v0(predicate, &mut provenance);
996    provenance
997}
998
999fn collect_refinement_predicate_provenance_v0(
1000    predicate: &RefinementPropertyPredicateV0,
1001    provenance: &mut Vec<omena_refinement_trait::RefinementProvenanceV0>,
1002) {
1003    push_refinement_provenance_v0(provenance, "property-grammar", None);
1004    match predicate {
1005        RefinementPropertyPredicateV0::Any => {}
1006        RefinementPropertyPredicateV0::ExactValue { .. }
1007        | RefinementPropertyPredicateV0::OneOfValues { .. } => {
1008            push_refinement_provenance_v0(provenance, "finite-property-domain", None);
1009        }
1010        RefinementPropertyPredicateV0::CustomPropertyReference { .. } => {
1011            push_refinement_provenance_v0(provenance, "custom-property-reference", None);
1012        }
1013        RefinementPropertyPredicateV0::NumericRange { .. } => {
1014            push_refinement_provenance_v0(provenance, "numeric-range-interval", None);
1015        }
1016        RefinementPropertyPredicateV0::HasPseudoState { .. } => {
1017            push_refinement_provenance_v0(provenance, "pseudo-state-refinement", None);
1018        }
1019        RefinementPropertyPredicateV0::And { predicates }
1020        | RefinementPropertyPredicateV0::Or { predicates } => {
1021            push_refinement_provenance_v0(provenance, "predicate-composition", None);
1022            for predicate in predicates {
1023                collect_refinement_predicate_provenance_v0(predicate, provenance);
1024            }
1025        }
1026        RefinementPropertyPredicateV0::Not { predicate } => {
1027            push_refinement_provenance_v0(provenance, "predicate-composition", None);
1028            collect_refinement_predicate_provenance_v0(predicate, provenance);
1029        }
1030    }
1031}
1032
1033fn push_refinement_provenance_v0(
1034    provenance: &mut Vec<omena_refinement_trait::RefinementProvenanceV0>,
1035    source: &'static str,
1036    legacy_proof_primitive: Option<&'static str>,
1037) {
1038    if provenance.iter().any(|entry| {
1039        entry.source == source && entry.legacy_proof_primitive == legacy_proof_primitive
1040    }) {
1041        return;
1042    }
1043    provenance.push(refinement_provenance_v0(source, legacy_proof_primitive));
1044}
1045
1046fn numeric_range_contains_value_v0(
1047    value: &str,
1048    min_inclusive: Option<i64>,
1049    max_inclusive: Option<i64>,
1050    expected_unit: Option<&str>,
1051) -> bool {
1052    let Some((magnitude, unit)) = parse_css_integer_with_unit_v0(value) else {
1053        return false;
1054    };
1055    if let Some(expected_unit) = expected_unit
1056        && unit != expected_unit
1057    {
1058        return false;
1059    }
1060    if let Some(min_inclusive) = min_inclusive
1061        && magnitude < min_inclusive
1062    {
1063        return false;
1064    }
1065    if let Some(max_inclusive) = max_inclusive
1066        && magnitude > max_inclusive
1067    {
1068        return false;
1069    }
1070    true
1071}
1072
1073fn parse_css_integer_with_unit_v0(value: &str) -> Option<(i64, &str)> {
1074    let trimmed = value.trim();
1075    let mut end = 0;
1076    for (index, ch) in trimmed.char_indices() {
1077        if ch.is_ascii_digit() || (index == 0 && (ch == '-' || ch == '+')) {
1078            end = index + ch.len_utf8();
1079        } else {
1080            break;
1081        }
1082    }
1083    if end == 0 || trimmed[..end].ends_with(['-', '+']) {
1084        return None;
1085    }
1086    let magnitude = trimmed[..end].parse::<i64>().ok()?;
1087    Some((magnitude, trimmed[end..].trim()))
1088}
1089
1090fn calc_dimension_diagnostic_for_value_v0(
1091    property_name: &str,
1092    value: &str,
1093) -> Option<CalcDimensionDiagnosticV0> {
1094    let expression = extract_calc_expression_v0(value)?;
1095    let dimension_values = parse_dimension_vector_values_from_expression_v0(expression);
1096    let non_number_values = dimension_values
1097        .iter()
1098        .filter(|entry| entry.vector != dimension_vector_number_v0())
1099        .collect::<Vec<_>>();
1100    if non_number_values.len() < 2 {
1101        return None;
1102    }
1103
1104    let observed_units = non_number_values
1105        .iter()
1106        .map(|entry| entry.unit.clone())
1107        .collect::<BTreeSet<_>>()
1108        .into_iter()
1109        .collect::<Vec<_>>();
1110    let observed_vectors = non_number_values
1111        .iter()
1112        .map(|entry| entry.vector)
1113        .collect::<BTreeSet<_>>()
1114        .into_iter()
1115        .collect::<Vec<_>>();
1116    let unit_families = non_number_values
1117        .iter()
1118        .map(|entry| entry.unit_family)
1119        .collect::<BTreeSet<_>>();
1120    let has_percentage = non_number_values
1121        .iter()
1122        .any(|entry| entry.vector.percentage != 0);
1123    let has_non_percentage = non_number_values
1124        .iter()
1125        .any(|entry| entry.vector.percentage == 0);
1126    let kind = if has_percentage && has_non_percentage {
1127        CalcDimensionDiagnosticKindV0::ContextDependentDimension
1128    } else if observed_vectors.len() > 1 {
1129        CalcDimensionDiagnosticKindV0::DimensionalMismatch
1130    } else if unit_families.len() > 1 {
1131        CalcDimensionDiagnosticKindV0::MixedDimension
1132    } else {
1133        return None;
1134    };
1135
1136    Some(CalcDimensionDiagnosticV0 {
1137        schema_version: REFINEMENT_SCHEMA_VERSION_V0,
1138        product: "omena-refinement.calc-dimension-diagnostic",
1139        feature_gate: "calc-dimension-diagnostics-v0",
1140        claim_level: "researchGradeHint",
1141        theorem_claimed: false,
1142        public_safety_claim_ready: false,
1143        kind,
1144        property_name: property_name.to_string(),
1145        expression: expression.to_string(),
1146        observed_units,
1147        observed_vectors,
1148    })
1149}
1150
1151fn parse_dimension_vector_values_from_expression_v0(
1152    expression: &str,
1153) -> Vec<DimensionVectorValueV0> {
1154    expression
1155        .split(|ch: char| {
1156            ch.is_ascii_whitespace() || matches!(ch, '+' | '-' | '*' | '/' | '(' | ')' | ',')
1157        })
1158        .filter_map(parse_dimension_vector_value_v0)
1159        .collect()
1160}
1161
1162fn parse_dimension_vector_value_v0(value: &str) -> Option<DimensionVectorValueV0> {
1163    let trimmed = value.trim();
1164    if trimmed.is_empty() {
1165        return None;
1166    }
1167    let (unit_start, _) = trimmed
1168        .char_indices()
1169        .find(|(index, ch)| {
1170            *index > 0 && !(ch.is_ascii_digit() || *ch == '.' || *ch == '_' || *ch == '-')
1171        })
1172        .unwrap_or((trimmed.len(), '\0'));
1173    if unit_start == 0 {
1174        return None;
1175    }
1176    let number = trimmed[..unit_start].replace('_', "");
1177    if number.parse::<f64>().is_err() {
1178        return None;
1179    }
1180    let unit = trimmed[unit_start..].trim().to_ascii_lowercase();
1181    let (unit_family, vector) = dimension_vector_for_unit_v0(&unit)?;
1182
1183    Some(DimensionVectorValueV0 {
1184        source_value: trimmed.to_string(),
1185        unit,
1186        unit_family,
1187        vector,
1188    })
1189}
1190
1191fn extract_calc_expression_v0(value: &str) -> Option<&str> {
1192    let start = value.find("calc(")? + "calc(".len();
1193    let rest = &value[start..];
1194    let end = rest.rfind(')')?;
1195    Some(rest[..end].trim())
1196}
1197
1198fn dimension_vector_for_unit_v0(unit: &str) -> Option<(&'static str, DimensionVectorV0)> {
1199    let vector = match unit {
1200        "" => ("number", dimension_vector_number_v0()),
1201        "%" => (
1202            "percentage",
1203            DimensionVectorV0 {
1204                length: 0,
1205                angle: 0,
1206                time: 0,
1207                percentage: 1,
1208                number: 0,
1209            },
1210        ),
1211        "px" | "cm" | "mm" | "q" | "in" | "pt" | "pc" => {
1212            ("absoluteLength", dimension_vector_length_v0())
1213        }
1214        "em" | "rem" | "ex" | "ch" | "ic" | "lh" | "rlh" => {
1215            ("fontRelativeLength", dimension_vector_length_v0())
1216        }
1217        "vw" | "vh" | "vi" | "vb" | "vmin" | "vmax" | "svw" | "svh" | "lvw" | "lvh" | "dvw"
1218        | "dvh" => ("viewportLength", dimension_vector_length_v0()),
1219        "deg" | "grad" | "rad" | "turn" => (
1220            "angle",
1221            DimensionVectorV0 {
1222                length: 0,
1223                angle: 1,
1224                time: 0,
1225                percentage: 0,
1226                number: 0,
1227            },
1228        ),
1229        "s" | "ms" => (
1230            "time",
1231            DimensionVectorV0 {
1232                length: 0,
1233                angle: 0,
1234                time: 1,
1235                percentage: 0,
1236                number: 0,
1237            },
1238        ),
1239        _ => return None,
1240    };
1241    Some(vector)
1242}
1243
1244fn dimension_vector_length_v0() -> DimensionVectorV0 {
1245    DimensionVectorV0 {
1246        length: 1,
1247        angle: 0,
1248        time: 0,
1249        percentage: 0,
1250        number: 0,
1251    }
1252}
1253
1254fn dimension_vector_number_v0() -> DimensionVectorV0 {
1255    DimensionVectorV0 {
1256        length: 0,
1257        angle: 0,
1258        time: 0,
1259        percentage: 0,
1260        number: 1,
1261    }
1262}
1263
1264fn deterministic_refinement_digest_v0(bytes: &[u8]) -> u64 {
1265    bytes.iter().fold(0xcbf29ce484222325, |hash, byte| {
1266        (hash ^ u64::from(*byte)).wrapping_mul(0x100000001b3)
1267    })
1268}
1269
1270#[cfg(feature = "refinement-smt")]
1271pub fn refinement_smt_backend_available_v0() -> bool {
1272    let _ = omena_smt::cascade_theory_signature_v0();
1273    true
1274}
1275
1276#[cfg(not(feature = "refinement-smt"))]
1277pub fn refinement_smt_backend_available_v0() -> bool {
1278    false
1279}
1280
1281#[cfg(test)]
1282mod tests {
1283    use super::*;
1284    use omena_abstract_value::{
1285        CascadeContextV0, CascadeValueFamilyMemberV0, derive_cascade_restriction_maps_v0,
1286        summarize_cascade_value_family_v0,
1287    };
1288
1289    #[test]
1290    fn refined_value_round_trips_to_legacy_without_mutating_v0() {
1291        let top = TopPredicateV0::default();
1292        let any = AnyPropertyIndexV0::default();
1293        assert_eq!(top.schema_version, "0");
1294        assert_eq!(any.layer_marker, "refinement-cascade");
1295
1296        let legacy = AbstractPropertyValueV0::Top {
1297            property_name: "color".to_string(),
1298        };
1299        let refined =
1300            project_legacy_to_refined_v0::<AnyPropertyIndexV0, TopPredicateV0>(legacy.clone());
1301        assert_eq!(refined.schema_version, "0");
1302        assert_eq!(refined.layer_marker, "refinement-cascade");
1303        assert!(refined.strict_superset_of_legacy_v0);
1304        assert!(refined_projection_preserves_legacy_value_v0::<
1305            AnyPropertyIndexV0,
1306            TopPredicateV0,
1307        >(&refined));
1308        assert_eq!(project_refined_to_legacy_v0(&refined), legacy);
1309    }
1310
1311    #[test]
1312    fn refinement_property_grammar_evaluates_exact_and_one_of_values() {
1313        let exact = AbstractPropertyValueV0::Exact {
1314            property_name: "display".to_string(),
1315            value: "grid".to_string(),
1316            pseudo_state: None,
1317        };
1318        let predicate = RefinementPropertyPredicateV0::OneOfValues {
1319            property_name: "display".to_string(),
1320            values: vec!["grid".to_string(), "flex".to_string()],
1321        };
1322        let evaluation = evaluate_refinement_property_predicate_v0(&predicate, &exact);
1323
1324        assert_eq!(evaluation.schema_version, "0");
1325        assert_eq!(
1326            evaluation.product,
1327            "omena-refinement.property-predicate-evaluation"
1328        );
1329        assert_eq!(evaluation.value_shape, AbstractValueShapeV0::Exact);
1330        assert_eq!(evaluation.verdict, RefinementVerdictV0::SatisfiedAll);
1331        assert_eq!(evaluation.matched_clause_count, 1);
1332        assert_eq!(evaluation.witness.predicate_id, "property-grammar");
1333        assert!(evaluation.witness.legacy_proofs_byte_untouched);
1334    }
1335
1336    #[test]
1337    fn refinement_predicate_composition_tracks_partial_and_negative_witnesses() {
1338        let finite = AbstractPropertyValueV0::FiniteSet {
1339            property_name: "color".to_string(),
1340            values: vec!["red".to_string(), "blue".to_string()],
1341            pseudo_states: Vec::new(),
1342        };
1343        let predicate = RefinementPropertyPredicateV0::And {
1344            predicates: vec![
1345                RefinementPropertyPredicateV0::OneOfValues {
1346                    property_name: "color".to_string(),
1347                    values: vec!["red".to_string()],
1348                },
1349                RefinementPropertyPredicateV0::Not {
1350                    predicate: Box::new(RefinementPropertyPredicateV0::ExactValue {
1351                        property_name: "color".to_string(),
1352                        value: "green".to_string(),
1353                    }),
1354                },
1355            ],
1356        };
1357        let evaluation = evaluate_refinement_property_predicate_v0(&predicate, &finite);
1358
1359        assert_eq!(evaluation.value_shape, AbstractValueShapeV0::FiniteSet);
1360        assert_eq!(evaluation.verdict, RefinementVerdictV0::SatisfiedSome);
1361        assert_eq!(evaluation.matched_clause_count, 2);
1362        assert_eq!(
1363            evaluation.predicate_expression_id,
1364            "and(one-of:color:red,not(exact:color:green))"
1365        );
1366        assert_eq!(evaluation.witness.provenance[0].source, "property-grammar");
1367    }
1368
1369    #[test]
1370    fn refinement_custom_property_reference_predicate_is_not_wrapper_only() {
1371        let reference = AbstractPropertyValueV0::CustomPropertyReference {
1372            property_name: "color".to_string(),
1373            custom_property_name: "--brand".to_string(),
1374            pseudo_state: None,
1375        };
1376        let predicate = RefinementPropertyPredicateV0::CustomPropertyReference {
1377            property_name: "color".to_string(),
1378            custom_property_name: "--brand".to_string(),
1379        };
1380        let evaluation = evaluate_refinement_property_predicate_v0(&predicate, &reference);
1381
1382        assert_eq!(evaluation.verdict, RefinementVerdictV0::SatisfiedAll);
1383        assert_eq!(
1384            evaluation.predicate_expression_id,
1385            "custom-ref:color:--brand"
1386        );
1387    }
1388
1389    #[test]
1390    fn refinement_numeric_range_and_pseudo_state_predicates_are_evaluated() {
1391        let finite = AbstractPropertyValueV0::FiniteSet {
1392            property_name: "opacity".to_string(),
1393            values: vec!["0".to_string(), "50%".to_string(), "100%".to_string()],
1394            pseudo_states: vec![":hover".to_string(), ":focus".to_string()],
1395        };
1396        let predicate = RefinementPropertyPredicateV0::And {
1397            predicates: vec![
1398                RefinementPropertyPredicateV0::NumericRange {
1399                    property_name: "opacity".to_string(),
1400                    min_inclusive: Some(0),
1401                    max_inclusive: Some(100),
1402                    unit: Some("%".to_string()),
1403                },
1404                RefinementPropertyPredicateV0::HasPseudoState {
1405                    property_name: "opacity".to_string(),
1406                    pseudo_state: ":hover".to_string(),
1407                },
1408            ],
1409        };
1410        let evaluation = evaluate_refinement_property_predicate_v0(&predicate, &finite);
1411
1412        assert_eq!(evaluation.value_shape, AbstractValueShapeV0::FiniteSet);
1413        assert_eq!(evaluation.verdict, RefinementVerdictV0::SatisfiedSome);
1414        assert_eq!(
1415            evaluation.predicate_expression_id,
1416            "and(numeric-range:opacity:0..100:%,pseudo-state:opacity::hover)"
1417        );
1418        assert!(
1419            evaluation
1420                .witness
1421                .provenance
1422                .iter()
1423                .any(|entry| entry.source == "numeric-range-interval")
1424        );
1425        assert!(
1426            evaluation
1427                .witness
1428                .provenance
1429                .iter()
1430                .any(|entry| entry.source == "pseudo-state-refinement")
1431        );
1432        assert!(
1433            evaluation
1434                .witness
1435                .provenance
1436                .iter()
1437                .any(|entry| entry.source == "predicate-composition")
1438        );
1439    }
1440
1441    #[test]
1442    fn refinement_context_digest_is_order_stable_and_invalidation_sensitive() {
1443        let range = RefinementPropertyPredicateV0::NumericRange {
1444            property_name: "z-index".to_string(),
1445            min_inclusive: Some(0),
1446            max_inclusive: Some(10),
1447            unit: None,
1448        };
1449        let exact = RefinementPropertyPredicateV0::ExactValue {
1450            property_name: "display".to_string(),
1451            value: "grid".to_string(),
1452        };
1453        let first = summarize_refinement_context_v0(&[range.clone(), exact.clone()]);
1454        let reordered = summarize_refinement_context_v0(&[exact.clone(), range.clone()]);
1455        let changed = summarize_refinement_context_v0(&[
1456            exact,
1457            RefinementPropertyPredicateV0::NumericRange {
1458                property_name: "z-index".to_string(),
1459                min_inclusive: Some(0),
1460                max_inclusive: Some(11),
1461                unit: None,
1462            },
1463        ]);
1464
1465        assert_eq!(first.schema_version, "0");
1466        assert_eq!(first.product, "omena-refinement.context-summary");
1467        assert_eq!(first.predicate_count, 2);
1468        assert!(first.downstream_invalidation_required);
1469        assert_eq!(first.context_digest, reordered.context_digest);
1470        assert_ne!(first.context_digest, changed.context_digest);
1471        assert!(first.witness_provenance_count >= 3);
1472    }
1473
1474    #[test]
1475    fn cascade_dimensional_refinement_bridge_reuses_existing_substrates() {
1476        let members = vec![
1477            CascadeValueFamilyMemberV0 {
1478                context: CascadeContextV0 {
1479                    id: "base".to_string(),
1480                    parent_id: None,
1481                    selectors: vec![":root".to_string()],
1482                    conditions: Vec::new(),
1483                    layers: vec!["tokens".to_string()],
1484                },
1485                value: AbstractPropertyValueV0::Exact {
1486                    property_name: "width".to_string(),
1487                    value: "12px".to_string(),
1488                    pseudo_state: None,
1489                },
1490            },
1491            CascadeValueFamilyMemberV0 {
1492                context: CascadeContextV0 {
1493                    id: "fluid".to_string(),
1494                    parent_id: Some("base".to_string()),
1495                    selectors: vec![":root".to_string()],
1496                    conditions: vec!["@media (orientation: portrait)".to_string()],
1497                    layers: vec!["tokens".to_string()],
1498                },
1499                value: AbstractPropertyValueV0::Exact {
1500                    property_name: "width".to_string(),
1501                    value: "50%".to_string(),
1502                    pseudo_state: None,
1503                },
1504            },
1505            CascadeValueFamilyMemberV0 {
1506                context: CascadeContextV0 {
1507                    id: "unknown".to_string(),
1508                    parent_id: Some("base".to_string()),
1509                    selectors: vec![":root".to_string()],
1510                    conditions: vec!["@container card".to_string()],
1511                    layers: vec!["tokens".to_string()],
1512                },
1513                value: AbstractPropertyValueV0::Top {
1514                    property_name: "width".to_string(),
1515                },
1516            },
1517        ];
1518        let restrictions = derive_cascade_restriction_maps_v0(&members);
1519        let family = summarize_cascade_value_family_v0("width", members, restrictions);
1520        let predicate = RefinementPropertyPredicateV0::NumericRange {
1521            property_name: "width".to_string(),
1522            min_inclusive: Some(0),
1523            max_inclusive: Some(100),
1524            unit: Some("px".to_string()),
1525        };
1526
1527        let bridge = summarize_cascade_dimensional_refinement_bridge_v0(&family, &[predicate]);
1528
1529        assert_eq!(
1530            bridge.product,
1531            "omena-refinement.cascade-dimensional-refinement-bridge"
1532        );
1533        assert_eq!(bridge.claim_level, REFINEMENT_BRIDGE_CLAIM_LEVEL_V0);
1534        assert_eq!(bridge.cascade_family_product, family.product);
1535        assert_eq!(bridge.context_evaluation_count, 3);
1536        assert_eq!(bridge.restriction_map_count, 2);
1537        assert_eq!(bridge.satisfied_all_context_count, 1);
1538        assert_eq!(bridge.unsatisfiable_context_count, 1);
1539        assert_eq!(bridge.unknown_context_count, 1);
1540        assert_eq!(bridge.witness_provenance_count, 2);
1541        assert!(bridge.uses_existing_abstract_property_value_substrate);
1542        assert!(bridge.uses_existing_cascade_family_substrate);
1543        assert!(bridge.uses_existing_refinement_predicate_substrate);
1544        assert!(!bridge.forks_unit_system);
1545        assert!(!bridge.liquid_haskell_complete);
1546        assert!(!bridge.smt_complete);
1547        assert!(!bridge.theorem_claimed);
1548        assert!(bridge.product_path_evidence_ready);
1549        assert!(!bridge.stronger_type_safety_claim_ready);
1550        assert!(bridge.dimension_vector_domain_ready);
1551        assert!(bridge.calc_dimension_diagnostics_ready);
1552        assert!(!bridge.dimension_vector_domain.forks_unit_system);
1553        assert!(!bridge.dimension_vector_domain.theorem_claimed);
1554        assert_eq!(bridge.dimension_vector_domain.value_count, 2);
1555        assert_eq!(bridge.calc_dimension_diagnostics.diagnostic_count, 0);
1556        assert!(!bridge.calc_dimension_diagnostics.smt_complete);
1557        assert!(!bridge.calc_dimension_diagnostics.liquid_haskell_complete);
1558        assert!(
1559            !bridge
1560                .calc_dimension_diagnostics
1561                .stronger_type_safety_claim_ready
1562        );
1563        assert_eq!(bridge.evaluations[0].context_id, "base");
1564        assert_eq!(
1565            bridge.evaluations[0].combined_verdict,
1566            RefinementVerdictV0::SatisfiedAll
1567        );
1568        assert_eq!(
1569            bridge.evaluations[0].predicate_expression_ids,
1570            vec!["numeric-range:width:0..100:px".to_string()]
1571        );
1572    }
1573
1574    #[test]
1575    fn calc_dimension_diagnostics_classify_fixture_mismatches_without_unit_fork() {
1576        let values = vec![
1577            "calc(1px + 2s)".to_string(),
1578            "calc(1px + 2rem)".to_string(),
1579            "calc(100% - 1rem)".to_string(),
1580            "calc(1px + 2px)".to_string(),
1581        ];
1582        let domain = summarize_dimension_vector_domain_v0(&values);
1583        let diagnostics = summarize_calc_dimension_diagnostics_v0("width", &values);
1584
1585        assert_eq!(domain.product, "omena-refinement.dimension-vector-domain");
1586        assert_eq!(domain.feature_gate, "dimension-vector-domain-v0");
1587        assert_eq!(domain.claim_level, "fixtureWitnessDimensionVectorDomain");
1588        assert!(!domain.forks_unit_system);
1589        assert!(!domain.theorem_claimed);
1590        assert!(domain.vector_count >= 3);
1591
1592        assert_eq!(
1593            diagnostics.product,
1594            "omena-refinement.calc-dimension-diagnostics"
1595        );
1596        assert_eq!(diagnostics.feature_gate, "calc-dimension-diagnostics-v0");
1597        assert_eq!(diagnostics.claim_level, "researchGradeHint");
1598        assert!(!diagnostics.forks_unit_system);
1599        assert!(!diagnostics.smt_complete);
1600        assert!(!diagnostics.liquid_haskell_complete);
1601        assert!(!diagnostics.stronger_type_safety_claim_ready);
1602        assert!(!diagnostics.theorem_claimed);
1603        assert_eq!(diagnostics.diagnostic_count, 3);
1604        assert!(diagnostics.diagnostics.iter().any(|diagnostic| {
1605            diagnostic.kind == CalcDimensionDiagnosticKindV0::DimensionalMismatch
1606                && diagnostic.expression == "1px + 2s"
1607        }));
1608        assert!(diagnostics.diagnostics.iter().any(|diagnostic| {
1609            diagnostic.kind == CalcDimensionDiagnosticKindV0::MixedDimension
1610                && diagnostic.expression == "1px + 2rem"
1611        }));
1612        assert!(diagnostics.diagnostics.iter().any(|diagnostic| {
1613            diagnostic.kind == CalcDimensionDiagnosticKindV0::ContextDependentDimension
1614                && diagnostic.expression == "100% - 1rem"
1615        }));
1616    }
1617}