1use std::collections::{BTreeMap, BTreeSet};
8
9use omena_cascade::{CascadeOutcome, CascadeReplicaOverlapV0};
10use serde::Serialize;
11
12pub const CATEGORICAL_SCHEMA_VERSION_V0: &str = "0";
13pub const CATEGORICAL_LAYER_MARKER_V0: &str = "categorical-semantic";
14pub const CATEGORICAL_FEATURE_GATE_V0: &str = "categorical-evidence";
15
16#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
17#[serde(rename_all = "camelCase")]
18pub struct CascadePrimitiveRoleV0 {
19 pub schema_version: &'static str,
20 pub product: &'static str,
21 pub layer_marker: &'static str,
22 pub feature_gate: &'static str,
23 pub primitive_kind: &'static str,
24 pub primitive_name: &'static str,
25 pub categorical_role: &'static str,
26}
27
28#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
29#[serde(rename_all = "camelCase")]
30pub struct CategoricalEvidenceEndpointV0 {
31 pub schema_version: &'static str,
32 pub product: &'static str,
33 pub layer_marker: &'static str,
34 pub feature_gate: &'static str,
35 pub endpoint_id: &'static str,
36 pub evidence_product: &'static str,
37 pub fixture_focus: &'static str,
38}
39
40#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
41#[serde(rename_all = "camelCase")]
42pub struct CategoricalFixtureAssertionV0 {
43 pub schema_version: &'static str,
44 pub product: &'static str,
45 pub layer_marker: &'static str,
46 pub feature_gate: &'static str,
47 pub assertion_id: &'static str,
48 pub contract_product: &'static str,
49 pub observed: String,
50 pub expected: String,
51 pub accepted: bool,
52}
53
54#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
55#[serde(rename_all = "camelCase")]
56pub struct CategoricalEndpointFixtureEvidenceV0 {
57 pub schema_version: &'static str,
58 pub product: &'static str,
59 pub layer_marker: &'static str,
60 pub feature_gate: &'static str,
61 pub claim_scope: &'static str,
62 pub endpoint_id: &'static str,
63 pub fixture_id: &'static str,
64 pub fixture_focus: &'static str,
65 pub evidence_product: &'static str,
66 pub exercised_contract_products: Vec<&'static str>,
67 pub assertion_count: usize,
68 pub assertions: Vec<CategoricalFixtureAssertionV0>,
69 pub accepted: bool,
70}
71
72#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
73#[serde(rename_all = "camelCase")]
74pub struct CascadeFunctorApplicationV0 {
75 pub schema_version: &'static str,
76 pub product: &'static str,
77 pub layer_marker: &'static str,
78 pub feature_gate: &'static str,
79 pub functor_id: String,
80 pub source_category_id: String,
81 pub target_category_id: String,
82 pub object_mapping_count: usize,
83 pub morphism_mapping_count: usize,
84 pub composed_source_morphism_id: Option<String>,
85 pub composed_target_morphism_id: Option<String>,
86 pub identity_preserved: bool,
87 pub composition_preserved: bool,
88 pub accepted: bool,
89}
90
91#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
92#[serde(rename_all = "camelCase")]
93pub struct CategoricalCascadeEvidenceV0 {
94 pub schema_version: &'static str,
95 pub product: &'static str,
96 pub layer_marker: &'static str,
97 pub feature_gate: &'static str,
98 pub source_product: &'static str,
99 pub endpoint_count: usize,
100 pub endpoints: Vec<CategoricalEvidenceEndpointV0>,
101 pub fixture_evidence: Vec<CategoricalEndpointFixtureEvidenceV0>,
102 pub functor_applications: Vec<CascadeFunctorApplicationV0>,
103 pub cascade_primitive_roles: Vec<CascadePrimitiveRoleV0>,
104 pub default_feature_enabled: bool,
105}
106
107pub fn 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#[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#[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
1068pub 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#[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
1176pub 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#[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#[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}