use antigen_fingerprint::Fingerprint;
use crate::learn::self_tolerance::{is_near_miss, is_near_miss_capable};
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum SilentStatus {
Obsolete,
Dormant,
Evading,
Indeterminate,
}
#[must_use]
pub fn silent_status(draft: &Fingerprint, corpus: &[syn::Item]) -> SilentStatus {
if corpus.iter().any(|item| is_near_miss(draft, item)) {
return SilentStatus::Evading;
}
if corpus.iter().any(|item| draft.matches(item)) {
return SilentStatus::Dormant;
}
if is_near_miss_capable(draft) {
SilentStatus::Obsolete
} else {
SilentStatus::Indeterminate
}
}
#[cfg(test)]
mod tests {
use antigen_fingerprint::Constraint;
use super::*;
fn corpus(src: &str) -> Vec<syn::Item> {
syn::parse_file(src).expect("test corpus parses").items
}
fn derives_clone_and_debug() -> Fingerprint {
Fingerprint {
constraints: vec![
Constraint::Derives("Clone".into()),
Constraint::Derives("Debug".into()),
],
}
}
#[test]
fn shape_gone_is_obsolete() {
let c = corpus("struct Unrelated;");
assert_eq!(
silent_status(&derives_clone_and_debug(), &c),
SilentStatus::Obsolete,
"a draft whose shape matches NO live item is OBSOLETE (the failure-shape \
is gone) — the only no-FIRE state that is safe to forget."
);
}
#[test]
fn shape_present_no_near_miss_is_dormant() {
let c = corpus("#[derive(Clone, Debug)] struct Bound;");
assert_eq!(
silent_status(&derives_clone_and_debug(), &c),
SilentStatus::Dormant,
"shape present (the draft binds a live item) with no near-miss is \
DORMANT — the shape is alive, keep the class; it is NOT obsolete."
);
}
#[test]
fn shape_present_with_near_miss_is_evading() {
let c = corpus(
"#[derive(Clone, Debug)] struct Bound;\n\
#[derive(Clone)] struct NearMiss;",
);
assert_eq!(
silent_status(&derives_clone_and_debug(), &c),
SilentStatus::Evading,
"shape present AND a near-miss appeared (an item one constraint from \
binding) is EVADING — the red-queen cell ADWIN is blind to for silent \
classes. It must NOT be read as obsolete (forget) or dormant (ignore)."
);
}
#[test]
fn evading_is_not_masked_by_dormant() {
let c = corpus(
"#[derive(Clone, Debug)] struct Bound;\n\
#[derive(Clone)] struct NearMiss;",
);
let status = silent_status(&derives_clone_and_debug(), &c);
assert_ne!(
status,
SilentStatus::Dormant,
"a present-shape-WITH-near-miss must not collapse to Dormant — the \
near-miss (evasion) signal takes precedence over bare presence."
);
assert_eq!(status, SilentStatus::Evading);
}
fn body_calls_unwrap() -> Fingerprint {
Fingerprint {
constraints: vec![Constraint::BodyCalls("unwrap".into())],
}
}
#[test]
fn single_conjunct_shape_absent_is_indeterminate_not_obsolete() {
let c = corpus("fn evaded() { x.expect(\"msg\"); }");
let status = silent_status(&body_calls_unwrap(), &c);
assert_ne!(
status,
SilentStatus::Obsolete,
"a single-conjunct class whose shape is absent must NOT read Obsolete \
(forget) — is_near_miss is structurally blind for it, so the defect may \
have mutated within the conjunct's family (unwrap → expect). Reading \
Obsolete here would let CURATE forget a still-live evading class."
);
assert_eq!(
status,
SilentStatus::Indeterminate,
"the conservative verdict (ADR-057): gone-vs-evaded is undecidable for a \
single-conjunct draft → route-to-human."
);
}
#[test]
fn single_conjunct_shape_present_is_dormant() {
let c = corpus("fn live() { x.unwrap(); }");
assert_eq!(
silent_status(&body_calls_unwrap(), &c),
SilentStatus::Dormant,
"single-conjunct with the shape PRESENT is Dormant (alive, keep) — \
Indeterminate is only for the undecidable shape-absent case."
);
}
#[test]
fn multi_conjunct_shape_mutated_away_with_near_miss_is_evading() {
let c = corpus("#[derive(Clone)] struct OnlyClone;");
assert_eq!(
silent_status(&derives_clone_and_debug(), &c),
SilentStatus::Evading,
"a multi-conjunct draft whose exact shape is absent but which has a \
near-miss in the corpus is EVADING — the near-miss check must not be \
gated on shape-present, or a mutated-away defect reads obsolete."
);
}
}