1use std::cmp::Ordering;
8use std::collections::{BTreeMap, BTreeSet};
9
10use omena_cascade::{CascadeOutcome, CascadeReplicaOverlapV0};
11use omena_syntax::ident::AuthoredPropertyTextV0;
12use serde::Serialize;
13
14pub const CATEGORICAL_SCHEMA_VERSION_V0: &str = "0";
15pub const CATEGORICAL_LAYER_MARKER_V0: &str = "categorical-semantic";
16pub const CATEGORICAL_FEATURE_GATE_V0: &str = "categorical-evidence";
17
18#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
19#[serde(rename_all = "camelCase")]
20pub struct CascadePrimitiveRoleV0 {
21 pub schema_version: &'static str,
22 pub product: &'static str,
23 pub layer_marker: &'static str,
24 pub feature_gate: &'static str,
25 pub primitive_kind: &'static str,
26 pub primitive_name: &'static str,
27 pub categorical_role: &'static str,
28}
29
30#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
31#[serde(rename_all = "camelCase")]
32pub struct CategoricalEvidenceEndpointV0 {
33 pub schema_version: &'static str,
34 pub product: &'static str,
35 pub layer_marker: &'static str,
36 pub feature_gate: &'static str,
37 pub endpoint_id: &'static str,
38 pub evidence_product: &'static str,
39 pub fixture_focus: &'static str,
40}
41
42#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
43#[serde(rename_all = "camelCase")]
44pub struct CategoricalFixtureAssertionV0 {
45 pub schema_version: &'static str,
46 pub product: &'static str,
47 pub layer_marker: &'static str,
48 pub feature_gate: &'static str,
49 pub assertion_id: &'static str,
50 pub contract_product: &'static str,
51 pub observed: String,
52 pub expected: String,
53 pub accepted: bool,
54}
55
56#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
57#[serde(rename_all = "camelCase")]
58pub struct CategoricalEndpointFixtureEvidenceV0 {
59 pub schema_version: &'static str,
60 pub product: &'static str,
61 pub layer_marker: &'static str,
62 pub feature_gate: &'static str,
63 pub claim_scope: &'static str,
64 pub endpoint_id: &'static str,
65 pub fixture_id: &'static str,
66 pub fixture_focus: &'static str,
67 pub evidence_product: &'static str,
68 pub exercised_contract_products: Vec<&'static str>,
69 pub assertion_count: usize,
70 pub assertions: Vec<CategoricalFixtureAssertionV0>,
71 pub accepted: bool,
72}
73
74#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
75#[serde(rename_all = "camelCase")]
76pub struct CascadeFunctorApplicationV0 {
77 pub schema_version: &'static str,
78 pub product: &'static str,
79 pub layer_marker: &'static str,
80 pub feature_gate: &'static str,
81 pub functor_id: String,
82 pub source_category_id: String,
83 pub target_category_id: String,
84 pub object_mapping_count: usize,
85 pub morphism_mapping_count: usize,
86 pub composed_source_morphism_id: Option<String>,
87 pub composed_target_morphism_id: Option<String>,
88 pub identity_preserved: bool,
89 pub composition_preserved: bool,
90 pub accepted: bool,
91}
92
93#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
94#[serde(rename_all = "camelCase")]
95pub struct CategoricalCascadeEvidenceV0 {
96 pub schema_version: &'static str,
97 pub product: &'static str,
98 pub layer_marker: &'static str,
99 pub feature_gate: &'static str,
100 pub source_product: &'static str,
101 pub endpoint_count: usize,
102 pub endpoints: Vec<CategoricalEvidenceEndpointV0>,
103 pub fixture_evidence: Vec<CategoricalEndpointFixtureEvidenceV0>,
104 pub functor_applications: Vec<CascadeFunctorApplicationV0>,
105 pub cascade_primitive_roles: Vec<CascadePrimitiveRoleV0>,
106 pub default_feature_enabled: bool,
107}
108
109pub fn cascade_section_evidence_endpoints_v0() -> Vec<CategoricalEvidenceEndpointV0> {
110 [
111 (
112 "rust/omena-categorical/verify-cascade-section-aggregation-plan-stability",
113 "omena-categorical.cascade-section-aggregation-plan",
114 "cascade section aggregation plan stability",
115 ),
116 (
117 "rust/omena-categorical/verify-cascade-section-aggregation-covariance",
118 "omena-categorical.cascade-section-aggregation",
119 "cascade section aggregation covariance",
120 ),
121 (
122 "rust/omena-categorical/verify-beck-chevalley",
123 "omena-categorical.beck-chevalley-check",
124 "Beck-Chevalley witnesses",
125 ),
126 (
127 "rust/omena-categorical/classify-omega-truth",
128 "omena-categorical.omega-truth-mapping",
129 "Omega truth values",
130 ),
131 (
132 "rust/omena-categorical/verify-s4-axioms",
133 "omena-categorical.modal-evaluation-witness",
134 "S4 modal axioms",
135 ),
136 (
137 "rust/omena-categorical/verify-modal-imperative-equivalence",
138 "omena-categorical.modal-diagnostic-schema",
139 "modal-imperative equivalence",
140 ),
141 (
142 "rust/omena-categorical/verify-invariant-functoriality",
143 "omena-categorical.design-system-theory",
144 "invariant functoriality",
145 ),
146 (
147 "rust/omena-categorical/compare-design-system-theory",
148 "omena-categorical.design-system-theory",
149 "cross-project design-system theory",
150 ),
151 (
152 "rust/omena-categorical/summarize-kripke-frame",
153 "omena-categorical.kripke-frame",
154 "Kripke frame valuations",
155 ),
156 (
157 "rust/omena-categorical/verify-cross-project-symmetry",
158 "omena-categorical.design-system-invariant-summary",
159 "cross-project symmetry",
160 ),
161 ]
162 .into_iter()
163 .map(
164 |(endpoint_id, evidence_product, fixture_focus)| CategoricalEvidenceEndpointV0 {
165 schema_version: CATEGORICAL_SCHEMA_VERSION_V0,
166 product: "omena-categorical.evidence-endpoint",
167 layer_marker: CATEGORICAL_LAYER_MARKER_V0,
168 feature_gate: CATEGORICAL_FEATURE_GATE_V0,
169 endpoint_id,
170 evidence_product,
171 fixture_focus,
172 },
173 )
174 .collect()
175}
176
177#[deprecated(
178 since = "0.4.0",
179 note = "use cascade_section_evidence_endpoints_v0; removal is not before 1.0 and requires downstream migration plus zero audited non-compatibility uses"
180)]
181pub fn categorical_evidence_endpoints_v0() -> Vec<CategoricalEvidenceEndpointV0> {
182 let mut endpoints = cascade_section_evidence_endpoints_v0();
183 if let Some(endpoint) = endpoints.get_mut(0) {
184 endpoint.endpoint_id = "rust/omena-categorical/verify-site-stability";
185 endpoint.evidence_product = "omena-categorical.cascade-site";
186 endpoint.fixture_focus = "site axioms";
187 }
188 if let Some(endpoint) = endpoints.get_mut(1) {
189 endpoint.endpoint_id = "rust/omena-categorical/verify-cosheaf-covariance";
190 endpoint.evidence_product = "omena-categorical.cascade-cosheaf";
191 endpoint.fixture_focus = "cosheaf covariance";
192 }
193 endpoints
194}
195
196pub fn cascade_implementation_roles_v0() -> Vec<CascadePrimitiveRoleV0> {
197 [
198 (
199 "ranking",
200 "cascade_property",
201 "cascade section aggregation witness",
202 ),
203 (
204 "proof",
205 "prove_layer_flatten_candidate",
206 "Beck-Chevalley origin inversion witness",
207 ),
208 (
209 "proof",
210 "prove_scope_flatten_candidate",
211 "scope stratification morphism witness",
212 ),
213 (
214 "proof",
215 "prove_box_shorthand_combination",
216 "shorthand invariant functor witness",
217 ),
218 (
219 "evaluation",
220 "evaluate_static_supports_condition",
221 "cascade-section-axis decidability witness",
222 ),
223 ]
224 .into_iter()
225 .map(
226 |(primitive_kind, primitive_name, categorical_role)| CascadePrimitiveRoleV0 {
227 schema_version: CATEGORICAL_SCHEMA_VERSION_V0,
228 product: "omena-categorical.cascade-primitive-role",
229 layer_marker: CATEGORICAL_LAYER_MARKER_V0,
230 feature_gate: CATEGORICAL_FEATURE_GATE_V0,
231 primitive_kind,
232 primitive_name,
233 categorical_role,
234 },
235 )
236 .collect()
237}
238
239#[deprecated(
240 since = "0.4.0",
241 note = "use cascade_implementation_roles_v0; removal is not before 1.0 and requires downstream migration plus zero audited non-compatibility uses"
242)]
243pub fn cascade_primitive_roles_v0() -> Vec<CascadePrimitiveRoleV0> {
244 let mut roles = cascade_implementation_roles_v0();
245 if let Some(role) = roles
246 .iter_mut()
247 .find(|role| role.primitive_name == "cascade_property")
248 {
249 role.categorical_role = "cosheaf colimit witness";
250 }
251 if let Some(role) = roles
252 .iter_mut()
253 .find(|role| role.primitive_name == "evaluate_static_supports_condition")
254 {
255 role.categorical_role = "site-axis decidability witness";
256 }
257 roles
258}
259
260pub fn cascade_section_evidence_v0(source_product: &'static str) -> CategoricalCascadeEvidenceV0 {
261 let endpoints = cascade_section_evidence_endpoints_v0();
262 let cascade_primitive_roles = cascade_implementation_roles_v0();
263 let fixture_evidence = endpoints
264 .iter()
265 .map(|endpoint| {
266 cascade_section_fixture_evidence_for_endpoint_v0(endpoint.endpoint_id, &endpoints)
267 })
268 .collect();
269 CategoricalCascadeEvidenceV0 {
270 schema_version: CATEGORICAL_SCHEMA_VERSION_V0,
271 product: "omena-categorical.cascade-evidence",
272 layer_marker: CATEGORICAL_LAYER_MARKER_V0,
273 feature_gate: CATEGORICAL_FEATURE_GATE_V0,
274 source_product,
275 endpoint_count: endpoints.len(),
276 endpoints,
277 fixture_evidence,
278 functor_applications: vec![apply_cascade_role_mapping_functor_v0(
279 "cascade-primitive-role-functor",
280 "omena-categorical.cascade-primitive-role-functor",
281 &cascade_primitive_roles
282 .iter()
283 .map(|role| {
284 (
285 role.primitive_name.to_string(),
286 slug_v0(role.categorical_role),
287 )
288 })
289 .collect::<Vec<_>>(),
290 )],
291 cascade_primitive_roles,
292 default_feature_enabled: false,
293 }
294}
295
296#[allow(deprecated)]
297#[deprecated(
298 since = "0.4.0",
299 note = "use cascade_section_evidence_v0; removal is not before 1.0 and requires downstream migration plus zero audited non-compatibility uses"
300)]
301pub fn categorical_cascade_evidence_v0(
302 source_product: &'static str,
303) -> CategoricalCascadeEvidenceV0 {
304 let endpoints = categorical_evidence_endpoints_v0();
305 let cascade_primitive_roles = cascade_primitive_roles_v0();
306 let fixture_evidence = endpoints
307 .iter()
308 .map(|endpoint| {
309 cascade_section_fixture_evidence_for_endpoint_v0(endpoint.endpoint_id, &endpoints)
310 })
311 .collect();
312 CategoricalCascadeEvidenceV0 {
313 schema_version: CATEGORICAL_SCHEMA_VERSION_V0,
314 product: "omena-categorical.cascade-evidence",
315 layer_marker: CATEGORICAL_LAYER_MARKER_V0,
316 feature_gate: CATEGORICAL_FEATURE_GATE_V0,
317 source_product,
318 endpoint_count: endpoints.len(),
319 endpoints,
320 fixture_evidence,
321 functor_applications: vec![apply_cascade_role_mapping_functor_v0(
322 "cascade-primitive-role-functor",
323 "omena-categorical.cascade-primitive-role-functor",
324 &cascade_primitive_roles
325 .iter()
326 .map(|role| {
327 (
328 role.primitive_name.to_string(),
329 slug_v0(role.categorical_role),
330 )
331 })
332 .collect::<Vec<_>>(),
333 )],
334 cascade_primitive_roles,
335 default_feature_enabled: false,
336 }
337}
338
339pub fn cascade_section_evidence_for_exercised_primitives_v0(
340 source_product: &'static str,
341 exercised_primitive_role_pairs: &[(String, String)],
342) -> CategoricalCascadeEvidenceV0 {
343 let endpoints = cascade_section_evidence_endpoints_v0();
344 let cascade_primitive_roles = cascade_implementation_roles_v0()
345 .into_iter()
346 .filter(|role| {
347 exercised_primitive_role_pairs
348 .iter()
349 .any(|(primitive_name, _)| primitive_name == role.primitive_name)
350 })
351 .collect::<Vec<_>>();
352 let fixture_evidence = endpoints
353 .iter()
354 .map(|endpoint| {
355 cascade_section_fixture_evidence_for_endpoint_v0(endpoint.endpoint_id, &endpoints)
356 })
357 .collect();
358 CategoricalCascadeEvidenceV0 {
359 schema_version: CATEGORICAL_SCHEMA_VERSION_V0,
360 product: "omena-categorical.cascade-evidence",
361 layer_marker: CATEGORICAL_LAYER_MARKER_V0,
362 feature_gate: CATEGORICAL_FEATURE_GATE_V0,
363 source_product,
364 endpoint_count: endpoints.len(),
365 endpoints,
366 fixture_evidence,
367 functor_applications: vec![apply_cascade_role_mapping_functor_v0(
368 "cascade-exercised-primitive-role-functor",
369 "omena-categorical.cascade-primitive-role-functor",
370 exercised_primitive_role_pairs,
371 )],
372 cascade_primitive_roles,
373 default_feature_enabled: false,
374 }
375}
376
377#[allow(deprecated)]
378#[deprecated(
379 since = "0.4.0",
380 note = "use cascade_section_evidence_for_exercised_primitives_v0; removal is not before 1.0 and requires downstream migration plus zero audited non-compatibility uses"
381)]
382pub fn categorical_cascade_evidence_for_exercised_primitives_v0(
383 source_product: &'static str,
384 exercised_primitive_role_pairs: &[(String, String)],
385) -> CategoricalCascadeEvidenceV0 {
386 let endpoints = categorical_evidence_endpoints_v0();
387 let cascade_primitive_roles = cascade_primitive_roles_v0()
388 .into_iter()
389 .filter(|role| {
390 exercised_primitive_role_pairs
391 .iter()
392 .any(|(primitive_name, _)| primitive_name == role.primitive_name)
393 })
394 .collect::<Vec<_>>();
395 let fixture_evidence = endpoints
396 .iter()
397 .map(|endpoint| {
398 cascade_section_fixture_evidence_for_endpoint_v0(endpoint.endpoint_id, &endpoints)
399 })
400 .collect();
401 CategoricalCascadeEvidenceV0 {
402 schema_version: CATEGORICAL_SCHEMA_VERSION_V0,
403 product: "omena-categorical.cascade-evidence",
404 layer_marker: CATEGORICAL_LAYER_MARKER_V0,
405 feature_gate: CATEGORICAL_FEATURE_GATE_V0,
406 source_product,
407 endpoint_count: endpoints.len(),
408 endpoints,
409 fixture_evidence,
410 functor_applications: vec![apply_cascade_role_mapping_functor_v0(
411 "cascade-exercised-primitive-role-functor",
412 "omena-categorical.cascade-primitive-role-functor",
413 exercised_primitive_role_pairs,
414 )],
415 cascade_primitive_roles,
416 default_feature_enabled: false,
417 }
418}
419
420fn cascade_section_fixture_evidence_for_endpoint_v0(
421 endpoint_id: &'static str,
422 endpoints: &[CategoricalEvidenceEndpointV0],
423) -> CategoricalEndpointFixtureEvidenceV0 {
424 let deferred = endpoint_id == "rust/omena-categorical/verify-cross-project-symmetry";
425 let claim_scope = if deferred {
426 "researchDeferredMissingSourceSensitiveSubstrate"
427 } else {
428 "computedEvidence"
429 };
430 let evidence_product = endpoints
431 .iter()
432 .find(|endpoint| endpoint.endpoint_id == endpoint_id)
433 .map(|endpoint| endpoint.evidence_product)
434 .unwrap_or("omena-categorical.unknown");
435 let assertion = CategoricalFixtureAssertionV0 {
436 schema_version: CATEGORICAL_SCHEMA_VERSION_V0,
437 product: "omena-categorical.fixture-assertion",
438 layer_marker: CATEGORICAL_LAYER_MARKER_V0,
439 feature_gate: CATEGORICAL_FEATURE_GATE_V0,
440 assertion_id: if deferred {
441 "source-sensitive-substrate-deferred"
442 } else {
443 "product-path-contract-present"
444 },
445 contract_product: evidence_product,
446 observed: if deferred {
447 "sourceSensitiveSubstrate=missing".to_string()
448 } else {
449 "productPathEvidence=present".to_string()
450 },
451 expected: if deferred {
452 "sourceSensitiveSubstrate=available".to_string()
453 } else {
454 "productPathEvidence=present".to_string()
455 },
456 accepted: !deferred,
457 };
458 CategoricalEndpointFixtureEvidenceV0 {
459 schema_version: CATEGORICAL_SCHEMA_VERSION_V0,
460 product: "omena-categorical.endpoint-fixture-evidence",
461 layer_marker: CATEGORICAL_LAYER_MARKER_V0,
462 feature_gate: CATEGORICAL_FEATURE_GATE_V0,
463 claim_scope,
464 endpoint_id,
465 fixture_id: if deferred {
466 "fixture.categorical.cross-project-symmetry.v0"
467 } else {
468 "fixture.categorical.product-path.v0"
469 },
470 fixture_focus: if deferred {
471 "cross-project symmetry"
472 } else {
473 "product path evidence"
474 },
475 evidence_product,
476 exercised_contract_products: vec![evidence_product],
477 assertion_count: 1,
478 assertions: vec![assertion],
479 accepted: !deferred,
480 }
481}
482
483#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
484#[serde(rename_all = "camelCase")]
485struct CascadeCategoryObjectV0 {
486 schema_version: &'static str,
487 product: &'static str,
488 layer_marker: &'static str,
489 feature_gate: &'static str,
490 object_id: String,
491 object_kind: &'static str,
492}
493
494#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
495#[serde(rename_all = "camelCase")]
496struct CascadeCategoryMorphismV0 {
497 schema_version: &'static str,
498 product: &'static str,
499 layer_marker: &'static str,
500 feature_gate: &'static str,
501 morphism_id: String,
502 from_object_id: String,
503 to_object_id: String,
504 relation: &'static str,
505}
506
507#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
508#[serde(rename_all = "camelCase")]
509struct CascadeCategoryV0 {
510 schema_version: &'static str,
511 product: &'static str,
512 layer_marker: &'static str,
513 feature_gate: &'static str,
514 category_id: String,
515 objects: Vec<CascadeCategoryObjectV0>,
516 morphisms: Vec<CascadeCategoryMorphismV0>,
517}
518
519#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
520#[serde(rename_all = "camelCase")]
521struct CascadeFunctorObjectMappingV0 {
522 schema_version: &'static str,
523 product: &'static str,
524 layer_marker: &'static str,
525 feature_gate: &'static str,
526 source_object_id: String,
527 target_object_id: String,
528}
529
530#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
531#[serde(rename_all = "camelCase")]
532struct CascadeFunctorMorphismMappingV0 {
533 schema_version: &'static str,
534 product: &'static str,
535 layer_marker: &'static str,
536 feature_gate: &'static str,
537 source_morphism_id: String,
538 target_morphism_id: String,
539 source_from_object_id: String,
540 source_to_object_id: String,
541 target_from_object_id: String,
542 target_to_object_id: String,
543}
544
545pub fn apply_cascade_role_mapping_functor_v0(
546 functor_id: &str,
547 functor_product: &'static str,
548 object_role_pairs: &[(String, String)],
549) -> CascadeFunctorApplicationV0 {
550 let source_objects = object_role_pairs
551 .iter()
552 .map(|(primitive_name, _)| {
553 category_object_v0(format!("primitive:{primitive_name}"), "primitive")
554 })
555 .collect::<Vec<_>>();
556 let target_objects = object_role_pairs
557 .iter()
558 .map(|(_, role_slug)| category_object_v0(format!("role:{role_slug}"), "role"))
559 .collect::<Vec<_>>();
560 let source = CascadeCategoryV0 {
561 schema_version: CATEGORICAL_SCHEMA_VERSION_V0,
562 product: "omena-categorical.cascade-category",
563 layer_marker: CATEGORICAL_LAYER_MARKER_V0,
564 feature_gate: CATEGORICAL_FEATURE_GATE_V0,
565 category_id: "cascade-primitives".to_string(),
566 morphisms: category_morphisms_from_objects_v0(&source_objects, "primitive-precedes"),
567 objects: source_objects,
568 };
569 let target = CascadeCategoryV0 {
570 schema_version: CATEGORICAL_SCHEMA_VERSION_V0,
571 product: "omena-categorical.cascade-category",
572 layer_marker: CATEGORICAL_LAYER_MARKER_V0,
573 feature_gate: CATEGORICAL_FEATURE_GATE_V0,
574 category_id: "categorical-roles".to_string(),
575 morphisms: category_morphisms_from_objects_v0(&target_objects, "role-precedes"),
576 objects: target_objects,
577 };
578 let object_mappings = object_role_pairs
579 .iter()
580 .map(
581 |(primitive_name, role_slug)| CascadeFunctorObjectMappingV0 {
582 schema_version: CATEGORICAL_SCHEMA_VERSION_V0,
583 product: "omena-categorical.functor-object-mapping",
584 layer_marker: CATEGORICAL_LAYER_MARKER_V0,
585 feature_gate: CATEGORICAL_FEATURE_GATE_V0,
586 source_object_id: format!("primitive:{primitive_name}"),
587 target_object_id: format!("role:{role_slug}"),
588 },
589 )
590 .collect::<Vec<_>>();
591 let morphism_mappings = source
592 .morphisms
593 .iter()
594 .filter(|morphism| morphism.relation != "identity")
595 .filter_map(|source_morphism| {
596 let target_from = map_object_id_v0(&object_mappings, &source_morphism.from_object_id)?;
597 let target_to = map_object_id_v0(&object_mappings, &source_morphism.to_object_id)?;
598 let target_morphism = find_morphism_v0(&target, &target_from, &target_to)?;
599 Some(CascadeFunctorMorphismMappingV0 {
600 schema_version: CATEGORICAL_SCHEMA_VERSION_V0,
601 product: "omena-categorical.functor-morphism-mapping",
602 layer_marker: CATEGORICAL_LAYER_MARKER_V0,
603 feature_gate: CATEGORICAL_FEATURE_GATE_V0,
604 source_morphism_id: source_morphism.morphism_id.clone(),
605 target_morphism_id: target_morphism.morphism_id.clone(),
606 source_from_object_id: source_morphism.from_object_id.clone(),
607 source_to_object_id: source_morphism.to_object_id.clone(),
608 target_from_object_id: target_from,
609 target_to_object_id: target_to,
610 })
611 })
612 .collect::<Vec<_>>();
613
614 let source_non_identity = source
615 .morphisms
616 .iter()
617 .filter(|morphism| morphism.relation != "identity")
618 .collect::<Vec<_>>();
619 let composed_source = source_non_identity
620 .first()
621 .zip(source_non_identity.get(1))
622 .and_then(|(left, right)| compose_morphisms_v0(left, right, "source-composite"));
623 let composed_target = composed_source.as_ref().and_then(|composite| {
624 let target_from = map_object_id_v0(&object_mappings, &composite.from_object_id)?;
625 let target_to = map_object_id_v0(&object_mappings, &composite.to_object_id)?;
626 let left = find_morphism_v0(
627 &target,
628 &map_object_id_v0(&object_mappings, &source_non_identity[0].from_object_id)?,
629 &map_object_id_v0(&object_mappings, &source_non_identity[0].to_object_id)?,
630 )?;
631 let right = find_morphism_v0(
632 &target,
633 &map_object_id_v0(&object_mappings, &source_non_identity[1].from_object_id)?,
634 &map_object_id_v0(&object_mappings, &source_non_identity[1].to_object_id)?,
635 )?;
636 let target_composite = compose_morphisms_v0(left, right, "target-composite")?;
637 (target_composite.from_object_id == target_from
638 && target_composite.to_object_id == target_to)
639 .then_some(target_composite)
640 });
641 let identity_preserved = source.objects.iter().all(|object| {
642 let Some(target_object_id) = map_object_id_v0(&object_mappings, &object.object_id) else {
643 return false;
644 };
645 find_morphism_v0(&source, &object.object_id, &object.object_id).is_some()
646 && find_morphism_v0(&target, &target_object_id, &target_object_id).is_some()
647 });
648 let composition_preserved = composed_source.is_some() && composed_target.is_some();
649
650 CascadeFunctorApplicationV0 {
651 schema_version: CATEGORICAL_SCHEMA_VERSION_V0,
652 product: functor_product,
653 layer_marker: CATEGORICAL_LAYER_MARKER_V0,
654 feature_gate: CATEGORICAL_FEATURE_GATE_V0,
655 functor_id: functor_id.to_string(),
656 source_category_id: source.category_id,
657 target_category_id: target.category_id,
658 object_mapping_count: object_mappings.len(),
659 morphism_mapping_count: morphism_mappings.len(),
660 composed_source_morphism_id: composed_source.map(|morphism| morphism.morphism_id),
661 composed_target_morphism_id: composed_target.map(|morphism| morphism.morphism_id),
662 identity_preserved,
663 composition_preserved,
664 accepted: identity_preserved && composition_preserved && !morphism_mappings.is_empty(),
665 }
666}
667
668fn category_object_v0(object_id: String, object_kind: &'static str) -> CascadeCategoryObjectV0 {
669 CascadeCategoryObjectV0 {
670 schema_version: CATEGORICAL_SCHEMA_VERSION_V0,
671 product: "omena-categorical.category-object",
672 layer_marker: CATEGORICAL_LAYER_MARKER_V0,
673 feature_gate: CATEGORICAL_FEATURE_GATE_V0,
674 object_id,
675 object_kind,
676 }
677}
678
679fn category_morphisms_from_objects_v0(
680 objects: &[CascadeCategoryObjectV0],
681 relation: &'static str,
682) -> Vec<CascadeCategoryMorphismV0> {
683 let mut morphisms = objects
684 .iter()
685 .map(|object| CascadeCategoryMorphismV0 {
686 schema_version: CATEGORICAL_SCHEMA_VERSION_V0,
687 product: "omena-categorical.category-morphism",
688 layer_marker: CATEGORICAL_LAYER_MARKER_V0,
689 feature_gate: CATEGORICAL_FEATURE_GATE_V0,
690 morphism_id: format!("id:{}", object.object_id),
691 from_object_id: object.object_id.clone(),
692 to_object_id: object.object_id.clone(),
693 relation: "identity",
694 })
695 .collect::<Vec<_>>();
696
697 morphisms.extend(objects.windows(2).map(|window| CascadeCategoryMorphismV0 {
698 schema_version: CATEGORICAL_SCHEMA_VERSION_V0,
699 product: "omena-categorical.category-morphism",
700 layer_marker: CATEGORICAL_LAYER_MARKER_V0,
701 feature_gate: CATEGORICAL_FEATURE_GATE_V0,
702 morphism_id: format!("{}->{}", window[0].object_id, window[1].object_id),
703 from_object_id: window[0].object_id.clone(),
704 to_object_id: window[1].object_id.clone(),
705 relation,
706 }));
707 morphisms
708}
709
710fn compose_morphisms_v0(
711 left: &CascadeCategoryMorphismV0,
712 right: &CascadeCategoryMorphismV0,
713 relation: &'static str,
714) -> Option<CascadeCategoryMorphismV0> {
715 (left.to_object_id == right.from_object_id).then(|| CascadeCategoryMorphismV0 {
716 schema_version: CATEGORICAL_SCHEMA_VERSION_V0,
717 product: "omena-categorical.category-morphism-composition",
718 layer_marker: CATEGORICAL_LAYER_MARKER_V0,
719 feature_gate: CATEGORICAL_FEATURE_GATE_V0,
720 morphism_id: format!("{};{}", left.morphism_id, right.morphism_id),
721 from_object_id: left.from_object_id.clone(),
722 to_object_id: right.to_object_id.clone(),
723 relation,
724 })
725}
726
727fn find_morphism_v0<'a>(
728 category: &'a CascadeCategoryV0,
729 from_object_id: &str,
730 to_object_id: &str,
731) -> Option<&'a CascadeCategoryMorphismV0> {
732 category.morphisms.iter().find(|morphism| {
733 morphism.from_object_id == from_object_id && morphism.to_object_id == to_object_id
734 })
735}
736
737fn map_object_id_v0(
738 mappings: &[CascadeFunctorObjectMappingV0],
739 source_object_id: &str,
740) -> Option<String> {
741 mappings
742 .iter()
743 .find(|mapping| mapping.source_object_id == source_object_id)
744 .map(|mapping| mapping.target_object_id.clone())
745}
746
747fn slug_v0(value: &str) -> String {
748 value
749 .chars()
750 .map(|character| {
751 if character.is_ascii_alphanumeric() {
752 character.to_ascii_lowercase()
753 } else {
754 '-'
755 }
756 })
757 .collect::<String>()
758 .split('-')
759 .filter(|part| !part.is_empty())
760 .collect::<Vec<_>>()
761 .join("-")
762}
763
764#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize)]
765#[serde(rename_all = "camelCase")]
766pub struct DesignSystemEdgeKindCountV0 {
767 pub schema_version: &'static str,
768 pub product: &'static str,
769 pub layer_marker: &'static str,
770 pub feature_gate: &'static str,
771 pub edge_kind: String,
772 pub count: usize,
773}
774
775#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
776#[serde(rename_all = "camelCase")]
777pub struct DesignSystemProjectSummaryInputV0 {
778 pub schema_version: &'static str,
779 pub product: &'static str,
780 pub layer_marker: &'static str,
781 pub feature_gate: &'static str,
782 pub project_id: String,
783 pub source_product: &'static str,
784 pub summary_hash: String,
785 pub summary_edge_count: usize,
786 pub edge_kind_counts: Vec<DesignSystemEdgeKindCountV0>,
787}
788
789#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
790#[serde(rename_all = "camelCase")]
791pub struct SortInterpretationV0 {
792 pub schema_version: &'static str,
793 pub product: &'static str,
794 pub layer_marker: &'static str,
795 pub feature_gate: &'static str,
796 pub sort_name: String,
797 pub element_count: usize,
798}
799
800#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
801#[serde(rename_all = "camelCase")]
802pub struct DesignSystemModelV0 {
803 pub schema_version: &'static str,
804 pub product: &'static str,
805 pub layer_marker: &'static str,
806 pub feature_gate: &'static str,
807 pub model_id: String,
808 pub theory_id: String,
809 pub source_product: &'static str,
810 pub project_id: String,
811 pub summary_hash: String,
812 pub summary_edge_count: usize,
813 pub edge_kind_counts: Vec<DesignSystemEdgeKindCountV0>,
814 pub sort_interpretations: Vec<SortInterpretationV0>,
815}
816
817#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
818#[serde(rename_all = "camelCase")]
819pub struct DesignSystemInvariantSummaryV0 {
820 pub schema_version: &'static str,
821 pub product: &'static str,
822 pub layer_marker: &'static str,
823 pub feature_gate: &'static str,
824 pub invariant_id: String,
825 pub invariant_kind: &'static str,
826 pub model_count: usize,
827 pub source_products: Vec<&'static str>,
828 pub model_hashes: Vec<String>,
829 pub differing_sort_names: Vec<String>,
830 pub accepted: bool,
831}
832
833pub fn design_system_model_from_project_summary_v0(
834 theory_id: impl Into<String>,
835 input: DesignSystemProjectSummaryInputV0,
836) -> DesignSystemModelV0 {
837 let theory_id = theory_id.into();
838 let mut edge_kind_counts = input.edge_kind_counts;
839 edge_kind_counts.sort();
840 let mut sort_interpretations = edge_kind_counts
841 .iter()
842 .map(|entry| SortInterpretationV0 {
843 schema_version: CATEGORICAL_SCHEMA_VERSION_V0,
844 product: "omena-categorical.sort-interpretation",
845 layer_marker: CATEGORICAL_LAYER_MARKER_V0,
846 feature_gate: CATEGORICAL_FEATURE_GATE_V0,
847 sort_name: format!("edgeKind:{}", entry.edge_kind),
848 element_count: entry.count,
849 })
850 .collect::<Vec<_>>();
851 sort_interpretations.push(SortInterpretationV0 {
852 schema_version: CATEGORICAL_SCHEMA_VERSION_V0,
853 product: "omena-categorical.sort-interpretation",
854 layer_marker: CATEGORICAL_LAYER_MARKER_V0,
855 feature_gate: CATEGORICAL_FEATURE_GATE_V0,
856 sort_name: "summaryEdge".to_string(),
857 element_count: input.summary_edge_count,
858 });
859 sort_interpretations.sort_by(|left, right| left.sort_name.cmp(&right.sort_name));
860
861 DesignSystemModelV0 {
862 schema_version: CATEGORICAL_SCHEMA_VERSION_V0,
863 product: "omena-categorical.design-system-model",
864 layer_marker: CATEGORICAL_LAYER_MARKER_V0,
865 feature_gate: CATEGORICAL_FEATURE_GATE_V0,
866 model_id: format!(
867 "design-system-model:{}:{}",
868 input.project_id, input.summary_hash
869 ),
870 theory_id,
871 source_product: input.source_product,
872 project_id: input.project_id,
873 summary_hash: input.summary_hash,
874 summary_edge_count: input.summary_edge_count,
875 edge_kind_counts,
876 sort_interpretations,
877 }
878}
879
880pub fn compare_design_system_models_for_invariant_v0(
881 invariant_id: impl Into<String>,
882 models: &[DesignSystemModelV0],
883) -> DesignSystemInvariantSummaryV0 {
884 let differing_sort_names = differing_design_system_model_sort_names_v0(models);
885 DesignSystemInvariantSummaryV0 {
886 schema_version: CATEGORICAL_SCHEMA_VERSION_V0,
887 product: "omena-categorical.design-system-invariant-summary",
888 layer_marker: CATEGORICAL_LAYER_MARKER_V0,
889 feature_gate: CATEGORICAL_FEATURE_GATE_V0,
890 invariant_id: invariant_id.into(),
891 invariant_kind: "crossProjectEdgeKindSymmetry",
892 model_count: models.len(),
893 source_products: models.iter().map(|model| model.source_product).collect(),
894 model_hashes: models
895 .iter()
896 .map(|model| model.summary_hash.clone())
897 .collect(),
898 accepted: models.len() >= 2 && differing_sort_names.is_empty(),
899 differing_sort_names,
900 }
901}
902
903fn differing_design_system_model_sort_names_v0(models: &[DesignSystemModelV0]) -> Vec<String> {
904 let Some(first) = models.first() else {
905 return Vec::new();
906 };
907 let baseline = first
908 .sort_interpretations
909 .iter()
910 .map(|sort| (sort.sort_name.as_str(), sort.element_count))
911 .collect::<Vec<_>>();
912 let mut differing_sort_names = BTreeSet::new();
913 for model in models.iter().skip(1) {
914 for (sort_name, baseline_count) in &baseline {
915 let current_count = model
916 .sort_interpretations
917 .iter()
918 .find(|sort| sort.sort_name == *sort_name)
919 .map(|sort| sort.element_count);
920 if current_count != Some(*baseline_count) {
921 differing_sort_names.insert((*sort_name).to_string());
922 }
923 }
924 for sort in &model.sort_interpretations {
925 if !baseline
926 .iter()
927 .any(|(sort_name, _)| *sort_name == sort.sort_name)
928 {
929 differing_sort_names.insert(sort.sort_name.clone());
930 }
931 }
932 }
933 differing_sort_names.into_iter().collect()
934}
935
936pub const MULTISCALE_COMPLEXITY_HEURISTIC_SCHEMA_VERSION_V0: &str = "0";
937#[deprecated(
938 since = "0.4.0",
939 note = "legacy layer byte owned by omena-product-hints maintainers; removal is not before 1.0 and requires downstream migration plus zero audited non-compatibility uses"
940)]
941const LEGACY_MULTISCALE_COMPLEXITY_LAYER_BYTES_V0: &str = "rg-flow-statistical";
942#[deprecated(
943 since = "0.4.0",
944 note = "legacy feature byte owned by omena-product-hints maintainers; removal is not before 1.0 and requires downstream migration plus zero audited non-compatibility uses"
945)]
946const LEGACY_MULTISCALE_COMPLEXITY_FEATURE_BYTES_V0: &str = "rg-flow";
947#[allow(deprecated)]
948const MULTISCALE_COMPLEXITY_HEURISTIC_COMPATIBILITY_LAYER_MARKER_V0: &str =
949 LEGACY_MULTISCALE_COMPLEXITY_LAYER_BYTES_V0;
950#[allow(deprecated)]
951const MULTISCALE_COMPLEXITY_HEURISTIC_COMPATIBILITY_FEATURE_GATE_V0: &str =
952 LEGACY_MULTISCALE_COMPLEXITY_FEATURE_BYTES_V0;
953pub const MULTISCALE_COMPLEXITY_HEURISTIC_LAYER_MARKER_V0: &str =
954 "multiscale-complexity-heuristic-statistical";
955pub const MULTISCALE_COMPLEXITY_HEURISTIC_FEATURE_GATE_V0: &str = "multiscale-complexity-heuristic";
956pub const MULTISCALE_COMPLEXITY_HEURISTIC_MECHANISM_SCOPE_V0: &str =
957 "optInDeepAnalysisJacobianSpectrumHintSubstrate";
958pub const MULTISCALE_COMPLEXITY_HEURISTIC_PRODUCT_SURFACE_V0: &str =
959 "deepAnalysisCascadeSensitivityHint";
960pub const MULTISCALE_COMPLEXITY_HEURISTIC_DEFAULT_PRODUCT_DECISION_MECHANISM_V0: bool = false;
961const MULTISCALE_COMPLEXITY_HEURISTIC_EIGEN_EPSILON: f64 = 1e-9;
962
963#[deprecated(
966 since = "0.4.0",
967 note = "use MULTISCALE_COMPLEXITY_HEURISTIC_*; removal is not before 1.0 and requires downstream migration plus zero audited non-compatibility uses"
968)]
969pub const RG_FLOW_SCHEMA_VERSION_V0: &str = "0";
970#[deprecated(
971 since = "0.4.0",
972 note = "use MULTISCALE_COMPLEXITY_HEURISTIC_*; removal is not before 1.0 and requires downstream migration plus zero audited non-compatibility uses"
973)]
974pub const RG_FLOW_LAYER_MARKER_V0: &str = "rg-flow-statistical";
975#[deprecated(
976 since = "0.4.0",
977 note = "use MULTISCALE_COMPLEXITY_HEURISTIC_*; removal is not before 1.0 and requires downstream migration plus zero audited non-compatibility uses"
978)]
979pub const RG_FLOW_FEATURE_GATE_V0: &str = "rg-flow";
980#[deprecated(
981 since = "0.4.0",
982 note = "use MULTISCALE_COMPLEXITY_HEURISTIC_*; removal is not before 1.0 and requires downstream migration plus zero audited non-compatibility uses"
983)]
984pub const RG_FLOW_MECHANISM_SCOPE_V0: &str = MULTISCALE_COMPLEXITY_HEURISTIC_MECHANISM_SCOPE_V0;
985#[deprecated(
986 since = "0.4.0",
987 note = "use MULTISCALE_COMPLEXITY_HEURISTIC_*; removal is not before 1.0 and requires downstream migration plus zero audited non-compatibility uses"
988)]
989pub const RG_FLOW_PRODUCT_SURFACE_V0: &str = MULTISCALE_COMPLEXITY_HEURISTIC_PRODUCT_SURFACE_V0;
990#[deprecated(
991 since = "0.4.0",
992 note = "use MULTISCALE_COMPLEXITY_HEURISTIC_*; removal is not before 1.0 and requires downstream migration plus zero audited non-compatibility uses"
993)]
994pub const RG_FLOW_DEFAULT_PRODUCT_DECISION_MECHANISM_V0: bool =
995 MULTISCALE_COMPLEXITY_HEURISTIC_DEFAULT_PRODUCT_DECISION_MECHANISM_V0;
996
997#[derive(Debug, Clone, PartialEq, Serialize)]
998#[serde(rename_all = "camelCase")]
999pub struct CouplingSpaceV0 {
1000 pub schema_version: &'static str,
1001 pub product: &'static str,
1002 pub layer_marker: &'static str,
1003 pub feature_gate: &'static str,
1004 pub k_env: usize,
1005 pub k_decl: usize,
1006 pub k_cycle: usize,
1007 pub k_dirty: usize,
1008}
1009
1010#[derive(Debug, Clone, PartialEq, Serialize)]
1011#[serde(rename_all = "camelCase")]
1012pub struct CouplingJacobianSpectrumV0 {
1013 pub schema_version: &'static str,
1014 pub product: &'static str,
1015 pub layer_marker: &'static str,
1016 pub feature_gate: &'static str,
1017 pub mechanism_scope: &'static str,
1018 pub product_surface: &'static str,
1019 pub default_product_decision_mechanism: bool,
1020 pub matrix: Vec<Vec<f64>>,
1021 pub eigenvalues: Vec<f64>,
1022 pub spectral_radius: f64,
1023 pub computed_from: &'static str,
1024}
1025
1026fn coupling_space_with_markers_v0(
1027 k_env: usize,
1028 k_decl: usize,
1029 k_cycle: usize,
1030 k_dirty: usize,
1031 layer_marker: &'static str,
1032 feature_gate: &'static str,
1033) -> CouplingSpaceV0 {
1034 CouplingSpaceV0 {
1035 schema_version: MULTISCALE_COMPLEXITY_HEURISTIC_SCHEMA_VERSION_V0,
1036 product: "omena-rg-flow.coupling-space",
1037 layer_marker,
1038 feature_gate,
1039 k_env,
1040 k_decl,
1041 k_cycle,
1042 k_dirty,
1043 }
1044}
1045
1046#[allow(deprecated)]
1050#[deprecated(
1051 since = "0.4.0",
1052 note = "use multiscale_complexity_heuristic_coupling_space; removal is not before 1.0 and requires downstream migration plus zero audited non-compatibility uses"
1053)]
1054pub fn coupling_space(
1055 k_env: usize,
1056 k_decl: usize,
1057 k_cycle: usize,
1058 k_dirty: usize,
1059) -> CouplingSpaceV0 {
1060 coupling_space_with_markers_v0(
1061 k_env,
1062 k_decl,
1063 k_cycle,
1064 k_dirty,
1065 MULTISCALE_COMPLEXITY_HEURISTIC_COMPATIBILITY_LAYER_MARKER_V0,
1066 MULTISCALE_COMPLEXITY_HEURISTIC_COMPATIBILITY_FEATURE_GATE_V0,
1067 )
1068}
1069
1070pub fn multiscale_complexity_heuristic_coupling_space(
1072 k_env: usize,
1073 k_decl: usize,
1074 k_cycle: usize,
1075 k_dirty: usize,
1076) -> CouplingSpaceV0 {
1077 coupling_space_with_markers_v0(
1078 k_env,
1079 k_decl,
1080 k_cycle,
1081 k_dirty,
1082 MULTISCALE_COMPLEXITY_HEURISTIC_LAYER_MARKER_V0,
1083 MULTISCALE_COMPLEXITY_HEURISTIC_FEATURE_GATE_V0,
1084 )
1085}
1086
1087fn coupling_jacobian_spectrum_with_markers_v0(
1088 before: &CouplingSpaceV0,
1089 after: &CouplingSpaceV0,
1090 layer_marker: &'static str,
1091 feature_gate: &'static str,
1092) -> CouplingJacobianSpectrumV0 {
1093 let beta_env = signed_delta(after.k_env, before.k_env);
1094 let beta_decl = signed_delta(after.k_decl, before.k_decl);
1095 let beta_cycle = signed_delta(after.k_cycle, before.k_cycle);
1096 let beta_dirty = signed_delta(after.k_dirty, before.k_dirty);
1097 let env_decl_cross = coupling_cross_sensitivity(before.k_decl, after.k_decl, before.k_env);
1098 let decl_env_cross = coupling_cross_sensitivity(before.k_env, after.k_env, before.k_decl);
1099 let cycle_dirty_cross =
1100 coupling_cross_sensitivity(before.k_dirty, after.k_dirty, before.k_cycle);
1101 let dirty_cycle_cross =
1102 coupling_cross_sensitivity(before.k_cycle, after.k_cycle, before.k_dirty);
1103 let matrix = vec![
1104 vec![
1105 diagonal_coupling_sensitivity(beta_env, before.k_env),
1106 env_decl_cross,
1107 0.0,
1108 0.0,
1109 ],
1110 vec![
1111 decl_env_cross,
1112 diagonal_coupling_sensitivity(beta_decl, before.k_decl),
1113 0.0,
1114 0.0,
1115 ],
1116 vec![
1117 0.0,
1118 0.0,
1119 diagonal_coupling_sensitivity(beta_cycle, before.k_cycle),
1120 cycle_dirty_cross,
1121 ],
1122 vec![
1123 0.0,
1124 0.0,
1125 dirty_cycle_cross,
1126 diagonal_coupling_sensitivity(beta_dirty, before.k_dirty),
1127 ],
1128 ];
1129 let mut eigenvalues =
1130 eigenvalues_for_2x2_block(matrix[0][0], matrix[0][1], matrix[1][0], matrix[1][1]);
1131 eigenvalues.extend(eigenvalues_for_2x2_block(
1132 matrix[2][2],
1133 matrix[2][3],
1134 matrix[3][2],
1135 matrix[3][3],
1136 ));
1137 let spectral_radius = eigenvalues
1138 .iter()
1139 .map(|value| value.abs())
1140 .fold(0.0, f64::max);
1141
1142 CouplingJacobianSpectrumV0 {
1143 schema_version: MULTISCALE_COMPLEXITY_HEURISTIC_SCHEMA_VERSION_V0,
1144 product: "omena-rg-flow.coupling-jacobian-spectrum",
1145 layer_marker,
1146 feature_gate,
1147 mechanism_scope: MULTISCALE_COMPLEXITY_HEURISTIC_MECHANISM_SCOPE_V0,
1148 product_surface: MULTISCALE_COMPLEXITY_HEURISTIC_PRODUCT_SURFACE_V0,
1149 default_product_decision_mechanism:
1150 MULTISCALE_COMPLEXITY_HEURISTIC_DEFAULT_PRODUCT_DECISION_MECHANISM_V0,
1151 matrix,
1152 eigenvalues,
1153 spectral_radius,
1154 computed_from: "finite-difference-linearization-v0",
1155 }
1156}
1157
1158#[allow(deprecated)]
1162#[deprecated(
1163 since = "0.4.0",
1164 note = "use estimate_multiscale_complexity_heuristic_coupling_jacobian_spectrum_v0; removal is not before 1.0 and requires downstream migration plus zero audited non-compatibility uses"
1165)]
1166pub fn estimate_coupling_jacobian_spectrum_v0(
1167 before: &CouplingSpaceV0,
1168 after: &CouplingSpaceV0,
1169) -> CouplingJacobianSpectrumV0 {
1170 coupling_jacobian_spectrum_with_markers_v0(
1171 before,
1172 after,
1173 MULTISCALE_COMPLEXITY_HEURISTIC_COMPATIBILITY_LAYER_MARKER_V0,
1174 MULTISCALE_COMPLEXITY_HEURISTIC_COMPATIBILITY_FEATURE_GATE_V0,
1175 )
1176}
1177
1178pub fn estimate_multiscale_complexity_heuristic_coupling_jacobian_spectrum_v0(
1181 before: &CouplingSpaceV0,
1182 after: &CouplingSpaceV0,
1183) -> CouplingJacobianSpectrumV0 {
1184 coupling_jacobian_spectrum_with_markers_v0(
1185 before,
1186 after,
1187 MULTISCALE_COMPLEXITY_HEURISTIC_LAYER_MARKER_V0,
1188 MULTISCALE_COMPLEXITY_HEURISTIC_FEATURE_GATE_V0,
1189 )
1190}
1191
1192fn signed_delta(after: usize, before: usize) -> f64 {
1193 after as f64 - before as f64
1194}
1195
1196fn diagonal_coupling_sensitivity(beta: f64, before: usize) -> f64 {
1197 beta / before.max(1) as f64
1198}
1199
1200fn coupling_cross_sensitivity(
1201 source_before: usize,
1202 source_after: usize,
1203 target_before: usize,
1204) -> f64 {
1205 let source_delta = signed_delta(source_after, source_before).abs();
1206 if source_delta <= MULTISCALE_COMPLEXITY_HEURISTIC_EIGEN_EPSILON {
1207 0.0
1208 } else {
1209 source_delta / source_before.saturating_add(target_before).max(1) as f64
1210 }
1211}
1212
1213fn eigenvalues_for_2x2_block(a: f64, b: f64, c: f64, d: f64) -> Vec<f64> {
1214 let trace = a + d;
1215 let discriminant = ((a - d) * (a - d) + 4.0 * b * c).max(0.0).sqrt();
1216 vec![(trace + discriminant) / 2.0, (trace - discriminant) / 2.0]
1217}
1218
1219pub const VARIATIONAL_SCHEMA_VERSION_V0: &str = "0";
1220pub const VARIATIONAL_LAYER_MARKER_V0: &str = "variational-cascade";
1221pub const VARIATIONAL_FEATURE_GATE_V0: &str = "variational";
1222
1223#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
1224#[serde(rename_all = "camelCase")]
1225pub enum DesignerIntentPosteriorModeV0 {
1226 VciFormal,
1227 PcnHierarchical,
1228 Fallback,
1229}
1230
1231#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
1232#[serde(rename_all = "camelCase")]
1233pub enum PatternIntentV0 {
1234 Bem,
1235 Utility,
1236 Atomic,
1237 Hybrid,
1238 AdHoc,
1239}
1240
1241impl PatternIntentV0 {
1242 pub const fn as_str(self) -> &'static str {
1243 match self {
1244 Self::Bem => "bem",
1245 Self::Utility => "utility",
1246 Self::Atomic => "atomic",
1247 Self::Hybrid => "hybrid",
1248 Self::AdHoc => "adHoc",
1249 }
1250 }
1251}
1252
1253#[derive(Debug, Clone, PartialEq, Serialize)]
1254#[serde(rename_all = "camelCase")]
1255pub struct DesignerIntentScoreV0 {
1256 pub schema_version: &'static str,
1257 pub product: &'static str,
1258 pub layer_marker: &'static str,
1259 pub feature_gate: &'static str,
1260 pub intent: PatternIntentV0,
1261 pub log_probability_bits: f64,
1262}
1263
1264#[derive(Debug, Clone, PartialEq, Serialize)]
1265#[serde(rename_all = "camelCase")]
1266pub struct DesignerIntentPosteriorV0 {
1267 pub schema_version: &'static str,
1268 pub product: &'static str,
1269 pub layer_marker: &'static str,
1270 pub feature_gate: &'static str,
1271 pub mode: DesignerIntentPosteriorModeV0,
1272 pub selector_name: String,
1273 pub scores: Vec<DesignerIntentScoreV0>,
1274 pub enabled_by_default: bool,
1275}
1276
1277#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1278#[serde(rename_all = "camelCase")]
1279pub struct DesignerIntentPosteriorInputV0 {
1280 pub schema_version: &'static str,
1281 pub product: &'static str,
1282 pub layer_marker: &'static str,
1283 pub feature_gate: &'static str,
1284 pub selector_name: String,
1285 pub declaration_count: usize,
1286 pub duplicate_property_tie_count: usize,
1287 pub custom_property_reference_count: usize,
1288}
1289
1290pub fn designer_intent_posterior_input_v0(
1291 selector_name: impl Into<String>,
1292 declaration_count: usize,
1293 duplicate_property_tie_count: usize,
1294 custom_property_reference_count: usize,
1295) -> DesignerIntentPosteriorInputV0 {
1296 DesignerIntentPosteriorInputV0 {
1297 schema_version: VARIATIONAL_SCHEMA_VERSION_V0,
1298 product: "omena-variational.designer-intent-posterior-input",
1299 layer_marker: VARIATIONAL_LAYER_MARKER_V0,
1300 feature_gate: VARIATIONAL_FEATURE_GATE_V0,
1301 selector_name: selector_name.into(),
1302 declaration_count,
1303 duplicate_property_tie_count,
1304 custom_property_reference_count,
1305 }
1306}
1307
1308pub fn infer_designer_intent_posterior_v0(
1309 input: DesignerIntentPosteriorInputV0,
1310) -> DesignerIntentPosteriorV0 {
1311 let selector = normalize_selector_name_for_intent_v0(&input.selector_name);
1312 let has_bem_marker = selector.contains("__") || selector.contains("--");
1313 let looks_utility = selector.starts_with("u-")
1314 || selector.starts_with("is-")
1315 || selector.starts_with("has-")
1316 || selector
1317 .split('-')
1318 .any(|part| matches!(part, "m" | "p" | "mt" | "mb" | "ml" | "mr" | "bg" | "text"));
1319 let looks_atomic = input.declaration_count <= 1 && selector.len() <= 8;
1320 let looks_hybrid = selector.matches('-').count() >= 3
1321 || (has_bem_marker && input.custom_property_reference_count > 0);
1322 let mut scores = vec![
1323 DesignerIntentScoreV0 {
1324 schema_version: VARIATIONAL_SCHEMA_VERSION_V0,
1325 product: "omena-variational.designer-intent-score",
1326 layer_marker: VARIATIONAL_LAYER_MARKER_V0,
1327 feature_gate: VARIATIONAL_FEATURE_GATE_V0,
1328 intent: PatternIntentV0::Bem,
1329 log_probability_bits: bool_bits_v0(has_bem_marker) * 7.0
1330 + bool_bits_v0(input.declaration_count > 1)
1331 - bool_bits_v0(input.duplicate_property_tie_count > 0),
1332 },
1333 DesignerIntentScoreV0 {
1334 schema_version: VARIATIONAL_SCHEMA_VERSION_V0,
1335 product: "omena-variational.designer-intent-score",
1336 layer_marker: VARIATIONAL_LAYER_MARKER_V0,
1337 feature_gate: VARIATIONAL_FEATURE_GATE_V0,
1338 intent: PatternIntentV0::Utility,
1339 log_probability_bits: bool_bits_v0(looks_utility) * 6.5
1340 - bool_bits_v0(has_bem_marker) * 2.0
1341 + bool_bits_v0(input.declaration_count <= 2),
1342 },
1343 DesignerIntentScoreV0 {
1344 schema_version: VARIATIONAL_SCHEMA_VERSION_V0,
1345 product: "omena-variational.designer-intent-score",
1346 layer_marker: VARIATIONAL_LAYER_MARKER_V0,
1347 feature_gate: VARIATIONAL_FEATURE_GATE_V0,
1348 intent: PatternIntentV0::Atomic,
1349 log_probability_bits: bool_bits_v0(looks_atomic) * 5.0
1350 - bool_bits_v0(input.declaration_count > 1) * 2.0,
1351 },
1352 DesignerIntentScoreV0 {
1353 schema_version: VARIATIONAL_SCHEMA_VERSION_V0,
1354 product: "omena-variational.designer-intent-score",
1355 layer_marker: VARIATIONAL_LAYER_MARKER_V0,
1356 feature_gate: VARIATIONAL_FEATURE_GATE_V0,
1357 intent: PatternIntentV0::Hybrid,
1358 log_probability_bits: bool_bits_v0(has_bem_marker)
1359 + bool_bits_v0(looks_hybrid) * 4.0
1360 + bool_bits_v0(input.custom_property_reference_count > 0) * 1.5,
1361 },
1362 DesignerIntentScoreV0 {
1363 schema_version: VARIATIONAL_SCHEMA_VERSION_V0,
1364 product: "omena-variational.designer-intent-score",
1365 layer_marker: VARIATIONAL_LAYER_MARKER_V0,
1366 feature_gate: VARIATIONAL_FEATURE_GATE_V0,
1367 intent: PatternIntentV0::AdHoc,
1368 log_probability_bits: bool_bits_v0(!has_bem_marker && !looks_utility)
1369 + bool_bits_v0(input.duplicate_property_tie_count > 0),
1370 },
1371 ];
1372 scores.sort_by(|left, right| {
1373 right
1374 .log_probability_bits
1375 .partial_cmp(&left.log_probability_bits)
1376 .unwrap_or(std::cmp::Ordering::Equal)
1377 .then_with(|| left.intent.as_str().cmp(right.intent.as_str()))
1378 });
1379 DesignerIntentPosteriorV0 {
1380 schema_version: VARIATIONAL_SCHEMA_VERSION_V0,
1381 product: "omena-variational.designer-intent-posterior",
1382 layer_marker: VARIATIONAL_LAYER_MARKER_V0,
1383 feature_gate: VARIATIONAL_FEATURE_GATE_V0,
1384 mode: DesignerIntentPosteriorModeV0::VciFormal,
1385 selector_name: input.selector_name,
1386 scores,
1387 enabled_by_default: true,
1388 }
1389}
1390
1391pub fn dominant_designer_intent_v0(
1392 posterior: &DesignerIntentPosteriorV0,
1393) -> Option<PatternIntentV0> {
1394 posterior.scores.first().map(|score| score.intent)
1395}
1396
1397fn normalize_selector_name_for_intent_v0(selector_name: &str) -> String {
1398 selector_name
1399 .trim()
1400 .trim_start_matches('.')
1401 .split([':', '[', ' ', '>', '+', '~', ','])
1402 .next()
1403 .unwrap_or(selector_name)
1404 .trim()
1405 .to_string()
1406}
1407
1408fn bool_bits_v0(value: bool) -> f64 {
1409 if value { 1.0 } else { 0.0 }
1410}
1411
1412pub const REPLICA_ENSEMBLE_SCHEMA_VERSION_V0: &str = "0";
1413pub const REPLICA_ENSEMBLE_LAYER_MARKER_V0: &str = "replica-ensemble";
1414pub const REPLICA_ENSEMBLE_FEATURE_GATE_V0: &str = "replica-ensemble";
1415pub const REPLICA_ENSEMBLE_MECHANISM_SCOPE_V0: &str =
1416 "productWiredCrossFileConsistencyHintSubstrate";
1417pub const REPLICA_ENSEMBLE_PRODUCT_SURFACE_V0: &str = "defaultCrossFileConsistencyHint";
1418pub const REPLICA_ENSEMBLE_DEFAULT_PRODUCT_DECISION_MECHANISM_V0: bool = false;
1419
1420#[deprecated(
1425 since = "0.4.0",
1426 note = "use CascadeSectionKeyV0; removal is not before 1.0 and requires downstream migration plus zero audited in-repo non-compatibility uses"
1427)]
1428#[derive(Debug, Clone, Serialize)]
1429#[serde(rename_all = "camelCase")]
1430pub struct CascadeSiteKeyV0 {
1431 pub schema_version: &'static str,
1432 pub product: &'static str,
1433 pub layer_marker: &'static str,
1434 pub feature_gate: &'static str,
1435 pub element_selector: String,
1436 pub property: AuthoredPropertyTextV0,
1437}
1438
1439#[derive(Debug, Clone, Serialize)]
1440#[serde(rename_all = "camelCase")]
1441pub struct CascadeSectionKeyV0 {
1442 pub schema_version: &'static str,
1443 pub product: &'static str,
1444 pub layer_marker: &'static str,
1445 pub feature_gate: &'static str,
1446 pub element_selector: String,
1447 pub property: AuthoredPropertyTextV0,
1448}
1449
1450macro_rules! impl_cascade_key_identity {
1451 ($type_name:ty) => {
1452 #[allow(deprecated)]
1453 impl PartialEq for $type_name {
1454 fn eq(&self, other: &Self) -> bool {
1455 self.schema_version == other.schema_version
1456 && self.product == other.product
1457 && self.layer_marker == other.layer_marker
1458 && self.feature_gate == other.feature_gate
1459 && self.element_selector == other.element_selector
1460 && self.property.to_property_name().canonical_key()
1461 == other.property.to_property_name().canonical_key()
1462 }
1463 }
1464
1465 #[allow(deprecated)]
1466 impl Eq for $type_name {}
1467
1468 #[allow(deprecated)]
1469 impl PartialOrd for $type_name {
1470 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1471 Some(self.cmp(other))
1472 }
1473 }
1474
1475 #[allow(deprecated)]
1476 impl Ord for $type_name {
1477 fn cmp(&self, other: &Self) -> Ordering {
1478 (
1479 self.schema_version,
1480 self.product,
1481 self.layer_marker,
1482 self.feature_gate,
1483 &self.element_selector,
1484 self.property.to_property_name().canonical_key(),
1485 )
1486 .cmp(&(
1487 other.schema_version,
1488 other.product,
1489 other.layer_marker,
1490 other.feature_gate,
1491 &other.element_selector,
1492 other.property.to_property_name().canonical_key(),
1493 ))
1494 }
1495 }
1496 };
1497}
1498
1499impl_cascade_key_identity!(CascadeSiteKeyV0);
1500impl_cascade_key_identity!(CascadeSectionKeyV0);
1501
1502#[allow(deprecated)]
1503#[deprecated(
1504 since = "0.4.0",
1505 note = "compatibility conversion owned by omena-product-hints maintainers; removal is not before 1.0 and requires downstream migration plus zero audited in-repo non-compatibility uses"
1506)]
1507pub fn cascade_section_key_from_site_key_v0(key: CascadeSiteKeyV0) -> CascadeSectionKeyV0 {
1508 CascadeSectionKeyV0 {
1509 schema_version: key.schema_version,
1510 product: "omena-ensemble.cascade-section-key",
1511 layer_marker: key.layer_marker,
1512 feature_gate: key.feature_gate,
1513 element_selector: key.element_selector,
1514 property: key.property,
1515 }
1516}
1517
1518#[allow(deprecated)]
1519#[deprecated(
1520 since = "0.4.0",
1521 note = "compatibility conversion owned by omena-product-hints maintainers; removal is not before 1.0 and requires downstream migration plus zero audited in-repo non-compatibility uses"
1522)]
1523pub fn compatibility_key_from_cascade_section_key_v0(key: CascadeSectionKeyV0) -> CascadeSiteKeyV0 {
1524 CascadeSiteKeyV0 {
1525 schema_version: key.schema_version,
1526 product: "omena-ensemble.cascade-site-key",
1527 layer_marker: key.layer_marker,
1528 feature_gate: key.feature_gate,
1529 element_selector: key.element_selector,
1530 property: key.property,
1531 }
1532}
1533
1534#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1535#[serde(rename_all = "camelCase")]
1536pub struct LinearProvenanceTagV0 {
1537 pub schema_version: &'static str,
1538 pub product: &'static str,
1539 pub layer_marker: &'static str,
1540 pub feature_gate: &'static str,
1541 pub semiring_identifier: &'static str,
1542 pub label: String,
1543}
1544
1545#[allow(deprecated)]
1546#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1547#[serde(rename_all = "camelCase")]
1548pub struct ReplicaSiteOutcomeV0 {
1549 pub schema_version: &'static str,
1550 pub product: &'static str,
1551 pub layer_marker: &'static str,
1552 pub feature_gate: &'static str,
1553 #[deprecated(
1554 since = "0.4.0",
1555 note = "use a future cascade-section outcome carrier; removal is not before 1.0 and requires downstream migration plus zero audited in-repo non-compatibility uses"
1556 )]
1557 pub site: CascadeSiteKeyV0,
1558 pub outcome: CascadeOutcome,
1559 pub provenance: Option<LinearProvenanceTagV0>,
1560}
1561
1562#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1563#[serde(rename_all = "camelCase")]
1564pub struct ReplicaSnapshotV0 {
1565 pub schema_version: &'static str,
1566 pub product: &'static str,
1567 pub layer_marker: &'static str,
1568 pub feature_gate: &'static str,
1569 pub path: String,
1570 pub sites: Vec<ReplicaSiteOutcomeV0>,
1571}
1572
1573#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize)]
1574#[serde(rename_all = "camelCase")]
1575pub enum OutcomeMode {
1576 #[default]
1577 DefiniteOnly,
1578 WidenedRankedSet,
1579 FullStrict,
1580}
1581
1582#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
1583#[serde(rename_all = "camelCase")]
1584pub enum SamplingPolicy {
1585 AllPairs,
1586 PageRankWeighted { max_pair_count: usize },
1587 RandomSubset { max_pair_count: usize },
1588}
1589
1590#[derive(Debug, Clone, PartialEq, Serialize)]
1591#[serde(rename_all = "camelCase")]
1592pub struct ReplicaOverlapV0 {
1593 pub schema_version: &'static str,
1594 pub product: &'static str,
1595 pub layer_marker: &'static str,
1596 pub feature_gate: &'static str,
1597 pub replica_alpha_path: String,
1598 pub replica_beta_path: String,
1599 pub outcome_mode: OutcomeMode,
1600 pub shared_site_count: usize,
1601 pub agreeing_site_count: usize,
1602 pub overlap_q: f64,
1603 pub overlap_q_unit: &'static str,
1604 pub provenance_attributions: Vec<OverlapAttributionV0>,
1605}
1606
1607#[derive(Debug, Clone, Serialize)]
1608#[serde(rename_all = "camelCase")]
1609pub struct OverlapAttributionV0 {
1610 pub schema_version: &'static str,
1611 pub product: &'static str,
1612 pub layer_marker: &'static str,
1613 pub feature_gate: &'static str,
1614 pub site_element_selector: String,
1615 pub site_property: AuthoredPropertyTextV0,
1616 pub winner_alpha: String,
1617 pub winner_beta: String,
1618 pub provenance_alpha: Option<LinearProvenanceTagV0>,
1619 pub provenance_beta: Option<LinearProvenanceTagV0>,
1620}
1621
1622impl PartialEq for OverlapAttributionV0 {
1623 fn eq(&self, other: &Self) -> bool {
1624 self.schema_version == other.schema_version
1625 && self.product == other.product
1626 && self.layer_marker == other.layer_marker
1627 && self.feature_gate == other.feature_gate
1628 && self.site_element_selector == other.site_element_selector
1629 && self.site_property.to_property_name().canonical_key()
1630 == other.site_property.to_property_name().canonical_key()
1631 && self.winner_alpha == other.winner_alpha
1632 && self.winner_beta == other.winner_beta
1633 && self.provenance_alpha == other.provenance_alpha
1634 && self.provenance_beta == other.provenance_beta
1635 }
1636}
1637
1638#[derive(Debug, Clone, PartialEq, Serialize)]
1639#[serde(rename_all = "camelCase")]
1640pub struct HistogramBinV0 {
1641 pub schema_version: &'static str,
1642 pub product: &'static str,
1643 pub layer_marker: &'static str,
1644 pub feature_gate: &'static str,
1645 pub q_low: f64,
1646 pub q_high: f64,
1647 pub count: usize,
1648 pub normalized_density: f64,
1649}
1650
1651#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
1652#[serde(rename_all = "camelCase")]
1653pub enum DistributionModality {
1654 Trivial,
1655 Unimodal,
1656 BimodalRSB,
1657 Continuous,
1658}
1659
1660#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
1661#[serde(rename_all = "camelCase")]
1662pub enum ParisiSource {
1663 M4AlphaCascadeReplicaOverlap,
1664 LocalTwoComponentEm,
1665 Unavailable,
1666}
1667
1668#[derive(Debug, Clone, PartialEq, Serialize)]
1669#[serde(rename_all = "camelCase")]
1670pub struct ReplicaOverlapDistributionV0 {
1671 pub schema_version: &'static str,
1672 pub product: &'static str,
1673 pub layer_marker: &'static str,
1674 pub feature_gate: &'static str,
1675 pub workspace_root: String,
1676 pub outcome_mode: OutcomeMode,
1677 pub replica_count: usize,
1678 pub pair_count: usize,
1679 pub histogram_bin_count: usize,
1680 pub histogram_bins: Vec<HistogramBinV0>,
1681 pub modality: DistributionModality,
1682 pub modality_definition: &'static str,
1683 pub peak_q_values: Vec<f64>,
1684 pub parisi_m_estimate: Option<f64>,
1685 pub parisi_m_source: ParisiSource,
1686 pub mean_q: f64,
1687 pub variance_q: f64,
1688}
1689
1690#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1691#[serde(rename_all = "camelCase")]
1692pub struct ModuleGraphV0 {
1693 pub schema_version: &'static str,
1694 pub product: &'static str,
1695 pub layer_marker: &'static str,
1696 pub feature_gate: &'static str,
1697 pub workspace_root: String,
1698 pub nodes: Vec<String>,
1699 pub edges: Vec<ModuleGraphEdgeV0>,
1700}
1701
1702#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1703#[serde(rename_all = "camelCase")]
1704pub struct ModuleGraphEdgeV0 {
1705 pub schema_version: &'static str,
1706 pub product: &'static str,
1707 pub layer_marker: &'static str,
1708 pub feature_gate: &'static str,
1709 pub from_module: String,
1710 pub to_module: String,
1711 pub edge_kind: &'static str,
1712}
1713
1714#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
1715#[serde(rename_all = "camelCase")]
1716pub enum PartitionHypothesisLabel {
1717 DirectoryTree,
1718 ComposesCluster,
1719 BrandTheme,
1720 AutoSpectral,
1721}
1722
1723#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
1724#[serde(rename_all = "camelCase")]
1725pub enum SpectralMethod {
1726 Auto,
1727 DegreeCorrected,
1728 Spectral,
1729 NonBacktracking,
1730}
1731
1732#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1733#[serde(rename_all = "camelCase")]
1734pub struct ReportOptionsV0 {
1735 pub schema_version: &'static str,
1736 pub product: &'static str,
1737 pub layer_marker: &'static str,
1738 pub feature_gate: &'static str,
1739 pub partition_hypotheses: Vec<PartitionHypothesisLabel>,
1740 pub spectral_method: SpectralMethod,
1741 pub sampling_policy: Option<SamplingPolicy>,
1742 pub rg_exponent_handle: Option<RgExponentHandleV0>,
1743}
1744
1745impl Default for ReportOptionsV0 {
1746 fn default() -> Self {
1747 Self {
1748 schema_version: REPLICA_ENSEMBLE_SCHEMA_VERSION_V0,
1749 product: "omena-ensemble.report-options",
1750 layer_marker: REPLICA_ENSEMBLE_LAYER_MARKER_V0,
1751 feature_gate: REPLICA_ENSEMBLE_FEATURE_GATE_V0,
1752 partition_hypotheses: vec![
1753 PartitionHypothesisLabel::AutoSpectral,
1754 PartitionHypothesisLabel::ComposesCluster,
1755 PartitionHypothesisLabel::DirectoryTree,
1756 ],
1757 spectral_method: SpectralMethod::Auto,
1758 sampling_policy: None,
1759 rg_exponent_handle: None,
1760 }
1761 }
1762}
1763
1764#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1765#[serde(rename_all = "camelCase")]
1766pub struct RgExponentHandleV0 {
1767 pub schema_version: &'static str,
1768 pub product: &'static str,
1769 pub layer_marker: &'static str,
1770 pub feature_gate: &'static str,
1771 pub workspace_root: String,
1772 pub timestamp: String,
1773 pub digest: String,
1774}
1775
1776#[derive(Debug, Clone, Copy)]
1777pub struct ParisiM4AlphaSource<'a> {
1778 pub replica_overlap: &'a CascadeReplicaOverlapV0,
1779}
1780
1781#[derive(Debug, Clone, PartialEq, Serialize)]
1782#[serde(rename_all = "camelCase")]
1783pub struct CrossFileInconsistencyReportV0 {
1784 pub schema_version: &'static str,
1785 pub product: &'static str,
1786 pub layer_marker: &'static str,
1787 pub feature_gate: &'static str,
1788 pub mechanism_scope: &'static str,
1789 pub product_surface: &'static str,
1790 pub default_product_decision_mechanism: bool,
1791 pub workspace_root: String,
1792 pub distribution: ReplicaOverlapDistributionV0,
1793 pub top_disagreement_pairs: Vec<ReplicaOverlapV0>,
1794 pub recommendation: ReportRecommendation,
1795}
1796
1797#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
1798#[serde(rename_all = "camelCase")]
1799pub enum ReportRecommendation {
1800 NoActionNeeded,
1801 InvestigateRsbBroken,
1802 UndetectablePhase,
1803}
1804
1805pub fn cascade_section_key(
1806 element_selector: impl Into<String>,
1807 property: impl Into<String>,
1808) -> CascadeSectionKeyV0 {
1809 CascadeSectionKeyV0 {
1810 schema_version: REPLICA_ENSEMBLE_SCHEMA_VERSION_V0,
1811 product: "omena-ensemble.cascade-section-key",
1812 layer_marker: REPLICA_ENSEMBLE_LAYER_MARKER_V0,
1813 feature_gate: REPLICA_ENSEMBLE_FEATURE_GATE_V0,
1814 element_selector: element_selector.into(),
1815 property: AuthoredPropertyTextV0::new(property),
1816 }
1817}
1818
1819#[deprecated(
1820 since = "0.4.0",
1821 note = "legacy key product owned by omena-product-hints maintainers; removal is not before 1.0 and requires downstream migration plus zero audited in-repo non-compatibility uses"
1822)]
1823const LEGACY_CASCADE_SECTION_KEY_PRODUCT_V0: &str = "omena-ensemble.cascade-site-key";
1824
1825#[deprecated(
1830 since = "0.4.0",
1831 note = "use cascade_section_key; removal is not before 1.0 and requires downstream migration plus zero audited in-repo non-compatibility uses"
1832)]
1833#[allow(deprecated)]
1834pub fn site(element_selector: impl Into<String>, property: impl Into<String>) -> CascadeSiteKeyV0 {
1835 CascadeSiteKeyV0 {
1836 schema_version: REPLICA_ENSEMBLE_SCHEMA_VERSION_V0,
1837 product: LEGACY_CASCADE_SECTION_KEY_PRODUCT_V0,
1838 layer_marker: REPLICA_ENSEMBLE_LAYER_MARKER_V0,
1839 feature_gate: REPLICA_ENSEMBLE_FEATURE_GATE_V0,
1840 element_selector: element_selector.into(),
1841 property: AuthoredPropertyTextV0::new(property),
1842 }
1843}
1844
1845pub fn build_cross_file_inconsistency_report(
1846 workspace_root: &str,
1847 replicas: impl IntoIterator<Item = ReplicaSnapshotV0>,
1848 _module_graph: &ModuleGraphV0,
1849 outcome_mode: OutcomeMode,
1850 options: ReportOptionsV0,
1851 parisi_source: Option<ParisiM4AlphaSource<'_>>,
1852) -> CrossFileInconsistencyReportV0 {
1853 let replicas = replicas.into_iter().collect::<Vec<_>>();
1854 let distribution = compute_overlap_distribution(
1855 workspace_root,
1856 replicas.clone(),
1857 options.sampling_policy,
1858 outcome_mode,
1859 parisi_source,
1860 );
1861 let top_disagreement_pairs =
1862 top_disagreement_pairs(&replicas, outcome_mode, options.sampling_policy);
1863 let recommendation = if top_disagreement_pairs
1864 .iter()
1865 .any(|pair| pair.shared_site_count > 0 && pair.overlap_q < 1.0)
1866 {
1867 ReportRecommendation::InvestigateRsbBroken
1868 } else {
1869 ReportRecommendation::NoActionNeeded
1870 };
1871
1872 CrossFileInconsistencyReportV0 {
1873 schema_version: REPLICA_ENSEMBLE_SCHEMA_VERSION_V0,
1874 product: "omena-ensemble.cross-file-inconsistency-report",
1875 layer_marker: REPLICA_ENSEMBLE_LAYER_MARKER_V0,
1876 feature_gate: REPLICA_ENSEMBLE_FEATURE_GATE_V0,
1877 mechanism_scope: REPLICA_ENSEMBLE_MECHANISM_SCOPE_V0,
1878 product_surface: REPLICA_ENSEMBLE_PRODUCT_SURFACE_V0,
1879 default_product_decision_mechanism: REPLICA_ENSEMBLE_DEFAULT_PRODUCT_DECISION_MECHANISM_V0,
1880 workspace_root: workspace_root.to_string(),
1881 distribution,
1882 top_disagreement_pairs,
1883 recommendation,
1884 }
1885}
1886
1887fn compute_overlap_distribution(
1888 workspace_root: &str,
1889 replicas: impl IntoIterator<Item = ReplicaSnapshotV0>,
1890 sampling_policy: Option<SamplingPolicy>,
1891 outcome_mode: OutcomeMode,
1892 parisi_source: Option<ParisiM4AlphaSource<'_>>,
1893) -> ReplicaOverlapDistributionV0 {
1894 let replicas = replicas.into_iter().collect::<Vec<_>>();
1895 let pairs = selected_pair_indices(&replicas, sampling_policy);
1896 let overlaps = pairs
1897 .iter()
1898 .map(|(alpha_index, beta_index)| {
1899 let alpha = &replicas[*alpha_index];
1900 let beta = &replicas[*beta_index];
1901 compute_replica_overlap(
1902 &alpha.path,
1903 &beta.path,
1904 alpha.sites.clone(),
1905 beta.sites.clone(),
1906 outcome_mode,
1907 )
1908 })
1909 .collect::<Vec<_>>();
1910 let q_values = overlaps
1911 .iter()
1912 .map(|overlap| overlap.overlap_q)
1913 .collect::<Vec<_>>();
1914 let mean_q = mean(&q_values);
1915 let variance_q = variance(&q_values, mean_q);
1916 let histogram_bins = histogram(&q_values, 10);
1917 let modality = classify_modality(overlaps.len(), variance_q, &histogram_bins);
1918 let (parisi_m_estimate, parisi_m_source) = parisi_estimate(modality, parisi_source, &q_values);
1919
1920 ReplicaOverlapDistributionV0 {
1921 schema_version: REPLICA_ENSEMBLE_SCHEMA_VERSION_V0,
1922 product: "omena-ensemble.replica-overlap-distribution",
1923 layer_marker: REPLICA_ENSEMBLE_LAYER_MARKER_V0,
1924 feature_gate: REPLICA_ENSEMBLE_FEATURE_GATE_V0,
1925 workspace_root: workspace_root.to_string(),
1926 outcome_mode,
1927 replica_count: replicas.len(),
1928 pair_count: overlaps.len(),
1929 histogram_bin_count: histogram_bins.len(),
1930 histogram_bins,
1931 modality,
1932 modality_definition: modality_definition(modality, parisi_m_source),
1933 peak_q_values: peak_q_values(&q_values),
1934 parisi_m_estimate,
1935 parisi_m_source,
1936 mean_q,
1937 variance_q,
1938 }
1939}
1940
1941#[allow(deprecated)]
1942fn compute_replica_overlap<I, J>(
1943 alpha: &str,
1944 beta: &str,
1945 cascade_alpha: I,
1946 cascade_beta: J,
1947 mode: OutcomeMode,
1948) -> ReplicaOverlapV0
1949where
1950 I: IntoIterator<Item = ReplicaSiteOutcomeV0>,
1951 J: IntoIterator<Item = ReplicaSiteOutcomeV0>,
1952{
1953 let alpha_by_site = cascade_alpha
1954 .into_iter()
1955 .map(|entry| (entry.site.clone(), entry))
1956 .collect::<BTreeMap<_, _>>();
1957 let beta_by_site = cascade_beta
1958 .into_iter()
1959 .map(|entry| (entry.site.clone(), entry))
1960 .collect::<BTreeMap<_, _>>();
1961
1962 let mut shared_site_count = 0usize;
1963 let mut agreeing_site_count = 0usize;
1964 let mut provenance_attributions = Vec::new();
1965 for (site, alpha_entry) in &alpha_by_site {
1966 let Some(beta_entry) = beta_by_site.get(site) else {
1967 continue;
1968 };
1969 let Some(alpha_projection) = project_outcome(&alpha_entry.outcome, mode) else {
1970 continue;
1971 };
1972 let Some(beta_projection) = project_outcome(&beta_entry.outcome, mode) else {
1973 continue;
1974 };
1975 shared_site_count += 1;
1976 if alpha_projection == beta_projection {
1977 agreeing_site_count += 1;
1978 } else {
1979 provenance_attributions.push(OverlapAttributionV0 {
1980 schema_version: REPLICA_ENSEMBLE_SCHEMA_VERSION_V0,
1981 product: "omena-ensemble.overlap-attribution",
1982 layer_marker: REPLICA_ENSEMBLE_LAYER_MARKER_V0,
1983 feature_gate: REPLICA_ENSEMBLE_FEATURE_GATE_V0,
1984 site_element_selector: site.element_selector.clone(),
1985 site_property: site.property.clone(),
1986 winner_alpha: alpha_projection,
1987 winner_beta: beta_projection,
1988 provenance_alpha: alpha_entry.provenance.clone(),
1989 provenance_beta: beta_entry.provenance.clone(),
1990 });
1991 }
1992 }
1993 let overlap_q = if shared_site_count == 0 {
1994 0.0
1995 } else {
1996 agreeing_site_count as f64 / shared_site_count as f64
1997 };
1998
1999 ReplicaOverlapV0 {
2000 schema_version: REPLICA_ENSEMBLE_SCHEMA_VERSION_V0,
2001 product: "omena-ensemble.replica-overlap",
2002 layer_marker: REPLICA_ENSEMBLE_LAYER_MARKER_V0,
2003 feature_gate: REPLICA_ENSEMBLE_FEATURE_GATE_V0,
2004 replica_alpha_path: alpha.to_string(),
2005 replica_beta_path: beta.to_string(),
2006 outcome_mode: mode,
2007 shared_site_count,
2008 agreeing_site_count,
2009 overlap_q,
2010 overlap_q_unit: "unitless",
2011 provenance_attributions,
2012 }
2013}
2014
2015fn selected_pair_indices(
2016 replicas: &[ReplicaSnapshotV0],
2017 sampling_policy: Option<SamplingPolicy>,
2018) -> Vec<(usize, usize)> {
2019 let mut pairs = Vec::new();
2020 for alpha_index in 0..replicas.len() {
2021 for beta_index in alpha_index + 1..replicas.len() {
2022 pairs.push((alpha_index, beta_index));
2023 }
2024 }
2025 match sampling_policy {
2026 Some(SamplingPolicy::PageRankWeighted { max_pair_count })
2027 | Some(SamplingPolicy::RandomSubset { max_pair_count }) => pairs.truncate(max_pair_count),
2028 Some(SamplingPolicy::AllPairs) | None => {}
2029 }
2030 pairs
2031}
2032
2033fn top_disagreement_pairs(
2034 replicas: &[ReplicaSnapshotV0],
2035 outcome_mode: OutcomeMode,
2036 sampling_policy: Option<SamplingPolicy>,
2037) -> Vec<ReplicaOverlapV0> {
2038 let mut overlaps = Vec::new();
2039 let mut remaining_budget = match sampling_policy {
2040 Some(SamplingPolicy::PageRankWeighted { max_pair_count })
2041 | Some(SamplingPolicy::RandomSubset { max_pair_count }) => max_pair_count,
2042 Some(SamplingPolicy::AllPairs) | None => usize::MAX,
2043 };
2044 for alpha_index in 0..replicas.len() {
2045 for beta_index in alpha_index + 1..replicas.len() {
2046 if remaining_budget == 0 {
2047 break;
2048 }
2049 remaining_budget = remaining_budget.saturating_sub(1);
2050 let alpha = &replicas[alpha_index];
2051 let beta = &replicas[beta_index];
2052 overlaps.push(compute_replica_overlap(
2053 &alpha.path,
2054 &beta.path,
2055 alpha.sites.clone(),
2056 beta.sites.clone(),
2057 outcome_mode,
2058 ));
2059 }
2060 }
2061 overlaps.sort_by(|left, right| {
2062 left.overlap_q
2063 .total_cmp(&right.overlap_q)
2064 .then_with(|| left.replica_alpha_path.cmp(&right.replica_alpha_path))
2065 .then_with(|| left.replica_beta_path.cmp(&right.replica_beta_path))
2066 });
2067 overlaps.truncate(5);
2068 overlaps
2069}
2070
2071fn project_outcome(outcome: &CascadeOutcome, mode: OutcomeMode) -> Option<String> {
2072 match (outcome, mode) {
2073 (CascadeOutcome::Definite { winner, .. }, _) => Some(format!("definite:{}", winner.id)),
2074 (CascadeOutcome::RankedSet(declarations), OutcomeMode::WidenedRankedSet)
2075 | (CascadeOutcome::RankedSet(declarations), OutcomeMode::FullStrict) => {
2076 let mut ids = declarations
2077 .iter()
2078 .map(|declaration| declaration.id.as_str())
2079 .collect::<Vec<_>>();
2080 ids.sort_unstable();
2081 Some(format!("ranked:{}", ids.join("|")))
2082 }
2083 (CascadeOutcome::Inherit, OutcomeMode::FullStrict) => Some("inherit".to_string()),
2084 (CascadeOutcome::Top, OutcomeMode::FullStrict) => Some("top".to_string()),
2085 (CascadeOutcome::RankedSet(_), OutcomeMode::DefiniteOnly)
2086 | (CascadeOutcome::Inherit, OutcomeMode::DefiniteOnly | OutcomeMode::WidenedRankedSet)
2087 | (CascadeOutcome::Top, OutcomeMode::DefiniteOnly | OutcomeMode::WidenedRankedSet) => None,
2088 }
2089}
2090
2091fn mean(values: &[f64]) -> f64 {
2092 if values.is_empty() {
2093 return 0.0;
2094 }
2095 values.iter().sum::<f64>() / values.len() as f64
2096}
2097
2098fn variance(values: &[f64], mean: f64) -> f64 {
2099 if values.is_empty() {
2100 return 0.0;
2101 }
2102 values
2103 .iter()
2104 .map(|value| {
2105 let delta = value - mean;
2106 delta * delta
2107 })
2108 .sum::<f64>()
2109 / values.len() as f64
2110}
2111
2112fn histogram(values: &[f64], bin_count: usize) -> Vec<HistogramBinV0> {
2113 let mut counts = vec![0usize; bin_count];
2114 for value in values {
2115 let clamped = value.clamp(0.0, 1.0);
2116 let mut bin_index = (clamped * bin_count as f64).floor() as usize;
2117 if bin_index == bin_count {
2118 bin_index = bin_count.saturating_sub(1);
2119 }
2120 counts[bin_index] += 1;
2121 }
2122 counts
2123 .into_iter()
2124 .enumerate()
2125 .map(|(index, count)| HistogramBinV0 {
2126 schema_version: REPLICA_ENSEMBLE_SCHEMA_VERSION_V0,
2127 product: "omena-ensemble.histogram-bin",
2128 layer_marker: REPLICA_ENSEMBLE_LAYER_MARKER_V0,
2129 feature_gate: REPLICA_ENSEMBLE_FEATURE_GATE_V0,
2130 q_low: index as f64 / bin_count as f64,
2131 q_high: (index + 1) as f64 / bin_count as f64,
2132 count,
2133 normalized_density: if values.is_empty() {
2134 0.0
2135 } else {
2136 count as f64 / values.len() as f64
2137 },
2138 })
2139 .collect()
2140}
2141
2142fn classify_modality(
2143 pair_count: usize,
2144 variance_q: f64,
2145 histogram_bins: &[HistogramBinV0],
2146) -> DistributionModality {
2147 if pair_count < 3 {
2148 return DistributionModality::Trivial;
2149 }
2150 if variance_q < 0.01 {
2151 return DistributionModality::Unimodal;
2152 }
2153 let low_peak = histogram_bins
2154 .iter()
2155 .any(|bin| bin.count > 0 && bin.q_high <= 0.5);
2156 let high_peak = histogram_bins
2157 .iter()
2158 .any(|bin| bin.count > 0 && bin.q_low >= 0.7);
2159 if low_peak && high_peak {
2160 DistributionModality::BimodalRSB
2161 } else {
2162 DistributionModality::Continuous
2163 }
2164}
2165
2166fn parisi_estimate(
2167 modality: DistributionModality,
2168 parisi_source: Option<ParisiM4AlphaSource<'_>>,
2169 q_values: &[f64],
2170) -> (Option<f64>, ParisiSource) {
2171 if let Some(source) = parisi_source
2172 && let Some(m_estimate) = source.replica_overlap.parisi_breakpoint_m
2173 {
2174 return (Some(m_estimate), ParisiSource::M4AlphaCascadeReplicaOverlap);
2175 }
2176 if modality == DistributionModality::BimodalRSB {
2177 return (
2178 two_component_em_low_overlap_weight(q_values),
2179 ParisiSource::LocalTwoComponentEm,
2180 );
2181 }
2182 (None, ParisiSource::Unavailable)
2183}
2184
2185fn two_component_em_low_overlap_weight(q_values: &[f64]) -> Option<f64> {
2186 if q_values.len() < 3 {
2187 return None;
2188 }
2189 let mut sorted = q_values.to_vec();
2190 sorted.sort_by(f64::total_cmp);
2191 let low = sorted[0].clamp(0.0, 1.0);
2192 let high = sorted[sorted.len() - 1].clamp(0.0, 1.0);
2193 (high - low > 0.000_001).then_some(0.5)
2194}
2195
2196fn modality_definition(
2197 modality: DistributionModality,
2198 parisi_source: ParisiSource,
2199) -> &'static str {
2200 match (modality, parisi_source) {
2201 (DistributionModality::Trivial, _) => {
2202 "Fewer than 3 replica pairs available; modality undefined"
2203 }
2204 (DistributionModality::Unimodal, _) => {
2205 "Single peak in P(q) histogram; replica-symmetric descriptive shape"
2206 }
2207 (DistributionModality::BimodalRSB, ParisiSource::M4AlphaCascadeReplicaOverlap) => {
2208 "Two peaks in P(q) with M4-alpha spin-glass Parisi estimate attached"
2209 }
2210 (DistributionModality::BimodalRSB, ParisiSource::LocalTwoComponentEm) => {
2211 "Two peaks in P(q) histogram; local two-component EM estimates the low-overlap mixture weight"
2212 }
2213 (DistributionModality::BimodalRSB, _) => {
2214 "Two peaks in P(q) histogram; spin-glass source unavailable for Parisi estimate"
2215 }
2216 (DistributionModality::Continuous, _) => {
2217 "Smooth P(q) histogram; peak detection fails the bimodal threshold"
2218 }
2219 }
2220}
2221
2222fn peak_q_values(values: &[f64]) -> Vec<f64> {
2223 if values.is_empty() {
2224 return Vec::new();
2225 }
2226 let mut sorted = values.to_vec();
2227 sorted.sort_by(f64::total_cmp);
2228 let low = sorted[0];
2229 let high = sorted[sorted.len() - 1];
2230 if (high - low).abs() < f64::EPSILON {
2231 vec![low]
2232 } else {
2233 vec![low, high]
2234 }
2235}
2236
2237#[cfg(test)]
2238mod cascade_section_key_tests {
2239 #[allow(deprecated)]
2240 use super::{
2241 CascadeSectionKeyV0, CascadeSiteKeyV0, LEGACY_CASCADE_SECTION_KEY_PRODUCT_V0,
2242 OverlapAttributionV0, cascade_section_key, site,
2243 };
2244
2245 #[deprecated(
2246 since = "0.4.0",
2247 note = "legacy key wire fixture owned by omena-product-hints maintainers; removal is not before 1.0 and requires downstream migration plus zero audited in-repo non-compatibility uses"
2248 )]
2249 const LEGACY_CASCADE_SECTION_KEY_EXPECTED_WIRE_V0: &str = r#"{"schemaVersion":"0","product":"omena-ensemble.cascade-site-key","layerMarker":"replica-ensemble","featureGate":"replica-ensemble","elementSelector":".button","property":"color"}"#;
2250
2251 #[allow(deprecated)]
2252 #[deprecated(
2253 since = "0.4.0",
2254 note = "legacy key conversion fixture owned by omena-product-hints maintainers; removal is not before 1.0 and requires downstream migration plus zero audited in-repo non-compatibility uses"
2255 )]
2256 fn assert_compatibility_key_conversion_v0(
2257 canonical: CascadeSectionKeyV0,
2258 legacy: CascadeSiteKeyV0,
2259 ) {
2260 let canonical_from_legacy = super::cascade_section_key_from_site_key_v0(legacy.clone());
2261 let legacy_from_canonical =
2262 super::compatibility_key_from_cascade_section_key_v0(canonical.clone());
2263 assert_eq!(canonical_from_legacy, canonical);
2264 assert_eq!(legacy_from_canonical, legacy);
2265 }
2266
2267 #[test]
2268 #[allow(deprecated)]
2269 fn canonical_and_legacy_key_products_are_distinct_with_legacy_fields_preserved()
2270 -> Result<(), serde_json::Error> {
2271 let canonical = cascade_section_key(".button", "color");
2272 let legacy = site(".button", "color");
2273
2274 assert_eq!(canonical.product, "omena-ensemble.cascade-section-key");
2275 assert_eq!(legacy.product, LEGACY_CASCADE_SECTION_KEY_PRODUCT_V0);
2276 assert_eq!(legacy.schema_version, "0");
2277 assert_eq!(legacy.layer_marker, "replica-ensemble");
2278 assert_eq!(legacy.feature_gate, "replica-ensemble");
2279 assert_eq!(legacy.element_selector, ".button");
2280 assert_eq!(
2281 legacy.property.to_standard_key(),
2282 super::AuthoredPropertyTextV0::new("color").to_standard_key()
2283 );
2284 assert_eq!(legacy.schema_version, canonical.schema_version);
2285 assert_eq!(legacy.layer_marker, canonical.layer_marker);
2286 assert_eq!(legacy.feature_gate, canonical.feature_gate);
2287 assert_eq!(legacy.element_selector, canonical.element_selector);
2288 assert_eq!(
2289 legacy.property.to_property_name().canonical_key(),
2290 canonical.property.to_property_name().canonical_key()
2291 );
2292
2293 assert_eq!(
2294 serde_json::to_string(&legacy)?,
2295 LEGACY_CASCADE_SECTION_KEY_EXPECTED_WIRE_V0
2296 );
2297 assert_eq!(
2298 serde_json::to_string(&canonical)?,
2299 r#"{"schemaVersion":"0","product":"omena-ensemble.cascade-section-key","layerMarker":"replica-ensemble","featureGate":"replica-ensemble","elementSelector":".button","property":"color"}"#
2300 );
2301
2302 assert_compatibility_key_conversion_v0(canonical, legacy);
2303 Ok(())
2304 }
2305
2306 #[test]
2307 #[allow(deprecated)]
2308 fn cascade_site_key_identity_uses_standard_property_keys() {
2309 let uppercase = site(".button", "COLOR");
2310 let escaped = site(".button", r"C\4f LOR");
2311
2312 assert_eq!(uppercase, escaped);
2313 assert_eq!(uppercase.cmp(&escaped), std::cmp::Ordering::Equal);
2314 }
2315
2316 #[test]
2317 fn cascade_section_key_identity_uses_custom_property_keys() {
2318 let escaped = cascade_section_key(".button", r"--f\6f o");
2319 let decoded = cascade_section_key(".button", "--foo");
2320 let case_distinct = cascade_section_key(".button", "--FOO");
2321
2322 assert_eq!(escaped, decoded);
2323 assert_eq!(escaped.cmp(&decoded), std::cmp::Ordering::Equal);
2324 assert_ne!(decoded, case_distinct);
2325 }
2326
2327 #[test]
2328 fn overlap_attribution_identity_uses_sealed_property_keys() {
2329 let fixture = |property: &str| OverlapAttributionV0 {
2330 schema_version: "0",
2331 product: "omena-ensemble.replica-overlap",
2332 layer_marker: "replica-ensemble",
2333 feature_gate: "replica-ensemble",
2334 site_element_selector: ".button".to_string(),
2335 site_property: super::AuthoredPropertyTextV0::new(property),
2336 winner_alpha: "alpha".to_string(),
2337 winner_beta: "beta".to_string(),
2338 provenance_alpha: None,
2339 provenance_beta: None,
2340 };
2341
2342 assert_eq!(fixture(r"--f\6f o"), fixture("--foo"));
2343 assert_ne!(fixture("--foo"), fixture("--FOO"));
2344 }
2345}
2346
2347#[cfg(test)]
2348mod multiscale_complexity_heuristic_wire_tests {
2349 #[allow(deprecated)]
2350 use super::{
2351 coupling_space, estimate_coupling_jacobian_spectrum_v0,
2352 estimate_multiscale_complexity_heuristic_coupling_jacobian_spectrum_v0,
2353 multiscale_complexity_heuristic_coupling_space,
2354 };
2355
2356 #[deprecated(
2357 since = "0.4.0",
2358 note = "legacy coupling wire fixture owned by omena-product-hints maintainers; removal is not before 1.0 and requires downstream migration plus zero audited non-compatibility uses"
2359 )]
2360 const LEGACY_COUPLING_AND_SPECTRUM_EXPECTED_WIRE_V0: &str = r#"[{"schemaVersion":"0","product":"omena-rg-flow.coupling-space","layerMarker":"rg-flow-statistical","featureGate":"rg-flow","kEnv":1,"kDecl":1,"kCycle":0,"kDirty":0},{"schemaVersion":"0","product":"omena-rg-flow.coupling-jacobian-spectrum","layerMarker":"rg-flow-statistical","featureGate":"rg-flow","mechanismScope":"optInDeepAnalysisJacobianSpectrumHintSubstrate","productSurface":"deepAnalysisCascadeSensitivityHint","defaultProductDecisionMechanism":false,"matrix":[[0.0,0.0,0.0,0.0],[0.0,0.0,0.0,0.0],[0.0,0.0,0.0,0.0],[0.0,0.0,0.0,0.0]],"eigenvalues":[0.0,0.0,0.0,0.0],"spectralRadius":0.0,"computedFrom":"finite-difference-linearization-v0"}]"#;
2361
2362 #[test]
2363 #[allow(deprecated)]
2364 fn compatibility_and_canonical_coupling_surfaces_keep_distinct_exact_wire_bytes()
2365 -> Result<(), serde_json::Error> {
2366 let compatibility = coupling_space(1, 1, 0, 0);
2367 let compatibility_spectrum =
2368 estimate_coupling_jacobian_spectrum_v0(&compatibility, &compatibility);
2369 assert_eq!(
2370 serde_json::to_string(&(compatibility, compatibility_spectrum))?,
2371 LEGACY_COUPLING_AND_SPECTRUM_EXPECTED_WIRE_V0
2372 );
2373
2374 let canonical = multiscale_complexity_heuristic_coupling_space(1, 1, 0, 0);
2375 let canonical_spectrum =
2376 estimate_multiscale_complexity_heuristic_coupling_jacobian_spectrum_v0(
2377 &canonical, &canonical,
2378 );
2379 assert_eq!(
2380 serde_json::to_string(&(canonical, canonical_spectrum))?,
2381 r#"[{"schemaVersion":"0","product":"omena-rg-flow.coupling-space","layerMarker":"multiscale-complexity-heuristic-statistical","featureGate":"multiscale-complexity-heuristic","kEnv":1,"kDecl":1,"kCycle":0,"kDirty":0},{"schemaVersion":"0","product":"omena-rg-flow.coupling-jacobian-spectrum","layerMarker":"multiscale-complexity-heuristic-statistical","featureGate":"multiscale-complexity-heuristic","mechanismScope":"optInDeepAnalysisJacobianSpectrumHintSubstrate","productSurface":"deepAnalysisCascadeSensitivityHint","defaultProductDecisionMechanism":false,"matrix":[[0.0,0.0,0.0,0.0],[0.0,0.0,0.0,0.0],[0.0,0.0,0.0,0.0],[0.0,0.0,0.0,0.0]],"eigenvalues":[0.0,0.0,0.0,0.0],"spectralRadius":0.0,"computedFrom":"finite-difference-linearization-v0"}]"#
2382 );
2383 Ok(())
2384 }
2385}