use std::sync::Arc;
use mempill_types::{Claim, ClaimRef};
use crate::config::EngineConfig;
#[derive(Debug, Clone, PartialEq)]
pub(crate) enum FirewallVerdict {
Admit,
CorroborateByIdentity {
existing_claim: ClaimRef,
provenance_independent: bool,
},
Quarantine {
reason: String,
},
DepthCapExceeded {
depth: u32,
cap: u32,
},
}
pub(crate) struct AmplificationGuard {
config: Arc<EngineConfig>,
}
impl AmplificationGuard {
pub(crate) fn new(config: Arc<EngineConfig>) -> Self {
Self { config }
}
pub(crate) fn check(
&self,
candidate: &Claim,
injected_claim_refs: &[ClaimRef],
burst_count_this_batch: u32,
) -> FirewallVerdict {
if burst_count_this_batch >= self.config.quarantine_burst_threshold {
return FirewallVerdict::Quarantine {
reason: format!(
"burst_detected: {} identical re-entries in batch (threshold: {})",
burst_count_this_batch,
self.config.quarantine_burst_threshold,
),
};
}
if candidate.provenance().is_recall_reentry() {
let existing_claim = candidate
.derived_from()
.first()
.cloned()
.or_else(|| injected_claim_refs.first().cloned());
if let Some(existing) = existing_claim {
return FirewallVerdict::CorroborateByIdentity {
existing_claim: existing,
provenance_independent: false,
};
}
}
let depth = candidate.external_anchor().derivation_depth;
let cap = self.config.derivation_depth_cap_for_overturning;
if depth > cap {
return FirewallVerdict::DepthCapExceeded { depth, cap };
}
FirewallVerdict::Admit
}
}
#[cfg(test)]
mod tests {
use super::*;
use mempill_types::{
AgentId, Cardinality, Claim, ClaimRef, Confidence, Criticality, ExternalAnchor,
ExternalKind, Fact, ProvenanceLabel, TransactionTime, ValidTime,
};
use chrono::{TimeZone, Utc};
fn tx() -> TransactionTime {
TransactionTime(Utc.with_ymd_and_hms(2026, 6, 22, 0, 0, 0).unwrap())
}
fn make_claim(
provenance: ProvenanceLabel,
derivation_depth: u32,
derived_from: Vec<ClaimRef>,
) -> Claim {
Claim::new(
ClaimRef::new_random(),
AgentId("agent-fw".into()),
Fact {
subject: "user".into(),
predicate: "city".into(),
value: serde_json::json!("Paris"),
},
Cardinality::Functional,
provenance,
ExternalAnchor {
nearest_external_anchor: None,
derivation_depth,
},
tx(),
ValidTime { start: None, end: None, valid_time_confidence: 0.0 },
Confidence { value_confidence: 0.9, valid_time_confidence: 0.0 },
Criticality::Medium,
derived_from,
None,
None,
)
}
fn external_claim(depth: u32) -> Claim {
make_claim(ProvenanceLabel::External(ExternalKind::ExternalFirstHand), depth, vec![])
}
fn recall_claim_with_ref(existing: ClaimRef) -> Claim {
make_claim(ProvenanceLabel::RecallReEntry, 0, vec![existing])
}
fn recall_claim_no_ref() -> Claim {
make_claim(ProvenanceLabel::RecallReEntry, 0, vec![])
}
fn guard(threshold: u32, depth_cap: u32) -> AmplificationGuard {
let cfg = EngineConfig {
quarantine_burst_threshold: threshold,
derivation_depth_cap_for_overturning: depth_cap,
..EngineConfig::default()
};
AmplificationGuard::new(Arc::new(cfg))
}
fn default_guard() -> AmplificationGuard {
AmplificationGuard::new(Arc::new(EngineConfig::default()))
}
#[test]
fn amplification_acid_808_copies_collapse_to_one_claim() {
let g = guard(1000, 10);
let existing_ref = ClaimRef::new_random();
let injected = vec![existing_ref.clone()];
let mut admitted_count = 0u32;
let mut corroborated_count = 0u32;
let mut quarantine_count = 0u32;
for _ in 0..808 {
let candidate = recall_claim_with_ref(existing_ref.clone());
match g.check(&candidate, &injected, 0) {
FirewallVerdict::Admit => admitted_count += 1,
FirewallVerdict::CorroborateByIdentity { .. } => corroborated_count += 1,
FirewallVerdict::Quarantine { .. } => quarantine_count += 1,
FirewallVerdict::DepthCapExceeded { .. } => {}
}
}
assert_eq!(admitted_count, 0,
"808 RecallReEntry re-ingestions must NOT be admitted as new claims");
assert_eq!(corroborated_count, 808,
"808 RecallReEntry re-ingestions must ALL be CorroborateByIdentity");
assert_eq!(quarantine_count, 0,
"burst threshold was 1000; no quarantine expected");
}
#[test]
fn amplification_acid_808_with_incrementing_burst_count_no_new_claim_rows() {
let g = guard(1000, 10);
let existing_ref = ClaimRef::new_random();
let injected = vec![existing_ref.clone()];
let mut new_claim_rows = 0u32;
for batch_seq in 0..808 {
let candidate = recall_claim_with_ref(existing_ref.clone());
let verdict = g.check(&candidate, &injected, batch_seq);
match verdict {
FirewallVerdict::Admit | FirewallVerdict::DepthCapExceeded { .. } => {
new_claim_rows += 1;
}
FirewallVerdict::CorroborateByIdentity { provenance_independent, .. } => {
assert!(!provenance_independent,
"RecallReEntry must NOT be marked provenance-independent (V3-7)");
}
FirewallVerdict::Quarantine { .. } => {}
}
}
assert_eq!(new_claim_rows, 0,
"ACID: 808 re-ingestions must result in ZERO new claim rows (mem0 #4573 defence)");
}
#[test]
fn burst_over_threshold_returns_quarantine() {
let g = guard(5, 10); let candidate = external_claim(0);
let verdict = g.check(&candidate, &[], 5); assert!(
matches!(verdict, FirewallVerdict::Quarantine { .. }),
"burst_count >= threshold must return Quarantine"
);
}
#[test]
fn burst_exactly_at_threshold_returns_quarantine() {
let g = guard(10, 10);
let candidate = external_claim(0);
let verdict = g.check(&candidate, &[], 10); assert!(matches!(verdict, FirewallVerdict::Quarantine { .. }));
}
#[test]
fn burst_one_below_threshold_does_not_quarantine() {
let g = guard(10, 10);
let candidate = external_claim(0);
let verdict = g.check(&candidate, &[], 9);
assert!(
!matches!(verdict, FirewallVerdict::Quarantine { .. }),
"burst_count < threshold must NOT quarantine"
);
}
#[test]
fn burst_quarantine_fires_before_identity_check() {
let g = guard(3, 10);
let existing_ref = ClaimRef::new_random();
let injected = vec![existing_ref.clone()];
let candidate = recall_claim_with_ref(existing_ref);
let verdict = g.check(&candidate, &injected, 3); assert!(
matches!(verdict, FirewallVerdict::Quarantine { .. }),
"burst detection must fire before identity check (step 1 before step 2)"
);
}
#[test]
fn genuine_external_claim_is_admitted() {
let g = default_guard();
let candidate = external_claim(0);
let verdict = g.check(&candidate, &[], 0);
assert_eq!(verdict, FirewallVerdict::Admit,
"first External claim with no prior injected refs must be Admitted");
}
#[test]
fn external_claim_with_injected_refs_is_still_admitted() {
let g = default_guard();
let injected = vec![ClaimRef::new_random(), ClaimRef::new_random()];
let candidate = external_claim(0);
let verdict = g.check(&candidate, &injected, 0);
assert_eq!(verdict, FirewallVerdict::Admit);
}
#[test]
fn depth_beyond_cap_returns_depth_cap_exceeded() {
let g = guard(10, 2); let candidate = external_claim(3); let verdict = g.check(&candidate, &[], 0);
assert!(
matches!(verdict, FirewallVerdict::DepthCapExceeded { depth: 3, cap: 2 }),
"derivation_depth > cap must return DepthCapExceeded"
);
}
#[test]
fn depth_exactly_at_cap_is_admitted() {
let g = guard(10, 2); let candidate = external_claim(2); let verdict = g.check(&candidate, &[], 0);
assert_eq!(verdict, FirewallVerdict::Admit,
"derivation_depth == cap is within limit; must Admit");
}
#[test]
fn depth_below_cap_is_admitted() {
let g = guard(10, 2);
let candidate = external_claim(1);
let verdict = g.check(&candidate, &[], 0);
assert_eq!(verdict, FirewallVerdict::Admit);
}
#[test]
fn op1_depth_cap_fires_after_burst_check() {
let g = guard(3, 2); let candidate = external_claim(5); let verdict = g.check(&candidate, &[], 3);
assert!(
matches!(verdict, FirewallVerdict::Quarantine { .. }),
"burst check (step 1) must fire before depth cap (step 3)"
);
}
#[test]
fn op1_recall_reentry_with_high_depth_corroborates_not_depth_exceeded() {
let g = guard(1000, 2); let existing_ref = ClaimRef::new_random();
let injected = vec![existing_ref.clone()];
let candidate = make_claim(ProvenanceLabel::RecallReEntry, 5, vec![existing_ref]);
let verdict = g.check(&candidate, &injected, 0);
assert!(
matches!(verdict, FirewallVerdict::CorroborateByIdentity { .. }),
"RecallReEntry (step 2) fires before depth cap (step 3)"
);
}
#[test]
fn corroborate_by_identity_is_never_provenance_independent() {
let g = default_guard();
let existing_ref = ClaimRef::new_random();
let injected = vec![existing_ref.clone()];
let candidate = recall_claim_with_ref(existing_ref);
let verdict = g.check(&candidate, &injected, 0);
match verdict {
FirewallVerdict::CorroborateByIdentity { provenance_independent, .. } => {
assert!(!provenance_independent,
"RecallReEntry corroboration must NEVER be provenance-independent (V3-7)");
}
other => panic!("expected CorroborateByIdentity, got {other:?}"),
}
}
#[test]
fn check_is_deterministic_same_inputs_same_verdict() {
let g = default_guard();
let existing_ref = ClaimRef::new_random();
let injected = vec![existing_ref.clone()];
let candidate = recall_claim_with_ref(existing_ref);
let v1 = g.check(&candidate, &injected, 0);
let v2 = g.check(&candidate, &injected, 0);
assert_eq!(v1, v2, "check() must be deterministic for identical inputs");
}
#[test]
fn check_is_deterministic_across_multiple_scenarios() {
let g = guard(5, 3);
let existing_ref = ClaimRef::new_random();
let injected = vec![existing_ref.clone()];
let scenarios: Vec<(Claim, &[ClaimRef], u32)> = vec![
(external_claim(0), &[], 0), (external_claim(4), &[], 0), (external_claim(0), &[], 5), (recall_claim_with_ref(existing_ref.clone()), &injected, 0), ];
for (candidate, refs, burst) in &scenarios {
let v1 = g.check(candidate, refs, *burst);
let v2 = g.check(candidate, refs, *burst);
assert_eq!(v1, v2, "non-deterministic result for burst={burst}");
}
}
#[test]
fn recall_reentry_with_no_derived_from_and_empty_injected_admits() {
let g = default_guard();
let candidate = recall_claim_no_ref();
let verdict = g.check(&candidate, &[], 0);
assert_eq!(verdict, FirewallVerdict::Admit,
"RecallReEntry with no matching prior ref must degrade gracefully to Admit");
}
#[test]
fn recall_reentry_with_no_derived_from_but_injected_ref_present_corroborates() {
let g = default_guard();
let existing_ref = ClaimRef::new_random();
let injected = vec![existing_ref.clone()];
let candidate = recall_claim_no_ref(); let verdict = g.check(&candidate, &injected, 0);
assert!(
matches!(verdict, FirewallVerdict::CorroborateByIdentity { .. }),
"RecallReEntry with no derived_from but with injected refs should corroborate"
);
}
}