Skip to main content

omena_product_hints/
lib.rs

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