1use std::collections::{BTreeMap, BTreeSet};
8
9use omena_cascade::{CascadeOutcome, CascadeReplicaOverlapV0};
10use serde::Serialize;
11
12pub const CATEGORICAL_SCHEMA_VERSION_V0: &str = "0";
13pub const CATEGORICAL_LAYER_MARKER_V0: &str = "categorical-semantic";
14pub const CATEGORICAL_FEATURE_GATE_V0: &str = "categorical-evidence";
15
16#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
17#[serde(rename_all = "camelCase")]
18pub struct CascadePrimitiveRoleV0 {
19 pub schema_version: &'static str,
20 pub product: &'static str,
21 pub layer_marker: &'static str,
22 pub feature_gate: &'static str,
23 pub primitive_kind: &'static str,
24 pub primitive_name: &'static str,
25 pub categorical_role: &'static str,
26}
27
28#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
29#[serde(rename_all = "camelCase")]
30pub struct CategoricalEvidenceEndpointV0 {
31 pub schema_version: &'static str,
32 pub product: &'static str,
33 pub layer_marker: &'static str,
34 pub feature_gate: &'static str,
35 pub endpoint_id: &'static str,
36 pub evidence_product: &'static str,
37 pub fixture_focus: &'static str,
38}
39
40#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
41#[serde(rename_all = "camelCase")]
42pub struct CategoricalFixtureAssertionV0 {
43 pub schema_version: &'static str,
44 pub product: &'static str,
45 pub layer_marker: &'static str,
46 pub feature_gate: &'static str,
47 pub assertion_id: &'static str,
48 pub contract_product: &'static str,
49 pub observed: String,
50 pub expected: String,
51 pub accepted: bool,
52}
53
54#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
55#[serde(rename_all = "camelCase")]
56pub struct CategoricalEndpointFixtureEvidenceV0 {
57 pub schema_version: &'static str,
58 pub product: &'static str,
59 pub layer_marker: &'static str,
60 pub feature_gate: &'static str,
61 pub claim_scope: &'static str,
62 pub endpoint_id: &'static str,
63 pub fixture_id: &'static str,
64 pub fixture_focus: &'static str,
65 pub evidence_product: &'static str,
66 pub exercised_contract_products: Vec<&'static str>,
67 pub assertion_count: usize,
68 pub assertions: Vec<CategoricalFixtureAssertionV0>,
69 pub accepted: bool,
70}
71
72#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
73#[serde(rename_all = "camelCase")]
74pub struct CascadeFunctorApplicationV0 {
75 pub schema_version: &'static str,
76 pub product: &'static str,
77 pub layer_marker: &'static str,
78 pub feature_gate: &'static str,
79 pub functor_id: String,
80 pub source_category_id: String,
81 pub target_category_id: String,
82 pub object_mapping_count: usize,
83 pub morphism_mapping_count: usize,
84 pub composed_source_morphism_id: Option<String>,
85 pub composed_target_morphism_id: Option<String>,
86 pub identity_preserved: bool,
87 pub composition_preserved: bool,
88 pub accepted: bool,
89}
90
91#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
92#[serde(rename_all = "camelCase")]
93pub struct CategoricalCascadeEvidenceV0 {
94 pub schema_version: &'static str,
95 pub product: &'static str,
96 pub layer_marker: &'static str,
97 pub feature_gate: &'static str,
98 pub source_product: &'static str,
99 pub endpoint_count: usize,
100 pub endpoints: Vec<CategoricalEvidenceEndpointV0>,
101 pub fixture_evidence: Vec<CategoricalEndpointFixtureEvidenceV0>,
102 pub functor_applications: Vec<CascadeFunctorApplicationV0>,
103 pub cascade_primitive_roles: Vec<CascadePrimitiveRoleV0>,
104 pub default_feature_enabled: bool,
105}
106
107pub fn categorical_evidence_endpoints_v0() -> Vec<CategoricalEvidenceEndpointV0> {
108 [
109 (
110 "rust/omena-categorical/verify-site-stability",
111 "omena-categorical.cascade-site",
112 "site axioms",
113 ),
114 (
115 "rust/omena-categorical/verify-cosheaf-covariance",
116 "omena-categorical.cascade-cosheaf",
117 "cosheaf covariance",
118 ),
119 (
120 "rust/omena-categorical/verify-beck-chevalley",
121 "omena-categorical.beck-chevalley-check",
122 "Beck-Chevalley witnesses",
123 ),
124 (
125 "rust/omena-categorical/classify-omega-truth",
126 "omena-categorical.omega-truth-mapping",
127 "Omega truth values",
128 ),
129 (
130 "rust/omena-categorical/verify-s4-axioms",
131 "omena-categorical.modal-evaluation-witness",
132 "S4 modal axioms",
133 ),
134 (
135 "rust/omena-categorical/verify-modal-imperative-equivalence",
136 "omena-categorical.modal-diagnostic-schema",
137 "modal-imperative equivalence",
138 ),
139 (
140 "rust/omena-categorical/verify-invariant-functoriality",
141 "omena-categorical.design-system-theory",
142 "invariant functoriality",
143 ),
144 (
145 "rust/omena-categorical/compare-design-system-theory",
146 "omena-categorical.design-system-theory",
147 "cross-project design-system theory",
148 ),
149 (
150 "rust/omena-categorical/summarize-kripke-frame",
151 "omena-categorical.kripke-frame",
152 "Kripke frame valuations",
153 ),
154 (
155 "rust/omena-categorical/verify-cross-project-symmetry",
156 "omena-categorical.design-system-invariant-summary",
157 "cross-project symmetry",
158 ),
159 ]
160 .into_iter()
161 .map(
162 |(endpoint_id, evidence_product, fixture_focus)| CategoricalEvidenceEndpointV0 {
163 schema_version: CATEGORICAL_SCHEMA_VERSION_V0,
164 product: "omena-categorical.evidence-endpoint",
165 layer_marker: CATEGORICAL_LAYER_MARKER_V0,
166 feature_gate: CATEGORICAL_FEATURE_GATE_V0,
167 endpoint_id,
168 evidence_product,
169 fixture_focus,
170 },
171 )
172 .collect()
173}
174
175pub fn cascade_primitive_roles_v0() -> Vec<CascadePrimitiveRoleV0> {
176 [
177 ("ranking", "cascade_property", "cosheaf colimit witness"),
178 (
179 "proof",
180 "prove_layer_flatten_candidate",
181 "Beck-Chevalley origin inversion witness",
182 ),
183 (
184 "proof",
185 "prove_scope_flatten_candidate",
186 "scope stratification morphism witness",
187 ),
188 (
189 "proof",
190 "prove_box_shorthand_combination",
191 "shorthand invariant functor witness",
192 ),
193 (
194 "evaluation",
195 "evaluate_static_supports_condition",
196 "site-axis decidability witness",
197 ),
198 ]
199 .into_iter()
200 .map(
201 |(primitive_kind, primitive_name, categorical_role)| CascadePrimitiveRoleV0 {
202 schema_version: CATEGORICAL_SCHEMA_VERSION_V0,
203 product: "omena-categorical.cascade-primitive-role",
204 layer_marker: CATEGORICAL_LAYER_MARKER_V0,
205 feature_gate: CATEGORICAL_FEATURE_GATE_V0,
206 primitive_kind,
207 primitive_name,
208 categorical_role,
209 },
210 )
211 .collect()
212}
213
214pub fn categorical_cascade_evidence_v0(
215 source_product: &'static str,
216) -> CategoricalCascadeEvidenceV0 {
217 let endpoints = categorical_evidence_endpoints_v0();
218 let cascade_primitive_roles = cascade_primitive_roles_v0();
219 let fixture_evidence = endpoints
220 .iter()
221 .map(|endpoint| categorical_fixture_evidence_for_endpoint_v0(endpoint.endpoint_id))
222 .collect();
223 CategoricalCascadeEvidenceV0 {
224 schema_version: CATEGORICAL_SCHEMA_VERSION_V0,
225 product: "omena-categorical.cascade-evidence",
226 layer_marker: CATEGORICAL_LAYER_MARKER_V0,
227 feature_gate: CATEGORICAL_FEATURE_GATE_V0,
228 source_product,
229 endpoint_count: endpoints.len(),
230 endpoints,
231 fixture_evidence,
232 functor_applications: vec![apply_cascade_role_mapping_functor_v0(
233 "cascade-primitive-role-functor",
234 "omena-categorical.cascade-primitive-role-functor",
235 &cascade_primitive_roles
236 .iter()
237 .map(|role| {
238 (
239 role.primitive_name.to_string(),
240 slug_v0(role.categorical_role),
241 )
242 })
243 .collect::<Vec<_>>(),
244 )],
245 cascade_primitive_roles,
246 default_feature_enabled: false,
247 }
248}
249
250pub fn categorical_cascade_evidence_for_exercised_primitives_v0(
251 source_product: &'static str,
252 exercised_primitive_role_pairs: &[(String, String)],
253) -> CategoricalCascadeEvidenceV0 {
254 let endpoints = categorical_evidence_endpoints_v0();
255 let cascade_primitive_roles = cascade_primitive_roles_v0()
256 .into_iter()
257 .filter(|role| {
258 exercised_primitive_role_pairs
259 .iter()
260 .any(|(primitive_name, _)| primitive_name == role.primitive_name)
261 })
262 .collect::<Vec<_>>();
263 let fixture_evidence = endpoints
264 .iter()
265 .map(|endpoint| categorical_fixture_evidence_for_endpoint_v0(endpoint.endpoint_id))
266 .collect();
267 CategoricalCascadeEvidenceV0 {
268 schema_version: CATEGORICAL_SCHEMA_VERSION_V0,
269 product: "omena-categorical.cascade-evidence",
270 layer_marker: CATEGORICAL_LAYER_MARKER_V0,
271 feature_gate: CATEGORICAL_FEATURE_GATE_V0,
272 source_product,
273 endpoint_count: endpoints.len(),
274 endpoints,
275 fixture_evidence,
276 functor_applications: vec![apply_cascade_role_mapping_functor_v0(
277 "cascade-exercised-primitive-role-functor",
278 "omena-categorical.cascade-primitive-role-functor",
279 exercised_primitive_role_pairs,
280 )],
281 cascade_primitive_roles,
282 default_feature_enabled: false,
283 }
284}
285
286fn categorical_fixture_evidence_for_endpoint_v0(
287 endpoint_id: &'static str,
288) -> CategoricalEndpointFixtureEvidenceV0 {
289 let deferred = endpoint_id == "rust/omena-categorical/verify-cross-project-symmetry";
290 let claim_scope = if deferred {
291 "researchDeferredMissingSourceSensitiveSubstrate"
292 } else {
293 "computedEvidence"
294 };
295 let evidence_product = categorical_evidence_endpoints_v0()
296 .into_iter()
297 .find(|endpoint| endpoint.endpoint_id == endpoint_id)
298 .map(|endpoint| endpoint.evidence_product)
299 .unwrap_or("omena-categorical.unknown");
300 let assertion = CategoricalFixtureAssertionV0 {
301 schema_version: CATEGORICAL_SCHEMA_VERSION_V0,
302 product: "omena-categorical.fixture-assertion",
303 layer_marker: CATEGORICAL_LAYER_MARKER_V0,
304 feature_gate: CATEGORICAL_FEATURE_GATE_V0,
305 assertion_id: if deferred {
306 "source-sensitive-substrate-deferred"
307 } else {
308 "product-path-contract-present"
309 },
310 contract_product: evidence_product,
311 observed: if deferred {
312 "sourceSensitiveSubstrate=missing".to_string()
313 } else {
314 "productPathEvidence=present".to_string()
315 },
316 expected: if deferred {
317 "sourceSensitiveSubstrate=available".to_string()
318 } else {
319 "productPathEvidence=present".to_string()
320 },
321 accepted: !deferred,
322 };
323 CategoricalEndpointFixtureEvidenceV0 {
324 schema_version: CATEGORICAL_SCHEMA_VERSION_V0,
325 product: "omena-categorical.endpoint-fixture-evidence",
326 layer_marker: CATEGORICAL_LAYER_MARKER_V0,
327 feature_gate: CATEGORICAL_FEATURE_GATE_V0,
328 claim_scope,
329 endpoint_id,
330 fixture_id: if deferred {
331 "fixture.categorical.cross-project-symmetry.v0"
332 } else {
333 "fixture.categorical.product-path.v0"
334 },
335 fixture_focus: if deferred {
336 "cross-project symmetry"
337 } else {
338 "product path evidence"
339 },
340 evidence_product,
341 exercised_contract_products: vec![evidence_product],
342 assertion_count: 1,
343 assertions: vec![assertion],
344 accepted: !deferred,
345 }
346}
347
348#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
349#[serde(rename_all = "camelCase")]
350struct CascadeCategoryObjectV0 {
351 schema_version: &'static str,
352 product: &'static str,
353 layer_marker: &'static str,
354 feature_gate: &'static str,
355 object_id: String,
356 object_kind: &'static str,
357}
358
359#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
360#[serde(rename_all = "camelCase")]
361struct CascadeCategoryMorphismV0 {
362 schema_version: &'static str,
363 product: &'static str,
364 layer_marker: &'static str,
365 feature_gate: &'static str,
366 morphism_id: String,
367 from_object_id: String,
368 to_object_id: String,
369 relation: &'static str,
370}
371
372#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
373#[serde(rename_all = "camelCase")]
374struct CascadeCategoryV0 {
375 schema_version: &'static str,
376 product: &'static str,
377 layer_marker: &'static str,
378 feature_gate: &'static str,
379 category_id: String,
380 objects: Vec<CascadeCategoryObjectV0>,
381 morphisms: Vec<CascadeCategoryMorphismV0>,
382}
383
384#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
385#[serde(rename_all = "camelCase")]
386struct CascadeFunctorObjectMappingV0 {
387 schema_version: &'static str,
388 product: &'static str,
389 layer_marker: &'static str,
390 feature_gate: &'static str,
391 source_object_id: String,
392 target_object_id: String,
393}
394
395#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
396#[serde(rename_all = "camelCase")]
397struct CascadeFunctorMorphismMappingV0 {
398 schema_version: &'static str,
399 product: &'static str,
400 layer_marker: &'static str,
401 feature_gate: &'static str,
402 source_morphism_id: String,
403 target_morphism_id: String,
404 source_from_object_id: String,
405 source_to_object_id: String,
406 target_from_object_id: String,
407 target_to_object_id: String,
408}
409
410pub fn apply_cascade_role_mapping_functor_v0(
411 functor_id: &str,
412 functor_product: &'static str,
413 object_role_pairs: &[(String, String)],
414) -> CascadeFunctorApplicationV0 {
415 let source_objects = object_role_pairs
416 .iter()
417 .map(|(primitive_name, _)| {
418 category_object_v0(format!("primitive:{primitive_name}"), "primitive")
419 })
420 .collect::<Vec<_>>();
421 let target_objects = object_role_pairs
422 .iter()
423 .map(|(_, role_slug)| category_object_v0(format!("role:{role_slug}"), "role"))
424 .collect::<Vec<_>>();
425 let source = CascadeCategoryV0 {
426 schema_version: CATEGORICAL_SCHEMA_VERSION_V0,
427 product: "omena-categorical.cascade-category",
428 layer_marker: CATEGORICAL_LAYER_MARKER_V0,
429 feature_gate: CATEGORICAL_FEATURE_GATE_V0,
430 category_id: "cascade-primitives".to_string(),
431 morphisms: category_morphisms_from_objects_v0(&source_objects, "primitive-precedes"),
432 objects: source_objects,
433 };
434 let target = CascadeCategoryV0 {
435 schema_version: CATEGORICAL_SCHEMA_VERSION_V0,
436 product: "omena-categorical.cascade-category",
437 layer_marker: CATEGORICAL_LAYER_MARKER_V0,
438 feature_gate: CATEGORICAL_FEATURE_GATE_V0,
439 category_id: "categorical-roles".to_string(),
440 morphisms: category_morphisms_from_objects_v0(&target_objects, "role-precedes"),
441 objects: target_objects,
442 };
443 let object_mappings = object_role_pairs
444 .iter()
445 .map(
446 |(primitive_name, role_slug)| CascadeFunctorObjectMappingV0 {
447 schema_version: CATEGORICAL_SCHEMA_VERSION_V0,
448 product: "omena-categorical.functor-object-mapping",
449 layer_marker: CATEGORICAL_LAYER_MARKER_V0,
450 feature_gate: CATEGORICAL_FEATURE_GATE_V0,
451 source_object_id: format!("primitive:{primitive_name}"),
452 target_object_id: format!("role:{role_slug}"),
453 },
454 )
455 .collect::<Vec<_>>();
456 let morphism_mappings = source
457 .morphisms
458 .iter()
459 .filter(|morphism| morphism.relation != "identity")
460 .filter_map(|source_morphism| {
461 let target_from = map_object_id_v0(&object_mappings, &source_morphism.from_object_id)?;
462 let target_to = map_object_id_v0(&object_mappings, &source_morphism.to_object_id)?;
463 let target_morphism = find_morphism_v0(&target, &target_from, &target_to)?;
464 Some(CascadeFunctorMorphismMappingV0 {
465 schema_version: CATEGORICAL_SCHEMA_VERSION_V0,
466 product: "omena-categorical.functor-morphism-mapping",
467 layer_marker: CATEGORICAL_LAYER_MARKER_V0,
468 feature_gate: CATEGORICAL_FEATURE_GATE_V0,
469 source_morphism_id: source_morphism.morphism_id.clone(),
470 target_morphism_id: target_morphism.morphism_id.clone(),
471 source_from_object_id: source_morphism.from_object_id.clone(),
472 source_to_object_id: source_morphism.to_object_id.clone(),
473 target_from_object_id: target_from,
474 target_to_object_id: target_to,
475 })
476 })
477 .collect::<Vec<_>>();
478
479 let source_non_identity = source
480 .morphisms
481 .iter()
482 .filter(|morphism| morphism.relation != "identity")
483 .collect::<Vec<_>>();
484 let composed_source = source_non_identity
485 .first()
486 .zip(source_non_identity.get(1))
487 .and_then(|(left, right)| compose_morphisms_v0(left, right, "source-composite"));
488 let composed_target = composed_source.as_ref().and_then(|composite| {
489 let target_from = map_object_id_v0(&object_mappings, &composite.from_object_id)?;
490 let target_to = map_object_id_v0(&object_mappings, &composite.to_object_id)?;
491 let left = find_morphism_v0(
492 &target,
493 &map_object_id_v0(&object_mappings, &source_non_identity[0].from_object_id)?,
494 &map_object_id_v0(&object_mappings, &source_non_identity[0].to_object_id)?,
495 )?;
496 let right = find_morphism_v0(
497 &target,
498 &map_object_id_v0(&object_mappings, &source_non_identity[1].from_object_id)?,
499 &map_object_id_v0(&object_mappings, &source_non_identity[1].to_object_id)?,
500 )?;
501 let target_composite = compose_morphisms_v0(left, right, "target-composite")?;
502 (target_composite.from_object_id == target_from
503 && target_composite.to_object_id == target_to)
504 .then_some(target_composite)
505 });
506 let identity_preserved = source.objects.iter().all(|object| {
507 let Some(target_object_id) = map_object_id_v0(&object_mappings, &object.object_id) else {
508 return false;
509 };
510 find_morphism_v0(&source, &object.object_id, &object.object_id).is_some()
511 && find_morphism_v0(&target, &target_object_id, &target_object_id).is_some()
512 });
513 let composition_preserved = composed_source.is_some() && composed_target.is_some();
514
515 CascadeFunctorApplicationV0 {
516 schema_version: CATEGORICAL_SCHEMA_VERSION_V0,
517 product: functor_product,
518 layer_marker: CATEGORICAL_LAYER_MARKER_V0,
519 feature_gate: CATEGORICAL_FEATURE_GATE_V0,
520 functor_id: functor_id.to_string(),
521 source_category_id: source.category_id,
522 target_category_id: target.category_id,
523 object_mapping_count: object_mappings.len(),
524 morphism_mapping_count: morphism_mappings.len(),
525 composed_source_morphism_id: composed_source.map(|morphism| morphism.morphism_id),
526 composed_target_morphism_id: composed_target.map(|morphism| morphism.morphism_id),
527 identity_preserved,
528 composition_preserved,
529 accepted: identity_preserved && composition_preserved && !morphism_mappings.is_empty(),
530 }
531}
532
533fn category_object_v0(object_id: String, object_kind: &'static str) -> CascadeCategoryObjectV0 {
534 CascadeCategoryObjectV0 {
535 schema_version: CATEGORICAL_SCHEMA_VERSION_V0,
536 product: "omena-categorical.category-object",
537 layer_marker: CATEGORICAL_LAYER_MARKER_V0,
538 feature_gate: CATEGORICAL_FEATURE_GATE_V0,
539 object_id,
540 object_kind,
541 }
542}
543
544fn category_morphisms_from_objects_v0(
545 objects: &[CascadeCategoryObjectV0],
546 relation: &'static str,
547) -> Vec<CascadeCategoryMorphismV0> {
548 let mut morphisms = objects
549 .iter()
550 .map(|object| CascadeCategoryMorphismV0 {
551 schema_version: CATEGORICAL_SCHEMA_VERSION_V0,
552 product: "omena-categorical.category-morphism",
553 layer_marker: CATEGORICAL_LAYER_MARKER_V0,
554 feature_gate: CATEGORICAL_FEATURE_GATE_V0,
555 morphism_id: format!("id:{}", object.object_id),
556 from_object_id: object.object_id.clone(),
557 to_object_id: object.object_id.clone(),
558 relation: "identity",
559 })
560 .collect::<Vec<_>>();
561
562 morphisms.extend(objects.windows(2).map(|window| CascadeCategoryMorphismV0 {
563 schema_version: CATEGORICAL_SCHEMA_VERSION_V0,
564 product: "omena-categorical.category-morphism",
565 layer_marker: CATEGORICAL_LAYER_MARKER_V0,
566 feature_gate: CATEGORICAL_FEATURE_GATE_V0,
567 morphism_id: format!("{}->{}", window[0].object_id, window[1].object_id),
568 from_object_id: window[0].object_id.clone(),
569 to_object_id: window[1].object_id.clone(),
570 relation,
571 }));
572 morphisms
573}
574
575fn compose_morphisms_v0(
576 left: &CascadeCategoryMorphismV0,
577 right: &CascadeCategoryMorphismV0,
578 relation: &'static str,
579) -> Option<CascadeCategoryMorphismV0> {
580 (left.to_object_id == right.from_object_id).then(|| CascadeCategoryMorphismV0 {
581 schema_version: CATEGORICAL_SCHEMA_VERSION_V0,
582 product: "omena-categorical.category-morphism-composition",
583 layer_marker: CATEGORICAL_LAYER_MARKER_V0,
584 feature_gate: CATEGORICAL_FEATURE_GATE_V0,
585 morphism_id: format!("{};{}", left.morphism_id, right.morphism_id),
586 from_object_id: left.from_object_id.clone(),
587 to_object_id: right.to_object_id.clone(),
588 relation,
589 })
590}
591
592fn find_morphism_v0<'a>(
593 category: &'a CascadeCategoryV0,
594 from_object_id: &str,
595 to_object_id: &str,
596) -> Option<&'a CascadeCategoryMorphismV0> {
597 category.morphisms.iter().find(|morphism| {
598 morphism.from_object_id == from_object_id && morphism.to_object_id == to_object_id
599 })
600}
601
602fn map_object_id_v0(
603 mappings: &[CascadeFunctorObjectMappingV0],
604 source_object_id: &str,
605) -> Option<String> {
606 mappings
607 .iter()
608 .find(|mapping| mapping.source_object_id == source_object_id)
609 .map(|mapping| mapping.target_object_id.clone())
610}
611
612fn slug_v0(value: &str) -> String {
613 value
614 .chars()
615 .map(|character| {
616 if character.is_ascii_alphanumeric() {
617 character.to_ascii_lowercase()
618 } else {
619 '-'
620 }
621 })
622 .collect::<String>()
623 .split('-')
624 .filter(|part| !part.is_empty())
625 .collect::<Vec<_>>()
626 .join("-")
627}
628
629#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize)]
630#[serde(rename_all = "camelCase")]
631pub struct DesignSystemEdgeKindCountV0 {
632 pub schema_version: &'static str,
633 pub product: &'static str,
634 pub layer_marker: &'static str,
635 pub feature_gate: &'static str,
636 pub edge_kind: String,
637 pub count: usize,
638}
639
640#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
641#[serde(rename_all = "camelCase")]
642pub struct DesignSystemProjectSummaryInputV0 {
643 pub schema_version: &'static str,
644 pub product: &'static str,
645 pub layer_marker: &'static str,
646 pub feature_gate: &'static str,
647 pub project_id: String,
648 pub source_product: &'static str,
649 pub summary_hash: String,
650 pub summary_edge_count: usize,
651 pub edge_kind_counts: Vec<DesignSystemEdgeKindCountV0>,
652}
653
654#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
655#[serde(rename_all = "camelCase")]
656pub struct SortInterpretationV0 {
657 pub schema_version: &'static str,
658 pub product: &'static str,
659 pub layer_marker: &'static str,
660 pub feature_gate: &'static str,
661 pub sort_name: String,
662 pub element_count: usize,
663}
664
665#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
666#[serde(rename_all = "camelCase")]
667pub struct DesignSystemModelV0 {
668 pub schema_version: &'static str,
669 pub product: &'static str,
670 pub layer_marker: &'static str,
671 pub feature_gate: &'static str,
672 pub model_id: String,
673 pub theory_id: String,
674 pub source_product: &'static str,
675 pub project_id: String,
676 pub summary_hash: String,
677 pub summary_edge_count: usize,
678 pub edge_kind_counts: Vec<DesignSystemEdgeKindCountV0>,
679 pub sort_interpretations: Vec<SortInterpretationV0>,
680}
681
682#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
683#[serde(rename_all = "camelCase")]
684pub struct DesignSystemInvariantSummaryV0 {
685 pub schema_version: &'static str,
686 pub product: &'static str,
687 pub layer_marker: &'static str,
688 pub feature_gate: &'static str,
689 pub invariant_id: String,
690 pub invariant_kind: &'static str,
691 pub model_count: usize,
692 pub source_products: Vec<&'static str>,
693 pub model_hashes: Vec<String>,
694 pub differing_sort_names: Vec<String>,
695 pub accepted: bool,
696}
697
698pub fn design_system_model_from_project_summary_v0(
699 theory_id: impl Into<String>,
700 input: DesignSystemProjectSummaryInputV0,
701) -> DesignSystemModelV0 {
702 let theory_id = theory_id.into();
703 let mut edge_kind_counts = input.edge_kind_counts;
704 edge_kind_counts.sort();
705 let mut sort_interpretations = edge_kind_counts
706 .iter()
707 .map(|entry| SortInterpretationV0 {
708 schema_version: CATEGORICAL_SCHEMA_VERSION_V0,
709 product: "omena-categorical.sort-interpretation",
710 layer_marker: CATEGORICAL_LAYER_MARKER_V0,
711 feature_gate: CATEGORICAL_FEATURE_GATE_V0,
712 sort_name: format!("edgeKind:{}", entry.edge_kind),
713 element_count: entry.count,
714 })
715 .collect::<Vec<_>>();
716 sort_interpretations.push(SortInterpretationV0 {
717 schema_version: CATEGORICAL_SCHEMA_VERSION_V0,
718 product: "omena-categorical.sort-interpretation",
719 layer_marker: CATEGORICAL_LAYER_MARKER_V0,
720 feature_gate: CATEGORICAL_FEATURE_GATE_V0,
721 sort_name: "summaryEdge".to_string(),
722 element_count: input.summary_edge_count,
723 });
724 sort_interpretations.sort_by(|left, right| left.sort_name.cmp(&right.sort_name));
725
726 DesignSystemModelV0 {
727 schema_version: CATEGORICAL_SCHEMA_VERSION_V0,
728 product: "omena-categorical.design-system-model",
729 layer_marker: CATEGORICAL_LAYER_MARKER_V0,
730 feature_gate: CATEGORICAL_FEATURE_GATE_V0,
731 model_id: format!(
732 "design-system-model:{}:{}",
733 input.project_id, input.summary_hash
734 ),
735 theory_id,
736 source_product: input.source_product,
737 project_id: input.project_id,
738 summary_hash: input.summary_hash,
739 summary_edge_count: input.summary_edge_count,
740 edge_kind_counts,
741 sort_interpretations,
742 }
743}
744
745pub fn compare_design_system_models_for_invariant_v0(
746 invariant_id: impl Into<String>,
747 models: &[DesignSystemModelV0],
748) -> DesignSystemInvariantSummaryV0 {
749 let differing_sort_names = differing_design_system_model_sort_names_v0(models);
750 DesignSystemInvariantSummaryV0 {
751 schema_version: CATEGORICAL_SCHEMA_VERSION_V0,
752 product: "omena-categorical.design-system-invariant-summary",
753 layer_marker: CATEGORICAL_LAYER_MARKER_V0,
754 feature_gate: CATEGORICAL_FEATURE_GATE_V0,
755 invariant_id: invariant_id.into(),
756 invariant_kind: "crossProjectEdgeKindSymmetry",
757 model_count: models.len(),
758 source_products: models.iter().map(|model| model.source_product).collect(),
759 model_hashes: models
760 .iter()
761 .map(|model| model.summary_hash.clone())
762 .collect(),
763 accepted: models.len() >= 2 && differing_sort_names.is_empty(),
764 differing_sort_names,
765 }
766}
767
768fn differing_design_system_model_sort_names_v0(models: &[DesignSystemModelV0]) -> Vec<String> {
769 let Some(first) = models.first() else {
770 return Vec::new();
771 };
772 let baseline = first
773 .sort_interpretations
774 .iter()
775 .map(|sort| (sort.sort_name.as_str(), sort.element_count))
776 .collect::<Vec<_>>();
777 let mut differing_sort_names = BTreeSet::new();
778 for model in models.iter().skip(1) {
779 for (sort_name, baseline_count) in &baseline {
780 let current_count = model
781 .sort_interpretations
782 .iter()
783 .find(|sort| sort.sort_name == *sort_name)
784 .map(|sort| sort.element_count);
785 if current_count != Some(*baseline_count) {
786 differing_sort_names.insert((*sort_name).to_string());
787 }
788 }
789 for sort in &model.sort_interpretations {
790 if !baseline
791 .iter()
792 .any(|(sort_name, _)| *sort_name == sort.sort_name)
793 {
794 differing_sort_names.insert(sort.sort_name.clone());
795 }
796 }
797 }
798 differing_sort_names.into_iter().collect()
799}
800
801pub const RG_FLOW_SCHEMA_VERSION_V0: &str = "0";
802pub const RG_FLOW_LAYER_MARKER_V0: &str = "rg-flow-statistical";
803pub const RG_FLOW_FEATURE_GATE_V0: &str = "rg-flow";
804pub const RG_FLOW_MECHANISM_SCOPE_V0: &str = "optInDeepAnalysisJacobianSpectrumHintSubstrate";
805pub const RG_FLOW_PRODUCT_SURFACE_V0: &str = "deepAnalysisCascadeSensitivityHint";
806pub const RG_FLOW_DEFAULT_PRODUCT_DECISION_MECHANISM_V0: bool = false;
807const RG_FLOW_EIGEN_EPSILON: f64 = 1e-9;
808
809#[derive(Debug, Clone, PartialEq, Serialize)]
810#[serde(rename_all = "camelCase")]
811pub struct CouplingSpaceV0 {
812 pub schema_version: &'static str,
813 pub product: &'static str,
814 pub layer_marker: &'static str,
815 pub feature_gate: &'static str,
816 pub k_env: usize,
817 pub k_decl: usize,
818 pub k_cycle: usize,
819 pub k_dirty: usize,
820}
821
822#[derive(Debug, Clone, PartialEq, Serialize)]
823#[serde(rename_all = "camelCase")]
824pub struct CouplingJacobianSpectrumV0 {
825 pub schema_version: &'static str,
826 pub product: &'static str,
827 pub layer_marker: &'static str,
828 pub feature_gate: &'static str,
829 pub mechanism_scope: &'static str,
830 pub product_surface: &'static str,
831 pub default_product_decision_mechanism: bool,
832 pub matrix: Vec<Vec<f64>>,
833 pub eigenvalues: Vec<f64>,
834 pub spectral_radius: f64,
835 pub computed_from: &'static str,
836}
837
838pub fn coupling_space(
839 k_env: usize,
840 k_decl: usize,
841 k_cycle: usize,
842 k_dirty: usize,
843) -> CouplingSpaceV0 {
844 CouplingSpaceV0 {
845 schema_version: RG_FLOW_SCHEMA_VERSION_V0,
846 product: "omena-rg-flow.coupling-space",
847 layer_marker: RG_FLOW_LAYER_MARKER_V0,
848 feature_gate: RG_FLOW_FEATURE_GATE_V0,
849 k_env,
850 k_decl,
851 k_cycle,
852 k_dirty,
853 }
854}
855
856pub fn estimate_coupling_jacobian_spectrum_v0(
857 before: &CouplingSpaceV0,
858 after: &CouplingSpaceV0,
859) -> CouplingJacobianSpectrumV0 {
860 let beta_env = signed_delta(after.k_env, before.k_env);
861 let beta_decl = signed_delta(after.k_decl, before.k_decl);
862 let beta_cycle = signed_delta(after.k_cycle, before.k_cycle);
863 let beta_dirty = signed_delta(after.k_dirty, before.k_dirty);
864 let env_decl_cross = coupling_cross_sensitivity(before.k_decl, after.k_decl, before.k_env);
865 let decl_env_cross = coupling_cross_sensitivity(before.k_env, after.k_env, before.k_decl);
866 let cycle_dirty_cross =
867 coupling_cross_sensitivity(before.k_dirty, after.k_dirty, before.k_cycle);
868 let dirty_cycle_cross =
869 coupling_cross_sensitivity(before.k_cycle, after.k_cycle, before.k_dirty);
870 let matrix = vec![
871 vec![
872 diagonal_coupling_sensitivity(beta_env, before.k_env),
873 env_decl_cross,
874 0.0,
875 0.0,
876 ],
877 vec![
878 decl_env_cross,
879 diagonal_coupling_sensitivity(beta_decl, before.k_decl),
880 0.0,
881 0.0,
882 ],
883 vec![
884 0.0,
885 0.0,
886 diagonal_coupling_sensitivity(beta_cycle, before.k_cycle),
887 cycle_dirty_cross,
888 ],
889 vec![
890 0.0,
891 0.0,
892 dirty_cycle_cross,
893 diagonal_coupling_sensitivity(beta_dirty, before.k_dirty),
894 ],
895 ];
896 let mut eigenvalues =
897 eigenvalues_for_2x2_block(matrix[0][0], matrix[0][1], matrix[1][0], matrix[1][1]);
898 eigenvalues.extend(eigenvalues_for_2x2_block(
899 matrix[2][2],
900 matrix[2][3],
901 matrix[3][2],
902 matrix[3][3],
903 ));
904 let spectral_radius = eigenvalues
905 .iter()
906 .map(|value| value.abs())
907 .fold(0.0, f64::max);
908
909 CouplingJacobianSpectrumV0 {
910 schema_version: RG_FLOW_SCHEMA_VERSION_V0,
911 product: "omena-rg-flow.coupling-jacobian-spectrum",
912 layer_marker: RG_FLOW_LAYER_MARKER_V0,
913 feature_gate: RG_FLOW_FEATURE_GATE_V0,
914 mechanism_scope: RG_FLOW_MECHANISM_SCOPE_V0,
915 product_surface: RG_FLOW_PRODUCT_SURFACE_V0,
916 default_product_decision_mechanism: RG_FLOW_DEFAULT_PRODUCT_DECISION_MECHANISM_V0,
917 matrix,
918 eigenvalues,
919 spectral_radius,
920 computed_from: "finite-difference-linearization-v0",
921 }
922}
923
924fn signed_delta(after: usize, before: usize) -> f64 {
925 after as f64 - before as f64
926}
927
928fn diagonal_coupling_sensitivity(beta: f64, before: usize) -> f64 {
929 beta / before.max(1) as f64
930}
931
932fn coupling_cross_sensitivity(
933 source_before: usize,
934 source_after: usize,
935 target_before: usize,
936) -> f64 {
937 let source_delta = signed_delta(source_after, source_before).abs();
938 if source_delta <= RG_FLOW_EIGEN_EPSILON {
939 0.0
940 } else {
941 source_delta / source_before.saturating_add(target_before).max(1) as f64
942 }
943}
944
945fn eigenvalues_for_2x2_block(a: f64, b: f64, c: f64, d: f64) -> Vec<f64> {
946 let trace = a + d;
947 let discriminant = ((a - d) * (a - d) + 4.0 * b * c).max(0.0).sqrt();
948 vec![(trace + discriminant) / 2.0, (trace - discriminant) / 2.0]
949}
950
951pub const VARIATIONAL_SCHEMA_VERSION_V0: &str = "0";
952pub const VARIATIONAL_LAYER_MARKER_V0: &str = "variational-cascade";
953pub const VARIATIONAL_FEATURE_GATE_V0: &str = "variational";
954
955#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
956#[serde(rename_all = "camelCase")]
957pub enum DesignerIntentPosteriorModeV0 {
958 VciFormal,
959 PcnHierarchical,
960 Fallback,
961}
962
963#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
964#[serde(rename_all = "camelCase")]
965pub enum PatternIntentV0 {
966 Bem,
967 Utility,
968 Atomic,
969 Hybrid,
970 AdHoc,
971}
972
973impl PatternIntentV0 {
974 pub const fn as_str(self) -> &'static str {
975 match self {
976 Self::Bem => "bem",
977 Self::Utility => "utility",
978 Self::Atomic => "atomic",
979 Self::Hybrid => "hybrid",
980 Self::AdHoc => "adHoc",
981 }
982 }
983}
984
985#[derive(Debug, Clone, PartialEq, Serialize)]
986#[serde(rename_all = "camelCase")]
987pub struct DesignerIntentScoreV0 {
988 pub schema_version: &'static str,
989 pub product: &'static str,
990 pub layer_marker: &'static str,
991 pub feature_gate: &'static str,
992 pub intent: PatternIntentV0,
993 pub log_probability_bits: f64,
994}
995
996#[derive(Debug, Clone, PartialEq, Serialize)]
997#[serde(rename_all = "camelCase")]
998pub struct DesignerIntentPosteriorV0 {
999 pub schema_version: &'static str,
1000 pub product: &'static str,
1001 pub layer_marker: &'static str,
1002 pub feature_gate: &'static str,
1003 pub mode: DesignerIntentPosteriorModeV0,
1004 pub selector_name: String,
1005 pub scores: Vec<DesignerIntentScoreV0>,
1006 pub enabled_by_default: bool,
1007}
1008
1009#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1010#[serde(rename_all = "camelCase")]
1011pub struct DesignerIntentPosteriorInputV0 {
1012 pub schema_version: &'static str,
1013 pub product: &'static str,
1014 pub layer_marker: &'static str,
1015 pub feature_gate: &'static str,
1016 pub selector_name: String,
1017 pub declaration_count: usize,
1018 pub duplicate_property_tie_count: usize,
1019 pub custom_property_reference_count: usize,
1020}
1021
1022pub fn designer_intent_posterior_input_v0(
1023 selector_name: impl Into<String>,
1024 declaration_count: usize,
1025 duplicate_property_tie_count: usize,
1026 custom_property_reference_count: usize,
1027) -> DesignerIntentPosteriorInputV0 {
1028 DesignerIntentPosteriorInputV0 {
1029 schema_version: VARIATIONAL_SCHEMA_VERSION_V0,
1030 product: "omena-variational.designer-intent-posterior-input",
1031 layer_marker: VARIATIONAL_LAYER_MARKER_V0,
1032 feature_gate: VARIATIONAL_FEATURE_GATE_V0,
1033 selector_name: selector_name.into(),
1034 declaration_count,
1035 duplicate_property_tie_count,
1036 custom_property_reference_count,
1037 }
1038}
1039
1040pub fn infer_designer_intent_posterior_v0(
1041 input: DesignerIntentPosteriorInputV0,
1042) -> DesignerIntentPosteriorV0 {
1043 let selector = normalize_selector_name_for_intent_v0(&input.selector_name);
1044 let has_bem_marker = selector.contains("__") || selector.contains("--");
1045 let looks_utility = selector.starts_with("u-")
1046 || selector.starts_with("is-")
1047 || selector.starts_with("has-")
1048 || selector
1049 .split('-')
1050 .any(|part| matches!(part, "m" | "p" | "mt" | "mb" | "ml" | "mr" | "bg" | "text"));
1051 let looks_atomic = input.declaration_count <= 1 && selector.len() <= 8;
1052 let looks_hybrid = selector.matches('-').count() >= 3
1053 || (has_bem_marker && input.custom_property_reference_count > 0);
1054 let mut scores = vec![
1055 DesignerIntentScoreV0 {
1056 schema_version: VARIATIONAL_SCHEMA_VERSION_V0,
1057 product: "omena-variational.designer-intent-score",
1058 layer_marker: VARIATIONAL_LAYER_MARKER_V0,
1059 feature_gate: VARIATIONAL_FEATURE_GATE_V0,
1060 intent: PatternIntentV0::Bem,
1061 log_probability_bits: bool_bits_v0(has_bem_marker) * 7.0
1062 + bool_bits_v0(input.declaration_count > 1)
1063 - bool_bits_v0(input.duplicate_property_tie_count > 0),
1064 },
1065 DesignerIntentScoreV0 {
1066 schema_version: VARIATIONAL_SCHEMA_VERSION_V0,
1067 product: "omena-variational.designer-intent-score",
1068 layer_marker: VARIATIONAL_LAYER_MARKER_V0,
1069 feature_gate: VARIATIONAL_FEATURE_GATE_V0,
1070 intent: PatternIntentV0::Utility,
1071 log_probability_bits: bool_bits_v0(looks_utility) * 6.5
1072 - bool_bits_v0(has_bem_marker) * 2.0
1073 + bool_bits_v0(input.declaration_count <= 2),
1074 },
1075 DesignerIntentScoreV0 {
1076 schema_version: VARIATIONAL_SCHEMA_VERSION_V0,
1077 product: "omena-variational.designer-intent-score",
1078 layer_marker: VARIATIONAL_LAYER_MARKER_V0,
1079 feature_gate: VARIATIONAL_FEATURE_GATE_V0,
1080 intent: PatternIntentV0::Atomic,
1081 log_probability_bits: bool_bits_v0(looks_atomic) * 5.0
1082 - bool_bits_v0(input.declaration_count > 1) * 2.0,
1083 },
1084 DesignerIntentScoreV0 {
1085 schema_version: VARIATIONAL_SCHEMA_VERSION_V0,
1086 product: "omena-variational.designer-intent-score",
1087 layer_marker: VARIATIONAL_LAYER_MARKER_V0,
1088 feature_gate: VARIATIONAL_FEATURE_GATE_V0,
1089 intent: PatternIntentV0::Hybrid,
1090 log_probability_bits: bool_bits_v0(has_bem_marker)
1091 + bool_bits_v0(looks_hybrid) * 4.0
1092 + bool_bits_v0(input.custom_property_reference_count > 0) * 1.5,
1093 },
1094 DesignerIntentScoreV0 {
1095 schema_version: VARIATIONAL_SCHEMA_VERSION_V0,
1096 product: "omena-variational.designer-intent-score",
1097 layer_marker: VARIATIONAL_LAYER_MARKER_V0,
1098 feature_gate: VARIATIONAL_FEATURE_GATE_V0,
1099 intent: PatternIntentV0::AdHoc,
1100 log_probability_bits: bool_bits_v0(!has_bem_marker && !looks_utility)
1101 + bool_bits_v0(input.duplicate_property_tie_count > 0),
1102 },
1103 ];
1104 scores.sort_by(|left, right| {
1105 right
1106 .log_probability_bits
1107 .partial_cmp(&left.log_probability_bits)
1108 .unwrap_or(std::cmp::Ordering::Equal)
1109 .then_with(|| left.intent.as_str().cmp(right.intent.as_str()))
1110 });
1111 DesignerIntentPosteriorV0 {
1112 schema_version: VARIATIONAL_SCHEMA_VERSION_V0,
1113 product: "omena-variational.designer-intent-posterior",
1114 layer_marker: VARIATIONAL_LAYER_MARKER_V0,
1115 feature_gate: VARIATIONAL_FEATURE_GATE_V0,
1116 mode: DesignerIntentPosteriorModeV0::VciFormal,
1117 selector_name: input.selector_name,
1118 scores,
1119 enabled_by_default: true,
1120 }
1121}
1122
1123pub fn dominant_designer_intent_v0(
1124 posterior: &DesignerIntentPosteriorV0,
1125) -> Option<PatternIntentV0> {
1126 posterior.scores.first().map(|score| score.intent)
1127}
1128
1129fn normalize_selector_name_for_intent_v0(selector_name: &str) -> String {
1130 selector_name
1131 .trim()
1132 .trim_start_matches('.')
1133 .split([':', '[', ' ', '>', '+', '~', ','])
1134 .next()
1135 .unwrap_or(selector_name)
1136 .trim()
1137 .to_string()
1138}
1139
1140fn bool_bits_v0(value: bool) -> f64 {
1141 if value { 1.0 } else { 0.0 }
1142}
1143
1144pub const REPLICA_ENSEMBLE_SCHEMA_VERSION_V0: &str = "0";
1145pub const REPLICA_ENSEMBLE_LAYER_MARKER_V0: &str = "replica-ensemble";
1146pub const REPLICA_ENSEMBLE_FEATURE_GATE_V0: &str = "replica-ensemble";
1147pub const REPLICA_ENSEMBLE_MECHANISM_SCOPE_V0: &str =
1148 "productWiredCrossFileConsistencyHintSubstrate";
1149pub const REPLICA_ENSEMBLE_PRODUCT_SURFACE_V0: &str = "defaultCrossFileConsistencyHint";
1150pub const REPLICA_ENSEMBLE_DEFAULT_PRODUCT_DECISION_MECHANISM_V0: bool = false;
1151
1152#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize)]
1153#[serde(rename_all = "camelCase")]
1154pub struct CascadeSiteKeyV0 {
1155 pub schema_version: &'static str,
1156 pub product: &'static str,
1157 pub layer_marker: &'static str,
1158 pub feature_gate: &'static str,
1159 pub element_selector: String,
1160 pub property: String,
1161}
1162
1163#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1164#[serde(rename_all = "camelCase")]
1165pub struct LinearProvenanceTagV0 {
1166 pub schema_version: &'static str,
1167 pub product: &'static str,
1168 pub layer_marker: &'static str,
1169 pub feature_gate: &'static str,
1170 pub semiring_identifier: &'static str,
1171 pub label: String,
1172}
1173
1174#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1175#[serde(rename_all = "camelCase")]
1176pub struct ReplicaSiteOutcomeV0 {
1177 pub schema_version: &'static str,
1178 pub product: &'static str,
1179 pub layer_marker: &'static str,
1180 pub feature_gate: &'static str,
1181 pub site: CascadeSiteKeyV0,
1182 pub outcome: CascadeOutcome,
1183 pub provenance: Option<LinearProvenanceTagV0>,
1184}
1185
1186#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1187#[serde(rename_all = "camelCase")]
1188pub struct ReplicaSnapshotV0 {
1189 pub schema_version: &'static str,
1190 pub product: &'static str,
1191 pub layer_marker: &'static str,
1192 pub feature_gate: &'static str,
1193 pub path: String,
1194 pub sites: Vec<ReplicaSiteOutcomeV0>,
1195}
1196
1197#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize)]
1198#[serde(rename_all = "camelCase")]
1199pub enum OutcomeMode {
1200 #[default]
1201 DefiniteOnly,
1202 WidenedRankedSet,
1203 FullStrict,
1204}
1205
1206#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
1207#[serde(rename_all = "camelCase")]
1208pub enum SamplingPolicy {
1209 AllPairs,
1210 PageRankWeighted { max_pair_count: usize },
1211 RandomSubset { max_pair_count: usize },
1212}
1213
1214#[derive(Debug, Clone, PartialEq, Serialize)]
1215#[serde(rename_all = "camelCase")]
1216pub struct ReplicaOverlapV0 {
1217 pub schema_version: &'static str,
1218 pub product: &'static str,
1219 pub layer_marker: &'static str,
1220 pub feature_gate: &'static str,
1221 pub replica_alpha_path: String,
1222 pub replica_beta_path: String,
1223 pub outcome_mode: OutcomeMode,
1224 pub shared_site_count: usize,
1225 pub agreeing_site_count: usize,
1226 pub overlap_q: f64,
1227 pub overlap_q_unit: &'static str,
1228 pub provenance_attributions: Vec<OverlapAttributionV0>,
1229}
1230
1231#[derive(Debug, Clone, PartialEq, Serialize)]
1232#[serde(rename_all = "camelCase")]
1233pub struct OverlapAttributionV0 {
1234 pub schema_version: &'static str,
1235 pub product: &'static str,
1236 pub layer_marker: &'static str,
1237 pub feature_gate: &'static str,
1238 pub site_element_selector: String,
1239 pub site_property: String,
1240 pub winner_alpha: String,
1241 pub winner_beta: String,
1242 pub provenance_alpha: Option<LinearProvenanceTagV0>,
1243 pub provenance_beta: Option<LinearProvenanceTagV0>,
1244}
1245
1246#[derive(Debug, Clone, PartialEq, Serialize)]
1247#[serde(rename_all = "camelCase")]
1248pub struct HistogramBinV0 {
1249 pub schema_version: &'static str,
1250 pub product: &'static str,
1251 pub layer_marker: &'static str,
1252 pub feature_gate: &'static str,
1253 pub q_low: f64,
1254 pub q_high: f64,
1255 pub count: usize,
1256 pub normalized_density: f64,
1257}
1258
1259#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
1260#[serde(rename_all = "camelCase")]
1261pub enum DistributionModality {
1262 Trivial,
1263 Unimodal,
1264 BimodalRSB,
1265 Continuous,
1266}
1267
1268#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
1269#[serde(rename_all = "camelCase")]
1270pub enum ParisiSource {
1271 M4AlphaCascadeReplicaOverlap,
1272 LocalTwoComponentEm,
1273 Unavailable,
1274}
1275
1276#[derive(Debug, Clone, PartialEq, Serialize)]
1277#[serde(rename_all = "camelCase")]
1278pub struct ReplicaOverlapDistributionV0 {
1279 pub schema_version: &'static str,
1280 pub product: &'static str,
1281 pub layer_marker: &'static str,
1282 pub feature_gate: &'static str,
1283 pub workspace_root: String,
1284 pub outcome_mode: OutcomeMode,
1285 pub replica_count: usize,
1286 pub pair_count: usize,
1287 pub histogram_bin_count: usize,
1288 pub histogram_bins: Vec<HistogramBinV0>,
1289 pub modality: DistributionModality,
1290 pub modality_definition: &'static str,
1291 pub peak_q_values: Vec<f64>,
1292 pub parisi_m_estimate: Option<f64>,
1293 pub parisi_m_source: ParisiSource,
1294 pub mean_q: f64,
1295 pub variance_q: f64,
1296}
1297
1298#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1299#[serde(rename_all = "camelCase")]
1300pub struct ModuleGraphV0 {
1301 pub schema_version: &'static str,
1302 pub product: &'static str,
1303 pub layer_marker: &'static str,
1304 pub feature_gate: &'static str,
1305 pub workspace_root: String,
1306 pub nodes: Vec<String>,
1307 pub edges: Vec<ModuleGraphEdgeV0>,
1308}
1309
1310#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1311#[serde(rename_all = "camelCase")]
1312pub struct ModuleGraphEdgeV0 {
1313 pub schema_version: &'static str,
1314 pub product: &'static str,
1315 pub layer_marker: &'static str,
1316 pub feature_gate: &'static str,
1317 pub from_module: String,
1318 pub to_module: String,
1319 pub edge_kind: &'static str,
1320}
1321
1322#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
1323#[serde(rename_all = "camelCase")]
1324pub enum PartitionHypothesisLabel {
1325 DirectoryTree,
1326 ComposesCluster,
1327 BrandTheme,
1328 AutoSpectral,
1329}
1330
1331#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
1332#[serde(rename_all = "camelCase")]
1333pub enum SpectralMethod {
1334 Auto,
1335 DegreeCorrected,
1336 Spectral,
1337 NonBacktracking,
1338}
1339
1340#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1341#[serde(rename_all = "camelCase")]
1342pub struct ReportOptionsV0 {
1343 pub schema_version: &'static str,
1344 pub product: &'static str,
1345 pub layer_marker: &'static str,
1346 pub feature_gate: &'static str,
1347 pub partition_hypotheses: Vec<PartitionHypothesisLabel>,
1348 pub spectral_method: SpectralMethod,
1349 pub sampling_policy: Option<SamplingPolicy>,
1350 pub rg_exponent_handle: Option<RgExponentHandleV0>,
1351}
1352
1353impl Default for ReportOptionsV0 {
1354 fn default() -> Self {
1355 Self {
1356 schema_version: REPLICA_ENSEMBLE_SCHEMA_VERSION_V0,
1357 product: "omena-ensemble.report-options",
1358 layer_marker: REPLICA_ENSEMBLE_LAYER_MARKER_V0,
1359 feature_gate: REPLICA_ENSEMBLE_FEATURE_GATE_V0,
1360 partition_hypotheses: vec![
1361 PartitionHypothesisLabel::AutoSpectral,
1362 PartitionHypothesisLabel::ComposesCluster,
1363 PartitionHypothesisLabel::DirectoryTree,
1364 ],
1365 spectral_method: SpectralMethod::Auto,
1366 sampling_policy: None,
1367 rg_exponent_handle: None,
1368 }
1369 }
1370}
1371
1372#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1373#[serde(rename_all = "camelCase")]
1374pub struct RgExponentHandleV0 {
1375 pub schema_version: &'static str,
1376 pub product: &'static str,
1377 pub layer_marker: &'static str,
1378 pub feature_gate: &'static str,
1379 pub workspace_root: String,
1380 pub timestamp: String,
1381 pub digest: String,
1382}
1383
1384#[derive(Debug, Clone, Copy)]
1385pub struct ParisiM4AlphaSource<'a> {
1386 pub replica_overlap: &'a CascadeReplicaOverlapV0,
1387}
1388
1389#[derive(Debug, Clone, PartialEq, Serialize)]
1390#[serde(rename_all = "camelCase")]
1391pub struct CrossFileInconsistencyReportV0 {
1392 pub schema_version: &'static str,
1393 pub product: &'static str,
1394 pub layer_marker: &'static str,
1395 pub feature_gate: &'static str,
1396 pub mechanism_scope: &'static str,
1397 pub product_surface: &'static str,
1398 pub default_product_decision_mechanism: bool,
1399 pub workspace_root: String,
1400 pub distribution: ReplicaOverlapDistributionV0,
1401 pub top_disagreement_pairs: Vec<ReplicaOverlapV0>,
1402 pub recommendation: ReportRecommendation,
1403}
1404
1405#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
1406#[serde(rename_all = "camelCase")]
1407pub enum ReportRecommendation {
1408 NoActionNeeded,
1409 InvestigateRsbBroken,
1410 UndetectablePhase,
1411}
1412
1413pub fn site(element_selector: impl Into<String>, property: impl Into<String>) -> CascadeSiteKeyV0 {
1414 CascadeSiteKeyV0 {
1415 schema_version: REPLICA_ENSEMBLE_SCHEMA_VERSION_V0,
1416 product: "omena-ensemble.cascade-site-key",
1417 layer_marker: REPLICA_ENSEMBLE_LAYER_MARKER_V0,
1418 feature_gate: REPLICA_ENSEMBLE_FEATURE_GATE_V0,
1419 element_selector: element_selector.into(),
1420 property: property.into(),
1421 }
1422}
1423
1424pub fn build_cross_file_inconsistency_report(
1425 workspace_root: &str,
1426 replicas: impl IntoIterator<Item = ReplicaSnapshotV0>,
1427 _module_graph: &ModuleGraphV0,
1428 outcome_mode: OutcomeMode,
1429 options: ReportOptionsV0,
1430 parisi_source: Option<ParisiM4AlphaSource<'_>>,
1431) -> CrossFileInconsistencyReportV0 {
1432 let replicas = replicas.into_iter().collect::<Vec<_>>();
1433 let distribution = compute_overlap_distribution(
1434 workspace_root,
1435 replicas.clone(),
1436 options.sampling_policy,
1437 outcome_mode,
1438 parisi_source,
1439 );
1440 let top_disagreement_pairs =
1441 top_disagreement_pairs(&replicas, outcome_mode, options.sampling_policy);
1442 let recommendation = if top_disagreement_pairs
1443 .iter()
1444 .any(|pair| pair.shared_site_count > 0 && pair.overlap_q < 1.0)
1445 {
1446 ReportRecommendation::InvestigateRsbBroken
1447 } else {
1448 ReportRecommendation::NoActionNeeded
1449 };
1450
1451 CrossFileInconsistencyReportV0 {
1452 schema_version: REPLICA_ENSEMBLE_SCHEMA_VERSION_V0,
1453 product: "omena-ensemble.cross-file-inconsistency-report",
1454 layer_marker: REPLICA_ENSEMBLE_LAYER_MARKER_V0,
1455 feature_gate: REPLICA_ENSEMBLE_FEATURE_GATE_V0,
1456 mechanism_scope: REPLICA_ENSEMBLE_MECHANISM_SCOPE_V0,
1457 product_surface: REPLICA_ENSEMBLE_PRODUCT_SURFACE_V0,
1458 default_product_decision_mechanism: REPLICA_ENSEMBLE_DEFAULT_PRODUCT_DECISION_MECHANISM_V0,
1459 workspace_root: workspace_root.to_string(),
1460 distribution,
1461 top_disagreement_pairs,
1462 recommendation,
1463 }
1464}
1465
1466fn compute_overlap_distribution(
1467 workspace_root: &str,
1468 replicas: impl IntoIterator<Item = ReplicaSnapshotV0>,
1469 sampling_policy: Option<SamplingPolicy>,
1470 outcome_mode: OutcomeMode,
1471 parisi_source: Option<ParisiM4AlphaSource<'_>>,
1472) -> ReplicaOverlapDistributionV0 {
1473 let replicas = replicas.into_iter().collect::<Vec<_>>();
1474 let pairs = selected_pair_indices(&replicas, sampling_policy);
1475 let overlaps = pairs
1476 .iter()
1477 .map(|(alpha_index, beta_index)| {
1478 let alpha = &replicas[*alpha_index];
1479 let beta = &replicas[*beta_index];
1480 compute_replica_overlap(
1481 &alpha.path,
1482 &beta.path,
1483 alpha.sites.clone(),
1484 beta.sites.clone(),
1485 outcome_mode,
1486 )
1487 })
1488 .collect::<Vec<_>>();
1489 let q_values = overlaps
1490 .iter()
1491 .map(|overlap| overlap.overlap_q)
1492 .collect::<Vec<_>>();
1493 let mean_q = mean(&q_values);
1494 let variance_q = variance(&q_values, mean_q);
1495 let histogram_bins = histogram(&q_values, 10);
1496 let modality = classify_modality(overlaps.len(), variance_q, &histogram_bins);
1497 let (parisi_m_estimate, parisi_m_source) = parisi_estimate(modality, parisi_source, &q_values);
1498
1499 ReplicaOverlapDistributionV0 {
1500 schema_version: REPLICA_ENSEMBLE_SCHEMA_VERSION_V0,
1501 product: "omena-ensemble.replica-overlap-distribution",
1502 layer_marker: REPLICA_ENSEMBLE_LAYER_MARKER_V0,
1503 feature_gate: REPLICA_ENSEMBLE_FEATURE_GATE_V0,
1504 workspace_root: workspace_root.to_string(),
1505 outcome_mode,
1506 replica_count: replicas.len(),
1507 pair_count: overlaps.len(),
1508 histogram_bin_count: histogram_bins.len(),
1509 histogram_bins,
1510 modality,
1511 modality_definition: modality_definition(modality, parisi_m_source),
1512 peak_q_values: peak_q_values(&q_values),
1513 parisi_m_estimate,
1514 parisi_m_source,
1515 mean_q,
1516 variance_q,
1517 }
1518}
1519
1520fn compute_replica_overlap<I, J>(
1521 alpha: &str,
1522 beta: &str,
1523 cascade_alpha: I,
1524 cascade_beta: J,
1525 mode: OutcomeMode,
1526) -> ReplicaOverlapV0
1527where
1528 I: IntoIterator<Item = ReplicaSiteOutcomeV0>,
1529 J: IntoIterator<Item = ReplicaSiteOutcomeV0>,
1530{
1531 let alpha_by_site = cascade_alpha
1532 .into_iter()
1533 .map(|entry| (entry.site.clone(), entry))
1534 .collect::<BTreeMap<_, _>>();
1535 let beta_by_site = cascade_beta
1536 .into_iter()
1537 .map(|entry| (entry.site.clone(), entry))
1538 .collect::<BTreeMap<_, _>>();
1539
1540 let mut shared_site_count = 0usize;
1541 let mut agreeing_site_count = 0usize;
1542 let mut provenance_attributions = Vec::new();
1543 for (site, alpha_entry) in &alpha_by_site {
1544 let Some(beta_entry) = beta_by_site.get(site) else {
1545 continue;
1546 };
1547 let Some(alpha_projection) = project_outcome(&alpha_entry.outcome, mode) else {
1548 continue;
1549 };
1550 let Some(beta_projection) = project_outcome(&beta_entry.outcome, mode) else {
1551 continue;
1552 };
1553 shared_site_count += 1;
1554 if alpha_projection == beta_projection {
1555 agreeing_site_count += 1;
1556 } else {
1557 provenance_attributions.push(OverlapAttributionV0 {
1558 schema_version: REPLICA_ENSEMBLE_SCHEMA_VERSION_V0,
1559 product: "omena-ensemble.overlap-attribution",
1560 layer_marker: REPLICA_ENSEMBLE_LAYER_MARKER_V0,
1561 feature_gate: REPLICA_ENSEMBLE_FEATURE_GATE_V0,
1562 site_element_selector: site.element_selector.clone(),
1563 site_property: site.property.clone(),
1564 winner_alpha: alpha_projection,
1565 winner_beta: beta_projection,
1566 provenance_alpha: alpha_entry.provenance.clone(),
1567 provenance_beta: beta_entry.provenance.clone(),
1568 });
1569 }
1570 }
1571 let overlap_q = if shared_site_count == 0 {
1572 0.0
1573 } else {
1574 agreeing_site_count as f64 / shared_site_count as f64
1575 };
1576
1577 ReplicaOverlapV0 {
1578 schema_version: REPLICA_ENSEMBLE_SCHEMA_VERSION_V0,
1579 product: "omena-ensemble.replica-overlap",
1580 layer_marker: REPLICA_ENSEMBLE_LAYER_MARKER_V0,
1581 feature_gate: REPLICA_ENSEMBLE_FEATURE_GATE_V0,
1582 replica_alpha_path: alpha.to_string(),
1583 replica_beta_path: beta.to_string(),
1584 outcome_mode: mode,
1585 shared_site_count,
1586 agreeing_site_count,
1587 overlap_q,
1588 overlap_q_unit: "unitless",
1589 provenance_attributions,
1590 }
1591}
1592
1593fn selected_pair_indices(
1594 replicas: &[ReplicaSnapshotV0],
1595 sampling_policy: Option<SamplingPolicy>,
1596) -> Vec<(usize, usize)> {
1597 let mut pairs = Vec::new();
1598 for alpha_index in 0..replicas.len() {
1599 for beta_index in alpha_index + 1..replicas.len() {
1600 pairs.push((alpha_index, beta_index));
1601 }
1602 }
1603 match sampling_policy {
1604 Some(SamplingPolicy::PageRankWeighted { max_pair_count })
1605 | Some(SamplingPolicy::RandomSubset { max_pair_count }) => pairs.truncate(max_pair_count),
1606 Some(SamplingPolicy::AllPairs) | None => {}
1607 }
1608 pairs
1609}
1610
1611fn top_disagreement_pairs(
1612 replicas: &[ReplicaSnapshotV0],
1613 outcome_mode: OutcomeMode,
1614 sampling_policy: Option<SamplingPolicy>,
1615) -> Vec<ReplicaOverlapV0> {
1616 let mut overlaps = Vec::new();
1617 let mut remaining_budget = match sampling_policy {
1618 Some(SamplingPolicy::PageRankWeighted { max_pair_count })
1619 | Some(SamplingPolicy::RandomSubset { max_pair_count }) => max_pair_count,
1620 Some(SamplingPolicy::AllPairs) | None => usize::MAX,
1621 };
1622 for alpha_index in 0..replicas.len() {
1623 for beta_index in alpha_index + 1..replicas.len() {
1624 if remaining_budget == 0 {
1625 break;
1626 }
1627 remaining_budget = remaining_budget.saturating_sub(1);
1628 let alpha = &replicas[alpha_index];
1629 let beta = &replicas[beta_index];
1630 overlaps.push(compute_replica_overlap(
1631 &alpha.path,
1632 &beta.path,
1633 alpha.sites.clone(),
1634 beta.sites.clone(),
1635 outcome_mode,
1636 ));
1637 }
1638 }
1639 overlaps.sort_by(|left, right| {
1640 left.overlap_q
1641 .total_cmp(&right.overlap_q)
1642 .then_with(|| left.replica_alpha_path.cmp(&right.replica_alpha_path))
1643 .then_with(|| left.replica_beta_path.cmp(&right.replica_beta_path))
1644 });
1645 overlaps.truncate(5);
1646 overlaps
1647}
1648
1649fn project_outcome(outcome: &CascadeOutcome, mode: OutcomeMode) -> Option<String> {
1650 match (outcome, mode) {
1651 (CascadeOutcome::Definite { winner, .. }, _) => Some(format!("definite:{}", winner.id)),
1652 (CascadeOutcome::RankedSet(declarations), OutcomeMode::WidenedRankedSet)
1653 | (CascadeOutcome::RankedSet(declarations), OutcomeMode::FullStrict) => {
1654 let mut ids = declarations
1655 .iter()
1656 .map(|declaration| declaration.id.as_str())
1657 .collect::<Vec<_>>();
1658 ids.sort_unstable();
1659 Some(format!("ranked:{}", ids.join("|")))
1660 }
1661 (CascadeOutcome::Inherit, OutcomeMode::FullStrict) => Some("inherit".to_string()),
1662 (CascadeOutcome::Top, OutcomeMode::FullStrict) => Some("top".to_string()),
1663 (CascadeOutcome::RankedSet(_), OutcomeMode::DefiniteOnly)
1664 | (CascadeOutcome::Inherit, OutcomeMode::DefiniteOnly | OutcomeMode::WidenedRankedSet)
1665 | (CascadeOutcome::Top, OutcomeMode::DefiniteOnly | OutcomeMode::WidenedRankedSet) => None,
1666 }
1667}
1668
1669fn mean(values: &[f64]) -> f64 {
1670 if values.is_empty() {
1671 return 0.0;
1672 }
1673 values.iter().sum::<f64>() / values.len() as f64
1674}
1675
1676fn variance(values: &[f64], mean: f64) -> f64 {
1677 if values.is_empty() {
1678 return 0.0;
1679 }
1680 values
1681 .iter()
1682 .map(|value| {
1683 let delta = value - mean;
1684 delta * delta
1685 })
1686 .sum::<f64>()
1687 / values.len() as f64
1688}
1689
1690fn histogram(values: &[f64], bin_count: usize) -> Vec<HistogramBinV0> {
1691 let mut counts = vec![0usize; bin_count];
1692 for value in values {
1693 let clamped = value.clamp(0.0, 1.0);
1694 let mut bin_index = (clamped * bin_count as f64).floor() as usize;
1695 if bin_index == bin_count {
1696 bin_index = bin_count.saturating_sub(1);
1697 }
1698 counts[bin_index] += 1;
1699 }
1700 counts
1701 .into_iter()
1702 .enumerate()
1703 .map(|(index, count)| HistogramBinV0 {
1704 schema_version: REPLICA_ENSEMBLE_SCHEMA_VERSION_V0,
1705 product: "omena-ensemble.histogram-bin",
1706 layer_marker: REPLICA_ENSEMBLE_LAYER_MARKER_V0,
1707 feature_gate: REPLICA_ENSEMBLE_FEATURE_GATE_V0,
1708 q_low: index as f64 / bin_count as f64,
1709 q_high: (index + 1) as f64 / bin_count as f64,
1710 count,
1711 normalized_density: if values.is_empty() {
1712 0.0
1713 } else {
1714 count as f64 / values.len() as f64
1715 },
1716 })
1717 .collect()
1718}
1719
1720fn classify_modality(
1721 pair_count: usize,
1722 variance_q: f64,
1723 histogram_bins: &[HistogramBinV0],
1724) -> DistributionModality {
1725 if pair_count < 3 {
1726 return DistributionModality::Trivial;
1727 }
1728 if variance_q < 0.01 {
1729 return DistributionModality::Unimodal;
1730 }
1731 let low_peak = histogram_bins
1732 .iter()
1733 .any(|bin| bin.count > 0 && bin.q_high <= 0.5);
1734 let high_peak = histogram_bins
1735 .iter()
1736 .any(|bin| bin.count > 0 && bin.q_low >= 0.7);
1737 if low_peak && high_peak {
1738 DistributionModality::BimodalRSB
1739 } else {
1740 DistributionModality::Continuous
1741 }
1742}
1743
1744fn parisi_estimate(
1745 modality: DistributionModality,
1746 parisi_source: Option<ParisiM4AlphaSource<'_>>,
1747 q_values: &[f64],
1748) -> (Option<f64>, ParisiSource) {
1749 if let Some(source) = parisi_source
1750 && let Some(m_estimate) = source.replica_overlap.parisi_breakpoint_m
1751 {
1752 return (Some(m_estimate), ParisiSource::M4AlphaCascadeReplicaOverlap);
1753 }
1754 if modality == DistributionModality::BimodalRSB {
1755 return (
1756 two_component_em_low_overlap_weight(q_values),
1757 ParisiSource::LocalTwoComponentEm,
1758 );
1759 }
1760 (None, ParisiSource::Unavailable)
1761}
1762
1763fn two_component_em_low_overlap_weight(q_values: &[f64]) -> Option<f64> {
1764 if q_values.len() < 3 {
1765 return None;
1766 }
1767 let mut sorted = q_values.to_vec();
1768 sorted.sort_by(f64::total_cmp);
1769 let low = sorted[0].clamp(0.0, 1.0);
1770 let high = sorted[sorted.len() - 1].clamp(0.0, 1.0);
1771 (high - low > 0.000_001).then_some(0.5)
1772}
1773
1774fn modality_definition(
1775 modality: DistributionModality,
1776 parisi_source: ParisiSource,
1777) -> &'static str {
1778 match (modality, parisi_source) {
1779 (DistributionModality::Trivial, _) => {
1780 "Fewer than 3 replica pairs available; modality undefined"
1781 }
1782 (DistributionModality::Unimodal, _) => {
1783 "Single peak in P(q) histogram; replica-symmetric descriptive shape"
1784 }
1785 (DistributionModality::BimodalRSB, ParisiSource::M4AlphaCascadeReplicaOverlap) => {
1786 "Two peaks in P(q) with M4-alpha spin-glass Parisi estimate attached"
1787 }
1788 (DistributionModality::BimodalRSB, ParisiSource::LocalTwoComponentEm) => {
1789 "Two peaks in P(q) histogram; local two-component EM estimates the low-overlap mixture weight"
1790 }
1791 (DistributionModality::BimodalRSB, _) => {
1792 "Two peaks in P(q) histogram; spin-glass source unavailable for Parisi estimate"
1793 }
1794 (DistributionModality::Continuous, _) => {
1795 "Smooth P(q) histogram; peak detection fails the bimodal threshold"
1796 }
1797 }
1798}
1799
1800fn peak_q_values(values: &[f64]) -> Vec<f64> {
1801 if values.is_empty() {
1802 return Vec::new();
1803 }
1804 let mut sorted = values.to_vec();
1805 sorted.sort_by(f64::total_cmp);
1806 let low = sorted[0];
1807 let high = sorted[sorted.len() - 1];
1808 if (high - low).abs() < f64::EPSILON {
1809 vec![low]
1810 } else {
1811 vec![low, high]
1812 }
1813}