use antigen_fingerprint::{Constraint, Fingerprint};
use crate::learn::affinity::Affinity;
use crate::learn::life_record::{LifeEvent, LifeRecord};
use crate::learn::self_tolerance::has_discriminating_conjunct;
#[derive(Debug, Clone, PartialEq)]
pub struct Matured {
pub draft: Fingerprint,
pub affinity: Affinity,
pub trajectory: Vec<Affinity>,
}
#[must_use]
fn is_discriminating_conjunct(c: &Constraint) -> bool {
has_discriminating_conjunct(&Fingerprint {
constraints: vec![c.clone()],
})
}
#[allow(
clippy::cast_precision_loss,
clippy::cast_sign_loss,
clippy::cast_possible_truncation
)]
#[must_use]
fn mutation_budget(affinity: Affinity, max_budget: usize) -> usize {
if max_budget == 0 {
return 0;
}
let maturity = affinity.recall + affinity.precision;
let scaled = (max_budget as f64) * (1.0 - maturity / 2.0);
(scaled.round() as usize).clamp(1, max_budget)
}
#[must_use]
fn mutation_candidates(draft: &Fingerprint) -> Vec<Fingerprint> {
let conjuncts = flatten_top_level(&draft.constraints);
let mut out = Vec::new();
let droppable = conjuncts.len() >= 2;
for (i, c) in conjuncts.iter().enumerate() {
if !is_discriminating_conjunct(c) {
continue; }
if droppable {
let mut widened = conjuncts.clone();
widened.remove(i);
out.push(Fingerprint {
constraints: widened,
});
}
if let Constraint::AnyOf(arms) = c {
if arms.len() >= 2 {
for arm_idx in 0..arms.len() {
let mut narrowed_arms = arms.clone();
narrowed_arms.remove(arm_idx);
let mut widened = conjuncts.clone();
widened[i] = if narrowed_arms.len() == 1 {
narrowed_arms.into_iter().next().expect("len==1")
} else {
Constraint::AnyOf(narrowed_arms)
};
out.push(Fingerprint {
constraints: widened,
});
}
}
}
}
out
}
#[must_use]
fn flatten_top_level(constraints: &[Constraint]) -> Vec<Constraint> {
if let [Constraint::AllOf(children)] = constraints {
children.clone()
} else {
constraints.to_vec()
}
}
pub fn mature(
draft: Fingerprint,
cluster: &[syn::Item],
clean_corpus: &[syn::Item],
record: &mut LifeRecord,
max_budget: usize,
max_rounds: usize,
) -> Matured {
let mut current = draft;
let mut current_affinity = Affinity::measure(¤t, cluster, clean_corpus);
let mut trajectory = vec![current_affinity];
record.append(LifeEvent::Scored(current_affinity));
for _ in 0..max_rounds {
let budget = mutation_budget(current_affinity, max_budget);
let mut best: Option<(Fingerprint, Affinity)> = None;
for candidate in mutation_candidates(¤t).into_iter().take(budget) {
let cand_affinity = Affinity::measure(&candidate, cluster, clean_corpus);
if cand_affinity.pareto_improves_on(¤t_affinity)
&& best
.as_ref()
.is_none_or(|(_, best_a)| cand_affinity.pareto_improves_on(best_a))
{
best = Some((candidate, cand_affinity));
}
}
match best {
Some((next, next_affinity)) => {
current = next;
current_affinity = next_affinity;
trajectory.push(current_affinity);
record.append(LifeEvent::Matured);
record.append(LifeEvent::Scored(current_affinity));
},
None => break,
}
}
Matured {
draft: current,
affinity: current_affinity,
trajectory,
}
}
#[cfg(test)]
#[allow(clippy::float_cmp)]
mod tests {
use super::*;
fn fp(src: &str) -> Fingerprint {
Fingerprint::parse(src).expect("test fingerprint parses")
}
#[test]
fn framework_anchors_are_frozen_cdr_signals_mutate() {
assert!(!is_discriminating_conjunct(
&fp("item = struct").constraints[0]
));
assert!(!is_discriminating_conjunct(
&fp("impl_of_trait(\"Drop\")").constraints[0]
));
assert!(is_discriminating_conjunct(
&fp("body_calls(\"unwrap\")").constraints[0]
));
assert!(is_discriminating_conjunct(&fp("is_async").constraints[0]));
}
#[test]
fn budget_decays_as_affinity_rises() {
let max = 8;
let floor = mutation_budget(Affinity::new(0.0, 0.0), max);
let mid = mutation_budget(Affinity::new(0.5, 0.5), max);
let ceiling = mutation_budget(Affinity::PERFECT, max);
assert_eq!(floor, max, "a floor draft explores at full budget");
assert_eq!(ceiling, 1, "a frontier draft barely perturbs");
assert!(
mid < floor && mid > ceiling,
"monotone decay through the middle"
);
}
#[test]
fn budget_is_always_at_least_one() {
assert_eq!(mutation_budget(Affinity::PERFECT, 5), 1);
assert_eq!(mutation_budget(Affinity::PERFECT, 1), 1);
}
#[test]
fn candidates_drop_only_discriminating_conjuncts() {
let draft = fp("all_of([item = struct, body_calls(\"unwrap\")])");
let cands = mutation_candidates(&draft);
assert_eq!(cands.len(), 1, "exactly one CDR drop (the body_calls)");
assert_eq!(cands[0], fp("item = struct"));
}
#[test]
fn single_conjunct_draft_is_a_coldspot_no_drop() {
let draft = fp("body_calls(\"unwrap\")");
assert!(
mutation_candidates(&draft).is_empty(),
"a 1-conjunct draft yields no drop candidate (ATK-047-N4 vacuity)"
);
}
#[test]
fn no_discriminating_conjunct_is_a_coldspot() {
let draft = fp("all_of([item = struct, impl_of_trait(\"Drop\")])");
assert!(mutation_candidates(&draft).is_empty());
}
#[test]
fn any_of_arms_are_widened_individually() {
let draft =
fp("all_of([item = struct, any_of([body_calls(\"unwrap\"), body_calls(\"expect\")])])");
let cands = mutation_candidates(&draft);
assert_eq!(cands.len(), 3);
let flat = |cs: Vec<Constraint>| Fingerprint { constraints: cs };
let item_struct = fp("item = struct").constraints[0].clone();
let unwrap = fp("body_calls(\"unwrap\")").constraints[0].clone();
let expect = fp("body_calls(\"expect\")").constraints[0].clone();
assert!(cands.contains(&flat(vec![item_struct.clone(), expect])));
assert!(cands.contains(&flat(vec![item_struct.clone(), unwrap])));
assert!(cands.contains(&flat(vec![item_struct])));
}
#[test]
fn mature_widens_an_over_specific_draft_to_catch_a_missed_member() {
let m1: syn::Item = syn::parse_quote! {
impl Drop for A { fn drop(&mut self) { self.0.flush(); self.1.unwrap(); } }
};
let m2: syn::Item = syn::parse_quote! {
impl Drop for B { fn drop(&mut self) { self.1.unwrap(); } }
};
let cluster = [m1, m2];
let clean_item: syn::Item = syn::parse_quote! {
impl Drop for Clean { fn drop(&mut self) { self.0.ok(); } }
};
let clean = [clean_item];
let draft = fp(
"all_of([item = impl, impl_of_trait(\"Drop\"), body_calls(\"flush\"), body_calls(\"unwrap\")])",
);
let start = Affinity::measure(&draft, &cluster, &clean);
assert_eq!(start.recall, 0.5, "starting draft misses m2 (no flush)");
assert_eq!(
start.precision, 1.0,
"starting draft spares the clean sibling"
);
let mut record = LifeRecord::new("panic-in-drop");
let matured = mature(draft, &cluster, &clean, &mut record, 8, 16);
assert!(
matured.affinity.pareto_improves_on(&start),
"maturation must improve the over-specific draft"
);
assert_eq!(matured.affinity.recall, 1.0, "it caught the missed member");
assert_eq!(
matured.affinity.precision, 1.0,
"without binding the clean sibling (it still requires unwrap, which Clean lacks)"
);
}
#[test]
fn mature_writes_the_score_trajectory_to_the_life_record() {
let m1: syn::Item = syn::parse_quote! {
impl Drop for A { fn drop(&mut self) { self.0.flush(); self.1.unwrap(); } }
};
let m2: syn::Item = syn::parse_quote! {
impl Drop for B { fn drop(&mut self) { self.1.unwrap(); } }
};
let cluster = [m1, m2];
let clean_item: syn::Item = syn::parse_quote! {
impl Drop for Clean { fn drop(&mut self) { self.0.ok(); } }
};
let clean = [clean_item];
let draft = fp(
"all_of([item = impl, impl_of_trait(\"Drop\"), body_calls(\"flush\"), body_calls(\"unwrap\")])",
);
let mut record = LifeRecord::new("panic-in-drop");
let matured = mature(draft, &cluster, &clean, &mut record, 8, 16);
let scored: Vec<&Affinity> = record
.events()
.iter()
.filter_map(|e| match e {
LifeEvent::Scored(a) => Some(a),
_ => None,
})
.collect();
assert_eq!(scored.len(), matured.trajectory.len());
assert!(
scored.len() >= 2,
"start + at least one climb step recorded"
);
assert_eq!(*scored.last().unwrap(), &matured.affinity);
assert!(
record.events().contains(&LifeEvent::Matured),
"an accepted maturation appends a Matured milestone"
);
}
#[test]
fn an_already_perfect_draft_stops_immediately() {
let m1: syn::Item = syn::parse_quote! {
impl Drop for A { fn drop(&mut self) { self.1.unwrap(); } }
};
let cluster = [m1];
let clean_item: syn::Item = syn::parse_quote! {
impl Drop for Clean { fn drop(&mut self) { self.0.ok(); } }
};
let clean = [clean_item];
let draft = fp("all_of([item = impl, impl_of_trait(\"Drop\"), body_calls(\"unwrap\")])");
let start = Affinity::measure(&draft, &cluster, &clean);
assert_eq!(
start,
Affinity::PERFECT,
"the draft is already at the frontier"
);
let mut record = LifeRecord::new("panic-in-drop");
let matured = mature(draft, &cluster, &clean, &mut record, 8, 16);
assert_eq!(matured.affinity, Affinity::PERFECT);
assert_eq!(
matured.trajectory.len(),
1,
"no climb steps — already optimal"
);
assert!(
!record.events().contains(&LifeEvent::Matured),
"no Matured milestone for a draft that didn't move"
);
}
}