use std::path::{Path, PathBuf};
use antigen_macros::{antigen_tolerance, presents};
use serde::{Deserialize, Serialize};
use crate::scan::{Immunity, ScanReport};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(tag = "status", rename_all = "snake_case")]
pub enum WitnessStatus {
Resolved {
location: PathBuf,
witness_kind: WitnessKind,
},
External {
tool_hint: String,
},
Ambiguous {
candidates: Vec<PathBuf>,
},
NotFound {
reason: String,
},
Missing,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum WitnessKind {
Test,
IgnoredTest,
Proptest,
Function,
PhantomType {
proof_type: String,
type_params: Vec<String>,
constructor: Option<String>,
},
SubstrateWitness {
kind: antigen_attestation::RatificationKind,
},
CrossCrateWitness,
}
#[presents(ParallelStateTrackersDiverge)]
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[serde(rename_all = "snake_case")]
pub enum WitnessTier {
None = 0,
Reachability = 1,
Execution = 2,
FormalProof = 4,
}
#[presents(DeclaredCapabilityWithNoProductionPath)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub enum AuditHint {
NoneApplicable,
FunctionResolves,
TestAttributePresentNotInvoked,
TestAttributePresentIgnoreSkipped,
ProptestPresentNotInvoked,
ExternalToolPrefixRecognized,
ExternalToolInvoked,
PhantomTypeShapeRecognized,
PhantomTypeConstructionValidated,
AmbiguousResolution,
FabricatedPathPrefix,
InheritedPresentationNotReAttested,
DescendedFromFingerprintDivergence,
DisciplineSidecarMissing,
DisciplineSidecarSchemaInvalid,
DisciplinePredicateFailed,
DisciplinePredicateDeferred,
DisciplineSubstrateStale,
DisciplineSubstrateDeltaChainNearCap,
DisciplinePredicatePassedViaDeltaChain,
DisciplinePredicatePassedSubstrateCurrent,
ToleranceVibesGrade,
ToleranceSidecarMissing,
TolerancePredicateFailed,
TolerancePredicatePassedSubstrateCurrent,
DisciplineSidecarKindMismatchExpectedImmunityGotTolerance,
ToleranceSidecarKindMismatchExpectedToleranceGotImmunity,
DisciplineImmunityToleranceContradiction,
AnergyActive,
AnergyCostimulationNotArrived,
AnergyStale,
ImmunosuppressActive,
ImmunosuppressExpired,
ImmunosuppressDurationCapExceeded,
PoxpartyActive,
PoxpartyOutcomePending,
PoxpartyOutcomeRecorded,
PoxpartyOutsideIsolation,
OrientActive,
OrientPendingActionRequired,
DeferredDefenseHintSuppressedWithoutRationale,
UnpinnedDependency,
UnpinnedTransitiveDependency,
UnattestedDependencyInclusion,
DependencyUpgradeWithoutDiffReview,
MaintainerChangeWithoutReattestation,
MaintainerChangeDetectedAfterCargoUpdate,
SuddenDependencyExpansion,
UnsandboxedBuildScript,
UnsandboxedProcMacro,
PostInstallScriptInDependency,
ContentHashMismatch,
ContentHashNoAttestation,
DepAttestWithoutReviewableArtifact,
CratesIoMetadataQueryFailed,
DepAttestationStale,
AutoDependencyChainWithoutPinning,
ContentHashSidecarMalformed,
MalformedRequiresPredicate,
DiagnosticModalityInsufficient,
DiagnosticModalitiesClassCollapsed,
DiagnosticModalitiesEmpty,
DiagnosticMinIndependentZero,
IggMinReattestationsZero,
ClonalFixedSeedDetected,
ClonalIterationsBelowThreshold,
IggIdentityCollapseWarning,
IggSpanTooShort,
IggReattestationsInsufficient,
CrossreactiveFingerprintUnresolved,
PolyclonalInsufficientLineages,
AdccSingleMechanismOnly,
ItchNoticedNotAnchored,
RecurrenceThresholdReachedNoAction,
RecurrenceAnchorNoItchPrecondition,
CrystallizeWithoutAntigen,
ChronicSignalUnmanaged,
ChronicSignalPastReviewDate,
ChronicSinceNotADate,
SaturateNoAnchor,
StrandNoAnchors,
MucosalBoundaryUndefended,
MucosalKindMismatch,
MucosalRationaleInsufficient,
MucosalDisciplineDelegateTargetMissing,
MucosalDisciplineDelegateTargetNotMucosal,
MucosalDisciplineDelegateTargetKindMismatch,
MucosalDisciplineDelegateTargetAmbiguous,
MucosalTolerantRationaleInsufficient,
MucosalTolerantPastReviewDate,
MucosalTolerantAcceptsEmpty,
MucosalTolerantWithoutReviewer,
AntigenCategoryDefaultedImplicitFunctional,
AntigenCategoryClaimInconsistentWithPredicateType,
AntigenCategoryHybridIncompleteEvidence,
AntigenWitnessShapeMismatchForSilenceNoWitness,
AntigenWitnessShapeMismatchForSilenceWrongTier,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ImmunityAudit {
pub immunity: Immunity,
pub witness_status: WitnessStatus,
pub witness_tier: WitnessTier,
pub audit_hint: AuditHint,
#[serde(default = "default_evidence_kind")]
pub evidence_kind: antigen_attestation::EvidenceKind,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub signature_strength: Option<antigen_attestation::SignatureStrength>,
#[serde(default)]
pub compound_evidence: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub evaluated_predicate: Option<String>,
#[serde(default)]
pub code_witness_sidecar_ignored: bool,
#[serde(default)]
pub leaf_outcomes: Vec<antigen_attestation::LeafOutcome>,
}
const fn default_evidence_kind() -> antigen_attestation::EvidenceKind {
antigen_attestation::EvidenceKind::None
}
impl ImmunityAudit {
#[must_use]
pub const fn has_witness(&self) -> bool {
!matches!(self.witness_tier, WitnessTier::None)
}
#[must_use]
pub fn meets_tier(&self, minimum: WitnessTier) -> bool {
self.witness_tier >= minimum
}
#[must_use]
pub fn is_well_formed(&self) -> bool {
self.meets_tier(WitnessTier::Execution)
}
}
impl WitnessTier {
#[must_use]
pub const fn from_status(status: &WitnessStatus) -> Self {
match status {
WitnessStatus::Missing
| WitnessStatus::NotFound { .. }
| WitnessStatus::Ambiguous { .. } => Self::None,
WitnessStatus::External { .. } => Self::Reachability,
WitnessStatus::Resolved { witness_kind, .. } => match witness_kind {
WitnessKind::Test
| WitnessKind::IgnoredTest
| WitnessKind::Proptest
| WitnessKind::Function => Self::Reachability,
WitnessKind::PhantomType { .. } => Self::FormalProof,
WitnessKind::SubstrateWitness { .. } | WitnessKind::CrossCrateWitness => {
Self::Reachability
}
},
}
}
}
#[must_use]
pub const fn evidence_kind_from_status(
status: &WitnessStatus,
) -> antigen_attestation::EvidenceKind {
match status {
WitnessStatus::Missing
| WitnessStatus::NotFound { .. }
| WitnessStatus::Ambiguous { .. } => antigen_attestation::EvidenceKind::None,
WitnessStatus::External { .. } => antigen_attestation::EvidenceKind::Behavioral,
WitnessStatus::Resolved { witness_kind, .. } => match witness_kind {
WitnessKind::Test
| WitnessKind::IgnoredTest
| WitnessKind::Proptest
| WitnessKind::Function => antigen_attestation::EvidenceKind::Behavioral,
WitnessKind::PhantomType { .. } => antigen_attestation::EvidenceKind::TypeSystemProof,
WitnessKind::SubstrateWitness { .. } | WitnessKind::CrossCrateWitness => {
antigen_attestation::EvidenceKind::SubstrateState
}
},
}
}
impl AuditHint {
#[must_use]
pub const fn from_status(status: &WitnessStatus) -> Self {
match status {
WitnessStatus::Missing | WitnessStatus::NotFound { .. } => Self::NoneApplicable,
WitnessStatus::Ambiguous { .. } => Self::AmbiguousResolution,
WitnessStatus::External { .. } => Self::ExternalToolPrefixRecognized,
WitnessStatus::Resolved { witness_kind, .. } => match witness_kind {
WitnessKind::Test => Self::TestAttributePresentNotInvoked,
WitnessKind::IgnoredTest => Self::TestAttributePresentIgnoreSkipped,
WitnessKind::Proptest => Self::ProptestPresentNotInvoked,
WitnessKind::Function => Self::FunctionResolves,
WitnessKind::PhantomType { .. } => Self::PhantomTypeShapeRecognized,
WitnessKind::SubstrateWitness { .. } | WitnessKind::CrossCrateWitness => {
Self::ExternalToolPrefixRecognized
}
},
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InheritedUnaddressed {
pub presentation: crate::scan::Presentation,
pub audit_hint: AuditHint,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AuditReport {
pub audits: Vec<ImmunityAudit>,
pub resolved_count: usize,
pub external_count: usize,
pub ambiguous_count: usize,
pub broken_count: usize,
pub missing_count: usize,
#[serde(default)]
pub inherited_unaddressed: Vec<InheritedUnaddressed>,
#[serde(default)]
pub presentation_verdicts: Vec<PresentationVerdict>,
}
impl AuditReport {
#[must_use]
pub fn all_valid(&self) -> bool {
self.audits.iter().all(ImmunityAudit::is_well_formed)
}
#[must_use]
pub fn all_meet_tier(&self, minimum: WitnessTier) -> bool {
self.audits.iter().all(|a| a.meets_tier(minimum))
}
#[must_use]
pub fn problematic_audits(&self) -> Vec<&ImmunityAudit> {
self.audits.iter().filter(|a| !a.is_well_formed()).collect()
}
#[must_use]
pub fn undefended_verdicts(&self) -> Vec<&PresentationVerdict> {
self.presentation_verdicts
.iter()
.filter(|v| matches!(v.verdict, ImmuneVerdict::Undefended))
.collect()
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(tag = "verdict", rename_all = "kebab-case")]
pub enum ImmuneVerdict {
Defended {
tier: WitnessTier,
},
Undefended,
SubstrateGap,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum WorkVerdict {
Pending,
Fulfilled,
Overdue,
OutOfFrame,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FrameState {
None,
Within,
Past,
Unparseable,
}
impl FrameState {
#[must_use]
pub fn classify(frame: Option<&str>, today: chrono::NaiveDate) -> Self {
frame.map_or(Self::None, |s| {
match chrono::NaiveDate::parse_from_str(s.trim(), "%Y-%m-%d") {
Ok(d) if d >= today => Self::Within,
Ok(_) => Self::Past,
Err(_) => Self::Unparseable,
}
})
}
}
impl WorkVerdict {
#[must_use]
pub const fn project(satisfied: bool, evaluable: bool, frame: FrameState) -> Self {
if !evaluable {
return Self::OutOfFrame;
}
if satisfied {
return Self::Fulfilled;
}
match frame {
FrameState::Past => Self::Overdue,
FrameState::Within | FrameState::None => Self::Pending,
FrameState::Unparseable => Self::OutOfFrame,
}
}
#[must_use]
pub const fn is_loud(self) -> bool {
matches!(self, Self::Overdue)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PresentationVerdict {
pub presentation: crate::scan::Presentation,
pub antigen_type: String,
pub verdict: ImmuneVerdict,
#[serde(default)]
pub defended_by: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeferredDefenseAudit {
pub declaration: crate::scan::DeferredDefense,
pub hint: AuditHint,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct DeferredDefenseAuditReport {
pub audits: Vec<DeferredDefenseAudit>,
pub active_count: usize,
pub expired_count: usize,
pub stale_count: usize,
}
#[must_use]
pub fn audit_deferred_defenses(
scan: &crate::scan::ScanReport,
stale_grace_days: i64,
) -> DeferredDefenseAuditReport {
use chrono::Utc;
let today = Utc::now().date_naive();
let mut audits = Vec::new();
let mut active_count = 0usize;
let mut expired_count = 0usize;
let mut stale_count = 0usize;
for decl in &scan.deferred_defenses {
let hint = evaluate_deferred_defense_hint(decl, today, stale_grace_days);
match &hint {
AuditHint::AnergyActive
| AuditHint::ImmunosuppressActive
| AuditHint::PoxpartyActive
| AuditHint::OrientActive => {
active_count += 1;
}
AuditHint::AnergyCostimulationNotArrived
| AuditHint::ImmunosuppressExpired
| AuditHint::PoxpartyOutcomePending
| AuditHint::OrientPendingActionRequired => {
expired_count += 1;
}
AuditHint::AnergyStale | AuditHint::ImmunosuppressDurationCapExceeded => {
stale_count += 1;
}
_ => {}
}
audits.push(DeferredDefenseAudit {
declaration: decl.clone(),
hint,
});
}
DeferredDefenseAuditReport {
audits,
active_count,
expired_count,
stale_count,
}
}
fn evaluate_deferred_defense_hint(
decl: &crate::scan::DeferredDefense,
today: chrono::NaiveDate,
stale_grace_days: i64,
) -> AuditHint {
use crate::scan::DeferredDefenseKind;
match &decl.kind {
DeferredDefenseKind::Anergy => {
match decl.until.as_deref() {
None | Some("") => AuditHint::AnergyActive,
Some(s) => match parse_iso_date(s) {
Some(until) if until >= today => AuditHint::AnergyActive,
Some(until) => {
let days_past = (today - until).num_days();
if days_past > stale_grace_days {
AuditHint::AnergyStale
} else {
AuditHint::AnergyCostimulationNotArrived
}
}
None => AuditHint::AnergyCostimulationNotArrived,
},
}
}
DeferredDefenseKind::Immunosuppress => {
if let (Some(since), Some(cap)) = (decl.since.as_deref(), decl.duration_cap) {
if let Some(since_date) = parse_iso_date(since) {
let elapsed = (today - since_date).num_days();
if let Ok(cap_days) = i64::try_from(cap) {
if elapsed > cap_days {
return AuditHint::ImmunosuppressDurationCapExceeded;
}
}
}
}
match decl.until.as_deref() {
None | Some("") => AuditHint::ImmunosuppressActive,
Some(s) => match parse_iso_date(s) {
Some(until) if until >= today => AuditHint::ImmunosuppressActive,
_ => AuditHint::ImmunosuppressExpired,
},
}
}
DeferredDefenseKind::Poxparty => {
match decl.until.as_deref() {
None | Some("") => AuditHint::PoxpartyActive,
Some(s) => match parse_iso_date(s) {
Some(until) if until >= today => AuditHint::PoxpartyActive,
_ => AuditHint::PoxpartyOutcomePending,
},
}
}
DeferredDefenseKind::Orient => {
match decl.until.as_deref() {
None | Some("") => AuditHint::OrientActive,
Some(s) => match parse_iso_date(s) {
Some(until) if until >= today => AuditHint::OrientActive,
_ => AuditHint::OrientPendingActionRequired,
},
}
}
}
}
fn parse_iso_date(s: &str) -> Option<chrono::NaiveDate> {
chrono::NaiveDate::parse_from_str(s, "%Y-%m-%d").ok()
}
struct FilesystemAuditContext;
impl antigen_attestation::EvaluationContext for FilesystemAuditContext {
fn today(&self) -> chrono::NaiveDate {
chrono::Local::now().date_naive()
}
fn read_doc(&self, path: &std::path::Path) -> Option<String> {
std::fs::read_to_string(path).ok()
}
fn read_oracle(&self, path: &std::path::Path) -> Option<String> {
std::fs::read_to_string(path).ok()
}
fn read_git_trailers(
&self,
_item_source_file: &std::path::Path,
_item_path: &str,
) -> Vec<String> {
Vec::new()
}
}
enum SidecarLoad {
Missing,
SchemaInvalid,
Ok(antigen_attestation::Ratification),
}
fn load_sidecar(immunity_file: &Path, antigen_type: &str) -> SidecarLoad {
let Some(dir) = immunity_file.parent() else {
return SidecarLoad::Missing;
};
let stem = antigen_type.rsplit("::").next().unwrap_or(antigen_type);
let sidecar_path = dir.join(".attest").join(format!("{stem}.json"));
let Ok(content) = std::fs::read_to_string(&sidecar_path) else {
return SidecarLoad::Missing;
};
let Ok(ratification) = serde_json::from_str::<antigen_attestation::Ratification>(&content)
else {
return SidecarLoad::SchemaInvalid;
};
if ratification
.validate(
antigen_attestation::schema::DEFAULT_DELTA_CHAIN_CAP,
antigen_attestation::schema::DEFAULT_DELTA_RATIONALE_MIN_CHARS,
)
.is_err()
{
return SidecarLoad::SchemaInvalid;
}
SidecarLoad::Ok(ratification)
}
#[must_use]
pub fn audit(report: &ScanReport, workspace_root: &Path) -> AuditReport {
let workspace_functions = collect_function_index(workspace_root);
let mut audits = Vec::new();
for immunity in &report.immunities {
let immunity_audit = immunity.requires_predicate.as_ref().map_or_else(
|| {
let status = validate_witness(&immunity.witness, &workspace_functions);
let witness_tier = WitnessTier::from_status(&status);
let audit_hint = AuditHint::from_status(&status);
let evidence_kind = evidence_kind_from_status(&status);
let has_companion_requires = report.immunities.iter().any(|other| {
other.requires_predicate.is_some()
&& other.antigen_type == immunity.antigen_type
&& other.item_target == immunity.item_target
&& other.file == immunity.file
});
let code_witness_sidecar_ignored = !has_companion_requires
&& matches!(
load_sidecar(&immunity.file, &immunity.antigen_type),
SidecarLoad::Ok(_)
);
ImmunityAudit {
immunity: immunity.clone(),
witness_status: status,
witness_tier,
audit_hint,
evidence_kind,
signature_strength: None,
compound_evidence: false,
evaluated_predicate: None,
code_witness_sidecar_ignored,
leaf_outcomes: Vec::new(),
}
},
|predicate_json| audit_substrate_witness(immunity, predicate_json),
);
audits.push(immunity_audit);
}
let mut audit_report = AuditReport {
audits,
..AuditReport::default()
};
for a in &audit_report.audits {
match &a.witness_status {
WitnessStatus::Resolved { .. } => audit_report.resolved_count += 1,
WitnessStatus::External { .. } => audit_report.external_count += 1,
WitnessStatus::Ambiguous { .. } => audit_report.ambiguous_count += 1,
WitnessStatus::NotFound { .. } => audit_report.broken_count += 1,
WitnessStatus::Missing => audit_report.missing_count += 1,
}
}
for u in report.unaddressed_presentations() {
if u.presentation.inherited_from.is_some() {
audit_report
.inherited_unaddressed
.push(InheritedUnaddressed {
presentation: u.presentation,
audit_hint: AuditHint::InheritedPresentationNotReAttested,
});
}
}
audit_report.presentation_verdicts =
compute_presentation_verdicts(report, &audit_report.audits);
audit_report
}
fn compute_presentation_verdicts(
report: &ScanReport,
immunity_audits: &[ImmunityAudit],
) -> Vec<PresentationVerdict> {
let mut verdicts = Vec::new();
for p in &report.presentations {
if p.match_kind != crate::scan::MatchKind::ExplicitMarker {
continue;
}
let code_witnesses: Vec<&crate::scan::Defense> = report
.defenses
.iter()
.filter(|d| {
crate::scan::defense_addresses(d, p)
&& (d.item_kind == "fn" || d.item_kind == "impl_fn")
})
.collect();
let immune_audit: Option<&ImmunityAudit> = immunity_audits.iter().find(|a| {
a.immunity.antigen_type == p.antigen_type
&& a.immunity.file == p.file
&& a.immunity.item_target == p.item_target
});
let immune_any_substrate_gap = immunity_audits.iter().any(|a| {
a.immunity.antigen_type == p.antigen_type
&& a.immunity.file == p.file
&& a.immunity.item_target == p.item_target
&& immune_audit_is_substrate_gap(a)
});
let code_tier = if code_witnesses.is_empty() {
None
} else {
Some(WitnessTier::Reachability)
};
let immune_tier = immune_audit
.map(|a| a.witness_tier)
.filter(|t| *t != WitnessTier::None);
let site_requires_eval = p.requires_predicate.as_ref().map(|json| {
let adapter = Immunity {
antigen_type: p.antigen_type.clone(),
witness: String::new(),
requires_predicate: Some(json.clone()),
file: p.file.clone(),
line: p.line,
item_kind: p.item_kind.clone(),
item_target: p.item_target.clone(),
canonical_path: p.canonical_path.clone(),
structural_fingerprint: p.structural_fingerprint.clone(),
};
audit_substrate_witness(&adapter, json).witness_tier
});
let site_requires_tier = site_requires_eval.filter(|t| *t != WitnessTier::None);
let site_proof_tier = p
.proof
.as_deref()
.filter(|s| !s.trim().is_empty())
.map(|_| WitnessTier::FormalProof);
let best_tier = [code_tier, immune_tier, site_requires_tier, site_proof_tier]
.into_iter()
.flatten()
.max_by_key(|t| *t as u8);
let requires_present_and_failed =
site_requires_eval == Some(WitnessTier::None) || immune_any_substrate_gap;
let verdict = if requires_present_and_failed {
ImmuneVerdict::SubstrateGap
} else {
match best_tier {
Some(tier) => ImmuneVerdict::Defended { tier },
None if site_requires_eval.is_some() || immune_any_substrate_gap => {
ImmuneVerdict::SubstrateGap
}
None => ImmuneVerdict::Undefended,
}
};
let defended_by = code_witnesses
.iter()
.map(|d| format!("{}:{}", d.file.display(), d.line))
.collect();
verdicts.push(PresentationVerdict {
presentation: p.clone(),
antigen_type: p.antigen_type.clone(),
verdict,
defended_by,
});
}
verdicts
}
fn immune_audit_is_substrate_gap(a: &ImmunityAudit) -> bool {
a.evaluated_predicate.is_some()
&& a.witness_tier == WitnessTier::None
&& a.audit_hint != AuditHint::DisciplinePredicateDeferred
}
#[presents(AuditFingerprintSelfReferential)]
fn audit_substrate_witness(immunity: &Immunity, predicate_json: &str) -> ImmunityAudit {
use antigen_attestation::evaluate::evaluate_predicate_with_kind;
let Ok(predicate) = serde_json::from_str::<antigen_attestation::Predicate>(predicate_json)
else {
let result = antigen_attestation::EvaluatedPredicate::sidecar_schema_invalid();
return immunity_audit_from_evaluated(
immunity,
result,
predicate_json.to_string(),
antigen_attestation::RatificationKind::Immunity,
);
};
let sidecar = match load_sidecar(&immunity.file, &immunity.antigen_type) {
SidecarLoad::Missing => {
let result = antigen_attestation::EvaluatedPredicate::sidecar_missing();
return immunity_audit_from_evaluated(
immunity,
result,
predicate_json.to_string(),
antigen_attestation::RatificationKind::Immunity,
);
}
SidecarLoad::SchemaInvalid => {
let result = antigen_attestation::EvaluatedPredicate::sidecar_schema_invalid();
return immunity_audit_from_evaluated(
immunity,
result,
predicate_json.to_string(),
antigen_attestation::RatificationKind::Immunity,
);
}
SidecarLoad::Ok(r) => r,
};
let immunity_label = immunity.item_target.label();
let Some(item) = sidecar
.items
.iter()
.find(|item| item.item_path == immunity_label)
else {
let result = antigen_attestation::EvaluatedPredicate::sidecar_missing();
return immunity_audit_from_evaluated(
immunity,
result,
predicate_json.to_string(),
sidecar.kind,
);
};
let ctx = FilesystemAuditContext;
let current_fingerprint: &str = if immunity.structural_fingerprint.is_empty() {
&item.current_fingerprint
} else {
&immunity.structural_fingerprint
};
let result = evaluate_predicate_with_kind(
&predicate,
item,
current_fingerprint,
&immunity.file,
sidecar.kind,
&ctx,
)
.unwrap_or_else(|_| antigen_attestation::EvaluatedPredicate::sidecar_schema_invalid());
immunity_audit_from_evaluated(immunity, result, predicate_json.to_string(), sidecar.kind)
}
fn immunity_audit_from_evaluated(
immunity: &Immunity,
result: antigen_attestation::EvaluatedPredicate,
predicate_json: String,
sidecar_kind: antigen_attestation::RatificationKind,
) -> ImmunityAudit {
let status = WitnessStatus::Resolved {
location: immunity.file.clone(),
witness_kind: WitnessKind::SubstrateWitness { kind: sidecar_kind },
};
ImmunityAudit {
immunity: immunity.clone(),
witness_status: status,
witness_tier: map_attestation_tier(result.witness_tier),
audit_hint: map_attestation_audit_hint(result.audit_hint),
evidence_kind: result.evidence_kind,
signature_strength: result.signature_strength,
compound_evidence: false,
evaluated_predicate: Some(predicate_json),
code_witness_sidecar_ignored: false,
leaf_outcomes: result.leaf_outcomes,
}
}
const fn map_attestation_tier(tier: antigen_attestation::WitnessTier) -> WitnessTier {
match tier {
antigen_attestation::WitnessTier::None => WitnessTier::None,
antigen_attestation::WitnessTier::Reachability => WitnessTier::Reachability,
antigen_attestation::WitnessTier::Execution => WitnessTier::Execution,
antigen_attestation::WitnessTier::FormalProof => WitnessTier::FormalProof,
}
}
const fn map_attestation_audit_hint(hint: antigen_attestation::AuditHint) -> AuditHint {
use antigen_attestation::AuditHint as AH;
match hint {
AH::DisciplineSidecarMissing => AuditHint::DisciplineSidecarMissing,
AH::DisciplineSidecarSchemaInvalid => AuditHint::DisciplineSidecarSchemaInvalid,
AH::DisciplinePredicateFailed => AuditHint::DisciplinePredicateFailed,
AH::DisciplinePredicateDeferred => AuditHint::DisciplinePredicateDeferred,
AH::DisciplineSubstrateStale => AuditHint::DisciplineSubstrateStale,
AH::DisciplineSubstrateDeltaChainNearCap => AuditHint::DisciplineSubstrateDeltaChainNearCap,
AH::DisciplinePredicatePassedViaDeltaChain => {
AuditHint::DisciplinePredicatePassedViaDeltaChain
}
AH::DisciplinePredicatePassedSubstrateCurrent => {
AuditHint::DisciplinePredicatePassedSubstrateCurrent
}
AH::ToleranceVibesGrade => AuditHint::ToleranceVibesGrade,
AH::ToleranceSidecarMissing => AuditHint::ToleranceSidecarMissing,
AH::TolerancePredicateFailed => AuditHint::TolerancePredicateFailed,
AH::TolerancePredicatePassedSubstrateCurrent => {
AuditHint::TolerancePredicatePassedSubstrateCurrent
}
AH::DisciplineSidecarKindMismatchExpectedImmunityGotTolerance => {
AuditHint::DisciplineSidecarKindMismatchExpectedImmunityGotTolerance
}
AH::ToleranceSidecarKindMismatchExpectedToleranceGotImmunity => {
AuditHint::ToleranceSidecarKindMismatchExpectedToleranceGotImmunity
}
AH::DisciplineImmunityToleranceContradiction => {
AuditHint::DisciplineImmunityToleranceContradiction
}
}
}
#[derive(Debug, Clone)]
struct FunctionEntry {
location: PathBuf,
kind: WitnessKind,
}
type FunctionIndex = std::collections::HashMap<String, Vec<FunctionEntry>>;
fn collect_function_index(root: &Path) -> FunctionIndex {
use syn::visit::Visit;
use walkdir::WalkDir;
let exclusions = ["target", ".git", "node_modules"];
let mut index = FunctionIndex::new();
for entry in WalkDir::new(root)
.follow_links(false)
.into_iter()
.filter_entry(|e| {
if e.file_type().is_dir() {
let name = e.file_name().to_string_lossy();
!exclusions.iter().any(|x| *x == name)
} else {
true
}
})
{
let Ok(entry) = entry else { continue };
if !entry.file_type().is_file() {
continue;
}
if entry.path().extension().and_then(|e| e.to_str()) != Some("rs") {
continue;
}
let Ok(content) = std::fs::read_to_string(entry.path()) else {
continue;
};
if let Ok(file) = syn::parse_file(&content) {
let mut visitor = FunctionIndexVisitor {
file_path: entry.path().to_path_buf(),
source: &content,
index: &mut index,
};
visitor.visit_file(&file);
}
}
index
}
struct FunctionIndexVisitor<'a> {
file_path: PathBuf,
#[allow(
dead_code,
reason = "reserved for span-anchored diagnostic work \
that mirrors scan::ScanVisitor::source"
)]
source: &'a str,
index: &'a mut FunctionIndex,
}
impl FunctionIndexVisitor<'_> {
fn detect_kind(attrs: &[syn::Attribute]) -> WitnessKind {
let has_test = attrs.iter().any(|a| a.path().is_ident("test"));
let has_ignore = attrs.iter().any(|a| a.path().is_ident("ignore"));
match (has_test, has_ignore) {
(true, true) => WitnessKind::IgnoredTest,
(true, false) => WitnessKind::Test,
(false, _) => WitnessKind::Function,
}
}
}
fn extract_proptest_fn_names(tokens: &proc_macro2::TokenStream) -> Vec<String> {
use proc_macro2::TokenTree;
let mut names = Vec::new();
let mut iter = tokens.clone().into_iter();
while let Some(tt) = iter.next() {
if let TokenTree::Ident(i) = &tt {
if i == "fn" {
if let Some(TokenTree::Ident(name)) = iter.next() {
names.push(name.to_string());
}
}
}
}
names
}
fn macro_path_last_is(path: &syn::Path, name: &str) -> bool {
path.segments.last().is_some_and(|s| s.ident == name)
}
impl FunctionIndexVisitor<'_> {
fn push(&mut self, name: String, kind: WitnessKind) {
self.index.entry(name).or_default().push(FunctionEntry {
location: self.file_path.clone(),
kind,
});
}
}
impl<'ast> syn::visit::Visit<'ast> for FunctionIndexVisitor<'_> {
fn visit_item_fn(&mut self, item: &'ast syn::ItemFn) {
let name = item.sig.ident.to_string();
let kind = Self::detect_kind(&item.attrs);
self.push(name, kind);
syn::visit::visit_item_fn(self, item);
}
fn visit_impl_item_fn(&mut self, item: &'ast syn::ImplItemFn) {
let name = item.sig.ident.to_string();
let kind = Self::detect_kind(&item.attrs);
self.push(name, kind);
syn::visit::visit_impl_item_fn(self, item);
}
fn visit_macro(&mut self, mac: &'ast syn::Macro) {
if macro_path_last_is(&mac.path, "proptest") {
for name in extract_proptest_fn_names(&mac.tokens) {
self.push(name, WitnessKind::Proptest);
}
}
syn::visit::visit_macro(self, mac);
}
}
fn validate_witness(witness: &str, index: &FunctionIndex) -> WitnessStatus {
let normalized_owned: String = {
let collapsed = witness.split_whitespace().collect::<Vec<_>>().join(" ");
collapsed
.replace(" :: ", "::")
.replace(":: ", "::")
.replace(" ::", "::")
.replace("< ", "<")
.replace(" >", ">")
};
let trimmed = normalized_owned.trim();
if trimmed.is_empty() {
return WitnessStatus::Missing;
}
if let Some(tool) = detect_external_tool(trimmed) {
return WitnessStatus::External {
tool_hint: tool.to_string(),
};
}
if let Some(phantom) = detect_phantom_type_witness(trimmed) {
return WitnessStatus::Resolved {
location: PathBuf::new(),
witness_kind: phantom,
};
}
let function_name = trimmed
.rsplit("::")
.next()
.unwrap_or(trimmed)
.trim_end_matches("()")
.trim();
let candidates = index.get(function_name);
let Some(candidates) = candidates else {
return WitnessStatus::NotFound {
reason: format!(
"no function named `{function_name}` found in any .rs file under the scan root"
),
};
};
match candidates.as_slice() {
[] => WitnessStatus::NotFound {
reason: format!(
"no function named `{function_name}` found in any .rs file under the scan root"
),
},
[only] => WitnessStatus::Resolved {
location: only.location.clone(),
witness_kind: only.kind.clone(),
},
many => WitnessStatus::Ambiguous {
candidates: many.iter().map(|e| e.location.clone()).collect(),
},
}
}
fn detect_phantom_type_witness(witness: &str) -> Option<WitnessKind> {
let trimmed = witness.trim().trim_end_matches("()").trim();
let has_turbofish = trimmed.contains("::<");
if !has_turbofish {
return None;
}
let (before, after) = trimmed.split_once("::<")?;
let (params_raw, ctor_part) = after.split_once('>')?;
let open_count = params_raw.chars().filter(|&c| c == '<').count();
if open_count > 0 {
return None;
}
let proof_type = before.trim().to_string();
let type_params: Vec<String> = params_raw
.split(',')
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect();
let constructor = ctor_part
.trim_start_matches(['>', ':'])
.trim()
.trim_end_matches("()")
.trim();
let constructor = if constructor.is_empty() {
None
} else {
Some(constructor.to_string())
};
Some(WitnessKind::PhantomType {
proof_type,
type_params,
constructor,
})
}
fn detect_external_tool(witness: &str) -> Option<&'static str> {
let lower = witness.to_ascii_lowercase();
if lower.starts_with("clippy::") || lower.contains("clippy_") {
Some("clippy")
} else if lower.starts_with("kani::") || lower.contains("kani_proof") {
Some("kani")
} else if lower.starts_with("prusti::") {
Some("prusti")
} else if lower.starts_with("creusot::") {
Some("creusot")
} else if lower.starts_with("verus::") {
Some("verus")
} else if lower.starts_with("mutants::") {
Some("cargo-mutants")
} else {
None
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SupplyChainAudit {
pub antigen_type: String,
pub file: PathBuf,
pub line: usize,
pub crate_name: String,
pub version: String,
pub hint: AuditHint,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub detail: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct SupplyChainAuditReport {
pub audits: Vec<SupplyChainAudit>,
pub pass_count: usize,
pub fail_count: usize,
}
impl SupplyChainAuditReport {
#[must_use]
pub const fn all_pass(&self) -> bool {
self.fail_count == 0
}
}
#[must_use]
pub fn audit_supply_chain(report: &ScanReport, workspace_root: &Path) -> SupplyChainAuditReport {
let mut audits: Vec<SupplyChainAudit> = Vec::new();
for immunity in &report.immunities {
let Some(json) = &immunity.requires_predicate else {
continue;
};
let Ok(predicate) = serde_json::from_str::<antigen_attestation::Predicate>(json) else {
continue;
};
if predicate.validate().is_err() {
audits.push(SupplyChainAudit {
antigen_type: immunity.antigen_type.clone(),
hint: AuditHint::MalformedRequiresPredicate,
file: immunity.file.clone(),
line: immunity.line,
crate_name: String::new(),
version: String::new(),
detail: Some(
"requires_predicate deserialized but failed structural validation \
(e.g., empty combinator — vacuous pass with no leaves)"
.to_string(),
),
});
continue;
}
let entries = eval_supply_chain_predicate(
&predicate,
workspace_root,
&immunity.antigen_type,
&immunity.file,
immunity.line,
);
audits.extend(entries.entries);
}
let mut pass_count = 0usize;
let mut fail_count = 0usize;
for a in &audits {
if is_supply_chain_pass_hint(&a.hint) {
pass_count += 1;
} else {
fail_count += 1;
}
}
SupplyChainAuditReport {
audits,
pass_count,
fail_count,
}
}
struct SupplyChainEval {
passed: bool,
entries: Vec<SupplyChainAudit>,
}
fn eval_supply_chain_predicate(
pred: &antigen_attestation::Predicate,
workspace_root: &Path,
antigen_type: &str,
file: &Path,
line: usize,
) -> SupplyChainEval {
use antigen_attestation::Predicate;
match pred {
Predicate::Leaf(l) => {
let entry = audit_supply_chain_leaf(l, workspace_root, antigen_type, file, line);
let passed = entry
.as_ref()
.is_some_and(|e| is_supply_chain_pass_hint(&e.hint));
let logical_passed = entry.as_ref().is_none_or(|_| passed);
SupplyChainEval {
passed: logical_passed,
entries: entry.into_iter().collect(),
}
}
Predicate::AllOf { children } => {
let mut entries = Vec::new();
let mut all_pass = true;
for c in children {
let sub = eval_supply_chain_predicate(c, workspace_root, antigen_type, file, line);
if !sub.passed {
all_pass = false;
}
entries.extend(sub.entries);
}
SupplyChainEval {
passed: all_pass,
entries,
}
}
Predicate::AnyOf { children } => {
let mut pass_entries = Vec::new();
let mut fail_entries = Vec::new();
let mut any_pass = false;
for c in children {
let sub = eval_supply_chain_predicate(c, workspace_root, antigen_type, file, line);
if sub.passed {
any_pass = true;
pass_entries.extend(sub.entries);
} else {
fail_entries.extend(sub.entries);
}
}
let entries = if any_pass { pass_entries } else { fail_entries };
SupplyChainEval {
passed: any_pass,
entries,
}
}
Predicate::Not { child } => {
let sub = eval_supply_chain_predicate(child, workspace_root, antigen_type, file, line);
SupplyChainEval {
passed: !sub.passed,
entries: Vec::new(),
}
}
}
}
fn audit_supply_chain_leaf(
leaf: &antigen_attestation::Leaf,
workspace_root: &Path,
antigen_type: &str,
file: &Path,
line: usize,
) -> Option<SupplyChainAudit> {
use antigen_attestation::Leaf;
let (crate_name, version, hint, detail) = match leaf {
Leaf::DepPinned { crate_name } => {
eval_dep_pinned_to_hint(workspace_root, crate_name.as_deref())
}
Leaf::DepAttested {
crate_name,
version,
exact_version,
..
} => eval_dep_attested_to_hint(workspace_root, crate_name, version, *exact_version),
Leaf::MaintainerUnchanged {
crate_name,
since_version,
} => eval_maintainer_unchanged_to_hint(workspace_root, crate_name, since_version),
Leaf::ContentHashMatches {
crate_name,
version,
} => eval_content_hash_matches_to_hint(workspace_root, crate_name, version),
Leaf::SandboxClean {
crate_name,
sandbox_kind,
} => eval_sandbox_clean_to_hint(crate_name, sandbox_kind),
Leaf::RatifiedDoc { .. }
| Leaf::Signers { .. }
| Leaf::SignedTrailer { .. }
| Leaf::OraclesComplete { .. }
| Leaf::FreshWithinDays { .. } => return None,
};
Some(SupplyChainAudit {
antigen_type: antigen_type.to_string(),
file: file.to_path_buf(),
line,
crate_name,
version,
hint,
detail,
})
}
fn eval_dep_pinned_to_hint(
workspace_root: &Path,
crate_name: Option<&str>,
) -> (String, String, AuditHint, Option<String>) {
use crate::supply_chain::{evaluate, witness::DepPinnedState};
let state = evaluate::evaluate_dep_pinned(workspace_root, crate_name);
let (hint, detail) = match &state {
DepPinnedState::AllPinned => (AuditHint::FunctionResolves, None),
DepPinnedState::Unpinned { unpinned_deps } => (
AuditHint::UnpinnedDependency,
Some(format!("unpinned: {unpinned_deps:?}")),
),
DepPinnedState::NotInManifest { crate_name: cn } => (
AuditHint::UnpinnedDependency,
Some(format!("crate not in manifest: {cn}")),
),
};
(
crate_name.map_or_else(|| "*".to_string(), str::to_string),
String::new(),
hint,
detail,
)
}
fn eval_dep_attested_to_hint(
workspace_root: &Path,
crate_name: &str,
version: &str,
exact_version: bool,
) -> (String, String, AuditHint, Option<String>) {
use crate::supply_chain::{evaluate, witness::DepAttestedState};
let state = evaluate::evaluate_dep_attested(workspace_root, crate_name, version, exact_version);
let (hint, detail) = match &state {
DepAttestedState::Attested { .. } => (AuditHint::FunctionResolves, None),
DepAttestedState::AttestedWithoutReviewableArtifact => {
(AuditHint::DepAttestWithoutReviewableArtifact, None)
}
DepAttestedState::SidecarMissing => (AuditHint::UnattestedDependencyInclusion, None),
DepAttestedState::SidecarMalformed { error } => (
AuditHint::UnattestedDependencyInclusion,
Some(format!("sidecar malformed: {error}")),
),
DepAttestedState::AttestationStale {
attested_version,
requested_version,
} => (
AuditHint::DepAttestationStale,
Some(format!(
"attested: {attested_version}; requested: {requested_version}"
)),
),
};
(crate_name.to_string(), version.to_string(), hint, detail)
}
fn eval_maintainer_unchanged_to_hint(
workspace_root: &Path,
crate_name: &str,
since_version: &str,
) -> (String, String, AuditHint, Option<String>) {
use crate::supply_chain::{evaluate, witness::MaintainerState};
let state = evaluate::evaluate_maintainer_unchanged(workspace_root, crate_name, since_version);
let (hint, detail) = match &state {
MaintainerState::Unchanged => (AuditHint::FunctionResolves, None),
MaintainerState::Changed { added, removed } => (
AuditHint::MaintainerChangeWithoutReattestation,
Some(format!("added: {added:?}; removed: {removed:?}")),
),
MaintainerState::SnapshotMissing => (
AuditHint::MaintainerChangeWithoutReattestation,
Some("snapshot missing".to_string()),
),
MaintainerState::CratesIoQueryUnavailable => (AuditHint::CratesIoMetadataQueryFailed, None),
};
(
crate_name.to_string(),
since_version.to_string(),
hint,
detail,
)
}
fn eval_content_hash_matches_to_hint(
workspace_root: &Path,
crate_name: &str,
version: &str,
) -> (String, String, AuditHint, Option<String>) {
use crate::supply_chain::{evaluate, witness::ContentHashState};
let state = evaluate::evaluate_content_hash_matches(workspace_root, crate_name, version);
let (hint, detail) = match &state {
ContentHashState::Matches => (AuditHint::FunctionResolves, None),
ContentHashState::Mismatch { recorded, current } => (
AuditHint::ContentHashMismatch,
Some(format!("recorded: {recorded}; current: {current}")),
),
ContentHashState::NoAttestation => (AuditHint::ContentHashNoAttestation, None),
ContentHashState::CrateNotInLockfile { crate_name: cn } => (
AuditHint::ContentHashNoAttestation,
Some(format!("crate not in Cargo.lock: {cn}")),
),
ContentHashState::SidecarMalformed { error } => (
AuditHint::ContentHashSidecarMalformed,
Some(format!("malformed: {error}")),
),
};
(crate_name.to_string(), version.to_string(), hint, detail)
}
fn eval_sandbox_clean_to_hint(
crate_name: &str,
sandbox_kind: &str,
) -> (String, String, AuditHint, Option<String>) {
let hint = if sandbox_kind == "proc-macro" {
AuditHint::UnsandboxedProcMacro
} else {
AuditHint::UnsandboxedBuildScript
};
(
crate_name.to_string(),
String::new(),
hint,
Some(format!(
"v0.2 sandbox tooling not yet available; kind={sandbox_kind}"
)),
)
}
const fn is_supply_chain_pass_hint(hint: &AuditHint) -> bool {
matches!(hint, AuditHint::FunctionResolves)
}
pub const CLONAL_ITERATIONS_DEFAULT_FLOOR: u64 = 100;
pub const IGG_HISTORICAL_SPAN_DEFAULT_FLOOR: u64 = 30;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConvergentEvidenceAudit {
pub declaration: crate::scan::ConvergentEvidence,
pub hints: Vec<AuditHint>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ConvergentEvidenceAuditReport {
pub audits: Vec<ConvergentEvidenceAudit>,
pub clean_count: usize,
pub concern_count: usize,
}
impl ConvergentEvidenceAuditReport {
#[must_use]
pub const fn all_clean(&self) -> bool {
self.concern_count == 0
}
}
#[must_use]
pub fn audit_convergent_evidence(report: &ScanReport) -> ConvergentEvidenceAuditReport {
let known_antigen_names: std::collections::HashSet<&str> = report
.antigens
.iter()
.map(|a| a.type_name.as_str())
.collect();
let mut audits: Vec<ConvergentEvidenceAudit> = Vec::new();
for decl in &report.convergent_evidences {
let hints = evaluate_convergent_evidence_hints(decl, &known_antigen_names);
audits.push(ConvergentEvidenceAudit {
declaration: decl.clone(),
hints,
});
}
let mut clean_count = 0usize;
let mut concern_count = 0usize;
for a in &audits {
if a.hints.is_empty() {
clean_count += 1;
} else {
concern_count += 1;
}
}
ConvergentEvidenceAuditReport {
audits,
clean_count,
concern_count,
}
}
fn evaluate_convergent_evidence_hints(
decl: &crate::scan::ConvergentEvidence,
known_antigen_names: &std::collections::HashSet<&str>,
) -> Vec<AuditHint> {
use crate::scan::ConvergentEvidenceKind;
let mut hints = Vec::new();
match decl.kind {
ConvergentEvidenceKind::Diagnostic => {
if decl.modality_classes.is_empty() {
hints.push(AuditHint::DiagnosticModalitiesEmpty);
return hints;
}
let distinct: std::collections::HashSet<&str> =
decl.modality_classes.iter().map(String::as_str).collect();
if distinct.len() == 1 && decl.modality_classes.len() > 1 {
hints.push(AuditHint::DiagnosticModalitiesClassCollapsed);
}
if let Some(min) = decl.min_independent {
if min == 0 {
hints.push(AuditHint::DiagnosticMinIndependentZero);
} else if u64::try_from(distinct.len()).unwrap_or(u64::MAX) < min {
hints.push(AuditHint::DiagnosticModalityInsufficient);
}
}
}
ConvergentEvidenceKind::Clonal => {
if matches!(decl.seed_kind.as_deref(), Some("Fixed")) {
hints.push(AuditHint::ClonalFixedSeedDetected);
}
if let Some(iters) = decl.iterations {
if iters < CLONAL_ITERATIONS_DEFAULT_FLOOR {
hints.push(AuditHint::ClonalIterationsBelowThreshold);
}
}
}
ConvergentEvidenceKind::Igg => {
if let Some(span) = decl.historical_span {
if span < IGG_HISTORICAL_SPAN_DEFAULT_FLOOR {
hints.push(AuditHint::IggSpanTooShort);
}
}
let unique_count: std::collections::HashSet<&str> =
decl.witnesses.iter().map(String::as_str).collect();
if let Some(min_re) = decl.min_reattestations {
if min_re == 0 {
hints.push(AuditHint::IggMinReattestationsZero);
} else if u64::try_from(unique_count.len()).unwrap_or(u64::MAX) < min_re {
hints.push(AuditHint::IggReattestationsInsufficient);
}
}
if decl.witnesses.len() > 1 && unique_count.len() == 1 {
hints.push(AuditHint::IggIdentityCollapseWarning);
}
}
ConvergentEvidenceKind::Crossreactive => {
for fp in &decl.fingerprints {
if !known_antigen_names.contains(fp.as_str()) {
hints.push(AuditHint::CrossreactiveFingerprintUnresolved);
break;
}
}
}
ConvergentEvidenceKind::Polyclonal
| ConvergentEvidenceKind::Monoclonal
| ConvergentEvidenceKind::Adcc => {
}
}
hints
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RecurrentAudit {
pub declaration: crate::scan::RecurrentDeclaration,
pub hints: Vec<AuditHint>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct RecurrentAuditReport {
pub audits: Vec<RecurrentAudit>,
pub clean_count: usize,
pub concern_count: usize,
}
impl RecurrentAuditReport {
#[must_use]
pub const fn all_clean(&self) -> bool {
self.concern_count == 0
}
}
const CHRONIC_REVIEW_HORIZON_DAYS: i64 = 365;
#[must_use]
pub fn audit_recurrent(report: &ScanReport) -> RecurrentAuditReport {
let acted_on: std::collections::HashSet<&str> = report
.immunities
.iter()
.map(|i| i.antigen_type.as_str())
.chain(report.presentations.iter().map(|p| p.antigen_type.as_str()))
.collect();
let itch_antigen_types: std::collections::HashSet<&str> = report
.recurrent_declarations
.iter()
.filter(|d| d.kind == crate::scan::RecurrentKind::Itch)
.filter_map(|d| d.antigen_type.as_deref())
.collect();
let parent_of: std::collections::HashMap<&str, Vec<&str>> = {
let mut m: std::collections::HashMap<&str, Vec<&str>> = std::collections::HashMap::new();
for e in &report.lineage_edges {
m.entry(e.child.as_str())
.or_default()
.push(e.parent.as_str());
}
m
};
let mut audits: Vec<RecurrentAudit> = Vec::new();
for decl in &report.recurrent_declarations {
let hints = evaluate_recurrent_hints(decl, &acted_on, &itch_antigen_types, &parent_of);
audits.push(RecurrentAudit {
declaration: decl.clone(),
hints,
});
}
let mut clean_count = 0usize;
let mut concern_count = 0usize;
for a in &audits {
if a.hints.is_empty() {
clean_count += 1;
} else {
concern_count += 1;
}
}
RecurrentAuditReport {
audits,
clean_count,
concern_count,
}
}
fn is_version_tag(s: &str) -> bool {
let had_v_prefix = s.starts_with(['v', 'V']);
let core = if had_v_prefix { &s[1..] } else { s };
let numeric_core: &str = core.split(['-', '+']).next().unwrap_or("");
if numeric_core.is_empty() {
return false;
}
let mut component_count = 0usize;
for part in numeric_core.split('.') {
if part.is_empty() || !part.bytes().all(|b| b.is_ascii_digit()) {
return false;
}
component_count += 1;
}
had_v_prefix || component_count >= 2
}
fn ancestors_of<'a>(
antigen: &'a str,
parent_of: &std::collections::HashMap<&'a str, Vec<&'a str>>,
) -> std::collections::HashSet<&'a str> {
let mut acc: std::collections::HashSet<&str> = std::collections::HashSet::new();
let mut stack: Vec<&str> = parent_of.get(antigen).cloned().unwrap_or_default();
while let Some(parent) = stack.pop() {
if acc.insert(parent) {
if let Some(grandparents) = parent_of.get(parent) {
stack.extend(grandparents.iter().copied());
}
}
}
acc
}
#[presents(AuditHintWithNoUpstreamPreconditionCheck)]
fn evaluate_recurrent_hints(
decl: &crate::scan::RecurrentDeclaration,
acted_on: &std::collections::HashSet<&str>,
itch_antigen_types: &std::collections::HashSet<&str>,
parent_of: &std::collections::HashMap<&str, Vec<&str>>,
) -> Vec<AuditHint> {
use crate::scan::RecurrentKind;
let mut hints = Vec::new();
match decl.kind {
RecurrentKind::Itch => {
if decl.antigen_type.is_none() {
hints.push(AuditHint::ItchNoticedNotAnchored);
}
}
RecurrentKind::RecurrenceAnchor => {
if let Some(antigen) = decl.antigen_type.as_deref() {
let ancestors = ancestors_of(antigen, parent_of);
let in_lineage = |itch: &str| itch == antigen || ancestors.contains(itch);
let has_valid_from_itches = !decl.from_itches.is_empty()
&& decl.from_itches.iter().any(|itch| {
in_lineage(itch.as_str()) && itch_antigen_types.contains(itch.as_str())
});
let has_implicit_itch = itch_antigen_types.contains(antigen);
if !has_valid_from_itches && !has_implicit_itch {
hints.push(AuditHint::RecurrenceAnchorNoItchPrecondition);
}
if !acted_on.contains(antigen) {
hints.push(AuditHint::RecurrenceThresholdReachedNoAction);
}
}
}
RecurrentKind::Crystallize => {
if decl.antigen_type.is_none() && decl.from_itches.is_empty() {
hints.push(AuditHint::CrystallizeWithoutAntigen);
}
}
RecurrentKind::Chronic => {
if decl.managed_by.is_none() {
hints.push(AuditHint::ChronicSignalUnmanaged);
}
if let Some(since) = decl.since.as_deref() {
if let Ok(since_date) = chrono::NaiveDate::parse_from_str(since, "%Y-%m-%d") {
let age = chrono::Utc::now().date_naive() - since_date;
if age.num_days() > CHRONIC_REVIEW_HORIZON_DAYS {
hints.push(AuditHint::ChronicSignalPastReviewDate);
}
} else if !is_version_tag(since) {
hints.push(AuditHint::ChronicSinceNotADate);
}
}
}
RecurrentKind::Saturate => {
if decl.contributing_to.is_none() {
hints.push(AuditHint::SaturateNoAnchor);
}
}
RecurrentKind::Strand => {
if decl.anchored_by.is_empty() {
hints.push(AuditHint::StrandNoAnchors);
}
}
}
hints
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MucosalAudit {
pub declaration: crate::scan::MucosalDeclaration,
pub hints: Vec<AuditHint>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct MucosalAuditReport {
pub audits: Vec<MucosalAudit>,
pub clean_count: usize,
pub concern_count: usize,
}
impl MucosalAuditReport {
#[must_use]
pub const fn all_clean(&self) -> bool {
self.concern_count == 0
}
}
const MUCOSAL_RATIONALE_FLOOR: usize = 20;
const MUCOSAL_TOLERANT_RATIONALE_FLOOR: usize = 40;
#[must_use]
#[presents(DelegateCrossCrateResolutionGap)]
#[antigen_tolerance(
DelegateCrossCrateResolutionGap,
rationale = "Accepted v0.2 limitation: handler_kinds is built from intra-crate #[mucosal] \
declarations only, so a #[mucosal_delegate] pointing at a cross-crate handler \
false-positives as MucosalDisciplineDelegateTargetMissing. The structural fix is a \
multi-crate scan pass (v0.3+ scope, same boundary as --include-deps cross-crate \
addressing). Until then the false-positive is the conservative failure (flags rather \
than silently trusts an unresolvable delegate).",
until = "v0.3"
)]
pub fn audit_mucosal(report: &ScanReport) -> MucosalAuditReport {
use crate::scan::{ItemTarget, MucosalKindTag};
let mut handler_kinds: std::collections::HashMap<&str, std::collections::HashSet<&str>> =
std::collections::HashMap::new();
let mut handler_files: std::collections::HashMap<
&str,
std::collections::HashSet<&std::path::Path>,
> = std::collections::HashMap::new();
for decl in &report.mucosal_declarations {
if decl.tag == MucosalKindTag::Mucosal {
if let ItemTarget::Fn(fn_name) = &decl.item_target {
if let Some(kind) = decl.boundary_kind.as_deref() {
handler_kinds
.entry(fn_name.as_str())
.or_default()
.insert(kind);
}
handler_files
.entry(fn_name.as_str())
.or_default()
.insert(decl.file.as_path());
}
}
}
let ambiguous_names: std::collections::HashSet<&str> = handler_files
.iter()
.filter(|(_, files)| files.len() > 1)
.map(|(name, _)| *name)
.collect();
let mut audits: Vec<MucosalAudit> = Vec::new();
for decl in &report.mucosal_declarations {
let hints = evaluate_mucosal_hints(decl, &handler_kinds, &ambiguous_names);
audits.push(MucosalAudit {
declaration: decl.clone(),
hints,
});
}
let mut clean_count = 0usize;
let mut concern_count = 0usize;
for a in &audits {
if a.hints.is_empty() {
clean_count += 1;
} else {
concern_count += 1;
}
}
MucosalAuditReport {
audits,
clean_count,
concern_count,
}
}
fn evaluate_mucosal_hints(
decl: &crate::scan::MucosalDeclaration,
handler_kinds: &std::collections::HashMap<&str, std::collections::HashSet<&str>>,
ambiguous_names: &std::collections::HashSet<&str>,
) -> Vec<AuditHint> {
use crate::scan::MucosalKindTag;
let mut hints = Vec::new();
match decl.tag {
MucosalKindTag::Mucosal => {
if decl.boundary_kind.is_none() {
hints.push(AuditHint::MucosalKindMismatch);
}
if decl
.rationale
.as_deref()
.is_none_or(|r| r.len() < MUCOSAL_RATIONALE_FLOOR)
{
hints.push(AuditHint::MucosalRationaleInsufficient);
}
}
MucosalKindTag::MucosalDelegate => {
if decl.boundary_kind.is_none() {
hints.push(AuditHint::MucosalKindMismatch);
}
match decl.handled_by.as_deref() {
None => hints.push(AuditHint::MucosalDisciplineDelegateTargetMissing),
Some(handler) => {
if ambiguous_names.contains(handler) {
hints.push(AuditHint::MucosalDisciplineDelegateTargetAmbiguous);
} else {
match handler_kinds.get(handler) {
None => hints.push(AuditHint::MucosalDisciplineDelegateTargetMissing),
Some(kinds) if kinds.is_empty() => {
hints.push(AuditHint::MucosalDisciplineDelegateTargetNotMucosal);
}
Some(kinds) => {
let matches = decl
.boundary_kind
.as_deref()
.is_some_and(|b| kinds.contains(b));
if !matches {
hints.push(
AuditHint::MucosalDisciplineDelegateTargetKindMismatch,
);
}
}
}
}
}
}
}
MucosalKindTag::MucosalTolerant => {
if decl.boundary_kind.is_none() {
hints.push(AuditHint::MucosalKindMismatch);
}
if decl
.rationale
.as_deref()
.is_none_or(|r| r.len() < MUCOSAL_TOLERANT_RATIONALE_FLOOR)
{
hints.push(AuditHint::MucosalTolerantRationaleInsufficient);
}
if decl.accepts.as_deref().is_none_or(|a| a.trim().is_empty()) {
hints.push(AuditHint::MucosalTolerantAcceptsEmpty);
}
if decl.reviewed_by.is_none() {
hints.push(AuditHint::MucosalTolerantWithoutReviewer);
}
if let Some(until) = decl.until.as_deref() {
if let Ok(until_date) = chrono::NaiveDate::parse_from_str(until, "%Y-%m-%d") {
if chrono::Utc::now().date_naive() > until_date {
hints.push(AuditHint::MucosalTolerantPastReviewDate);
}
}
}
}
}
hints
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CategoryAudit {
pub antigen_type: String,
pub file: std::path::PathBuf,
pub line: usize,
pub hints: Vec<AuditHint>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct CategoryAuditReport {
pub audits: Vec<CategoryAudit>,
pub explicit_count: usize,
pub defaulted_count: usize,
#[serde(default)]
pub mismatch_count: usize,
}
impl CategoryAuditReport {
#[must_use]
pub const fn all_explicit(&self) -> bool {
self.defaulted_count == 0
}
#[must_use]
pub const fn no_category_witness_mismatch(&self) -> bool {
self.mismatch_count == 0
}
#[must_use]
pub fn no_silence_witness_mismatch(&self) -> bool {
!self.audits.iter().any(|ca| {
ca.hints
.contains(&AuditHint::AntigenWitnessShapeMismatchForSilenceNoWitness)
|| ca
.hints
.contains(&AuditHint::AntigenWitnessShapeMismatchForSilenceWrongTier)
})
}
}
#[must_use]
pub fn audit_category(report: &ScanReport) -> CategoryAuditReport {
use crate::category::AntigenCategory;
let mut audits = Vec::new();
let mut explicit_count = 0usize;
let mut defaulted_count = 0usize;
let mut mismatch_count = 0usize;
for decl in &report.antigens {
if decl.category.is_empty() {
defaulted_count += 1;
audits.push(CategoryAudit {
antigen_type: decl.type_name.clone(),
file: decl.file.clone(),
line: decl.line,
hints: vec![AuditHint::AntigenCategoryDefaultedImplicitFunctional],
});
continue;
}
explicit_count += 1;
let mut has_substrate_witness = false;
let mut has_code_witness = false;
let mut has_any_immunity = false;
for imm in &report.immunities {
if imm.antigen_type != decl.type_name {
continue;
}
if !crate::scan::canonical_paths_match(
imm.canonical_path.as_deref(),
decl.canonical_path.as_deref(),
) {
continue;
}
has_any_immunity = true;
if imm.requires_predicate.is_some() {
has_substrate_witness = true;
}
if !imm.witness.is_empty() {
has_code_witness = true;
}
}
if report.defenses.iter().any(|d| {
d.antigen_type == decl.type_name
&& crate::scan::canonical_paths_match(
d.canonical_path.as_deref(),
decl.canonical_path.as_deref(),
)
}) {
has_any_immunity = true;
has_code_witness = true;
}
let wants_substrate = decl.category.contains(&AntigenCategory::SubstrateAlignment);
let wants_code = decl
.category
.contains(&AntigenCategory::FunctionalCorrectness);
let is_hybrid = wants_substrate && wants_code;
if !has_any_immunity {
if wants_substrate {
audits.push(CategoryAudit {
antigen_type: decl.type_name.clone(),
file: decl.file.clone(),
line: decl.line,
hints: vec![AuditHint::AntigenWitnessShapeMismatchForSilenceNoWitness],
});
}
continue;
}
let substrate_satisfied = !wants_substrate || has_substrate_witness;
let code_satisfied = !wants_code || has_code_witness;
if substrate_satisfied && code_satisfied {
continue;
}
let hybrid_one_axis_witnessed = is_hybrid && (has_substrate_witness ^ has_code_witness);
let hint = if hybrid_one_axis_witnessed {
AuditHint::AntigenCategoryHybridIncompleteEvidence
} else {
AuditHint::AntigenCategoryClaimInconsistentWithPredicateType
};
let mut hints = vec![hint];
if wants_substrate && has_code_witness && !has_substrate_witness {
hints.push(AuditHint::AntigenWitnessShapeMismatchForSilenceWrongTier);
}
mismatch_count += 1;
audits.push(CategoryAudit {
antigen_type: decl.type_name.clone(),
file: decl.file.clone(),
line: decl.line,
hints,
});
}
CategoryAuditReport {
audits,
explicit_count,
defaulted_count,
mismatch_count,
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LineageFidelityAudit {
pub child: String,
pub parent: String,
pub file: PathBuf,
pub line: usize,
pub hint: AuditHint,
pub detail: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct LineageFidelityAuditReport {
pub divergences: Vec<LineageFidelityAudit>,
}
#[must_use]
pub fn audit_lineage_fidelity(report: &ScanReport) -> LineageFidelityAuditReport {
use std::collections::HashMap;
let fingerprints: HashMap<(&str, Option<&str>), antigen_fingerprint::Fingerprint> = report
.antigens
.iter()
.filter_map(|a| {
let raw = a.fingerprint.as_deref()?;
antigen_fingerprint::Fingerprint::parse(raw)
.ok()
.map(|fp| ((a.type_name.as_str(), a.canonical_path.as_deref()), fp))
})
.collect();
let mut divergences = Vec::new();
for edge in &report.lineage_edges {
let (Some(child_fp), Some(parent_fp)) = (
fingerprints.get(&(edge.child.as_str(), edge.child_canonical_path.as_deref())),
fingerprints.get(&(edge.parent.as_str(), edge.parent_canonical_path.as_deref())),
) else {
continue;
};
if let Some(detail) = fingerprint_nonrefinement_reason(child_fp, parent_fp) {
divergences.push(LineageFidelityAudit {
child: edge.child.clone(),
parent: edge.parent.clone(),
file: edge.file.clone(),
line: edge.line,
hint: AuditHint::DescendedFromFingerprintDivergence,
detail,
});
}
}
LineageFidelityAuditReport { divergences }
}
fn fingerprint_nonrefinement_reason(
child: &antigen_fingerprint::Fingerprint,
parent: &antigen_fingerprint::Fingerprint,
) -> Option<String> {
match (parent.node_kind(), child.node_kind()) {
(Some(pk), Some(ck)) if pk != ck => {
return Some(format!(
"child `item = {ck:?}` differs from parent `item = {pk:?}` \
— disjoint item kinds cannot be a refinement"
));
}
(Some(pk), None) => {
return Some(format!(
"parent constrains `item = {pk:?}` but child has no item-kind \
constraint — child matches all item kinds and is broader, not a refinement"
));
}
_ => {}
}
let child_docs = collect_doc_contains_allof_only(&child.constraints);
let parent_docs = collect_doc_contains_allof_only(&parent.constraints);
for parent_needle in &parent_docs {
let covered = child_docs.iter().any(|cd| cd.contains(parent_needle));
if !covered {
return Some(format!(
"parent requires `doc_contains({parent_needle:?})` but no child \
`doc_contains` includes it — child can match where parent does not"
));
}
}
None
}
fn collect_doc_contains_allof_only(constraints: &[antigen_fingerprint::Constraint]) -> Vec<&str> {
use antigen_fingerprint::Constraint;
let mut out = Vec::new();
for c in constraints {
match c {
Constraint::DocContains(s) => out.push(s.as_str()),
Constraint::AllOf(children) => {
out.extend(collect_doc_contains_allof_only(children));
}
_ => {} }
}
out
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum UnreachedCause {
Barrier,
SubThreshold,
Cryptic,
}
impl UnreachedCause {
#[must_use]
pub const fn remedy(self) -> &'static str {
match self {
Self::Barrier => {
"coverage: extend the scan to include the unreached region \
(scan the member, build the cfg-gated path)"
}
Self::SubThreshold => {
"sensitivity: the site was scanned but not recognized — \
widen the fingerprint or mark it explicitly with #[presents]"
}
Self::Cryptic => {
"pre-processing: the site is in a form the scanner cannot see — \
macro-expand or normalize before scanning"
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct UnreachedSite {
pub region: String,
pub cause: UnreachedCause,
pub remedy: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct CoverageAuditReport {
pub unreached_sites: Vec<UnreachedSite>,
pub applicable: bool,
}
impl CoverageAuditReport {
#[must_use]
pub fn is_complete(&self) -> bool {
self.unreached_sites.is_empty()
}
#[must_use]
pub const fn coverage_was_applicable(&self) -> bool {
self.applicable
}
#[must_use]
pub fn count_by_cause(&self, cause: UnreachedCause) -> usize {
self.unreached_sites
.iter()
.filter(|s| s.cause == cause)
.count()
}
}
#[must_use]
pub fn audit_coverage(report: &ScanReport) -> CoverageAuditReport {
let mut unreached_sites = Vec::new();
let applicable = report.scan_coverage.is_some();
if let Some(coverage) = report.scan_coverage.as_ref() {
for member in coverage.unscanned_members() {
unreached_sites.push(UnreachedSite {
region: member.to_owned(),
cause: UnreachedCause::Barrier,
remedy: UnreachedCause::Barrier.remedy().to_owned(),
});
}
}
CoverageAuditReport {
unreached_sites,
applicable,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum StepState {
Attested,
Unattested,
Unevaluable,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum OutOfFrameCause {
UnknownWhoRef,
MissingWorkStep,
UnparseableFrame,
UnresolvableRef,
}
impl OutOfFrameCause {
#[must_use]
pub const fn remedy(self) -> &'static str {
match self {
Self::UnknownWhoRef => {
"scaffold + sign the .attest/<item>.json sidecar so the named \
who-ref's attestation is readable"
}
Self::MissingWorkStep => {
"declare the missing who-step (filled_by / ordered_by / triaged_by \
/ closure) — an empty work-need has nothing to attest"
}
Self::UnparseableFrame => {
"fix the malformed frame date (due / until / runs_until / \
re_triage_due must be ISO-8601 YYYY-MM-DD)"
}
Self::UnresolvableRef => {
"fix the dangling priority_order code-site reference (or, for a \
cross-crate target, await multi-crate Layer-2 resolution)"
}
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StepDetail {
pub role: String,
pub reference: String,
pub state: StepState,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PrescriptiveVerdict {
pub declaration: crate::scan::PrescriptiveDeclaration,
pub verdict: WorkVerdict,
pub steps: Vec<StepDetail>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub blocking: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub out_of_frame_cause: Option<OutOfFrameCause>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct PrescriptiveAuditReport {
pub verdicts: Vec<PrescriptiveVerdict>,
}
impl PrescriptiveAuditReport {
#[must_use]
pub fn is_clean(&self) -> bool {
!self.verdicts.iter().any(|v| v.verdict.is_loud())
}
#[must_use]
pub fn overdue_count(&self) -> usize {
self.verdicts
.iter()
.filter(|v| v.verdict == WorkVerdict::Overdue)
.count()
}
#[must_use]
pub fn count_by_verdict(&self, verdict: WorkVerdict) -> usize {
self.verdicts
.iter()
.filter(|v| v.verdict == verdict)
.count()
}
#[must_use]
pub fn board_ordered(&self) -> Vec<&PrescriptiveVerdict> {
let mut ordered: Vec<&PrescriptiveVerdict> = self.verdicts.iter().collect();
ordered.sort_by_key(|v| match v.verdict {
WorkVerdict::Overdue => 0u8,
WorkVerdict::OutOfFrame => 1,
WorkVerdict::Pending => 2,
WorkVerdict::Fulfilled => 3,
});
ordered
}
}
fn resolve_who_step(decl: &crate::scan::PrescriptiveDeclaration, who_ref: &str) -> StepState {
use antigen_attestation::evaluate::evaluate_predicate_with_kind;
use antigen_attestation::predicate::{Leaf, Predicate, SignerCurrency};
use antigen_attestation::AuditHint as AH;
let item_label = decl.item_target.label();
let sidecar = match load_sidecar(&decl.file, &item_label) {
SidecarLoad::Ok(r) => r,
SidecarLoad::Missing | SidecarLoad::SchemaInvalid => return StepState::Unevaluable,
};
let Some(item) = sidecar.items.iter().find(|i| i.item_path == item_label) else {
return StepState::Unevaluable;
};
let current_fingerprint: &str = if decl.structural_fingerprint.is_empty() {
&item.current_fingerprint
} else {
&decl.structural_fingerprint
};
let predicate = Predicate::leaf(Leaf::Signers {
required: vec![who_ref.to_string()],
roles: std::collections::BTreeMap::new(),
against: SignerCurrency::Current,
signature_allow: Vec::new(),
signature_prefer: None,
});
let ctx = FilesystemAuditContext;
let evaluated = evaluate_predicate_with_kind(
&predicate,
item,
current_fingerprint,
&decl.file,
sidecar.kind,
&ctx,
)
.unwrap_or_else(|_| antigen_attestation::EvaluatedPredicate::sidecar_schema_invalid());
match evaluated.audit_hint {
AH::DisciplineSidecarMissing | AH::DisciplineSidecarSchemaInvalid => StepState::Unevaluable,
_ if evaluated.witness_tier != antigen_attestation::WitnessTier::None => {
StepState::Attested
}
_ => StepState::Unattested,
}
}
fn priority_order_ref_resolves(report: &ScanReport, ref_text: &str) -> bool {
let needle = ref_text.trim();
if needle.is_empty() {
return false;
}
let needle_is_qualified = needle.contains("::");
let needle_leaf = needle.rsplit("::").next().unwrap_or(needle).trim();
let labels = report
.presentations
.iter()
.map(|p| p.item_target.label())
.chain(report.immunities.iter().map(|i| i.item_target.label()))
.chain(
report
.prescriptive_declarations
.iter()
.map(|d| d.item_target.label()),
);
for label in labels {
let label = label.trim().to_owned();
if needle_is_qualified {
if label == needle || label.ends_with(&format!("::{needle}")) {
return true;
}
} else {
let label_leaf = label.rsplit("::").next().unwrap_or(&label).trim();
if label == needle || label_leaf == needle_leaf {
return true;
}
}
}
false
}
#[must_use]
pub fn audit_prescriptive(report: &ScanReport, _workspace_root: &Path) -> PrescriptiveAuditReport {
use crate::scan::WorkShape;
let today = chrono::Local::now().date_naive();
let mut verdicts = Vec::with_capacity(report.prescriptive_declarations.len());
for decl in &report.prescriptive_declarations {
let frame = FrameState::classify(decl.frame.as_deref(), today);
let mut steps: Vec<StepDetail> = Vec::new();
let (satisfied, evaluable, blocking, shape_cause) = match decl.kind.shape() {
WorkShape::RoleWorkflow => eval_role_workflow(decl, &mut steps),
WorkShape::Elimination => eval_elimination(decl, &mut steps),
WorkShape::Ordering => eval_ordering(report, decl, frame, &mut steps),
WorkShape::FrameOnly => eval_frame_only(decl, frame, &mut steps),
};
let frame_unparseable = matches!(frame, FrameState::Unparseable);
let evaluable = evaluable && !frame_unparseable;
let verdict = WorkVerdict::project(satisfied, evaluable, frame);
let blocking = if verdict == WorkVerdict::Fulfilled {
None
} else {
Some(blocking)
};
let out_of_frame_cause = if verdict == WorkVerdict::OutOfFrame {
if frame_unparseable {
Some(OutOfFrameCause::UnparseableFrame)
} else {
shape_cause
}
} else {
None
};
verdicts.push(PrescriptiveVerdict {
declaration: decl.clone(),
verdict,
steps,
blocking,
out_of_frame_cause,
});
}
PrescriptiveAuditReport { verdicts }
}
fn eval_role_workflow(
decl: &crate::scan::PrescriptiveDeclaration,
steps: &mut Vec<StepDetail>,
) -> (bool, bool, String, Option<OutOfFrameCause>) {
let mut chain: Vec<(&str, &str)> = Vec::new();
if let Some(orderer) = decl.ordered_by.as_deref() {
chain.push(("ordered_by", orderer));
}
for f in &decl.filled_by {
chain.push(("filled_by", f));
}
for (role, who) in &chain {
let state = resolve_who_step(decl, who);
steps.push(StepDetail {
role: (*role).to_string(),
reference: (*who).to_string(),
state,
});
}
let no_fillers = steps.is_empty();
let any_unevaluable = steps.iter().any(|s| s.state == StepState::Unevaluable);
let has_closing_step = steps.iter().any(|s| s.role != "ordered_by");
let all_fillers_attested =
has_closing_step && steps.iter().all(|s| s.state == StepState::Attested);
let mut reviewers_attested = true;
let mut any_reviewer = false;
let mut reviewer_unevaluable = false;
for r in &decl.reviewed_by {
any_reviewer = true;
let state = resolve_who_step(decl, r);
if state == StepState::Unevaluable {
reviewer_unevaluable = true;
}
if !all_fillers_attested || state != StepState::Attested {
reviewers_attested = false;
}
steps.push(StepDetail {
role: "reviewed_by".to_string(),
reference: r.clone(),
state,
});
}
let evaluable = !any_unevaluable && !reviewer_unevaluable && !no_fillers;
let satisfied = all_fillers_attested && (!any_reviewer || reviewers_attested);
let cause = if no_fillers {
Some(OutOfFrameCause::MissingWorkStep)
} else if any_unevaluable || reviewer_unevaluable {
Some(OutOfFrameCause::UnknownWhoRef)
} else {
None
};
let blocking = if no_fillers {
"no who-step declared — nothing to attest (declare filled_by/ordered_by)".to_string()
} else if any_unevaluable || reviewer_unevaluable {
"a who-step is un-evaluable (no sidecar / unknown who-ref) — out of frame".to_string()
} else if !has_closing_step {
"awaiting fill: ordered but no filled_by step — an opener never alone fulfills".to_string()
} else if !all_fillers_attested {
"awaiting fill: not every filled_by step is attested".to_string()
} else {
"awaiting review: reviewed_by not yet attested (all fillers done)".to_string()
};
(satisfied, evaluable, blocking, cause)
}
fn eval_elimination(
decl: &crate::scan::PrescriptiveDeclaration,
steps: &mut Vec<StepDetail>,
) -> (bool, bool, String, Option<OutOfFrameCause>) {
for alt in &decl.items {
steps.push(StepDetail {
role: "rule_out".to_string(),
reference: alt.clone(),
state: StepState::Unattested,
});
}
let mut closure_refs: Vec<(&str, &str)> = Vec::new();
for f in &decl.filled_by {
closure_refs.push(("investigator", f));
}
for r in &decl.reviewed_by {
closure_refs.push(("reviewer", r));
}
let mut any_unevaluable = false;
let mut all_attested = !closure_refs.is_empty();
for (role, who) in &closure_refs {
let state = resolve_who_step(decl, who);
if state == StepState::Unevaluable {
any_unevaluable = true;
}
if state != StepState::Attested {
all_attested = false;
}
steps.push(StepDetail {
role: (*role).to_string(),
reference: (*who).to_string(),
state,
});
}
let evaluable = !any_unevaluable && !closure_refs.is_empty();
let satisfied = all_attested;
let cause = if closure_refs.is_empty() {
Some(OutOfFrameCause::MissingWorkStep)
} else if any_unevaluable {
Some(OutOfFrameCause::UnknownWhoRef)
} else {
None
};
let blocking = if closure_refs.is_empty() {
"no investigator/reviewer declared — the differential cannot be closed".to_string()
} else if any_unevaluable {
"a closure who-step is un-evaluable (no sidecar / unknown who-ref)".to_string()
} else {
"awaiting elimination: investigator/reviewer not yet attested".to_string()
};
(satisfied, evaluable, blocking, cause)
}
fn eval_ordering(
report: &ScanReport,
decl: &crate::scan::PrescriptiveDeclaration,
frame: FrameState,
steps: &mut Vec<StepDetail>,
) -> (bool, bool, String, Option<OutOfFrameCause>) {
let mut all_refs_resolve = !decl.items.is_empty();
let mut unresolved: Vec<&str> = Vec::new();
for ref_text in &decl.items {
let resolves = priority_order_ref_resolves(report, ref_text);
if !resolves {
all_refs_resolve = false;
unresolved.push(ref_text);
}
steps.push(StepDetail {
role: "priority_order".to_string(),
reference: ref_text.clone(),
state: if resolves {
StepState::Attested
} else {
StepState::Unevaluable
},
});
}
let mut triaged_attested = !decl.filled_by.is_empty();
let mut triager_unevaluable = false;
for who in &decl.filled_by {
let state = resolve_who_step(decl, who);
if state == StepState::Unevaluable {
triager_unevaluable = true;
}
if state != StepState::Attested {
triaged_attested = false;
}
steps.push(StepDetail {
role: "triaged_by".to_string(),
reference: who.clone(),
state,
});
}
let evaluable = all_refs_resolve && !triager_unevaluable && !decl.items.is_empty();
let satisfied = triaged_attested && !matches!(frame, FrameState::Past);
let cause = if decl.items.is_empty() {
Some(OutOfFrameCause::MissingWorkStep)
} else if !all_refs_resolve {
Some(OutOfFrameCause::UnresolvableRef)
} else if triager_unevaluable {
Some(OutOfFrameCause::UnknownWhoRef)
} else {
None
};
let blocking = if decl.items.is_empty() {
"no priority_order declared — nothing to order".to_string()
} else if !all_refs_resolve {
format!("priority_order ref(s) do not resolve to a scanned code site: {unresolved:?} — out of frame (ADR-017-Amd1)")
} else if triager_unevaluable {
"triaged_by is un-evaluable (no sidecar / unknown who-ref)".to_string()
} else if !triaged_attested {
"awaiting triage: triaged_by not yet attested".to_string()
} else {
"re-triage owed: triaged_by attested but re_triage_due elapsed (the ordering is stale)"
.to_string()
};
(satisfied, evaluable, blocking, cause)
}
fn eval_frame_only(
decl: &crate::scan::PrescriptiveDeclaration,
_frame: FrameState,
steps: &mut Vec<StepDetail>,
) -> (bool, bool, String, Option<OutOfFrameCause>) {
let mut closure_attested = !decl.filled_by.is_empty();
let mut any_unevaluable = false;
for who in &decl.filled_by {
let state = resolve_who_step(decl, who);
if state == StepState::Unevaluable {
any_unevaluable = true;
}
if state != StepState::Attested {
closure_attested = false;
}
steps.push(StepDetail {
role: "closure".to_string(),
reference: who.clone(),
state,
});
}
let evaluable = !any_unevaluable;
let satisfied = closure_attested;
let cause = if any_unevaluable {
Some(OutOfFrameCause::UnknownWhoRef)
} else {
None
};
let blocking = if decl.filled_by.is_empty() {
"no closure attestation declared — frame-expiry alone never fulfills (positive-closure guard, ATK-PRES-13)".to_string()
} else if any_unevaluable {
"closure who-step is un-evaluable (no sidecar / unknown who-ref)".to_string()
} else {
"awaiting closure: the named closure attestation is not yet recorded".to_string()
};
(satisfied, evaluable, blocking, cause)
}
#[cfg(test)]
mod tests {
use super::*;
use antigen_macros::defended_by;
#[test]
fn detect_clippy_external_tool() {
assert_eq!(
detect_external_tool("clippy::no_panic_in_drop"),
Some("clippy")
);
}
#[test]
fn detect_kani_external_tool() {
assert_eq!(
detect_external_tool("kani::proof_drop_safety"),
Some("kani")
);
}
#[test]
fn detect_no_tool_for_local_function() {
assert_eq!(detect_external_tool("safe_type_drop_no_panic_test"), None);
}
#[test]
fn validate_witness_strips_path_prefix() {
let mut idx = FunctionIndex::new();
idx.insert(
"my_test".to_string(),
vec![FunctionEntry {
location: PathBuf::from("src/lib.rs"),
kind: WitnessKind::Test,
}],
);
let status = validate_witness("module::path::my_test", &idx);
assert!(matches!(status, WitnessStatus::Resolved { .. }));
}
#[test]
fn validate_witness_reports_missing_when_empty() {
let idx = FunctionIndex::new();
let status = validate_witness("", &idx);
assert_eq!(status, WitnessStatus::Missing);
}
#[test]
fn validate_witness_reports_not_found_for_unknown() {
let idx = FunctionIndex::new();
let status = validate_witness("nonexistent_test", &idx);
assert!(matches!(status, WitnessStatus::NotFound { .. }));
}
fn index_from_str(source: &str) -> FunctionIndex {
use syn::visit::Visit;
let file = syn::parse_file(source).expect("source must parse");
let mut index = FunctionIndex::new();
let mut visitor = FunctionIndexVisitor {
file_path: PathBuf::from("<test>.rs"),
source,
index: &mut index,
};
visitor.visit_file(&file);
index
}
fn unique_kind(idx: &FunctionIndex, name: &str) -> WitnessKind {
let entries = idx.get(name).unwrap_or_else(|| panic!("{name} indexed"));
assert_eq!(
entries.len(),
1,
"expected single index entry for {name}, got {entries:?}",
);
entries[0].kind.clone()
}
#[test]
fn w5_proptest_inner_fns_are_classified_proptest() {
let src = r"
proptest! {
#[test]
fn first_proptest(x in 0u32..100) {
assert!(x < 100);
}
#[test]
fn second_proptest(x in 0u32..100, y in 0u32..100) {
assert!(x + y < 200);
}
}
";
let idx = index_from_str(src);
assert_eq!(unique_kind(&idx, "first_proptest"), WitnessKind::Proptest);
assert_eq!(unique_kind(&idx, "second_proptest"), WitnessKind::Proptest);
}
#[test]
fn w5_proptest_path_qualified_macro_is_recognized() {
let src = r"
proptest::proptest! {
#[test]
fn qualified_form_proptest(x in 0u32..100) {
assert!(x < 100);
}
}
";
let idx = index_from_str(src);
assert_eq!(
unique_kind(&idx, "qualified_form_proptest"),
WitnessKind::Proptest,
);
}
#[test]
fn w5_test_function_outside_proptest_is_classified_test() {
let src = r"
// Doc-style comment mentioning proptest! for explanation purposes.
// Pre-W5 this string in the source was sufficient to flag every
// function in the file as Proptest. W5 must not regress to that.
#[test]
fn plain_test() {
assert_eq!(2 + 2, 4);
}
proptest! {
#[test]
fn proptest_one(x in 0u32..10) {
assert!(x < 10);
}
}
";
let idx = index_from_str(src);
assert_eq!(
unique_kind(&idx, "plain_test"),
WitnessKind::Test,
"plain_test outside proptest! must be Test, not Proptest, even when \
the same file contains a proptest! invocation",
);
assert_eq!(unique_kind(&idx, "proptest_one"), WitnessKind::Proptest);
}
#[test]
fn w5_doc_comment_mentioning_proptest_does_not_over_classify() {
let src = r"
/// This function has nothing to do with proptest! — the macro
/// is named here only for documentation.
#[test]
fn doc_comment_only_test() {
assert!(true);
}
";
let idx = index_from_str(src);
assert_eq!(
unique_kind(&idx, "doc_comment_only_test"),
WitnessKind::Test,
"doc-comment mention must not trigger Proptest",
);
}
#[test]
fn w5_plain_function_is_classified_function() {
let src = r"
fn no_attribute_function() {}
";
let idx = index_from_str(src);
assert_eq!(
unique_kind(&idx, "no_attribute_function"),
WitnessKind::Function,
);
}
#[test]
fn w5_extract_proptest_fn_names_skips_nested() {
use proc_macro2::TokenStream;
let tokens: TokenStream = r"
#[test]
fn outer(x in 0u32..10) {
fn nested_helper() {}
assert!(x < 10);
}
"
.parse()
.unwrap();
let names = extract_proptest_fn_names(&tokens);
assert_eq!(names, vec!["outer".to_string()]);
}
#[test]
fn w5_macro_path_last_is_handles_qualified_paths() {
let bare: syn::Path = syn::parse_str("proptest").unwrap();
let qualified: syn::Path = syn::parse_str("proptest::proptest").unwrap();
let unrelated: syn::Path = syn::parse_str("other_crate::other_macro").unwrap();
assert!(macro_path_last_is(&bare, "proptest"));
assert!(macro_path_last_is(&qualified, "proptest"));
assert!(!macro_path_last_is(&unrelated, "proptest"));
}
#[test]
fn detect_phantom_nested_generic_returns_none() {
assert_eq!(
detect_phantom_type_witness("Witnessed::<Option<MyType>, MyWitness>::try_new"),
None,
);
assert!(matches!(
detect_phantom_type_witness("PolarityProof::<FrameTranslation>::verified"),
Some(WitnessKind::PhantomType { .. }),
));
}
fn recurrent_decl(
kind: crate::scan::RecurrentKind,
antigen_type: Option<&str>,
) -> crate::scan::RecurrentDeclaration {
crate::scan::RecurrentDeclaration {
kind,
name: None,
antigen_type: antigen_type.map(str::to_string),
description: None,
instances: None,
since: None,
rationale: None,
from_itches: Vec::new(),
anchored_by: Vec::new(),
managed_by: None,
contributing_to: None,
file: std::path::PathBuf::from("test.rs"),
line: 1,
item_kind: "fn".to_string(),
item_target: crate::scan::ItemTarget::Fn("t".to_string()),
}
}
#[test]
fn audit_recurrent_itch_without_antigen_flags_not_anchored() {
let mut report = ScanReport::default();
report
.recurrent_declarations
.push(recurrent_decl(crate::scan::RecurrentKind::Itch, None));
let out = audit_recurrent(&report);
assert_eq!(out.concern_count, 1);
assert!(out.audits[0]
.hints
.contains(&AuditHint::ItchNoticedNotAnchored));
}
#[test]
fn audit_recurrent_itch_with_antigen_is_clean() {
let mut report = ScanReport::default();
report.recurrent_declarations.push(recurrent_decl(
crate::scan::RecurrentKind::Itch,
Some("SomeAntigen"),
));
let out = audit_recurrent(&report);
assert!(out.all_clean());
}
#[test]
fn audit_recurrent_anchor_without_downstream_action_flags() {
let mut report = ScanReport::default();
report.recurrent_declarations.push(recurrent_decl(
crate::scan::RecurrentKind::RecurrenceAnchor,
Some("MsrvCreep"),
));
let out = audit_recurrent(&report);
assert!(out.audits[0]
.hints
.contains(&AuditHint::RecurrenceThresholdReachedNoAction));
}
#[test]
fn audit_recurrent_crystallize_empty_flags_without_antigen() {
let mut report = ScanReport::default();
report.recurrent_declarations.push(recurrent_decl(
crate::scan::RecurrentKind::Crystallize,
None,
));
let out = audit_recurrent(&report);
assert!(out.audits[0]
.hints
.contains(&AuditHint::CrystallizeWithoutAntigen));
}
#[test]
fn audit_recurrent_chronic_without_managed_by_flags_unmanaged() {
let mut report = ScanReport::default();
report.recurrent_declarations.push(recurrent_decl(
crate::scan::RecurrentKind::Chronic,
Some("FlakeyStep"),
));
let out = audit_recurrent(&report);
assert!(out.audits[0]
.hints
.contains(&AuditHint::ChronicSignalUnmanaged));
}
#[test]
fn audit_recurrent_chronic_old_iso_since_flags_past_review_date() {
let mut report = ScanReport::default();
let mut decl = recurrent_decl(crate::scan::RecurrentKind::Chronic, Some("X"));
decl.managed_by = Some("team".to_string());
decl.since = Some("2020-01-01".to_string()); report.recurrent_declarations.push(decl);
let out = audit_recurrent(&report);
assert!(out.audits[0]
.hints
.contains(&AuditHint::ChronicSignalPastReviewDate));
}
#[test]
fn audit_recurrent_chronic_version_since_skips_date_check() {
let mut report = ScanReport::default();
let mut decl = recurrent_decl(crate::scan::RecurrentKind::Chronic, Some("X"));
decl.managed_by = Some("team".to_string());
decl.since = Some("v0.2.0".to_string());
report.recurrent_declarations.push(decl);
let out = audit_recurrent(&report);
assert!(!out.audits[0]
.hints
.contains(&AuditHint::ChronicSignalPastReviewDate));
assert!(!out.audits[0]
.hints
.contains(&AuditHint::ChronicSinceNotADate));
}
#[test]
fn audit_recurrent_chronic_garbage_since_emits_not_a_date() {
let mut report = ScanReport::default();
let mut decl = recurrent_decl(crate::scan::RecurrentKind::Chronic, Some("X"));
decl.managed_by = Some("team".to_string());
decl.since = Some("not-a-date".to_string());
report.recurrent_declarations.push(decl);
let out = audit_recurrent(&report);
assert!(out.audits[0]
.hints
.contains(&AuditHint::ChronicSinceNotADate));
}
#[test]
fn is_version_tag_recognizes_versions_rejects_garbage() {
assert!(is_version_tag("v0.2.0"));
assert!(is_version_tag("V1.4.3"));
assert!(is_version_tag("1.4"));
assert!(is_version_tag("2.0.0-rc.1"));
assert!(is_version_tag("1.0.0+build42"));
assert!(!is_version_tag("not-a-date"));
assert!(!is_version_tag("yesterday"));
assert!(!is_version_tag("v"));
assert!(!is_version_tag(""));
assert!(!is_version_tag("release-2"));
assert!(!is_version_tag("3"));
}
#[test]
fn audit_recurrent_chronic_iso_date_not_flagged_not_a_date() {
let mut report = ScanReport::default();
let mut decl = recurrent_decl(crate::scan::RecurrentKind::Chronic, Some("X"));
decl.managed_by = Some("team".to_string());
let recent = (chrono::Utc::now().date_naive() - chrono::Duration::days(10))
.format("%Y-%m-%d")
.to_string();
decl.since = Some(recent);
report.recurrent_declarations.push(decl);
let out = audit_recurrent(&report);
assert!(out.audits[0].hints.is_empty());
}
#[test]
fn audit_recurrent_saturate_without_contributing_to_flags() {
let mut report = ScanReport::default();
report
.recurrent_declarations
.push(recurrent_decl(crate::scan::RecurrentKind::Saturate, None));
let out = audit_recurrent(&report);
assert!(out.audits[0].hints.contains(&AuditHint::SaturateNoAnchor));
}
#[test]
fn audit_recurrent_strand_without_anchors_flags() {
let mut report = ScanReport::default();
report
.recurrent_declarations
.push(recurrent_decl(crate::scan::RecurrentKind::Strand, None));
let out = audit_recurrent(&report);
assert!(out.audits[0].hints.contains(&AuditHint::StrandNoAnchors));
}
#[test]
fn audit_recurrent_hint_serializes_kebab_case() {
let s = serde_json::to_string(&AuditHint::ItchNoticedNotAnchored).unwrap();
assert_eq!(s, "\"itch-noticed-not-anchored\"");
let s2 = serde_json::to_string(&AuditHint::ChronicSignalPastReviewDate).unwrap();
assert_eq!(s2, "\"chronic-signal-past-review-date\"");
}
fn mucosal_decl(
tag: crate::scan::MucosalKindTag,
boundary_kind: Option<&str>,
rationale: Option<&str>,
target_fn: &str,
) -> crate::scan::MucosalDeclaration {
crate::scan::MucosalDeclaration {
tag,
boundary_kind: boundary_kind.map(str::to_string),
rationale: rationale.map(str::to_string),
handled_by: None,
accepts: None,
reviewed_by: None,
until: None,
file: std::path::PathBuf::from("test.rs"),
line: 1,
item_kind: "fn".to_string(),
item_target: crate::scan::ItemTarget::Fn(target_fn.to_string()),
}
}
#[test]
fn audit_mucosal_clean_when_kind_and_rationale_present() {
use crate::scan::MucosalKindTag;
let mut report = ScanReport::default();
report.mucosal_declarations.push(mucosal_decl(
MucosalKindTag::Mucosal,
Some("UserInput"),
Some("public form input; sanitized at template-render layer"),
"handle_form",
));
let out = audit_mucosal(&report);
assert!(out.all_clean());
}
#[test]
fn audit_mucosal_short_rationale_flags_insufficient() {
use crate::scan::MucosalKindTag;
let mut report = ScanReport::default();
report.mucosal_declarations.push(mucosal_decl(
MucosalKindTag::Mucosal,
Some("UserInput"),
Some("short"),
"f",
));
let out = audit_mucosal(&report);
assert!(out.audits[0]
.hints
.contains(&AuditHint::MucosalRationaleInsufficient));
}
#[test]
fn audit_mucosal_delegate_missing_handler_flags_tier1() {
use crate::scan::MucosalKindTag;
let mut report = ScanReport::default();
let mut d = mucosal_decl(
MucosalKindTag::MucosalDelegate,
Some("UserInput"),
Some("delegated to sanitizer module for central handling"),
"outer",
);
d.handled_by = Some("nonexistent_handler".to_string());
report.mucosal_declarations.push(d);
let out = audit_mucosal(&report);
assert!(out.audits[0]
.hints
.contains(&AuditHint::MucosalDisciplineDelegateTargetMissing));
}
#[test]
fn audit_mucosal_delegate_kind_mismatch_flags_tier3() {
use crate::scan::MucosalKindTag;
let mut report = ScanReport::default();
report.mucosal_declarations.push(mucosal_decl(
MucosalKindTag::Mucosal,
Some("DatabaseQuery"),
Some("parameterized queries enforced at this data-access layer"),
"sanitize_db",
));
let mut delegate = mucosal_decl(
MucosalKindTag::MucosalDelegate,
Some("UserInput"),
Some("delegated to the shared sanitizer used across endpoints"),
"outer",
);
delegate.handled_by = Some("sanitize_db".to_string());
report.mucosal_declarations.push(delegate);
let out = audit_mucosal(&report);
let delegate_audit = out
.audits
.iter()
.find(|a| a.declaration.tag == MucosalKindTag::MucosalDelegate)
.unwrap();
assert!(delegate_audit
.hints
.contains(&AuditHint::MucosalDisciplineDelegateTargetKindMismatch));
}
#[test]
fn audit_mucosal_delegate_matching_kind_is_clean() {
use crate::scan::MucosalKindTag;
let mut report = ScanReport::default();
report.mucosal_declarations.push(mucosal_decl(
MucosalKindTag::Mucosal,
Some("UserInput"),
Some("central user-input sanitizer; escapes + length-bounds"),
"sanitize_input",
));
let mut delegate = mucosal_decl(
MucosalKindTag::MucosalDelegate,
Some("UserInput"),
Some("delegated to the central user-input sanitizer routine"),
"outer",
);
delegate.handled_by = Some("sanitize_input".to_string());
report.mucosal_declarations.push(delegate);
let out = audit_mucosal(&report);
let delegate_audit = out
.audits
.iter()
.find(|a| a.declaration.tag == MucosalKindTag::MucosalDelegate)
.unwrap();
assert!(
delegate_audit.hints.is_empty(),
"matching-kind delegate must be clean; got {:?}",
delegate_audit.hints
);
}
#[test]
fn audit_mucosal_delegate_hybrid_handler_set_membership() {
use crate::scan::MucosalKindTag;
let mut report = ScanReport::default();
report.mucosal_declarations.push(mucosal_decl(
MucosalKindTag::Mucosal,
Some("UserInput"),
Some("hybrid handler: user-input branch sanitized here"),
"hybrid_handler",
));
report.mucosal_declarations.push(mucosal_decl(
MucosalKindTag::Mucosal,
Some("ShellArgument"),
Some("hybrid handler: shell-arg branch escaped here"),
"hybrid_handler",
));
let mut delegate = mucosal_decl(
MucosalKindTag::MucosalDelegate,
Some("ShellArgument"),
Some("delegated to the hybrid handler covering both kinds"),
"outer",
);
delegate.handled_by = Some("hybrid_handler".to_string());
report.mucosal_declarations.push(delegate);
let out = audit_mucosal(&report);
let delegate_audit = out
.audits
.iter()
.find(|a| a.declaration.tag == MucosalKindTag::MucosalDelegate)
.unwrap();
assert!(
delegate_audit.hints.is_empty(),
"hybrid-handler set-membership must match ShellArgument; got {:?}",
delegate_audit.hints
);
}
#[test]
fn audit_mucosal_tolerant_floors_and_fields() {
use crate::scan::MucosalKindTag;
let mut report = ScanReport::default();
let mut d = mucosal_decl(
MucosalKindTag::MucosalTolerant,
Some("UserInput"),
Some("twenty-five char rationale!!"), "intake",
);
d.accepts = None; d.reviewed_by = None; report.mucosal_declarations.push(d);
let out = audit_mucosal(&report);
let h = &out.audits[0].hints;
assert!(h.contains(&AuditHint::MucosalTolerantRationaleInsufficient));
assert!(h.contains(&AuditHint::MucosalTolerantAcceptsEmpty));
assert!(h.contains(&AuditHint::MucosalTolerantWithoutReviewer));
}
#[test]
fn audit_mucosal_tolerant_complete_is_clean() {
use crate::scan::MucosalKindTag;
let mut report = ScanReport::default();
let mut d = mucosal_decl(
MucosalKindTag::MucosalTolerant,
Some("ApiRequest"),
Some("internal admin endpoint behind VPN; trusted-network assumption documented"),
"admin_intake",
);
d.accepts = Some("admin-panel form posts".to_string());
d.reviewed_by = Some("security-team".to_string());
report.mucosal_declarations.push(d);
let out = audit_mucosal(&report);
assert!(out.all_clean());
}
#[test]
fn audit_mucosal_hint_serializes_kebab_case() {
let s =
serde_json::to_string(&AuditHint::MucosalDisciplineDelegateTargetKindMismatch).unwrap();
assert_eq!(s, "\"mucosal-discipline-delegate-target-kind-mismatch\"");
}
#[test]
fn atk_mucosal_1_same_name_collision_masks_kind_mismatch() {
use crate::scan::MucosalKindTag;
let mut report = ScanReport::default();
let mut mucosal_a = mucosal_decl(
MucosalKindTag::Mucosal,
Some("UserInput"),
Some("public form input sanitized at template-render layer"),
"process",
);
mucosal_a.file = std::path::PathBuf::from("src/a.rs");
report.mucosal_declarations.push(mucosal_a);
let mut mucosal_b = mucosal_decl(
MucosalKindTag::Mucosal,
Some("DatabaseQuery"),
Some("parameterized query builder; never interpolates raw user input"),
"process",
);
mucosal_b.file = std::path::PathBuf::from("src/b.rs");
report.mucosal_declarations.push(mucosal_b);
let mut delegate = mucosal_decl(
MucosalKindTag::MucosalDelegate,
Some("UserInput"),
Some("delegated to the central process handler for sanitisation"),
"outer",
);
delegate.file = std::path::PathBuf::from("src/c.rs");
delegate.handled_by = Some("process".to_string());
report.mucosal_declarations.push(delegate);
let out = audit_mucosal(&report);
let delegate_audit = out
.audits
.iter()
.find(|a| a.declaration.tag == MucosalKindTag::MucosalDelegate)
.unwrap();
assert!(
delegate_audit
.hints
.contains(&AuditHint::MucosalDisciplineDelegateTargetAmbiguous),
"ATK-MUCOSAL-1: delegate targeting 'process' when both src/a.rs::process \
and src/b.rs::process exist must emit MucosalDisciplineDelegateTargetAmbiguous. \
The delegate is underspecified — the bare name is not enough to identify the \
target uniquely. hints: {:?}",
delegate_audit.hints
);
}
fn antigen_decl(
type_name: &str,
category: Vec<crate::category::AntigenCategory>,
) -> crate::scan::AntigenDeclaration {
crate::scan::AntigenDeclaration {
name: type_name.to_lowercase(),
type_name: type_name.to_string(),
file: std::path::PathBuf::from("test.rs"),
line: 1,
family: None,
summary: None,
fingerprint: None,
canonical_path: None,
category,
}
}
#[test]
fn audit_category_flags_absent_category() {
let mut report = ScanReport::default();
report
.antigens
.push(antigen_decl("LegacyAntigen", Vec::new()));
let out = audit_category(&report);
assert_eq!(out.defaulted_count, 1);
assert_eq!(out.explicit_count, 0);
assert!(!out.all_explicit());
assert!(out.audits[0]
.hints
.contains(&AuditHint::AntigenCategoryDefaultedImplicitFunctional));
}
#[test]
fn audit_category_clean_when_explicit() {
use crate::category::AntigenCategory;
let mut report = ScanReport::default();
report.antigens.push(antigen_decl(
"VerbCorrectness",
vec![AntigenCategory::FunctionalCorrectness],
));
let out = audit_category(&report);
assert_eq!(out.explicit_count, 1);
assert_eq!(out.defaulted_count, 0);
assert!(out.all_explicit());
assert!(out.audits.is_empty());
}
#[test]
fn audit_category_mixed_counts() {
use crate::category::AntigenCategory;
let mut report = ScanReport::default();
report.antigens.push(antigen_decl("A", Vec::new()));
report.antigens.push(antigen_decl(
"B",
vec![AntigenCategory::FunctionalCorrectness],
));
report.antigens.push(antigen_decl("C", Vec::new()));
let out = audit_category(&report);
assert_eq!(out.defaulted_count, 2);
assert_eq!(out.explicit_count, 1);
assert_eq!(out.audits.len(), 2);
}
#[test]
fn audit_category_hint_serializes_kebab_case() {
let s =
serde_json::to_string(&AuditHint::AntigenCategoryDefaultedImplicitFunctional).unwrap();
assert_eq!(s, "\"antigen-category-defaulted-implicit-functional\"");
}
fn immunity_for(antigen_type: &str, substrate: bool) -> crate::scan::Immunity {
crate::scan::Immunity {
antigen_type: antigen_type.to_string(),
witness: if substrate {
String::new()
} else {
"some_test".to_string()
},
requires_predicate: if substrate {
Some("{\"leaf\":\"doc\"}".to_string())
} else {
None
},
file: std::path::PathBuf::from("test.rs"),
line: 1,
item_kind: "fn".to_string(),
item_target: crate::scan::ItemTarget::Fn("witness_site".to_string()),
canonical_path: None,
structural_fingerprint: String::new(),
}
}
#[test]
fn g2_substrate_alignment_with_only_code_witness_is_mismatch() {
use crate::category::AntigenCategory;
let mut report = ScanReport::default();
report.antigens.push(antigen_decl(
"DriftAntigen",
vec![AntigenCategory::SubstrateAlignment],
));
report.immunities.push(immunity_for("DriftAntigen", false));
let out = audit_category(&report);
assert_eq!(out.mismatch_count, 1);
assert!(!out.no_category_witness_mismatch());
assert!(out.audits[0]
.hints
.contains(&AuditHint::AntigenCategoryClaimInconsistentWithPredicateType));
}
#[test]
fn g2_functional_correctness_with_only_substrate_witness_is_mismatch() {
use crate::category::AntigenCategory;
let mut report = ScanReport::default();
report.antigens.push(antigen_decl(
"BugAntigen",
vec![AntigenCategory::FunctionalCorrectness],
));
report.immunities.push(immunity_for("BugAntigen", true));
let out = audit_category(&report);
assert_eq!(out.mismatch_count, 1);
assert!(out.audits[0]
.hints
.contains(&AuditHint::AntigenCategoryClaimInconsistentWithPredicateType));
}
#[test]
fn g2_matching_witness_type_is_clean() {
use crate::category::AntigenCategory;
let mut report = ScanReport::default();
report.antigens.push(antigen_decl(
"DriftAntigen",
vec![AntigenCategory::SubstrateAlignment],
));
report.immunities.push(immunity_for("DriftAntigen", true));
let out = audit_category(&report);
assert_eq!(out.mismatch_count, 0);
assert!(out.no_category_witness_mismatch());
assert!(out.audits.is_empty());
}
#[test]
fn g2_hybrid_needs_both_witness_types() {
use crate::category::AntigenCategory;
let mut report = ScanReport::default();
report.antigens.push(antigen_decl(
"HybridAntigen",
vec![
AntigenCategory::SubstrateAlignment,
AntigenCategory::FunctionalCorrectness,
],
));
report.immunities.push(immunity_for("HybridAntigen", true));
let out = audit_category(&report);
assert_eq!(
out.mismatch_count, 1,
"hybrid with only one axis is a mismatch"
);
assert!(
out.audits[0]
.hints
.contains(&AuditHint::AntigenCategoryHybridIncompleteEvidence),
"hybrid with one axis witnessed → hybrid-incomplete-evidence"
);
report.immunities.push(immunity_for("HybridAntigen", false));
let out = audit_category(&report);
assert_eq!(out.mismatch_count, 0, "hybrid with both axes is clean");
}
#[test]
fn g3_hybrid_with_zero_axes_is_claim_inconsistent_not_incomplete() {
use crate::category::AntigenCategory;
let mut report = ScanReport::default();
report.antigens.push(antigen_decl(
"HybridAntigen",
vec![
AntigenCategory::SubstrateAlignment,
AntigenCategory::FunctionalCorrectness,
],
));
report.immunities.push(crate::scan::Immunity {
antigen_type: "HybridAntigen".to_string(),
witness: String::new(),
requires_predicate: None,
file: std::path::PathBuf::from("test.rs"),
line: 1,
item_kind: "fn".to_string(),
item_target: crate::scan::ItemTarget::Fn("witness_site".to_string()),
canonical_path: None,
structural_fingerprint: String::new(),
});
let out = audit_category(&report);
assert_eq!(out.mismatch_count, 1);
assert!(
out.audits[0]
.hints
.contains(&AuditHint::AntigenCategoryClaimInconsistentWithPredicateType),
"hybrid with ZERO axes witnessed → claim-inconsistent (full violation)"
);
}
#[test]
fn g3_hybrid_incomplete_evidence_hint_serializes_kebab_case() {
let s = serde_json::to_string(&AuditHint::AntigenCategoryHybridIncompleteEvidence).unwrap();
assert_eq!(s, "\"antigen-category-hybrid-incomplete-evidence\"");
}
#[test]
fn g2_no_immunity_is_not_a_mismatch() {
use crate::category::AntigenCategory;
let mut report = ScanReport::default();
report.antigens.push(antigen_decl(
"UncoveredAntigen",
vec![AntigenCategory::FunctionalCorrectness],
));
let out = audit_category(&report);
assert_eq!(out.mismatch_count, 0);
assert!(out.no_category_witness_mismatch());
assert!(out.no_silence_witness_mismatch());
assert!(out.audits.is_empty());
}
#[test]
fn silence_no_witness_fires_for_substrate_alignment_with_no_witness() {
use crate::category::AntigenCategory;
let mut report = ScanReport::default();
report.antigens.push(antigen_decl(
"DriftAntigen",
vec![AntigenCategory::SubstrateAlignment],
));
let out = audit_category(&report);
assert_eq!(out.mismatch_count, 0);
assert!(out.no_category_witness_mismatch());
assert!(!out.no_silence_witness_mismatch());
assert!(out.audits[0]
.hints
.contains(&AuditHint::AntigenWitnessShapeMismatchForSilenceNoWitness));
}
#[test]
fn silence_no_witness_does_not_fire_for_functional_correctness_no_witness() {
use crate::category::AntigenCategory;
let mut report = ScanReport::default();
report.antigens.push(antigen_decl(
"BugAntigen",
vec![AntigenCategory::FunctionalCorrectness],
));
let out = audit_category(&report);
assert!(out.no_silence_witness_mismatch());
assert!(out.audits.is_empty());
}
#[test]
fn silence_wrong_tier_co_emits_with_g2_for_substrate_alignment_code_only() {
use crate::category::AntigenCategory;
let mut report = ScanReport::default();
report.antigens.push(antigen_decl(
"DriftAntigen",
vec![AntigenCategory::SubstrateAlignment],
));
report.immunities.push(immunity_for("DriftAntigen", false));
let out = audit_category(&report);
assert_eq!(out.mismatch_count, 1);
assert!(out.audits[0]
.hints
.contains(&AuditHint::AntigenCategoryClaimInconsistentWithPredicateType));
assert!(!out.no_silence_witness_mismatch());
assert!(out.audits[0]
.hints
.contains(&AuditHint::AntigenWitnessShapeMismatchForSilenceWrongTier));
}
#[test]
fn silence_wrong_tier_suppressed_when_substrate_witness_also_present() {
use crate::category::AntigenCategory;
let mut report = ScanReport::default();
report.antigens.push(antigen_decl(
"DriftAntigen",
vec![AntigenCategory::SubstrateAlignment],
));
report.immunities.push(immunity_for("DriftAntigen", true));
report.immunities.push(immunity_for("DriftAntigen", false));
let out = audit_category(&report);
assert_eq!(out.mismatch_count, 0);
assert!(out.no_silence_witness_mismatch());
}
#[test]
fn silence_wrong_tier_fires_for_substrate_alignment_defended_by_code_only() {
use crate::category::AntigenCategory;
let mut report = ScanReport::default();
report.antigens.push(antigen_decl(
"DriftAntigen",
vec![AntigenCategory::SubstrateAlignment],
));
report.defenses.push(crate::scan::Defense {
antigen_type: "DriftAntigen".to_string(),
file: std::path::PathBuf::from("test.rs"),
line: 1,
item_kind: "fn".to_string(),
item_target: crate::scan::ItemTarget::Fn("defending_test".to_string()),
canonical_path: None,
});
let out = audit_category(&report);
assert_eq!(out.mismatch_count, 1);
assert!(!out.no_silence_witness_mismatch());
assert!(out.audits[0]
.hints
.contains(&AuditHint::AntigenWitnessShapeMismatchForSilenceWrongTier));
}
#[test]
fn silence_witness_hints_serialize_kebab_case() {
let no_witness =
serde_json::to_string(&AuditHint::AntigenWitnessShapeMismatchForSilenceNoWitness)
.unwrap();
assert_eq!(
no_witness,
"\"antigen-witness-shape-mismatch-for-silence-no-witness\""
);
let wrong_tier =
serde_json::to_string(&AuditHint::AntigenWitnessShapeMismatchForSilenceWrongTier)
.unwrap();
assert_eq!(
wrong_tier,
"\"antigen-witness-shape-mismatch-for-silence-wrong-tier\""
);
}
#[test]
fn g2_hint_serializes_kebab_case() {
let s =
serde_json::to_string(&AuditHint::AntigenCategoryClaimInconsistentWithPredicateType)
.unwrap();
assert_eq!(
s,
"\"antigen-category-claim-inconsistent-with-predicate-type\""
);
}
#[test]
fn atk_g2_substrate_alignment_with_only_defended_by_triggers_g2_hint() {
use crate::category::AntigenCategory;
let mut report = ScanReport::default();
report.antigens.push(antigen_decl(
"DriftAntigen",
vec![AntigenCategory::SubstrateAlignment],
));
report.defenses.push(crate::scan::Defense {
antigen_type: "DriftAntigen".to_string(),
file: std::path::PathBuf::from("tests/test.rs"),
line: 1,
item_kind: "fn".to_string(),
item_target: crate::scan::ItemTarget::Fn("test_drift_antigen".to_string()),
canonical_path: None,
});
let out = audit_category(&report);
assert_eq!(
out.mismatch_count, 1,
"ATK-G2-migration (fixed): SubstrateAlignment antigen with only a \
code-tier #[defended_by] witness must trigger AntigenCategoryClaimInconsistentWithPredicateType. \
audit_category() now consults report.defenses alongside report.immunities."
);
assert_eq!(
out.audits.len(),
1,
"ATK-G2-migration (fixed): exactly one audit entry for the wrong-type ADR-029 witness"
);
assert!(
out.audits[0]
.hints
.contains(&AuditHint::AntigenCategoryClaimInconsistentWithPredicateType),
"ATK-G2-migration (fixed): the audit entry must include the category-mismatch hint"
);
}
#[test]
#[defended_by(AuditFingerprintSelfReferential)]
fn audit_sf1_scan_fingerprint_overrides_sidecar_stored_fingerprint() {
use antigen_attestation::predicate::{Leaf, SignerCurrency};
use antigen_attestation::schema::{
AntigenIdentifier, ItemRatification, Ratification, RatificationKind, SchemaVersion,
Signer, SignerBasis,
};
use chrono::NaiveDate;
use std::collections::BTreeMap;
let tmp = tempfile::tempdir().unwrap();
let source_file = tmp.path().join("src").join("lib.rs");
std::fs::create_dir_all(source_file.parent().unwrap()).unwrap();
let attest_dir = tmp.path().join("src").join(".attest");
std::fs::create_dir_all(&attest_dir).unwrap();
let sidecar = Ratification {
schema_version: SchemaVersion::V1,
kind: RatificationKind::Immunity,
antigen: AntigenIdentifier {
name: "DriftTestAntigen".to_string(),
defined_in: None,
},
source_file: source_file.clone(),
items: vec![ItemRatification {
item_path: "the_fn".to_string(),
current_fingerprint: "fp-old".to_string(),
doc_ref: None,
signers: vec![Signer {
name: "alice".to_string(),
role: None,
date: NaiveDate::from_ymd_opt(2026, 1, 1).unwrap(),
signed_against_fingerprint: "fp-old".to_string(),
basis: SignerBasis::Fresh {
reasoning: Some("reviewed".to_string()),
},
strength: antigen_attestation::tier::SignatureStrength::TextStamp,
signature: None,
}],
oracles: vec![],
fresh_through: None,
extensions: BTreeMap::new(),
}],
};
let sidecar_json = serde_json::to_string_pretty(&sidecar).unwrap();
std::fs::write(attest_dir.join("DriftTestAntigen.json"), sidecar_json).unwrap();
let pred = antigen_attestation::Predicate::leaf(Leaf::Signers {
required: vec!["alice".to_string()],
roles: BTreeMap::new(),
against: SignerCurrency::Current,
signature_allow: vec![],
signature_prefer: None,
});
let pred_json = serde_json::to_string(&pred).unwrap();
let immunity = crate::scan::Immunity {
antigen_type: "DriftTestAntigen".to_string(),
witness: String::new(),
requires_predicate: Some(pred_json),
file: source_file,
line: 1,
item_kind: "fn".to_string(),
item_target: crate::scan::ItemTarget::Fn("the_fn".to_string()),
canonical_path: None,
structural_fingerprint: "fp-new".to_string(),
};
let pred_json_ref = immunity.requires_predicate.as_deref().unwrap();
let result = audit_substrate_witness(&immunity, pred_json_ref);
assert_eq!(
result.audit_hint,
AuditHint::DisciplinePredicateFailed,
"Audit-SF-1: scan-computed structural_fingerprint (fp-new) must override \
sidecar's stored current_fingerprint (fp-old) for staleness detection. \
Alice signed fp-old but the item is now fp-new → predicate correctly fails. \
Old behavior: sidecar's fp-old == alice's fp-old → false-green. Got: {result:?}"
);
assert_eq!(
result.witness_tier,
WitnessTier::None,
"failed predicate maps to tier=None"
);
}
#[test]
fn audit_sf1_legacy_path_no_structural_fingerprint_uses_sidecar_stored() {
use antigen_attestation::predicate::{Leaf, SignerCurrency};
use antigen_attestation::schema::{
AntigenIdentifier, ItemRatification, Ratification, RatificationKind, SchemaVersion,
Signer, SignerBasis,
};
use chrono::NaiveDate;
use std::collections::BTreeMap;
let tmp = tempfile::tempdir().unwrap();
let source_file = tmp.path().join("src").join("lib.rs");
std::fs::create_dir_all(source_file.parent().unwrap()).unwrap();
let attest_dir = tmp.path().join("src").join(".attest");
std::fs::create_dir_all(&attest_dir).unwrap();
let sidecar = Ratification {
schema_version: SchemaVersion::V1,
kind: RatificationKind::Immunity,
antigen: AntigenIdentifier {
name: "LegacyAntigen".to_string(),
defined_in: None,
},
source_file: source_file.clone(),
items: vec![ItemRatification {
item_path: "legacy_fn".to_string(),
current_fingerprint: "fp-consistent".to_string(),
doc_ref: None,
signers: vec![Signer {
name: "alice".to_string(),
role: None,
date: NaiveDate::from_ymd_opt(2026, 1, 1).unwrap(),
signed_against_fingerprint: "fp-consistent".to_string(),
basis: SignerBasis::Fresh {
reasoning: Some("reviewed".to_string()),
},
strength: antigen_attestation::tier::SignatureStrength::TextStamp,
signature: None,
}],
oracles: vec![],
fresh_through: None,
extensions: BTreeMap::new(),
}],
};
let sidecar_json = serde_json::to_string_pretty(&sidecar).unwrap();
std::fs::write(attest_dir.join("LegacyAntigen.json"), sidecar_json).unwrap();
let pred = antigen_attestation::Predicate::leaf(Leaf::Signers {
required: vec!["alice".to_string()],
roles: BTreeMap::new(),
against: SignerCurrency::Current,
signature_allow: vec![],
signature_prefer: None,
});
let pred_json = serde_json::to_string(&pred).unwrap();
let immunity = crate::scan::Immunity {
antigen_type: "LegacyAntigen".to_string(),
witness: String::new(),
requires_predicate: Some(pred_json),
file: source_file,
line: 1,
item_kind: "fn".to_string(),
item_target: crate::scan::ItemTarget::Fn("legacy_fn".to_string()),
canonical_path: None,
structural_fingerprint: String::new(),
};
let pred_json_ref = immunity.requires_predicate.as_deref().unwrap();
let result = audit_substrate_witness(&immunity, pred_json_ref);
assert_eq!(
result.audit_hint,
AuditHint::DisciplinePredicatePassedSubstrateCurrent,
"legacy path: empty structural_fingerprint falls back to sidecar's stored \
current_fingerprint for backwards-compat. Got: {result:?}"
);
assert_eq!(result.witness_tier, WitnessTier::Execution);
}
#[test]
fn sidecar_per_item_lookup_does_not_use_first_item_for_second_immunity() {
use antigen_attestation::predicate::{Leaf, SignerCurrency};
use antigen_attestation::schema::{
AntigenIdentifier, ItemRatification, Ratification, RatificationKind, SchemaVersion,
Signer, SignerBasis,
};
use chrono::NaiveDate;
use std::collections::BTreeMap;
let tmp = tempfile::tempdir().unwrap();
let source_file = tmp.path().join("src").join("lib.rs");
std::fs::create_dir_all(source_file.parent().unwrap()).unwrap();
let attest_dir = tmp.path().join("src").join(".attest");
std::fs::create_dir_all(&attest_dir).unwrap();
let sidecar = Ratification {
schema_version: SchemaVersion::V1,
kind: RatificationKind::Immunity,
antigen: AntigenIdentifier {
name: "TwoFnAntigen".to_string(),
defined_in: None,
},
source_file: source_file.clone(),
items: vec![
ItemRatification {
item_path: "first_fn".to_string(),
current_fingerprint: "fp-1".to_string(),
doc_ref: None,
signers: vec![], oracles: vec![],
fresh_through: None,
extensions: BTreeMap::new(),
},
ItemRatification {
item_path: "second_fn".to_string(),
current_fingerprint: "fp-2".to_string(),
doc_ref: None,
signers: vec![Signer {
name: "alice".to_string(),
role: None,
date: NaiveDate::from_ymd_opt(2026, 1, 1).unwrap(),
signed_against_fingerprint: "fp-2".to_string(),
basis: SignerBasis::Fresh {
reasoning: Some("reviewed second_fn".to_string()),
},
strength: antigen_attestation::tier::SignatureStrength::TextStamp,
signature: None,
}],
oracles: vec![],
fresh_through: None,
extensions: BTreeMap::new(),
},
],
};
let sidecar_json = serde_json::to_string_pretty(&sidecar).unwrap();
std::fs::write(attest_dir.join("TwoFnAntigen.json"), sidecar_json).unwrap();
let pred = antigen_attestation::Predicate::leaf(Leaf::Signers {
required: vec!["alice".to_string()],
roles: BTreeMap::new(),
against: SignerCurrency::Current,
signature_allow: vec![],
signature_prefer: None,
});
let pred_json = serde_json::to_string(&pred).unwrap();
let immunity = crate::scan::Immunity {
antigen_type: "TwoFnAntigen".to_string(),
witness: String::new(),
requires_predicate: Some(pred_json.clone()),
file: source_file,
line: 10,
item_kind: "fn".to_string(),
item_target: crate::scan::ItemTarget::Fn("second_fn".to_string()),
canonical_path: None,
structural_fingerprint: "fp-2".to_string(),
};
let result = audit_substrate_witness(&immunity, &pred_json);
assert_eq!(
result.audit_hint,
AuditHint::DisciplinePredicatePassedSubstrateCurrent,
"ATK-SIDECAR-FIRST-ITEM (FIXED): second_fn's immunity must consult \
second_fn's ratification (alice signed fp-2 == live fp-2 → PASS). A \
DisciplinePredicateFailed result here means the lookup regressed to \
`items.first()` and was reading first_fn's UNSIGNED entry. Got: {result:?}"
);
assert_eq!(result.witness_tier, WitnessTier::Execution);
}
fn presents_site(antigen: &str, file: &str, line: usize) -> crate::scan::Presentation {
crate::scan::Presentation {
antigen_type: antigen.to_string(),
file: PathBuf::from(file),
line,
item_kind: "fn".to_string(),
item_target: crate::scan::ItemTarget::Unknown { line },
match_kind: crate::scan::MatchKind::ExplicitMarker,
canonical_path: None,
inherited_from: None,
structural_fingerprint: String::new(),
requires_predicate: None,
proof: None,
}
}
fn defended_by_witness(antigen: &str, file: &str, line: usize) -> crate::scan::Defense {
crate::scan::Defense {
antigen_type: antigen.to_string(),
file: PathBuf::from(file),
line,
item_kind: "fn".to_string(),
item_target: crate::scan::ItemTarget::Fn(format!("witness_{antigen}")),
canonical_path: None,
}
}
#[test]
fn verdict_defended_by_grants_reachability() {
let mut report = ScanReport::default();
report
.presentations
.push(presents_site("FailureClass", "src/lib.rs", 10));
report
.defenses
.push(defended_by_witness("FailureClass", "src/tests.rs", 5));
let out = audit(&report, Path::new("."));
assert_eq!(out.presentation_verdicts.len(), 1);
let v = &out.presentation_verdicts[0];
assert_eq!(v.antigen_type, "FailureClass");
assert_eq!(
v.verdict,
ImmuneVerdict::Defended {
tier: WitnessTier::Reachability
},
"a registered code-tier witness grants Defended/Reachability; got {:?}",
v.verdict
);
assert_eq!(v.defended_by, vec!["src/tests.rs:5".to_string()]);
assert!(
out.undefended_verdicts().is_empty(),
"the defended site must not appear in undefended_verdicts()"
);
}
#[test]
fn verdict_no_witness_is_undefended() {
let mut report = ScanReport::default();
report
.presentations
.push(presents_site("FailureClass", "src/lib.rs", 10));
let out = audit(&report, Path::new("."));
assert_eq!(out.presentation_verdicts.len(), 1);
assert_eq!(
out.presentation_verdicts[0].verdict,
ImmuneVerdict::Undefended
);
assert_eq!(out.undefended_verdicts().len(), 1);
}
#[test]
fn verdict_wrong_class_witness_does_not_pollute() {
let mut report = ScanReport::default();
report
.presentations
.push(presents_site("RightClass", "src/lib.rs", 10));
report
.defenses
.push(defended_by_witness("WrongClass", "src/tests.rs", 5));
let out = audit(&report, Path::new("."));
assert_eq!(out.presentation_verdicts.len(), 1);
assert_eq!(
out.presentation_verdicts[0].verdict,
ImmuneVerdict::Undefended,
"WrongClass witness must not cross-reference to RightClass; got {:?}",
out.presentation_verdicts[0].verdict
);
assert!(out.presentation_verdicts[0].defended_by.is_empty());
}
#[test]
fn verdict_immune_backward_compat_still_defends() {
let mut report = ScanReport::default();
report
.presentations
.push(presents_site("PanickingInDrop", "src/lib.rs", 20));
report.immunities.push(crate::scan::Immunity {
antigen_type: "PanickingInDrop".to_string(),
witness: "no_panic_drop_test".to_string(),
requires_predicate: None,
file: PathBuf::from("src/lib.rs"),
line: 20,
item_kind: "fn".to_string(),
item_target: crate::scan::ItemTarget::Unknown { line: 20 },
canonical_path: None,
structural_fingerprint: String::new(),
});
let out = audit(&report, Path::new("."));
assert_eq!(out.presentation_verdicts.len(), 1);
assert_eq!(
out.presentation_verdicts[0].verdict,
ImmuneVerdict::Undefended,
"an unresolvable #[immune] witness must not grant a defended verdict"
);
}
#[test]
fn verdict_skips_fingerprint_inferred_presentations() {
let mut report = ScanReport::default();
let mut inferred = presents_site("SomeClass", "src/lib.rs", 10);
inferred.match_kind = crate::scan::MatchKind::FingerprintMatch;
report.presentations.push(inferred);
report
.presentations
.push(presents_site("ExplicitClass", "src/lib.rs", 20));
let out = audit(&report, Path::new("."));
assert_eq!(
out.presentation_verdicts.len(),
1,
"only the explicit presents-site gets a verdict; the fingerprint-\
inferred match is skipped. got: {:?}",
out.presentation_verdicts
);
assert_eq!(out.presentation_verdicts[0].antigen_type, "ExplicitClass");
}
fn presents_site_with_requires(
antigen: &str,
file: &str,
line: usize,
pred_json: &str,
) -> crate::scan::Presentation {
let mut site = presents_site(antigen, file, line);
site.requires_predicate = Some(pred_json.to_string());
site
}
#[test]
fn atk_pv_requires_masked_by_code_witness() {
let pred_json = r#"{"Signers":{"required":["alice"],"roles":{},"against":"Current","signature_allow":[],"signature_prefer":null}}"#;
let mut report = ScanReport::default();
report.presentations.push(presents_site_with_requires(
"SubstrateDriftClass",
"src/lib.rs",
10,
pred_json,
));
report.defenses.push(defended_by_witness(
"SubstrateDriftClass",
"src/tests.rs",
5,
));
let out = audit(&report, Path::new("."));
assert_eq!(out.presentation_verdicts.len(), 1);
let v = &out.presentation_verdicts[0];
assert_eq!(
v.verdict,
ImmuneVerdict::SubstrateGap,
"ATK-PV-REQUIRES-MASKED: a failing requires= predicate must surface \
SubstrateGap even when a code witness exists. The developer declared \
substrate intent (requires=) that is broken; a code witness in a different \
channel does not resolve it. verdict: {:?}",
v.verdict
);
}
#[test]
fn atk_pv_immune_channel_substrate_gap_not_masked_by_code_witness() {
let pred_json = r#"{"Signers":{"required":["alice"],"roles":{},"against":"Current","signature_allow":[],"signature_prefer":null}}"#;
let mut report = ScanReport::default();
let site = presents_site("ImmuneChannelClass", "src/lib.rs", 20);
report.presentations.push(site);
report.defenses.push(defended_by_witness(
"ImmuneChannelClass",
"src/tests.rs",
15,
));
let imm = crate::scan::Immunity {
antigen_type: "ImmuneChannelClass".to_string(),
witness: String::new(),
requires_predicate: Some(pred_json.to_string()),
file: std::path::PathBuf::from("src/lib.rs"),
line: 20,
item_kind: "fn".to_string(),
item_target: crate::scan::ItemTarget::Unknown { line: 20 },
canonical_path: None,
structural_fingerprint: String::new(),
};
report.immunities.push(imm);
let out = audit(&report, Path::new("."));
assert_eq!(out.presentation_verdicts.len(), 1);
let v = &out.presentation_verdicts[0];
assert_eq!(
v.verdict,
ImmuneVerdict::SubstrateGap,
"ATK-PV-IMMUNE-CHANNEL: a failing #[immune(requires=)] (deprecated channel) \
must surface SubstrateGap even when a code witness exists. The deprecated \
substrate claim is broken; a code witness in a different channel does not \
resolve it. ADR-029 Amendment 1 §Channel-generality covers this case. \
verdict: {:?}",
v.verdict
);
}
fn orient_decl(until: &str) -> crate::scan::DeferredDefense {
crate::scan::DeferredDefense {
kind: crate::scan::DeferredDefenseKind::Orient,
antigen_type: Some("SomeClass".to_string()),
text: String::new(),
until: Some(until.to_string()),
expected_co_stimulation: None,
signed_by: None,
see: Vec::new(),
since: None,
duration_cap: None,
file: PathBuf::from("src/lib.rs"),
line: 1,
item_kind: "fn".to_string(),
item_target: crate::scan::ItemTarget::Fn("t".to_string()),
}
}
#[test]
fn orient_future_until_is_active() {
let mut report = ScanReport::default();
report.deferred_defenses.push(orient_decl("2999-12-31"));
let out = audit_deferred_defenses(&report, 30);
assert_eq!(out.audits.len(), 1);
assert_eq!(out.audits[0].hint, AuditHint::OrientActive);
assert_eq!(out.active_count, 1);
assert_eq!(out.expired_count, 0);
}
#[test]
fn orient_past_until_escalates_to_action_required() {
let mut report = ScanReport::default();
report.deferred_defenses.push(orient_decl("2000-01-01"));
let out = audit_deferred_defenses(&report, 30);
assert_eq!(out.audits.len(), 1);
assert_eq!(
out.audits[0].hint,
AuditHint::OrientPendingActionRequired,
"a past orient until-date must escalate to OrientPendingActionRequired, \
not stay OrientActive (ADR-023: orient observes its required deadline)"
);
assert_eq!(out.expired_count, 1);
assert_eq!(out.active_count, 0);
}
fn orient_decl_no_until() -> crate::scan::DeferredDefense {
crate::scan::DeferredDefense {
kind: crate::scan::DeferredDefenseKind::Orient,
antigen_type: Some("SomeClass".to_string()),
text: String::new(),
until: None, expected_co_stimulation: None,
signed_by: None,
see: Vec::new(),
since: None,
duration_cap: None,
file: PathBuf::from("src/lib.rs"),
line: 1,
item_kind: "fn".to_string(),
item_target: crate::scan::ItemTarget::Fn("t".to_string()),
}
}
#[test]
fn atk_orient_none_until_is_silently_active_forever() {
let mut report = ScanReport::default();
report.deferred_defenses.push(orient_decl_no_until());
let out = audit_deferred_defenses(&report, 30);
assert_eq!(out.audits.len(), 1);
assert_eq!(
out.audits[0].hint,
AuditHint::OrientActive,
"ATK-orient(a): orient with until=None must land in OrientActive (grace path for \
hand-built/legacy records). If this changes, the None arm's escalation logic must \
be deliberately designed, not accidental."
);
assert_eq!(
out.active_count, 1,
"ATK-orient(a): None-until orient counts as active"
);
assert_eq!(out.expired_count, 0);
}
#[test]
fn atk_orient_invalid_date_string_escalates_not_silently_active() {
let mut report = ScanReport::default();
let mut decl = orient_decl("not-a-date");
decl.until = Some("not-a-date".to_string());
report.deferred_defenses.push(decl);
let out = audit_deferred_defenses(&report, 30);
assert_eq!(out.audits.len(), 1);
assert_eq!(
out.audits[0].hint,
AuditHint::OrientPendingActionRequired,
"a present-but-unparseable orient `until` must escalate (the author \
intended a deadline; a broken one is unresolved, not a grace)"
);
assert_eq!(out.active_count, 0);
}
#[test]
fn atk_orient_slash_date_format_typo_escalates_not_silently_active() {
let mut report = ScanReport::default();
let mut decl = orient_decl("2099-01-01"); decl.until = Some("2099/01/01".to_string()); report.deferred_defenses.push(decl);
let out = audit_deferred_defenses(&report, 30);
assert_eq!(
out.audits[0].hint,
AuditHint::OrientPendingActionRequired,
"a slash-format (unparseable) orient `until` must escalate to \
action-required, not silently fall to OrientActive"
);
}
#[test]
fn atk_orient_two_decls_one_past_one_future_counted_independently() {
let mut report = ScanReport::default();
report.deferred_defenses.push(orient_decl("2000-01-01")); report.deferred_defenses.push(orient_decl("2999-12-31")); let out = audit_deferred_defenses(&report, 30);
assert_eq!(
out.audits.len(),
2,
"ATK-orient(d): both decls must be evaluated"
);
assert_eq!(out.active_count, 1, "ATK-orient(d): one active orient");
assert_eq!(out.expired_count, 1, "ATK-orient(d): one expired orient");
assert_eq!(
out.audits[0].hint,
AuditHint::OrientPendingActionRequired,
"ATK-orient(d): past orient must escalate"
);
assert_eq!(
out.audits[1].hint,
AuditHint::OrientActive,
"ATK-orient(d): future orient must stay active"
);
}
#[test]
fn atk_orient_extreme_past_1970_escalates() {
let mut report = ScanReport::default();
report.deferred_defenses.push(orient_decl("1970-01-01"));
let out = audit_deferred_defenses(&report, 30);
assert_eq!(
out.audits[0].hint,
AuditHint::OrientPendingActionRequired,
"ATK-orient(e): epoch date (1970-01-01) must escalate to OrientPendingActionRequired"
);
}
#[test]
fn atk_orient_empty_string_until_is_silently_active() {
let mut report = ScanReport::default();
report.deferred_defenses.push(orient_decl(""));
let out = audit_deferred_defenses(&report, 30);
assert_eq!(
out.audits[0].hint,
AuditHint::OrientActive,
"ATK-orient(f): orient with until=Some(\"\") (empty string) lands in OrientActive \
via the same None-parse grace path. SILENT GAP: an empty string looks like \
'field was set' but behaves like 'field was absent'."
);
assert_eq!(out.active_count, 1);
}
fn immunosuppress_decl_with_duration_cap(
duration_cap_days: u64,
since: &str,
) -> crate::scan::DeferredDefense {
use crate::scan::{DeferredDefenseKind, ItemTarget};
use std::path::PathBuf;
crate::scan::DeferredDefense {
kind: DeferredDefenseKind::Immunosuppress,
antigen_type: None,
text: "test rationale".to_string(),
until: None,
expected_co_stimulation: None,
signed_by: None,
see: Vec::new(),
since: Some(since.to_string()),
duration_cap: Some(duration_cap_days),
file: PathBuf::from("src/lib.rs"),
line: 1,
item_kind: "fn".to_string(),
item_target: ItemTarget::Fn("suppress_me".to_string()),
}
}
#[test]
fn atk_immunosuppress_duration_cap_exceeded_is_emitted() {
let decl = immunosuppress_decl_with_duration_cap(30, "2020-01-01");
let mut report = ScanReport::default();
report.deferred_defenses.push(decl);
let out = audit_deferred_defenses(&report, 30);
assert_eq!(
out.audits[0].hint,
AuditHint::ImmunosuppressDurationCapExceeded,
"an immunosuppress past its since+duration_cap must emit \
ImmunosuppressDurationCapExceeded (the cap is now enforceable at \
audit time via typed since/duration_cap fields)"
);
assert_eq!(
out.stale_count, 1,
"a cap-exceeded immunosuppress tallies as stale (overstayed its cap)"
);
}
#[test]
fn atk_immunosuppress_duration_cap_within_limit_is_active() {
let decl = immunosuppress_decl_with_duration_cap(30, "2099-01-01");
let mut report = ScanReport::default();
report.deferred_defenses.push(decl);
let out = audit_deferred_defenses(&report, 30);
assert_eq!(
out.audits[0].hint,
AuditHint::ImmunosuppressActive,
"a within-cap immunosuppress (since far-future, cap not exceeded) \
stays Active — the discrimination exceeded-vs-within-limit now works"
);
}
#[test]
fn atk_immunosuppress_malformed_since_silently_skips_cap_check() {
let mut decl = immunosuppress_decl_with_duration_cap(1, "2020-01-01");
decl.since = Some("not-a-date".to_string());
let mut report = ScanReport::default();
report.deferred_defenses.push(decl);
let out = audit_deferred_defenses(&report, 9999);
assert_eq!(
out.audits[0].hint,
AuditHint::ImmunosuppressActive,
"ATK-IMMUNOSUPPRESS-MALFORMED-SINCE (documented gap): malformed since= \
silently skips the duration_cap check, yielding ImmunosuppressActive. \
A typo in since= grants the suppression infinite duration -- the cap \
enforcement is completely invisible. Fix: split the None arm -- \
since=Some(bad) should emit a parse failure diagnostic instead of \
silently treating since as absent."
);
assert_eq!(
out.stale_count, 0,
"ATK-IMMUNOSUPPRESS-MALFORMED-SINCE: stale_count is 0 -- the cap-exceeded \
path was never reached because since parse failed silently"
);
}
fn anergy_decl_with_until(until: &str) -> crate::scan::DeferredDefense {
use crate::scan::{DeferredDefenseKind, ItemTarget};
crate::scan::DeferredDefense {
kind: DeferredDefenseKind::Anergy,
antigen_type: Some("SomeClass".to_string()),
text: "test reason".to_string(),
until: Some(until.to_string()),
expected_co_stimulation: None,
signed_by: None,
see: Vec::new(),
since: None,
duration_cap: None,
file: std::path::PathBuf::from("src/lib.rs"),
line: 1,
item_kind: "fn".to_string(),
item_target: ItemTarget::Fn("deferred_fn".to_string()),
}
}
fn immunosuppress_decl_with_until(until: &str) -> crate::scan::DeferredDefense {
use crate::scan::{DeferredDefenseKind, ItemTarget};
crate::scan::DeferredDefense {
kind: DeferredDefenseKind::Immunosuppress,
antigen_type: Some("SomeClass".to_string()),
text: "test rationale".to_string(),
until: Some(until.to_string()),
expected_co_stimulation: None,
signed_by: None,
see: Vec::new(),
since: None,
duration_cap: None,
file: std::path::PathBuf::from("src/lib.rs"),
line: 1,
item_kind: "fn".to_string(),
item_target: ItemTarget::Fn("suppressed_fn".to_string()),
}
}
fn poxparty_decl_with_until(until: &str) -> crate::scan::DeferredDefense {
use crate::scan::{DeferredDefenseKind, ItemTarget};
crate::scan::DeferredDefense {
kind: DeferredDefenseKind::Poxparty,
antigen_type: Some("SomeClass".to_string()),
text: "UserInput".to_string(),
until: Some(until.to_string()),
expected_co_stimulation: None,
signed_by: None,
see: Vec::new(),
since: None,
duration_cap: None,
file: std::path::PathBuf::from("src/lib.rs"),
line: 1,
item_kind: "fn".to_string(),
item_target: ItemTarget::Fn("pox_fn".to_string()),
}
}
#[test]
fn atk_deferred_until_1_anergy_malformed_until_escalates() {
let decl = anergy_decl_with_until("not-a-date");
let mut report = ScanReport::default();
report.deferred_defenses.push(decl);
let out = audit_deferred_defenses(&report, 30);
assert_eq!(
out.audits[0].hint,
AuditHint::AnergyCostimulationNotArrived,
"ATK-DEFERRED-UNTIL-1: anergy with until=Some('not-a-date') must escalate \
to AnergyCostimulationNotArrived (present-but-broken deadline = unresolved), \
not silently land in AnergyActive. The author intended a deadline; a typo \
must not grant permanent active status."
);
assert_eq!(
out.active_count, 0,
"ATK-DEFERRED-UNTIL-1: a malformed-until anergy must NOT count as active"
);
assert_eq!(out.expired_count, 1);
assert_eq!(out.stale_count, 0);
}
#[test]
fn atk_deferred_until_2_immunosuppress_malformed_until_escalates() {
let decl = immunosuppress_decl_with_until("2026/01/01");
let mut report = ScanReport::default();
report.deferred_defenses.push(decl);
let out = audit_deferred_defenses(&report, 30);
assert_eq!(
out.audits[0].hint,
AuditHint::ImmunosuppressExpired,
"ATK-DEFERRED-UNTIL-2: immunosuppress with until=Some('2026/01/01') \
(present-but-unparseable) must escalate to ImmunosuppressExpired, not \
silently stay Active. A suppression intended to expire must not run forever."
);
assert_eq!(out.active_count, 0);
assert_eq!(out.expired_count, 1);
}
#[test]
fn atk_deferred_until_3_poxparty_malformed_until_escalates() {
let decl = poxparty_decl_with_until("soon"); let mut report = ScanReport::default();
report.deferred_defenses.push(decl);
let out = audit_deferred_defenses(&report, 30);
assert_eq!(
out.audits[0].hint,
AuditHint::PoxpartyOutcomePending,
"ATK-DEFERRED-UNTIL-3: poxparty with until=Some('soon') must escalate to \
PoxpartyOutcomePending, not silently stay Active. An intended-but-broken \
expiry must surface as outcome-pending, not permanent green."
);
assert_eq!(out.active_count, 0);
assert_eq!(out.expired_count, 1);
}
fn antigen_with_fp(type_name: &str, fingerprint: &str) -> crate::scan::AntigenDeclaration {
crate::scan::AntigenDeclaration {
fingerprint: Some(fingerprint.to_string()),
..antigen_decl(type_name, Vec::new())
}
}
fn lineage_edge(child: &str, parent: &str) -> crate::scan::LineageEdge {
crate::scan::LineageEdge {
child: child.to_string(),
parent: parent.to_string(),
file: PathBuf::from("src/lib.rs"),
line: 1,
parent_canonical_path: None,
child_canonical_path: None,
}
}
#[test]
fn lineage_fidelity_flags_item_kind_divergence() {
let mut report = ScanReport::default();
report
.antigens
.push(antigen_with_fp("Parent", "item = struct"));
report
.antigens
.push(antigen_with_fp("Child", "item = enum"));
report.lineage_edges.push(lineage_edge("Child", "Parent"));
let out = audit_lineage_fidelity(&report);
assert_eq!(out.divergences.len(), 1);
assert_eq!(
out.divergences[0].hint,
AuditHint::DescendedFromFingerprintDivergence
);
assert!(out.divergences[0].detail.contains("item"));
}
#[test]
fn lineage_fidelity_flags_missing_parent_doc_substring() {
let mut report = ScanReport::default();
report.antigens.push(antigen_with_fp(
"P",
r#"item = struct, doc_contains("error")"#,
));
report.antigens.push(antigen_with_fp(
"C",
r#"item = struct, doc_contains("panic")"#,
));
report.lineage_edges.push(lineage_edge("C", "P"));
let out = audit_lineage_fidelity(&report);
assert_eq!(out.divergences.len(), 1);
assert!(out.divergences[0].detail.contains("doc_contains"));
}
#[test]
fn lineage_fidelity_clean_when_child_refines_parent() {
let mut report = ScanReport::default();
report.antigens.push(antigen_with_fp(
"P",
r#"item = struct, doc_contains("error")"#,
));
report.antigens.push(antigen_with_fp(
"C",
r#"item = struct, doc_contains("parse error")"#,
));
report.lineage_edges.push(lineage_edge("C", "P"));
let out = audit_lineage_fidelity(&report);
assert!(
out.divergences.is_empty(),
"a genuine refinement (same kind + superstring doc) must not flag; got: {:?}",
out.divergences
);
}
#[test]
fn lineage_fidelity_silent_when_a_fingerprint_is_absent() {
let mut report = ScanReport::default();
report.antigens.push(antigen_with_fp("P", "item = struct"));
report.antigens.push(antigen_decl("C", Vec::new())); report.lineage_edges.push(lineage_edge("C", "P"));
let out = audit_lineage_fidelity(&report);
assert!(
out.divergences.is_empty(),
"an absent (verify-only) fingerprint must not produce a divergence advisory"
);
}
#[test]
fn atk_lf_1_item_kind_nested_in_all_of_silently_bypasses_divergence_check() {
let mut report = ScanReport::default();
report.antigens.push(antigen_with_fp(
"P",
r#"all_of([item = struct, doc_contains("error")])"#,
));
report.antigens.push(antigen_with_fp(
"C",
r#"all_of([item = enum, doc_contains("error")])"#,
));
report.lineage_edges.push(lineage_edge("C", "P"));
let out = audit_lineage_fidelity(&report);
assert_eq!(
out.divergences.len(),
1,
"ATK-LF-1 (FIXED): parent `all_of(item=struct, ...)` and child `all_of(item=enum, ...)` \
must fire DescendedFromFingerprintDivergence — node_kind() descends into AllOf and \
surfaces the disjoint item-kinds. A zero-length result means the item-kind check no \
longer delegates to node_kind(). Got: {:?}",
out.divergences
);
assert_eq!(
out.divergences[0].hint,
AuditHint::DescendedFromFingerprintDivergence
);
}
#[test]
fn atk_lf_2_parent_doc_contains_nested_in_all_of_silently_bypasses_divergence_check() {
let mut report = ScanReport::default();
report.antigens.push(antigen_with_fp(
"P",
r#"all_of([item = struct, doc_contains("error")])"#,
));
report.antigens.push(antigen_with_fp("C", "item = struct"));
report.lineage_edges.push(lineage_edge("C", "P"));
let out = audit_lineage_fidelity(&report);
assert_eq!(
out.divergences.len(),
1,
"ATK-LF-2 (FIXED): parent `all_of(item=struct, doc_contains('error'))` requires \
'error' in the doc — child `item = struct` (no doc_contains) does not cover it. \
collect_doc_contains_allof_only must descend into AllOf and surface the nested \
requirement. A zero-length result means the AllOf descent was removed. Got: {:?}",
out.divergences
);
assert_eq!(
out.divergences[0].hint,
AuditHint::DescendedFromFingerprintDivergence
);
}
#[test]
fn atk_lf_3_bare_type_name_index_cross_crate_collision_non_deterministic_advisory() {
let mut report = ScanReport::default();
let mut foo_a = antigen_with_fp("Foo", "item = struct");
foo_a.canonical_path = Some("crate-a@1.0".to_string());
report.antigens.push(foo_a);
let mut foo_b = antigen_with_fp("Foo", "item = fn");
foo_b.canonical_path = Some("crate-b@2.0".to_string());
report.antigens.push(foo_b);
let mut bar = antigen_with_fp("Bar", "item = struct");
bar.canonical_path = Some("crate-a@1.0".to_string());
report.antigens.push(bar);
let mut edge = lineage_edge("Bar", "Foo");
edge.child_canonical_path = Some("crate-a@1.0".to_string());
edge.parent_canonical_path = Some("crate-a@1.0".to_string());
report.lineage_edges.push(edge);
let out = audit_lineage_fidelity(&report);
assert_eq!(
out.divergences.len(),
0,
"ATK-LF-3 (FIXED): (type_name, canonical_path)-keyed index resolves Bar's parent \
to crate-A's struct Foo deterministically — a valid refinement, no divergence. \
A non-zero result means the cross-crate Foo collided back in. Got: {:?}",
out.divergences
);
}
#[test]
fn atk_lf_4_any_of_nested_doc_contains_must_not_false_positive() {
let mut report = ScanReport::default();
report.antigens.push(antigen_with_fp(
"P",
r#"any_of([doc_contains("A"), doc_contains("B")])"#,
));
report
.antigens
.push(antigen_with_fp("C", r#"doc_contains("A")"#));
report.lineage_edges.push(lineage_edge("C", "P"));
let out = audit_lineage_fidelity(&report);
assert_eq!(
out.divergences.len(),
0,
"ATK-LF-4: a child with doc_contains('A') IS a valid refinement of a parent with \
any_of([doc_contains('A'), doc_contains('B')]) — the child's match-set is a subset \
of the parent's OR-union. No DescendedFromFingerprintDivergence should fire. \
If this assertion FAILS after ATK-LF-2 fix landed: the fix naively descends into \
AnyOf and requires the child to cover both arms — a false positive. Restrict \
doc_contains collection to AllOf children only."
);
}
#[test]
fn atk_lf_6_child_no_item_kind_flags_as_non_refinement() {
let mut report = ScanReport::default();
report.antigens.push(antigen_with_fp(
"P",
r#"item = struct, doc_contains("error")"#,
));
report
.antigens
.push(antigen_with_fp("C", r#"doc_contains("error")"#));
report.lineage_edges.push(lineage_edge("C", "P"));
let out = audit_lineage_fidelity(&report);
assert_eq!(
out.divergences.len(),
1,
"ATK-LF-6: child `doc_contains('error')` with no item kind is wider \
than parent `item=struct, doc_contains('error')` — not a refinement. \
DescendedFromFingerprintDivergence must fire. parent=Some(Struct) + \
child=None is unconditionally broader (decidable, unlike ATK-LF-5). \
divergences: {:?}",
out.divergences
);
}
#[test]
fn atk_lf_5_any_of_item_kind_widening_flagged_via_none_node_kind() {
let mut report = ScanReport::default();
report.antigens.push(antigen_with_fp("P", "item = struct"));
report.antigens.push(antigen_with_fp(
"C",
r"any_of([item = struct, item = enum])",
));
report.lineage_edges.push(lineage_edge("C", "P"));
let out = audit_lineage_fidelity(&report);
assert_eq!(
out.divergences.len(),
1,
"ATK-LF-5: child `any_of([item=struct, item=enum])` is wider than parent \
`item=struct` — not a refinement. node_kind() is None for the child (any_of \
has no single kind hint), and the (Some(pk), None) arm flags this as a widening. \
Previously a silent false negative; the ATK-LF-6 fix closes it. divergences: {:?}",
out.divergences
);
}
}