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,
1286        derive_context_indexed_cascade_restriction_maps_v0,
1287        summarize_context_indexed_cascade_value_family_v0,
1288    };
1289
1290    #[test]
1291    fn refined_value_round_trips_to_legacy_without_mutating_v0() {
1292        let top = TopPredicateV0::default();
1293        let any = AnyPropertyIndexV0::default();
1294        assert_eq!(top.schema_version, "0");
1295        assert_eq!(any.layer_marker, "refinement-cascade");
1296
1297        let legacy = AbstractPropertyValueV0::Top {
1298            property_name: "color".to_string(),
1299        };
1300        let refined =
1301            project_legacy_to_refined_v0::<AnyPropertyIndexV0, TopPredicateV0>(legacy.clone());
1302        assert_eq!(refined.schema_version, "0");
1303        assert_eq!(refined.layer_marker, "refinement-cascade");
1304        assert!(refined.strict_superset_of_legacy_v0);
1305        assert!(refined_projection_preserves_legacy_value_v0::<
1306            AnyPropertyIndexV0,
1307            TopPredicateV0,
1308        >(&refined));
1309        assert_eq!(project_refined_to_legacy_v0(&refined), legacy);
1310    }
1311
1312    #[test]
1313    fn refinement_property_grammar_evaluates_exact_and_one_of_values() {
1314        let exact = AbstractPropertyValueV0::Exact {
1315            property_name: "display".to_string(),
1316            value: "grid".to_string(),
1317            pseudo_state: None,
1318        };
1319        let predicate = RefinementPropertyPredicateV0::OneOfValues {
1320            property_name: "display".to_string(),
1321            values: vec!["grid".to_string(), "flex".to_string()],
1322        };
1323        let evaluation = evaluate_refinement_property_predicate_v0(&predicate, &exact);
1324
1325        assert_eq!(evaluation.schema_version, "0");
1326        assert_eq!(
1327            evaluation.product,
1328            "omena-refinement.property-predicate-evaluation"
1329        );
1330        assert_eq!(evaluation.value_shape, AbstractValueShapeV0::Exact);
1331        assert_eq!(evaluation.verdict, RefinementVerdictV0::SatisfiedAll);
1332        assert_eq!(evaluation.matched_clause_count, 1);
1333        assert_eq!(evaluation.witness.predicate_id, "property-grammar");
1334        assert!(evaluation.witness.legacy_proofs_byte_untouched);
1335    }
1336
1337    #[test]
1338    fn refinement_predicate_composition_tracks_partial_and_negative_witnesses() {
1339        let finite = AbstractPropertyValueV0::FiniteSet {
1340            property_name: "color".to_string(),
1341            values: vec!["red".to_string(), "blue".to_string()],
1342            pseudo_states: Vec::new(),
1343        };
1344        let predicate = RefinementPropertyPredicateV0::And {
1345            predicates: vec![
1346                RefinementPropertyPredicateV0::OneOfValues {
1347                    property_name: "color".to_string(),
1348                    values: vec!["red".to_string()],
1349                },
1350                RefinementPropertyPredicateV0::Not {
1351                    predicate: Box::new(RefinementPropertyPredicateV0::ExactValue {
1352                        property_name: "color".to_string(),
1353                        value: "green".to_string(),
1354                    }),
1355                },
1356            ],
1357        };
1358        let evaluation = evaluate_refinement_property_predicate_v0(&predicate, &finite);
1359
1360        assert_eq!(evaluation.value_shape, AbstractValueShapeV0::FiniteSet);
1361        assert_eq!(evaluation.verdict, RefinementVerdictV0::SatisfiedSome);
1362        assert_eq!(evaluation.matched_clause_count, 2);
1363        assert_eq!(
1364            evaluation.predicate_expression_id,
1365            "and(one-of:color:red,not(exact:color:green))"
1366        );
1367        assert_eq!(evaluation.witness.provenance[0].source, "property-grammar");
1368    }
1369
1370    #[test]
1371    fn refinement_custom_property_reference_predicate_is_not_wrapper_only() {
1372        let reference = AbstractPropertyValueV0::CustomPropertyReference {
1373            property_name: "color".to_string(),
1374            custom_property_name: "--brand".to_string(),
1375            pseudo_state: None,
1376        };
1377        let predicate = RefinementPropertyPredicateV0::CustomPropertyReference {
1378            property_name: "color".to_string(),
1379            custom_property_name: "--brand".to_string(),
1380        };
1381        let evaluation = evaluate_refinement_property_predicate_v0(&predicate, &reference);
1382
1383        assert_eq!(evaluation.verdict, RefinementVerdictV0::SatisfiedAll);
1384        assert_eq!(
1385            evaluation.predicate_expression_id,
1386            "custom-ref:color:--brand"
1387        );
1388    }
1389
1390    #[test]
1391    fn refinement_numeric_range_and_pseudo_state_predicates_are_evaluated() {
1392        let finite = AbstractPropertyValueV0::FiniteSet {
1393            property_name: "opacity".to_string(),
1394            values: vec!["0".to_string(), "50%".to_string(), "100%".to_string()],
1395            pseudo_states: vec![":hover".to_string(), ":focus".to_string()],
1396        };
1397        let predicate = RefinementPropertyPredicateV0::And {
1398            predicates: vec![
1399                RefinementPropertyPredicateV0::NumericRange {
1400                    property_name: "opacity".to_string(),
1401                    min_inclusive: Some(0),
1402                    max_inclusive: Some(100),
1403                    unit: Some("%".to_string()),
1404                },
1405                RefinementPropertyPredicateV0::HasPseudoState {
1406                    property_name: "opacity".to_string(),
1407                    pseudo_state: ":hover".to_string(),
1408                },
1409            ],
1410        };
1411        let evaluation = evaluate_refinement_property_predicate_v0(&predicate, &finite);
1412
1413        assert_eq!(evaluation.value_shape, AbstractValueShapeV0::FiniteSet);
1414        assert_eq!(evaluation.verdict, RefinementVerdictV0::SatisfiedSome);
1415        assert_eq!(
1416            evaluation.predicate_expression_id,
1417            "and(numeric-range:opacity:0..100:%,pseudo-state:opacity::hover)"
1418        );
1419        assert!(
1420            evaluation
1421                .witness
1422                .provenance
1423                .iter()
1424                .any(|entry| entry.source == "numeric-range-interval")
1425        );
1426        assert!(
1427            evaluation
1428                .witness
1429                .provenance
1430                .iter()
1431                .any(|entry| entry.source == "pseudo-state-refinement")
1432        );
1433        assert!(
1434            evaluation
1435                .witness
1436                .provenance
1437                .iter()
1438                .any(|entry| entry.source == "predicate-composition")
1439        );
1440    }
1441
1442    #[test]
1443    fn refinement_context_digest_is_order_stable_and_invalidation_sensitive() {
1444        let range = RefinementPropertyPredicateV0::NumericRange {
1445            property_name: "z-index".to_string(),
1446            min_inclusive: Some(0),
1447            max_inclusive: Some(10),
1448            unit: None,
1449        };
1450        let exact = RefinementPropertyPredicateV0::ExactValue {
1451            property_name: "display".to_string(),
1452            value: "grid".to_string(),
1453        };
1454        let first = summarize_refinement_context_v0(&[range.clone(), exact.clone()]);
1455        let reordered = summarize_refinement_context_v0(&[exact.clone(), range.clone()]);
1456        let changed = summarize_refinement_context_v0(&[
1457            exact,
1458            RefinementPropertyPredicateV0::NumericRange {
1459                property_name: "z-index".to_string(),
1460                min_inclusive: Some(0),
1461                max_inclusive: Some(11),
1462                unit: None,
1463            },
1464        ]);
1465
1466        assert_eq!(first.schema_version, "0");
1467        assert_eq!(first.product, "omena-refinement.context-summary");
1468        assert_eq!(first.predicate_count, 2);
1469        assert!(first.downstream_invalidation_required);
1470        assert_eq!(first.context_digest, reordered.context_digest);
1471        assert_ne!(first.context_digest, changed.context_digest);
1472        assert!(first.witness_provenance_count >= 3);
1473    }
1474
1475    #[test]
1476    fn cascade_dimensional_refinement_bridge_reuses_existing_substrates() {
1477        let members = vec![
1478            CascadeValueFamilyMemberV0 {
1479                context: CascadeContextV0 {
1480                    id: "base".to_string(),
1481                    parent_id: None,
1482                    selectors: vec![":root".to_string()],
1483                    conditions: Vec::new(),
1484                    layers: vec!["tokens".to_string()],
1485                },
1486                value: AbstractPropertyValueV0::Exact {
1487                    property_name: "width".to_string(),
1488                    value: "12px".to_string(),
1489                    pseudo_state: None,
1490                },
1491            },
1492            CascadeValueFamilyMemberV0 {
1493                context: CascadeContextV0 {
1494                    id: "fluid".to_string(),
1495                    parent_id: Some("base".to_string()),
1496                    selectors: vec![":root".to_string()],
1497                    conditions: vec!["@media (orientation: portrait)".to_string()],
1498                    layers: vec!["tokens".to_string()],
1499                },
1500                value: AbstractPropertyValueV0::Exact {
1501                    property_name: "width".to_string(),
1502                    value: "50%".to_string(),
1503                    pseudo_state: None,
1504                },
1505            },
1506            CascadeValueFamilyMemberV0 {
1507                context: CascadeContextV0 {
1508                    id: "unknown".to_string(),
1509                    parent_id: Some("base".to_string()),
1510                    selectors: vec![":root".to_string()],
1511                    conditions: vec!["@container card".to_string()],
1512                    layers: vec!["tokens".to_string()],
1513                },
1514                value: AbstractPropertyValueV0::Top {
1515                    property_name: "width".to_string(),
1516                },
1517            },
1518        ];
1519        let restrictions = derive_context_indexed_cascade_restriction_maps_v0(&members);
1520        let family =
1521            summarize_context_indexed_cascade_value_family_v0("width", members, restrictions);
1522        let predicate = RefinementPropertyPredicateV0::NumericRange {
1523            property_name: "width".to_string(),
1524            min_inclusive: Some(0),
1525            max_inclusive: Some(100),
1526            unit: Some("px".to_string()),
1527        };
1528
1529        let bridge = summarize_cascade_dimensional_refinement_bridge_v0(&family, &[predicate]);
1530
1531        assert_eq!(
1532            bridge.product,
1533            "omena-refinement.cascade-dimensional-refinement-bridge"
1534        );
1535        assert_eq!(bridge.claim_level, REFINEMENT_BRIDGE_CLAIM_LEVEL_V0);
1536        assert_eq!(bridge.cascade_family_product, family.product);
1537        assert_eq!(bridge.context_evaluation_count, 3);
1538        assert_eq!(bridge.restriction_map_count, 2);
1539        assert_eq!(bridge.satisfied_all_context_count, 1);
1540        assert_eq!(bridge.unsatisfiable_context_count, 1);
1541        assert_eq!(bridge.unknown_context_count, 1);
1542        assert_eq!(bridge.witness_provenance_count, 2);
1543        assert!(bridge.uses_existing_abstract_property_value_substrate);
1544        assert!(bridge.uses_existing_cascade_family_substrate);
1545        assert!(bridge.uses_existing_refinement_predicate_substrate);
1546        assert!(!bridge.forks_unit_system);
1547        assert!(!bridge.liquid_haskell_complete);
1548        assert!(!bridge.smt_complete);
1549        assert!(!bridge.theorem_claimed);
1550        assert!(bridge.product_path_evidence_ready);
1551        assert!(!bridge.stronger_type_safety_claim_ready);
1552        assert!(bridge.dimension_vector_domain_ready);
1553        assert!(bridge.calc_dimension_diagnostics_ready);
1554        assert!(!bridge.dimension_vector_domain.forks_unit_system);
1555        assert!(!bridge.dimension_vector_domain.theorem_claimed);
1556        assert_eq!(bridge.dimension_vector_domain.value_count, 2);
1557        assert_eq!(bridge.calc_dimension_diagnostics.diagnostic_count, 0);
1558        assert!(!bridge.calc_dimension_diagnostics.smt_complete);
1559        assert!(!bridge.calc_dimension_diagnostics.liquid_haskell_complete);
1560        assert!(
1561            !bridge
1562                .calc_dimension_diagnostics
1563                .stronger_type_safety_claim_ready
1564        );
1565        assert_eq!(bridge.evaluations[0].context_id, "base");
1566        assert_eq!(
1567            bridge.evaluations[0].combined_verdict,
1568            RefinementVerdictV0::SatisfiedAll
1569        );
1570        assert_eq!(
1571            bridge.evaluations[0].predicate_expression_ids,
1572            vec!["numeric-range:width:0..100:px".to_string()]
1573        );
1574    }
1575
1576    #[test]
1577    fn calc_dimension_diagnostics_classify_fixture_mismatches_without_unit_fork() {
1578        let values = vec![
1579            "calc(1px + 2s)".to_string(),
1580            "calc(1px + 2rem)".to_string(),
1581            "calc(100% - 1rem)".to_string(),
1582            "calc(1px + 2px)".to_string(),
1583        ];
1584        let domain = summarize_dimension_vector_domain_v0(&values);
1585        let diagnostics = summarize_calc_dimension_diagnostics_v0("width", &values);
1586
1587        assert_eq!(domain.product, "omena-refinement.dimension-vector-domain");
1588        assert_eq!(domain.feature_gate, "dimension-vector-domain-v0");
1589        assert_eq!(domain.claim_level, "fixtureWitnessDimensionVectorDomain");
1590        assert!(!domain.forks_unit_system);
1591        assert!(!domain.theorem_claimed);
1592        assert!(domain.vector_count >= 3);
1593
1594        assert_eq!(
1595            diagnostics.product,
1596            "omena-refinement.calc-dimension-diagnostics"
1597        );
1598        assert_eq!(diagnostics.feature_gate, "calc-dimension-diagnostics-v0");
1599        assert_eq!(diagnostics.claim_level, "researchGradeHint");
1600        assert!(!diagnostics.forks_unit_system);
1601        assert!(!diagnostics.smt_complete);
1602        assert!(!diagnostics.liquid_haskell_complete);
1603        assert!(!diagnostics.stronger_type_safety_claim_ready);
1604        assert!(!diagnostics.theorem_claimed);
1605        assert_eq!(diagnostics.diagnostic_count, 3);
1606        assert!(diagnostics.diagnostics.iter().any(|diagnostic| {
1607            diagnostic.kind == CalcDimensionDiagnosticKindV0::DimensionalMismatch
1608                && diagnostic.expression == "1px + 2s"
1609        }));
1610        assert!(diagnostics.diagnostics.iter().any(|diagnostic| {
1611            diagnostic.kind == CalcDimensionDiagnosticKindV0::MixedDimension
1612                && diagnostic.expression == "1px + 2rem"
1613        }));
1614        assert!(diagnostics.diagnostics.iter().any(|diagnostic| {
1615            diagnostic.kind == CalcDimensionDiagnosticKindV0::ContextDependentDimension
1616                && diagnostic.expression == "100% - 1rem"
1617        }));
1618    }
1619}