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