use std::collections::BTreeMap;
use super::types::{AuditReport, WitnessTier};
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct ClassDefenseStatus {
pub antigen_type: String,
pub max_witness_tier: WitnessTier,
pub site_count: usize,
pub resolving_site_count: usize,
}
impl ClassDefenseStatus {
#[must_use]
pub fn is_defended_on_resolution(&self) -> bool {
self.max_witness_tier > WitnessTier::None
}
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, Default)]
pub struct DefendedStatusReport {
pub by_class: BTreeMap<String, ClassDefenseStatus>,
}
impl DefendedStatusReport {
#[must_use]
pub fn undefended_classes(&self) -> Vec<&ClassDefenseStatus> {
self.by_class
.values()
.filter(|c| !c.is_defended_on_resolution())
.collect()
}
}
#[must_use]
pub fn audit_defended_status(report: &AuditReport) -> DefendedStatusReport {
let mut by_class: BTreeMap<String, ClassDefenseStatus> = BTreeMap::new();
for audit in &report.audits {
let class = &audit.immunity.antigen_type;
let resolves = audit.witness_tier > WitnessTier::None;
let entry = by_class
.entry(class.clone())
.or_insert_with(|| ClassDefenseStatus {
antigen_type: class.clone(),
max_witness_tier: WitnessTier::None,
site_count: 0,
resolving_site_count: 0,
});
entry.site_count += 1;
if resolves {
entry.resolving_site_count += 1;
}
if audit.witness_tier > entry.max_witness_tier {
entry.max_witness_tier = audit.witness_tier;
}
}
DefendedStatusReport { by_class }
}
#[cfg(test)]
mod tests {
use std::path::PathBuf;
use super::*;
use crate::audit::{AuditHint, ImmunityAudit, WitnessStatus};
use crate::scan::{Immunity, ItemTarget};
fn audit_for(class: &str, tier: WitnessTier) -> ImmunityAudit {
ImmunityAudit {
immunity: Immunity {
antigen_type: class.to_owned(),
witness: "w".to_owned(),
requires_predicate: None,
file: PathBuf::from("lib.rs"),
line: 1,
item_kind: "fn".to_owned(),
item_target: ItemTarget::Fn("w".to_owned()),
canonical_path: None,
structural_fingerprint: String::new(),
},
witness_status: WitnessStatus::Missing,
witness_tier: tier,
audit_hint: AuditHint::NoneApplicable,
evidence_kind: antigen_attestation::EvidenceKind::None,
signature_strength: None,
compound_evidence: false,
evaluated_predicate: None,
code_witness_sidecar_ignored: false,
leaf_outcomes: Vec::new(),
}
}
#[test]
fn well_defended_is_distinguishable_from_obsolete_by_resolution_tier() {
let report = AuditReport {
audits: vec![
audit_for("well-defended-class", WitnessTier::Reachability),
audit_for("obsolete-class", WitnessTier::None),
],
..Default::default()
};
let sensor = audit_defended_status(&report);
let well = &sensor.by_class["well-defended-class"];
let obsolete = &sensor.by_class["obsolete-class"];
assert!(
well.is_defended_on_resolution(),
"a class with a resolving (tier>None) witness is WELL-DEFENDED on the \
resolution axis — the discriminator must NOT forget it."
);
assert!(
!obsolete.is_defended_on_resolution(),
"a class with only tier=None sites carries NO resolving witness — it is \
an OBSOLETE-candidate, NOT well-defended. If the sensor called this \
defended (the 'has-a-row' homonym), WELL-DEFENDED would collapse into \
OBSOLETE and the discriminator would refuse to forget dead classes."
);
}
#[test]
fn max_tier_rolls_up_across_a_classes_sites() {
let report = AuditReport {
audits: vec![
audit_for("multi-site", WitnessTier::None),
audit_for("multi-site", WitnessTier::Execution),
audit_for("multi-site", WitnessTier::Reachability),
],
..Default::default()
};
let sensor = audit_defended_status(&report);
let c = &sensor.by_class["multi-site"];
assert_eq!(c.site_count, 3, "all three sites roll into the one class");
assert_eq!(
c.resolving_site_count, 2,
"two of the three sites resolve (tier>None)"
);
assert_eq!(
c.max_witness_tier,
WitnessTier::Execution,
"the class's defended-strength is the MAX tier across its sites — one \
Execution-tier site makes the class Execution-defended."
);
assert!(c.is_defended_on_resolution());
}
#[test]
fn all_bare_sites_make_a_class_undefended() {
let report = AuditReport {
audits: vec![
audit_for("bare", WitnessTier::None),
audit_for("bare", WitnessTier::None),
],
..Default::default()
};
let sensor = audit_defended_status(&report);
let undefended = sensor.undefended_classes();
assert_eq!(undefended.len(), 1);
assert_eq!(undefended[0].antigen_type, "bare");
assert_eq!(undefended[0].resolving_site_count, 0);
}
}