//! #147 acceptance at the library boundary: the registry's generic //! deflation machinery dispatches to the IC vocabulary's OWN permutation //! null, deterministically (C1), and the C10 wall refuses cross-vocabulary //! metric names both ways. Relocated from the shell's test module with #295 //! (these are pure library properties — a library-only consumer must be able //! to run them; the registry/engine edges are dev-only, layering-exempt). use aura_measurement::IcMetrics; fn ic_report(sigs: Vec, frs: Vec) -> aura_engine::RunReport { aura_engine::RunReport { manifest: aura_engine::RunManifest { commit: "c".to_string(), params: vec![("p".to_string(), aura_core::Scalar::f64(1.0))], defaults: vec![], window: (aura_core::Timestamp(1), aura_core::Timestamp(2)), seed: 0, broker: "b".to_string(), selection: None, instrument: None, topology_hash: None, project: None, }, metrics: IcMetrics { information_coefficient: aura_analysis::pearson_corr(&sigs, &frs), sigs, frs, }, } } fn ic_family(members: Vec>) -> aura_engine::SweepFamily { aura_engine::SweepFamily { space: vec![], points: members .into_iter() .enumerate() .map(|(i, report)| aura_engine::SweepPoint { params: vec![aura_core::Cell::from_f64(i as f64)], report, }) .collect(), } } /// #147 acceptance: the registry's deflation dispatches to the IC /// vocabulary's OWN permutation null — a genuinely predictive family /// deflates to a low overfit probability, a noise family to a high one. #[test] fn ic_family_deflation_dispatches_the_permutation_null() { let ramp: Vec = (0..24).map(|i| i as f64).collect(); let engineered = ic_family(vec![ ic_report(ramp.clone(), ramp.clone()), // corr = 1.0: a real edge ic_report(ramp.clone(), ramp.iter().rev().cloned().collect()), // corr = -1.0 ]); let (winner, sel) = aura_registry::optimize_deflated(&engineered, "information_coefficient", 500, 5, 0) .expect("ic family deflates"); assert!((winner.report.metrics.information_coefficient - 1.0).abs() < 1e-12); let p = sel.overfit_probability.expect("resampling-null arm sets a probability"); assert!(p < 0.05, "perfectly predictive family must not look like overfit: p={p}"); // orthogonal signal — IC exactly 0.0; permuted draws beat it ~half the time. let noise = ic_family(vec![ic_report( vec![0.03, 0.01, 0.04, 0.02, 0.05, 0.00, 0.06, -0.01], vec![0.01, 0.02, 0.03, 0.04, -0.04, -0.03, -0.02, -0.01], )]); let (_, sel) = aura_registry::optimize_deflated(&noise, "information_coefficient", 500, 5, 0) .expect("noise family deflates"); let p = sel.overfit_probability.expect("probability present"); assert!(p > 0.1, "a noise family must carry a high overfit probability: p={p}"); } /// Same seed → identical selection provenance (C1 through the generic path). #[test] fn ic_family_deflation_is_deterministic_given_seed() { let ramp: Vec = (0..16).map(|i| i as f64).collect(); let fam = ic_family(vec![ic_report(ramp.clone(), ramp)]); let a = aura_registry::optimize_deflated(&fam, "information_coefficient", 300, 5, 7) .expect("deflate a"); let b = aura_registry::optimize_deflated(&fam, "information_coefficient", 300, 5, 7) .expect("deflate b"); assert_eq!(a.1, b.1, "same seed must yield identical FamilySelection"); } /// C10 guard: cross-vocabulary names are refused, and the refusal names /// the family's REAL vocabulary. #[test] fn cross_vocabulary_names_are_refused_both_ways() { let ramp: Vec = (0..8).map(|i| i as f64).collect(); let fam = ic_family(vec![ic_report(ramp.clone(), ramp)]); let err = aura_registry::optimize_deflated(&fam, "sqn", 100, 5, 0).unwrap_err(); let prose = err.to_string(); assert!( prose.contains("unknown metric 'sqn'") && prose.contains("information_coefficient"), "refusal must name the IC vocabulary: {prose}" ); // and the R side still refuses the IC name with ITS roster: assert!(aura_registry::check_r_metric("information_coefficient").is_err()); }