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: cascade refinement bridge substrate, 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
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
25#[serde(rename_all = "camelCase")]
26pub enum AbstractValueShapeV0 {
27    Bottom,
28    Exact,
29    FiniteSet,
30    CustomPropertyReference,
31    Top,
32}
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
35#[serde(rename_all = "camelCase")]
36pub struct TopPredicateV0 {
37    pub schema_version: &'static str,
38    pub product: &'static str,
39    pub layer_marker: &'static str,
40    pub feature_gate: &'static str,
41}
42
43impl Default for TopPredicateV0 {
44    fn default() -> Self {
45        Self {
46            schema_version: REFINEMENT_SCHEMA_VERSION_V0,
47            product: "omena-refinement.top-predicate",
48            layer_marker: REFINEMENT_LAYER_MARKER_V0,
49            feature_gate: REFINEMENT_FEATURE_GATE_V0,
50        }
51    }
52}
53
54impl RefinementPredicateV0 for TopPredicateV0 {
55    const PREDICATE_ID: &'static str = "top";
56}
57
58#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
59#[serde(rename_all = "camelCase")]
60pub struct AnyPropertyIndexV0 {
61    pub schema_version: &'static str,
62    pub product: &'static str,
63    pub layer_marker: &'static str,
64    pub feature_gate: &'static str,
65}
66
67impl Default for AnyPropertyIndexV0 {
68    fn default() -> Self {
69        Self {
70            schema_version: REFINEMENT_SCHEMA_VERSION_V0,
71            product: "omena-refinement.any-property-index",
72            layer_marker: REFINEMENT_LAYER_MARKER_V0,
73            feature_gate: REFINEMENT_FEATURE_GATE_V0,
74        }
75    }
76}
77
78impl PropertyIndexV0 for AnyPropertyIndexV0 {
79    const PROPERTY_NAME: &'static str = "*";
80}
81
82#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
83#[serde(rename_all = "camelCase", bound = "")]
84pub struct RefinedAbstractPropertyValueV0<P: PropertyIndexV0, R: RefinementPredicateV0> {
85    pub schema_version: &'static str,
86    pub product: &'static str,
87    pub layer_marker: &'static str,
88    pub feature_gate: &'static str,
89    pub property_name: &'static str,
90    pub predicate_id: &'static str,
91    pub value_shape: AbstractValueShapeV0,
92    pub legacy_value: AbstractPropertyValueV0,
93    pub strict_superset_of_legacy_v0: bool,
94    #[serde(skip)]
95    marker: PhantomData<(P, R)>,
96}
97
98#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
99#[serde(rename_all = "camelCase")]
100pub enum RefinementPropertyPredicateV0 {
101    Any,
102    ExactValue {
103        property_name: String,
104        value: String,
105    },
106    OneOfValues {
107        property_name: String,
108        values: Vec<String>,
109    },
110    CustomPropertyReference {
111        property_name: String,
112        custom_property_name: String,
113    },
114    NumericRange {
115        property_name: String,
116        min_inclusive: Option<i64>,
117        max_inclusive: Option<i64>,
118        unit: Option<String>,
119    },
120    HasPseudoState {
121        property_name: String,
122        pseudo_state: String,
123    },
124    And {
125        predicates: Vec<RefinementPropertyPredicateV0>,
126    },
127    Or {
128        predicates: Vec<RefinementPropertyPredicateV0>,
129    },
130    Not {
131        predicate: Box<RefinementPropertyPredicateV0>,
132    },
133}
134
135#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
136#[serde(rename_all = "camelCase")]
137pub struct RefinementPredicateEvaluationV0 {
138    pub schema_version: &'static str,
139    pub product: &'static str,
140    pub layer_marker: &'static str,
141    pub feature_gate: &'static str,
142    pub predicate_expression_id: String,
143    pub value_shape: AbstractValueShapeV0,
144    pub verdict: RefinementVerdictV0,
145    pub matched_clause_count: usize,
146    pub witness: RefinementWitnessV0,
147}
148
149#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
150#[serde(rename_all = "camelCase")]
151pub struct RefinementContextSummaryV0 {
152    pub schema_version: &'static str,
153    pub product: &'static str,
154    pub layer_marker: &'static str,
155    pub feature_gate: &'static str,
156    pub predicate_count: usize,
157    pub context_digest: u64,
158    pub witness_provenance_count: usize,
159    pub downstream_invalidation_required: bool,
160}
161
162/// M6 #69 bridge between context-indexed property values and refinement facts.
163///
164/// This is a research-staged substrate: it evaluates the existing cascade
165/// family through the existing refinement predicate evaluator. It does not
166/// claim Liquid-Haskell-style inference, SMT completeness, or a theorem.
167#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
168#[serde(rename_all = "camelCase")]
169pub struct CascadeDimensionalRefinementBridgeV0 {
170    pub schema_version: &'static str,
171    pub product: &'static str,
172    pub layer_marker: &'static str,
173    pub feature_gate: &'static str,
174    pub claim_level: &'static str,
175    pub property_name: String,
176    pub cascade_family_product: &'static str,
177    pub predicate_count: usize,
178    pub context_value_count: usize,
179    pub restriction_map_count: usize,
180    pub context_evaluation_count: usize,
181    pub satisfied_all_context_count: usize,
182    pub satisfied_some_context_count: usize,
183    pub unknown_context_count: usize,
184    pub unsatisfiable_context_count: usize,
185    pub witness_provenance_count: usize,
186    pub property_consistent: bool,
187    pub uses_existing_abstract_property_value_substrate: bool,
188    pub uses_existing_cascade_family_substrate: bool,
189    pub uses_existing_refinement_predicate_substrate: bool,
190    pub forks_unit_system: bool,
191    pub liquid_haskell_complete: bool,
192    pub smt_backend_available: bool,
193    pub smt_complete: bool,
194    pub theorem_claimed: bool,
195    pub product_path_evidence_ready: bool,
196    pub stronger_type_safety_claim_ready: bool,
197    pub evaluations: Vec<CascadeDimensionalRefinementContextEvaluationV0>,
198}
199
200#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
201#[serde(rename_all = "camelCase")]
202pub struct CascadeDimensionalRefinementContextEvaluationV0 {
203    pub schema_version: &'static str,
204    pub product: &'static str,
205    pub layer_marker: &'static str,
206    pub feature_gate: &'static str,
207    pub context_id: String,
208    pub selector_count: usize,
209    pub condition_count: usize,
210    pub layer_count: usize,
211    pub value_shape: AbstractValueShapeV0,
212    pub combined_verdict: RefinementVerdictV0,
213    pub predicate_evaluation_count: usize,
214    pub matched_clause_count: usize,
215    pub witness_provenance_count: usize,
216    pub predicate_expression_ids: Vec<String>,
217}
218
219pub fn project_legacy_to_refined_v0<P, R>(
220    legacy_value: AbstractPropertyValueV0,
221) -> RefinedAbstractPropertyValueV0<P, R>
222where
223    P: PropertyIndexV0,
224    R: RefinementPredicateV0,
225{
226    let mut refined = RefinedAbstractPropertyValueV0 {
227        schema_version: REFINEMENT_SCHEMA_VERSION_V0,
228        product: "omena-refinement.refined-abstract-property-value",
229        layer_marker: REFINEMENT_LAYER_MARKER_V0,
230        feature_gate: REFINEMENT_FEATURE_GATE_V0,
231        property_name: P::PROPERTY_NAME,
232        predicate_id: R::PREDICATE_ID,
233        value_shape: abstract_property_value_shape_v0(&legacy_value),
234        legacy_value,
235        strict_superset_of_legacy_v0: false,
236        marker: PhantomData,
237    };
238    refined.strict_superset_of_legacy_v0 =
239        refined_projection_preserves_legacy_value_v0::<P, R>(&refined);
240    refined
241}
242
243pub fn project_refined_to_legacy_v0<P, R>(
244    refined: &RefinedAbstractPropertyValueV0<P, R>,
245) -> AbstractPropertyValueV0
246where
247    P: PropertyIndexV0,
248    R: RefinementPredicateV0,
249{
250    refined.legacy_value.clone()
251}
252
253pub fn refined_projection_preserves_legacy_value_v0<P, R>(
254    refined: &RefinedAbstractPropertyValueV0<P, R>,
255) -> bool
256where
257    P: PropertyIndexV0,
258    R: RefinementPredicateV0,
259{
260    refined.schema_version == REFINEMENT_SCHEMA_VERSION_V0
261        && refined.layer_marker == REFINEMENT_LAYER_MARKER_V0
262        && refined.feature_gate == REFINEMENT_FEATURE_GATE_V0
263        && refined.property_name == P::PROPERTY_NAME
264        && refined.predicate_id == R::PREDICATE_ID
265        && abstract_property_value_shape_v0(&project_refined_to_legacy_v0(refined))
266            == refined.value_shape
267}
268
269pub fn evaluate_refinement_property_predicate_v0(
270    predicate: &RefinementPropertyPredicateV0,
271    value: &AbstractPropertyValueV0,
272) -> RefinementPredicateEvaluationV0 {
273    let verdict = evaluate_refinement_predicate_verdict_v0(predicate, value);
274    let matched_clause_count = count_satisfied_refinement_clauses_v0(predicate, value);
275    let predicate_expression_id = refinement_predicate_expression_id_v0(predicate);
276    let witness = refinement_witness_v0(
277        "property-grammar",
278        verdict,
279        refinement_predicate_provenance_v0(predicate),
280    );
281
282    RefinementPredicateEvaluationV0 {
283        schema_version: REFINEMENT_SCHEMA_VERSION_V0,
284        product: "omena-refinement.property-predicate-evaluation",
285        layer_marker: REFINEMENT_LAYER_MARKER_V0,
286        feature_gate: REFINEMENT_FEATURE_GATE_V0,
287        predicate_expression_id,
288        value_shape: abstract_property_value_shape_v0(value),
289        verdict,
290        matched_clause_count,
291        witness,
292    }
293}
294
295pub fn refine_declaration_in_context(
296    declaration: &CascadeDeclaration,
297    context: &CascadeRefinementContextV0,
298) -> RefinementWitnessV0 {
299    refine_cascade_declaration_in_context(declaration, context)
300}
301
302pub fn summarize_refinement_context_v0(
303    predicates: &[RefinementPropertyPredicateV0],
304) -> RefinementContextSummaryV0 {
305    let mut expression_ids = predicates
306        .iter()
307        .map(refinement_predicate_expression_id_v0)
308        .collect::<Vec<_>>();
309    expression_ids.sort();
310
311    let witness_provenance_count = predicates
312        .iter()
313        .flat_map(refinement_predicate_provenance_v0)
314        .map(|provenance| provenance.source)
315        .collect::<std::collections::BTreeSet<_>>()
316        .len();
317    let context_digest = deterministic_refinement_digest_v0(expression_ids.join("\n").as_bytes());
318
319    RefinementContextSummaryV0 {
320        schema_version: REFINEMENT_SCHEMA_VERSION_V0,
321        product: "omena-refinement.context-summary",
322        layer_marker: REFINEMENT_LAYER_MARKER_V0,
323        feature_gate: REFINEMENT_FEATURE_GATE_V0,
324        predicate_count: predicates.len(),
325        context_digest,
326        witness_provenance_count,
327        downstream_invalidation_required: !predicates.is_empty(),
328    }
329}
330
331pub fn summarize_cascade_dimensional_refinement_bridge_v0(
332    family: &CascadeValueFamilyV0,
333    predicates: &[RefinementPropertyPredicateV0],
334) -> CascadeDimensionalRefinementBridgeV0 {
335    let mut global_provenance_sources = BTreeSet::new();
336    let mut evaluations = family
337        .members
338        .iter()
339        .map(|member| {
340            let predicate_evaluations = predicates
341                .iter()
342                .map(|predicate| {
343                    evaluate_refinement_property_predicate_v0(predicate, &member.value)
344                })
345                .collect::<Vec<_>>();
346            let verdicts = predicate_evaluations
347                .iter()
348                .map(|evaluation| evaluation.verdict)
349                .collect::<Vec<_>>();
350            let combined_verdict = combine_and_refinement_verdicts_v0(&verdicts);
351            let mut context_provenance_sources = BTreeSet::new();
352            for evaluation in &predicate_evaluations {
353                for provenance in &evaluation.witness.provenance {
354                    context_provenance_sources.insert(provenance.source);
355                    global_provenance_sources.insert(provenance.source);
356                }
357            }
358
359            CascadeDimensionalRefinementContextEvaluationV0 {
360                schema_version: REFINEMENT_SCHEMA_VERSION_V0,
361                product: "omena-refinement.cascade-dimensional-refinement-context-evaluation",
362                layer_marker: REFINEMENT_LAYER_MARKER_V0,
363                feature_gate: REFINEMENT_FEATURE_GATE_V0,
364                context_id: member.context.id.clone(),
365                selector_count: member.context.selectors.len(),
366                condition_count: member.context.conditions.len(),
367                layer_count: member.context.layers.len(),
368                value_shape: abstract_property_value_shape_v0(&member.value),
369                combined_verdict,
370                predicate_evaluation_count: predicate_evaluations.len(),
371                matched_clause_count: predicate_evaluations
372                    .iter()
373                    .map(|evaluation| evaluation.matched_clause_count)
374                    .sum(),
375                witness_provenance_count: context_provenance_sources.len(),
376                predicate_expression_ids: predicate_evaluations
377                    .into_iter()
378                    .map(|evaluation| evaluation.predicate_expression_id)
379                    .collect(),
380            }
381        })
382        .collect::<Vec<_>>();
383    evaluations.sort_by(|left, right| left.context_id.cmp(&right.context_id));
384
385    CascadeDimensionalRefinementBridgeV0 {
386        schema_version: REFINEMENT_SCHEMA_VERSION_V0,
387        product: "omena-refinement.cascade-dimensional-refinement-bridge",
388        layer_marker: REFINEMENT_LAYER_MARKER_V0,
389        feature_gate: REFINEMENT_FEATURE_GATE_V0,
390        claim_level: "m6DimensionalRefinementBridgeSubstrate",
391        property_name: family.property_name.clone(),
392        cascade_family_product: family.product,
393        predicate_count: predicates.len(),
394        context_value_count: family.context_value_count,
395        restriction_map_count: family.restriction_map_count,
396        context_evaluation_count: evaluations.len(),
397        satisfied_all_context_count: count_context_verdicts_v0(
398            &evaluations,
399            RefinementVerdictV0::SatisfiedAll,
400        ),
401        satisfied_some_context_count: count_context_verdicts_v0(
402            &evaluations,
403            RefinementVerdictV0::SatisfiedSome,
404        ),
405        unknown_context_count: count_context_verdicts_v0(
406            &evaluations,
407            RefinementVerdictV0::Unknown,
408        ),
409        unsatisfiable_context_count: count_context_verdicts_v0(
410            &evaluations,
411            RefinementVerdictV0::Unsatisfiable,
412        ),
413        witness_provenance_count: global_provenance_sources.len(),
414        property_consistent: family.property_consistent,
415        uses_existing_abstract_property_value_substrate: true,
416        uses_existing_cascade_family_substrate: true,
417        uses_existing_refinement_predicate_substrate: true,
418        forks_unit_system: false,
419        liquid_haskell_complete: false,
420        smt_backend_available: refinement_smt_backend_available_v0(),
421        smt_complete: false,
422        theorem_claimed: false,
423        product_path_evidence_ready: true,
424        stronger_type_safety_claim_ready: false,
425        evaluations,
426    }
427}
428
429pub fn abstract_property_value_shape_v0(value: &AbstractPropertyValueV0) -> AbstractValueShapeV0 {
430    match value {
431        AbstractPropertyValueV0::Bottom { .. } => AbstractValueShapeV0::Bottom,
432        AbstractPropertyValueV0::Exact { .. } => AbstractValueShapeV0::Exact,
433        AbstractPropertyValueV0::FiniteSet { .. } => AbstractValueShapeV0::FiniteSet,
434        AbstractPropertyValueV0::CustomPropertyReference { .. } => {
435            AbstractValueShapeV0::CustomPropertyReference
436        }
437        AbstractPropertyValueV0::Top { .. } => AbstractValueShapeV0::Top,
438    }
439}
440
441fn count_context_verdicts_v0(
442    evaluations: &[CascadeDimensionalRefinementContextEvaluationV0],
443    verdict: RefinementVerdictV0,
444) -> usize {
445    evaluations
446        .iter()
447        .filter(|evaluation| evaluation.combined_verdict == verdict)
448        .count()
449}
450
451fn evaluate_refinement_predicate_verdict_v0(
452    predicate: &RefinementPropertyPredicateV0,
453    value: &AbstractPropertyValueV0,
454) -> RefinementVerdictV0 {
455    match predicate {
456        RefinementPropertyPredicateV0::Any => RefinementVerdictV0::SatisfiedAll,
457        RefinementPropertyPredicateV0::ExactValue {
458            property_name,
459            value: expected,
460        } => evaluate_exact_value_predicate_v0(property_name, expected, value),
461        RefinementPropertyPredicateV0::OneOfValues {
462            property_name,
463            values,
464        } => evaluate_one_of_values_predicate_v0(property_name, values, value),
465        RefinementPropertyPredicateV0::CustomPropertyReference {
466            property_name,
467            custom_property_name,
468        } => evaluate_custom_property_reference_predicate_v0(
469            property_name,
470            custom_property_name,
471            value,
472        ),
473        RefinementPropertyPredicateV0::NumericRange {
474            property_name,
475            min_inclusive,
476            max_inclusive,
477            unit,
478        } => evaluate_numeric_range_predicate_v0(
479            property_name,
480            *min_inclusive,
481            *max_inclusive,
482            unit.as_deref(),
483            value,
484        ),
485        RefinementPropertyPredicateV0::HasPseudoState {
486            property_name,
487            pseudo_state,
488        } => evaluate_pseudo_state_predicate_v0(property_name, pseudo_state, value),
489        RefinementPropertyPredicateV0::And { predicates } => combine_and_refinement_verdicts_v0(
490            &predicates
491                .iter()
492                .map(|predicate| evaluate_refinement_predicate_verdict_v0(predicate, value))
493                .collect::<Vec<_>>(),
494        ),
495        RefinementPropertyPredicateV0::Or { predicates } => combine_or_refinement_verdicts_v0(
496            &predicates
497                .iter()
498                .map(|predicate| evaluate_refinement_predicate_verdict_v0(predicate, value))
499                .collect::<Vec<_>>(),
500        ),
501        RefinementPropertyPredicateV0::Not { predicate } => {
502            match evaluate_refinement_predicate_verdict_v0(predicate, value) {
503                RefinementVerdictV0::SatisfiedAll => RefinementVerdictV0::Unsatisfiable,
504                RefinementVerdictV0::Unsatisfiable => RefinementVerdictV0::SatisfiedAll,
505                RefinementVerdictV0::SatisfiedSome | RefinementVerdictV0::Unknown => {
506                    RefinementVerdictV0::Unknown
507                }
508            }
509        }
510    }
511}
512
513fn evaluate_numeric_range_predicate_v0(
514    property_name: &str,
515    min_inclusive: Option<i64>,
516    max_inclusive: Option<i64>,
517    unit: Option<&str>,
518    value: &AbstractPropertyValueV0,
519) -> RefinementVerdictV0 {
520    match value {
521        AbstractPropertyValueV0::Exact {
522            property_name: actual_property,
523            value: actual_value,
524            ..
525        } if actual_property == property_name => {
526            if numeric_range_contains_value_v0(actual_value, min_inclusive, max_inclusive, unit) {
527                RefinementVerdictV0::SatisfiedAll
528            } else {
529                RefinementVerdictV0::Unsatisfiable
530            }
531        }
532        AbstractPropertyValueV0::FiniteSet {
533            property_name: actual_property,
534            values,
535            ..
536        } if actual_property == property_name => {
537            let matched = values
538                .iter()
539                .filter(|candidate| {
540                    numeric_range_contains_value_v0(candidate, min_inclusive, max_inclusive, unit)
541                })
542                .count();
543            if matched == values.len() {
544                RefinementVerdictV0::SatisfiedAll
545            } else if matched > 0 {
546                RefinementVerdictV0::SatisfiedSome
547            } else {
548                RefinementVerdictV0::Unsatisfiable
549            }
550        }
551        AbstractPropertyValueV0::Top {
552            property_name: actual_property,
553        }
554        | AbstractPropertyValueV0::CustomPropertyReference {
555            property_name: actual_property,
556            ..
557        } if actual_property == property_name => RefinementVerdictV0::Unknown,
558        _ => RefinementVerdictV0::Unsatisfiable,
559    }
560}
561
562fn evaluate_pseudo_state_predicate_v0(
563    property_name: &str,
564    expected_pseudo_state: &str,
565    value: &AbstractPropertyValueV0,
566) -> RefinementVerdictV0 {
567    match value {
568        AbstractPropertyValueV0::Exact {
569            property_name: actual_property,
570            pseudo_state,
571            ..
572        }
573        | AbstractPropertyValueV0::CustomPropertyReference {
574            property_name: actual_property,
575            pseudo_state,
576            ..
577        } if actual_property == property_name => {
578            if pseudo_state.as_deref() == Some(expected_pseudo_state) {
579                RefinementVerdictV0::SatisfiedAll
580            } else {
581                RefinementVerdictV0::Unsatisfiable
582            }
583        }
584        AbstractPropertyValueV0::FiniteSet {
585            property_name: actual_property,
586            pseudo_states,
587            ..
588        } if actual_property == property_name => {
589            if pseudo_states.len() == 1
590                && pseudo_states
591                    .iter()
592                    .any(|pseudo_state| pseudo_state == expected_pseudo_state)
593            {
594                RefinementVerdictV0::SatisfiedAll
595            } else if pseudo_states
596                .iter()
597                .any(|pseudo_state| pseudo_state == expected_pseudo_state)
598            {
599                RefinementVerdictV0::SatisfiedSome
600            } else {
601                RefinementVerdictV0::Unsatisfiable
602            }
603        }
604        AbstractPropertyValueV0::Top {
605            property_name: actual_property,
606        } if actual_property == property_name => RefinementVerdictV0::Unknown,
607        _ => RefinementVerdictV0::Unsatisfiable,
608    }
609}
610
611fn evaluate_exact_value_predicate_v0(
612    property_name: &str,
613    expected: &str,
614    value: &AbstractPropertyValueV0,
615) -> RefinementVerdictV0 {
616    match value {
617        AbstractPropertyValueV0::Exact {
618            property_name: actual_property,
619            value: actual_value,
620            ..
621        } if actual_property == property_name && actual_value == expected => {
622            RefinementVerdictV0::SatisfiedAll
623        }
624        AbstractPropertyValueV0::FiniteSet {
625            property_name: actual_property,
626            values,
627            ..
628        } if actual_property == property_name && values.iter().any(|value| value == expected) => {
629            if values.len() == 1 {
630                RefinementVerdictV0::SatisfiedAll
631            } else {
632                RefinementVerdictV0::SatisfiedSome
633            }
634        }
635        AbstractPropertyValueV0::Top {
636            property_name: actual_property,
637        }
638        | AbstractPropertyValueV0::CustomPropertyReference {
639            property_name: actual_property,
640            ..
641        } if actual_property == property_name => RefinementVerdictV0::Unknown,
642        _ => RefinementVerdictV0::Unsatisfiable,
643    }
644}
645
646fn evaluate_one_of_values_predicate_v0(
647    property_name: &str,
648    expected_values: &[String],
649    value: &AbstractPropertyValueV0,
650) -> RefinementVerdictV0 {
651    match value {
652        AbstractPropertyValueV0::Exact {
653            property_name: actual_property,
654            value: actual_value,
655            ..
656        } if actual_property == property_name => {
657            if expected_values.contains(actual_value) {
658                RefinementVerdictV0::SatisfiedAll
659            } else {
660                RefinementVerdictV0::Unsatisfiable
661            }
662        }
663        AbstractPropertyValueV0::FiniteSet {
664            property_name: actual_property,
665            values,
666            ..
667        } if actual_property == property_name => {
668            let matched = values
669                .iter()
670                .filter(|value| expected_values.contains(*value))
671                .count();
672            if matched == values.len() {
673                RefinementVerdictV0::SatisfiedAll
674            } else if matched > 0 {
675                RefinementVerdictV0::SatisfiedSome
676            } else {
677                RefinementVerdictV0::Unsatisfiable
678            }
679        }
680        AbstractPropertyValueV0::Top {
681            property_name: actual_property,
682        }
683        | AbstractPropertyValueV0::CustomPropertyReference {
684            property_name: actual_property,
685            ..
686        } if actual_property == property_name => RefinementVerdictV0::Unknown,
687        _ => RefinementVerdictV0::Unsatisfiable,
688    }
689}
690
691fn evaluate_custom_property_reference_predicate_v0(
692    property_name: &str,
693    expected_custom_property: &str,
694    value: &AbstractPropertyValueV0,
695) -> RefinementVerdictV0 {
696    match value {
697        AbstractPropertyValueV0::CustomPropertyReference {
698            property_name: actual_property,
699            custom_property_name,
700            ..
701        } if actual_property == property_name
702            && custom_property_name == expected_custom_property =>
703        {
704            RefinementVerdictV0::SatisfiedAll
705        }
706        AbstractPropertyValueV0::Top {
707            property_name: actual_property,
708        } if actual_property == property_name => RefinementVerdictV0::Unknown,
709        _ => RefinementVerdictV0::Unsatisfiable,
710    }
711}
712
713fn combine_and_refinement_verdicts_v0(verdicts: &[RefinementVerdictV0]) -> RefinementVerdictV0 {
714    if verdicts.is_empty()
715        || verdicts
716            .iter()
717            .all(|verdict| *verdict == RefinementVerdictV0::SatisfiedAll)
718    {
719        RefinementVerdictV0::SatisfiedAll
720    } else if verdicts.contains(&RefinementVerdictV0::Unsatisfiable) {
721        RefinementVerdictV0::Unsatisfiable
722    } else if verdicts.contains(&RefinementVerdictV0::SatisfiedAll)
723        || verdicts.contains(&RefinementVerdictV0::SatisfiedSome)
724    {
725        RefinementVerdictV0::SatisfiedSome
726    } else {
727        RefinementVerdictV0::Unknown
728    }
729}
730
731fn combine_or_refinement_verdicts_v0(verdicts: &[RefinementVerdictV0]) -> RefinementVerdictV0 {
732    if verdicts.is_empty() || verdicts.contains(&RefinementVerdictV0::SatisfiedAll) {
733        RefinementVerdictV0::SatisfiedAll
734    } else if verdicts.contains(&RefinementVerdictV0::SatisfiedSome) {
735        RefinementVerdictV0::SatisfiedSome
736    } else if verdicts
737        .iter()
738        .all(|verdict| *verdict == RefinementVerdictV0::Unsatisfiable)
739    {
740        RefinementVerdictV0::Unsatisfiable
741    } else {
742        RefinementVerdictV0::Unknown
743    }
744}
745
746fn count_satisfied_refinement_clauses_v0(
747    predicate: &RefinementPropertyPredicateV0,
748    value: &AbstractPropertyValueV0,
749) -> usize {
750    match predicate {
751        RefinementPropertyPredicateV0::And { predicates }
752        | RefinementPropertyPredicateV0::Or { predicates } => predicates
753            .iter()
754            .map(|predicate| count_satisfied_refinement_clauses_v0(predicate, value))
755            .sum(),
756        RefinementPropertyPredicateV0::Not { predicate } => usize::from(matches!(
757            evaluate_refinement_predicate_verdict_v0(predicate, value),
758            RefinementVerdictV0::Unsatisfiable
759        )),
760        _ => usize::from(matches!(
761            evaluate_refinement_predicate_verdict_v0(predicate, value),
762            RefinementVerdictV0::SatisfiedAll | RefinementVerdictV0::SatisfiedSome
763        )),
764    }
765}
766
767fn refinement_predicate_expression_id_v0(predicate: &RefinementPropertyPredicateV0) -> String {
768    match predicate {
769        RefinementPropertyPredicateV0::Any => "any".to_string(),
770        RefinementPropertyPredicateV0::ExactValue {
771            property_name,
772            value,
773        } => format!("exact:{property_name}:{value}"),
774        RefinementPropertyPredicateV0::OneOfValues {
775            property_name,
776            values,
777        } => format!("one-of:{property_name}:{}", values.join("|")),
778        RefinementPropertyPredicateV0::CustomPropertyReference {
779            property_name,
780            custom_property_name,
781        } => format!("custom-ref:{property_name}:{custom_property_name}"),
782        RefinementPropertyPredicateV0::NumericRange {
783            property_name,
784            min_inclusive,
785            max_inclusive,
786            unit,
787        } => format!(
788            "numeric-range:{property_name}:{}..{}:{}",
789            min_inclusive
790                .map(|value| value.to_string())
791                .unwrap_or_else(|| "-inf".to_string()),
792            max_inclusive
793                .map(|value| value.to_string())
794                .unwrap_or_else(|| "inf".to_string()),
795            unit.as_deref().unwrap_or("*")
796        ),
797        RefinementPropertyPredicateV0::HasPseudoState {
798            property_name,
799            pseudo_state,
800        } => format!("pseudo-state:{property_name}:{pseudo_state}"),
801        RefinementPropertyPredicateV0::And { predicates } => format!(
802            "and({})",
803            predicates
804                .iter()
805                .map(refinement_predicate_expression_id_v0)
806                .collect::<Vec<_>>()
807                .join(",")
808        ),
809        RefinementPropertyPredicateV0::Or { predicates } => format!(
810            "or({})",
811            predicates
812                .iter()
813                .map(refinement_predicate_expression_id_v0)
814                .collect::<Vec<_>>()
815                .join(",")
816        ),
817        RefinementPropertyPredicateV0::Not { predicate } => {
818            format!("not({})", refinement_predicate_expression_id_v0(predicate))
819        }
820    }
821}
822
823fn refinement_predicate_provenance_v0(
824    predicate: &RefinementPropertyPredicateV0,
825) -> Vec<omena_refinement_trait::RefinementProvenanceV0> {
826    let mut provenance = Vec::new();
827    collect_refinement_predicate_provenance_v0(predicate, &mut provenance);
828    provenance
829}
830
831fn collect_refinement_predicate_provenance_v0(
832    predicate: &RefinementPropertyPredicateV0,
833    provenance: &mut Vec<omena_refinement_trait::RefinementProvenanceV0>,
834) {
835    push_refinement_provenance_v0(provenance, "property-grammar", None);
836    match predicate {
837        RefinementPropertyPredicateV0::Any => {}
838        RefinementPropertyPredicateV0::ExactValue { .. }
839        | RefinementPropertyPredicateV0::OneOfValues { .. } => {
840            push_refinement_provenance_v0(provenance, "finite-property-domain", None);
841        }
842        RefinementPropertyPredicateV0::CustomPropertyReference { .. } => {
843            push_refinement_provenance_v0(provenance, "custom-property-reference", None);
844        }
845        RefinementPropertyPredicateV0::NumericRange { .. } => {
846            push_refinement_provenance_v0(provenance, "numeric-range-interval", None);
847        }
848        RefinementPropertyPredicateV0::HasPseudoState { .. } => {
849            push_refinement_provenance_v0(provenance, "pseudo-state-refinement", None);
850        }
851        RefinementPropertyPredicateV0::And { predicates }
852        | RefinementPropertyPredicateV0::Or { predicates } => {
853            push_refinement_provenance_v0(provenance, "predicate-composition", None);
854            for predicate in predicates {
855                collect_refinement_predicate_provenance_v0(predicate, provenance);
856            }
857        }
858        RefinementPropertyPredicateV0::Not { predicate } => {
859            push_refinement_provenance_v0(provenance, "predicate-composition", None);
860            collect_refinement_predicate_provenance_v0(predicate, provenance);
861        }
862    }
863}
864
865fn push_refinement_provenance_v0(
866    provenance: &mut Vec<omena_refinement_trait::RefinementProvenanceV0>,
867    source: &'static str,
868    legacy_proof_primitive: Option<&'static str>,
869) {
870    if provenance.iter().any(|entry| {
871        entry.source == source && entry.legacy_proof_primitive == legacy_proof_primitive
872    }) {
873        return;
874    }
875    provenance.push(refinement_provenance_v0(source, legacy_proof_primitive));
876}
877
878fn numeric_range_contains_value_v0(
879    value: &str,
880    min_inclusive: Option<i64>,
881    max_inclusive: Option<i64>,
882    expected_unit: Option<&str>,
883) -> bool {
884    let Some((magnitude, unit)) = parse_css_integer_with_unit_v0(value) else {
885        return false;
886    };
887    if let Some(expected_unit) = expected_unit
888        && unit != expected_unit
889    {
890        return false;
891    }
892    if let Some(min_inclusive) = min_inclusive
893        && magnitude < min_inclusive
894    {
895        return false;
896    }
897    if let Some(max_inclusive) = max_inclusive
898        && magnitude > max_inclusive
899    {
900        return false;
901    }
902    true
903}
904
905fn parse_css_integer_with_unit_v0(value: &str) -> Option<(i64, &str)> {
906    let trimmed = value.trim();
907    let mut end = 0;
908    for (index, ch) in trimmed.char_indices() {
909        if ch.is_ascii_digit() || (index == 0 && (ch == '-' || ch == '+')) {
910            end = index + ch.len_utf8();
911        } else {
912            break;
913        }
914    }
915    if end == 0 || trimmed[..end].ends_with(['-', '+']) {
916        return None;
917    }
918    let magnitude = trimmed[..end].parse::<i64>().ok()?;
919    Some((magnitude, trimmed[end..].trim()))
920}
921
922fn deterministic_refinement_digest_v0(bytes: &[u8]) -> u64 {
923    bytes.iter().fold(0xcbf29ce484222325, |hash, byte| {
924        (hash ^ u64::from(*byte)).wrapping_mul(0x100000001b3)
925    })
926}
927
928#[cfg(feature = "refinement-smt")]
929pub fn refinement_smt_backend_available_v0() -> bool {
930    let _ = omena_smt::cascade_theory_signature_v0();
931    true
932}
933
934#[cfg(not(feature = "refinement-smt"))]
935pub fn refinement_smt_backend_available_v0() -> bool {
936    false
937}
938
939#[cfg(test)]
940mod tests {
941    use super::*;
942    use omena_abstract_value::{
943        CascadeContextV0, CascadeValueFamilyMemberV0, derive_cascade_restriction_maps_v0,
944        summarize_cascade_value_family_v0,
945    };
946
947    #[test]
948    fn refined_value_round_trips_to_legacy_without_mutating_v0() {
949        let top = TopPredicateV0::default();
950        let any = AnyPropertyIndexV0::default();
951        assert_eq!(top.schema_version, "0");
952        assert_eq!(any.layer_marker, "refinement-cascade");
953
954        let legacy = AbstractPropertyValueV0::Top {
955            property_name: "color".to_string(),
956        };
957        let refined =
958            project_legacy_to_refined_v0::<AnyPropertyIndexV0, TopPredicateV0>(legacy.clone());
959        assert_eq!(refined.schema_version, "0");
960        assert_eq!(refined.layer_marker, "refinement-cascade");
961        assert!(refined.strict_superset_of_legacy_v0);
962        assert!(refined_projection_preserves_legacy_value_v0::<
963            AnyPropertyIndexV0,
964            TopPredicateV0,
965        >(&refined));
966        assert_eq!(project_refined_to_legacy_v0(&refined), legacy);
967    }
968
969    #[test]
970    fn refinement_property_grammar_evaluates_exact_and_one_of_values() {
971        let exact = AbstractPropertyValueV0::Exact {
972            property_name: "display".to_string(),
973            value: "grid".to_string(),
974            pseudo_state: None,
975        };
976        let predicate = RefinementPropertyPredicateV0::OneOfValues {
977            property_name: "display".to_string(),
978            values: vec!["grid".to_string(), "flex".to_string()],
979        };
980        let evaluation = evaluate_refinement_property_predicate_v0(&predicate, &exact);
981
982        assert_eq!(evaluation.schema_version, "0");
983        assert_eq!(
984            evaluation.product,
985            "omena-refinement.property-predicate-evaluation"
986        );
987        assert_eq!(evaluation.value_shape, AbstractValueShapeV0::Exact);
988        assert_eq!(evaluation.verdict, RefinementVerdictV0::SatisfiedAll);
989        assert_eq!(evaluation.matched_clause_count, 1);
990        assert_eq!(evaluation.witness.predicate_id, "property-grammar");
991        assert!(evaluation.witness.legacy_proofs_byte_untouched);
992    }
993
994    #[test]
995    fn refinement_predicate_composition_tracks_partial_and_negative_witnesses() {
996        let finite = AbstractPropertyValueV0::FiniteSet {
997            property_name: "color".to_string(),
998            values: vec!["red".to_string(), "blue".to_string()],
999            pseudo_states: Vec::new(),
1000        };
1001        let predicate = RefinementPropertyPredicateV0::And {
1002            predicates: vec![
1003                RefinementPropertyPredicateV0::OneOfValues {
1004                    property_name: "color".to_string(),
1005                    values: vec!["red".to_string()],
1006                },
1007                RefinementPropertyPredicateV0::Not {
1008                    predicate: Box::new(RefinementPropertyPredicateV0::ExactValue {
1009                        property_name: "color".to_string(),
1010                        value: "green".to_string(),
1011                    }),
1012                },
1013            ],
1014        };
1015        let evaluation = evaluate_refinement_property_predicate_v0(&predicate, &finite);
1016
1017        assert_eq!(evaluation.value_shape, AbstractValueShapeV0::FiniteSet);
1018        assert_eq!(evaluation.verdict, RefinementVerdictV0::SatisfiedSome);
1019        assert_eq!(evaluation.matched_clause_count, 2);
1020        assert_eq!(
1021            evaluation.predicate_expression_id,
1022            "and(one-of:color:red,not(exact:color:green))"
1023        );
1024        assert_eq!(evaluation.witness.provenance[0].source, "property-grammar");
1025    }
1026
1027    #[test]
1028    fn refinement_custom_property_reference_predicate_is_not_wrapper_only() {
1029        let reference = AbstractPropertyValueV0::CustomPropertyReference {
1030            property_name: "color".to_string(),
1031            custom_property_name: "--brand".to_string(),
1032            pseudo_state: None,
1033        };
1034        let predicate = RefinementPropertyPredicateV0::CustomPropertyReference {
1035            property_name: "color".to_string(),
1036            custom_property_name: "--brand".to_string(),
1037        };
1038        let evaluation = evaluate_refinement_property_predicate_v0(&predicate, &reference);
1039
1040        assert_eq!(evaluation.verdict, RefinementVerdictV0::SatisfiedAll);
1041        assert_eq!(
1042            evaluation.predicate_expression_id,
1043            "custom-ref:color:--brand"
1044        );
1045    }
1046
1047    #[test]
1048    fn refinement_numeric_range_and_pseudo_state_predicates_are_evaluated() {
1049        let finite = AbstractPropertyValueV0::FiniteSet {
1050            property_name: "opacity".to_string(),
1051            values: vec!["0".to_string(), "50%".to_string(), "100%".to_string()],
1052            pseudo_states: vec![":hover".to_string(), ":focus".to_string()],
1053        };
1054        let predicate = RefinementPropertyPredicateV0::And {
1055            predicates: vec![
1056                RefinementPropertyPredicateV0::NumericRange {
1057                    property_name: "opacity".to_string(),
1058                    min_inclusive: Some(0),
1059                    max_inclusive: Some(100),
1060                    unit: Some("%".to_string()),
1061                },
1062                RefinementPropertyPredicateV0::HasPseudoState {
1063                    property_name: "opacity".to_string(),
1064                    pseudo_state: ":hover".to_string(),
1065                },
1066            ],
1067        };
1068        let evaluation = evaluate_refinement_property_predicate_v0(&predicate, &finite);
1069
1070        assert_eq!(evaluation.value_shape, AbstractValueShapeV0::FiniteSet);
1071        assert_eq!(evaluation.verdict, RefinementVerdictV0::SatisfiedSome);
1072        assert_eq!(
1073            evaluation.predicate_expression_id,
1074            "and(numeric-range:opacity:0..100:%,pseudo-state:opacity::hover)"
1075        );
1076        assert!(
1077            evaluation
1078                .witness
1079                .provenance
1080                .iter()
1081                .any(|entry| entry.source == "numeric-range-interval")
1082        );
1083        assert!(
1084            evaluation
1085                .witness
1086                .provenance
1087                .iter()
1088                .any(|entry| entry.source == "pseudo-state-refinement")
1089        );
1090        assert!(
1091            evaluation
1092                .witness
1093                .provenance
1094                .iter()
1095                .any(|entry| entry.source == "predicate-composition")
1096        );
1097    }
1098
1099    #[test]
1100    fn refinement_context_digest_is_order_stable_and_invalidation_sensitive() {
1101        let range = RefinementPropertyPredicateV0::NumericRange {
1102            property_name: "z-index".to_string(),
1103            min_inclusive: Some(0),
1104            max_inclusive: Some(10),
1105            unit: None,
1106        };
1107        let exact = RefinementPropertyPredicateV0::ExactValue {
1108            property_name: "display".to_string(),
1109            value: "grid".to_string(),
1110        };
1111        let first = summarize_refinement_context_v0(&[range.clone(), exact.clone()]);
1112        let reordered = summarize_refinement_context_v0(&[exact.clone(), range.clone()]);
1113        let changed = summarize_refinement_context_v0(&[
1114            exact,
1115            RefinementPropertyPredicateV0::NumericRange {
1116                property_name: "z-index".to_string(),
1117                min_inclusive: Some(0),
1118                max_inclusive: Some(11),
1119                unit: None,
1120            },
1121        ]);
1122
1123        assert_eq!(first.schema_version, "0");
1124        assert_eq!(first.product, "omena-refinement.context-summary");
1125        assert_eq!(first.predicate_count, 2);
1126        assert!(first.downstream_invalidation_required);
1127        assert_eq!(first.context_digest, reordered.context_digest);
1128        assert_ne!(first.context_digest, changed.context_digest);
1129        assert!(first.witness_provenance_count >= 3);
1130    }
1131
1132    #[test]
1133    fn cascade_dimensional_refinement_bridge_reuses_existing_substrates() {
1134        let members = vec![
1135            CascadeValueFamilyMemberV0 {
1136                context: CascadeContextV0 {
1137                    id: "base".to_string(),
1138                    parent_id: None,
1139                    selectors: vec![":root".to_string()],
1140                    conditions: Vec::new(),
1141                    layers: vec!["tokens".to_string()],
1142                },
1143                value: AbstractPropertyValueV0::Exact {
1144                    property_name: "width".to_string(),
1145                    value: "12px".to_string(),
1146                    pseudo_state: None,
1147                },
1148            },
1149            CascadeValueFamilyMemberV0 {
1150                context: CascadeContextV0 {
1151                    id: "fluid".to_string(),
1152                    parent_id: Some("base".to_string()),
1153                    selectors: vec![":root".to_string()],
1154                    conditions: vec!["@media (orientation: portrait)".to_string()],
1155                    layers: vec!["tokens".to_string()],
1156                },
1157                value: AbstractPropertyValueV0::Exact {
1158                    property_name: "width".to_string(),
1159                    value: "50%".to_string(),
1160                    pseudo_state: None,
1161                },
1162            },
1163            CascadeValueFamilyMemberV0 {
1164                context: CascadeContextV0 {
1165                    id: "unknown".to_string(),
1166                    parent_id: Some("base".to_string()),
1167                    selectors: vec![":root".to_string()],
1168                    conditions: vec!["@container card".to_string()],
1169                    layers: vec!["tokens".to_string()],
1170                },
1171                value: AbstractPropertyValueV0::Top {
1172                    property_name: "width".to_string(),
1173                },
1174            },
1175        ];
1176        let restrictions = derive_cascade_restriction_maps_v0(&members);
1177        let family = summarize_cascade_value_family_v0("width", members, restrictions);
1178        let predicate = RefinementPropertyPredicateV0::NumericRange {
1179            property_name: "width".to_string(),
1180            min_inclusive: Some(0),
1181            max_inclusive: Some(100),
1182            unit: Some("px".to_string()),
1183        };
1184
1185        let bridge = summarize_cascade_dimensional_refinement_bridge_v0(&family, &[predicate]);
1186
1187        assert_eq!(
1188            bridge.product,
1189            "omena-refinement.cascade-dimensional-refinement-bridge"
1190        );
1191        assert_eq!(bridge.claim_level, "m6DimensionalRefinementBridgeSubstrate");
1192        assert_eq!(bridge.cascade_family_product, family.product);
1193        assert_eq!(bridge.context_evaluation_count, 3);
1194        assert_eq!(bridge.restriction_map_count, 2);
1195        assert_eq!(bridge.satisfied_all_context_count, 1);
1196        assert_eq!(bridge.unsatisfiable_context_count, 1);
1197        assert_eq!(bridge.unknown_context_count, 1);
1198        assert_eq!(bridge.witness_provenance_count, 2);
1199        assert!(bridge.uses_existing_abstract_property_value_substrate);
1200        assert!(bridge.uses_existing_cascade_family_substrate);
1201        assert!(bridge.uses_existing_refinement_predicate_substrate);
1202        assert!(!bridge.forks_unit_system);
1203        assert!(!bridge.liquid_haskell_complete);
1204        assert!(!bridge.smt_complete);
1205        assert!(!bridge.theorem_claimed);
1206        assert!(bridge.product_path_evidence_ready);
1207        assert!(!bridge.stronger_type_safety_claim_ready);
1208        assert_eq!(bridge.evaluations[0].context_id, "base");
1209        assert_eq!(
1210            bridge.evaluations[0].combined_verdict,
1211            RefinementVerdictV0::SatisfiedAll
1212        );
1213        assert_eq!(
1214            bridge.evaluations[0].predicate_expression_ids,
1215            vec!["numeric-range:width:0..100:px".to_string()]
1216        );
1217    }
1218}