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