use chrono::Utc;
use mempill_core::application::{IngestClaimRequest, QueryMemoryRequest};
use mempill_core::application::ingest_claim::IngestClaimUseCase;
use mempill_core::application::query_memory::QueryMemoryUseCase;
use mempill_core::config::EngineConfig;
use mempill_core::noop::{NoOpOracle, NoOpVector};
use mempill_sqlite::{connection, open_default_in_memory, store::SqlitePersistenceStore};
use std::sync::Arc;
use mempill_types::{
AgentId, BeliefStatus, Cardinality, Confidence, Criticality,
Disposition, ExternalKind, ProvenanceLabel, ValidTime,
};
fn dt(rfc3339: &str) -> chrono::DateTime<Utc> {
chrono::DateTime::parse_from_rfc3339(rfc3339)
.unwrap()
.with_timezone(&Utc)
}
fn vt(start: &str, end: Option<&str>) -> ValidTime {
ValidTime {
start: Some(dt(start)),
end: end.map(dt),
valid_time_confidence: 0.9, start_granularity: None, end_granularity: None,
}
}
fn vt_low(start: &str, end: Option<&str>) -> ValidTime {
ValidTime {
start: Some(dt(start)),
end: end.map(dt),
valid_time_confidence: 0.5, start_granularity: None, end_granularity: None,
}
}
fn confident() -> Confidence {
Confidence { value_confidence: 0.9, valid_time_confidence: 0.9 }
}
#[tokio::test]
async fn succession_now() {
let engine = open_default_in_memory().unwrap();
let agent = AgentId("succ-now".into());
let r_alice = engine.ingest_claim(IngestClaimRequest {
agent_id: agent.clone(),
subject: "acme".into(),
predicate: "ceo".into(),
value: serde_json::json!("alice"),
provenance: ProvenanceLabel::External(ExternalKind::UserAsserted),
cardinality: Cardinality::Functional,
valid_time: Some(vt("2020-01-01T00:00:00Z", Some("2024-03-01T00:00:00Z"))),
confidence: confident(),
criticality: Criticality::Medium,
derived_from: vec![],
}).await.unwrap();
assert_eq!(r_alice.disposition, Disposition::CommittedCheap, "alice must be CommittedCheap");
let r_bob = engine.ingest_claim(IngestClaimRequest {
agent_id: agent.clone(),
subject: "acme".into(),
predicate: "ceo".into(),
value: serde_json::json!("bob"),
provenance: ProvenanceLabel::External(ExternalKind::UserAsserted),
cardinality: Cardinality::Functional,
valid_time: Some(vt("2024-03-01T00:00:00Z", None)),
confidence: confident(),
criticality: Criticality::Medium,
derived_from: vec![],
}).await.unwrap();
assert_eq!(r_bob.disposition, Disposition::CommittedCheap,
"succession ingest MUST be CommittedCheap (NOT Contested)");
let qr = engine.query_memory(QueryMemoryRequest {
agent_id: agent.clone(),
subject: "acme".into(),
predicate: "ceo".into(),
as_of_tx_time: None,
valid_at: None,
}).await.unwrap();
println!("[succession_now] status={:?}, primary={:?}", qr.belief.status,
qr.belief.primary.as_ref().map(|b| &b.fact.value));
assert_eq!(qr.belief.status, BeliefStatus::Resolved,
"NOW query MUST be Resolved (not Contested) — succession selected Bob");
assert!(qr.belief.primary.is_some(), "primary must be present");
assert_eq!(qr.belief.primary.unwrap().fact.value, serde_json::json!("bob"),
"primary at NOW MUST be Bob");
assert!(qr.belief.alternatives.is_empty(), "no alternatives in succession result");
}
#[tokio::test]
async fn succession_past_instant() {
let conn = connection::open_in_memory().unwrap();
let store = Arc::new(SqlitePersistenceStore::new(conn));
let agent = AgentId("succ-past".into());
let config = EngineConfig::default();
let ingest_uc = IngestClaimUseCase::new(
Arc::clone(&store),
None::<Arc<NoOpOracle>>,
None,
config.clone(),
);
let query_uc = QueryMemoryUseCase::new(
Arc::clone(&store),
None::<Arc<NoOpVector>>,
config.clone(),
);
let t_alice_tx = dt("2020-06-01T00:00:00Z");
let t_bob_tx = dt("2024-07-01T00:00:00Z");
let as_of = dt("2022-06-01T00:00:00Z");
ingest_uc.execute_with_time(IngestClaimRequest {
agent_id: agent.clone(),
subject: "widget".into(),
predicate: "owner".into(),
value: serde_json::json!("alice"),
provenance: ProvenanceLabel::External(ExternalKind::UserAsserted),
cardinality: Cardinality::Functional,
valid_time: Some(vt("2020-01-01T00:00:00Z", Some("2024-06-01T00:00:00Z"))),
confidence: confident(),
criticality: Criticality::Medium,
derived_from: vec![],
}, t_alice_tx).unwrap();
ingest_uc.execute_with_time(IngestClaimRequest {
agent_id: agent.clone(),
subject: "widget".into(),
predicate: "owner".into(),
value: serde_json::json!("bob"),
provenance: ProvenanceLabel::External(ExternalKind::UserAsserted),
cardinality: Cardinality::Functional,
valid_time: Some(vt("2024-06-01T00:00:00Z", None)),
confidence: confident(),
criticality: Criticality::Medium,
derived_from: vec![],
}, t_bob_tx).unwrap();
let query_now = t_bob_tx + chrono::Duration::days(30);
let qr_past = query_uc.execute_with_time(QueryMemoryRequest {
agent_id: agent.clone(),
subject: "widget".into(),
predicate: "owner".into(),
as_of_tx_time: Some(as_of),
valid_at: None,
}, query_now).unwrap();
println!("[succession_past_instant] status={:?}, primary={:?}",
qr_past.belief.status,
qr_past.belief.primary.as_ref().map(|b| &b.fact.value));
assert_eq!(qr_past.belief.status, BeliefStatus::Resolved,
"past instant (2022-06-01): Alice visible (tx=2020<=2022), Bob invisible (tx=2024>2022) → Resolved");
assert_eq!(
qr_past.belief.primary.as_ref().map(|b| b.fact.value.clone()),
Some(serde_json::json!("alice")),
"past instant in Alice's window [2020, 2024) → primary is Alice"
);
let qr_now = query_uc.execute_with_time(QueryMemoryRequest {
agent_id: agent.clone(),
subject: "widget".into(),
predicate: "owner".into(),
as_of_tx_time: None,
valid_at: None,
}, query_now).unwrap();
assert_eq!(qr_now.belief.status, BeliefStatus::Resolved,
"current view: both claims visible; Bob's open window → Resolved");
assert_eq!(
qr_now.belief.primary.as_ref().map(|b| b.fact.value.clone()),
Some(serde_json::json!("bob")),
"current view: Bob is the live claim (open-ended window)"
);
}
#[tokio::test]
async fn succession_boundary() {
let engine = open_default_in_memory().unwrap();
let agent = AgentId("succ-boundary".into());
engine.ingest_claim(IngestClaimRequest {
agent_id: agent.clone(),
subject: "corp".into(),
predicate: "ceo".into(),
value: serde_json::json!("alice"),
provenance: ProvenanceLabel::External(ExternalKind::UserAsserted),
cardinality: Cardinality::Functional,
valid_time: Some(vt("2020-01-01T00:00:00Z", Some("2024-03-01T00:00:00Z"))),
confidence: confident(),
criticality: Criticality::Medium,
derived_from: vec![],
}).await.unwrap();
let r_bob = engine.ingest_claim(IngestClaimRequest {
agent_id: agent.clone(),
subject: "corp".into(),
predicate: "ceo".into(),
value: serde_json::json!("bob"),
provenance: ProvenanceLabel::External(ExternalKind::UserAsserted),
cardinality: Cardinality::Functional,
valid_time: Some(vt("2024-03-01T00:00:00Z", None)),
confidence: confident(),
criticality: Criticality::Medium,
derived_from: vec![],
}).await.unwrap();
assert_eq!(r_bob.disposition, Disposition::CommittedCheap, "succession must be CommittedCheap");
let qr = engine.query_memory(QueryMemoryRequest {
agent_id: agent.clone(),
subject: "corp".into(),
predicate: "ceo".into(),
as_of_tx_time: None,
valid_at: None,
}).await.unwrap();
assert_eq!(qr.belief.status, BeliefStatus::Resolved, "boundary: NOW selects Bob");
assert_eq!(
qr.belief.primary.unwrap().fact.value, serde_json::json!("bob"),
"boundary: NOW selects Bob (his window starts at Mar 1, inclusive)"
);
}
#[tokio::test]
async fn succession_gap() {
let engine = open_default_in_memory().unwrap();
let agent = AgentId("succ-gap".into());
engine.ingest_claim(IngestClaimRequest {
agent_id: agent.clone(),
subject: "firm".into(),
predicate: "cfo".into(),
value: serde_json::json!("alice"),
provenance: ProvenanceLabel::External(ExternalKind::UserAsserted),
cardinality: Cardinality::Functional,
valid_time: Some(vt("2020-01-01T00:00:00Z", Some("2023-01-01T00:00:00Z"))),
confidence: confident(),
criticality: Criticality::Medium,
derived_from: vec![],
}).await.unwrap();
let r_bob = engine.ingest_claim(IngestClaimRequest {
agent_id: agent.clone(),
subject: "firm".into(),
predicate: "cfo".into(),
value: serde_json::json!("bob"),
provenance: ProvenanceLabel::External(ExternalKind::UserAsserted),
cardinality: Cardinality::Functional,
valid_time: Some(vt("2024-01-01T00:00:00Z", None)),
confidence: confident(),
criticality: Criticality::Medium,
derived_from: vec![],
}).await.unwrap();
assert_eq!(r_bob.disposition, Disposition::CommittedCheap,
"gap scenario: non-overlapping windows → CommittedCheap (succession)");
let qr_now = engine.query_memory(QueryMemoryRequest {
agent_id: agent.clone(),
subject: "firm".into(),
predicate: "cfo".into(),
as_of_tx_time: None,
valid_at: None,
}).await.unwrap();
assert_eq!(qr_now.belief.status, BeliefStatus::Resolved, "NOW in Bob's window → Resolved");
assert_eq!(qr_now.belief.primary.unwrap().fact.value, serde_json::json!("bob"));
println!("[succession_gap] PASS: non-overlapping with gap → succession; NOW selects Bob");
}
#[tokio::test]
async fn succession_n_chain() {
let engine = open_default_in_memory().unwrap();
let agent = AgentId("succ-chain".into());
let r_a = engine.ingest_claim(IngestClaimRequest {
agent_id: agent.clone(),
subject: "org".into(),
predicate: "cto".into(),
value: serde_json::json!("alpha"),
provenance: ProvenanceLabel::External(ExternalKind::UserAsserted),
cardinality: Cardinality::Functional,
valid_time: Some(vt("2019-01-01T00:00:00Z", Some("2021-01-01T00:00:00Z"))),
confidence: confident(),
criticality: Criticality::Medium,
derived_from: vec![],
}).await.unwrap();
assert_eq!(r_a.disposition, Disposition::CommittedCheap);
let r_b = engine.ingest_claim(IngestClaimRequest {
agent_id: agent.clone(),
subject: "org".into(),
predicate: "cto".into(),
value: serde_json::json!("beta"),
provenance: ProvenanceLabel::External(ExternalKind::UserAsserted),
cardinality: Cardinality::Functional,
valid_time: Some(vt("2021-01-01T00:00:00Z", Some("2023-01-01T00:00:00Z"))),
confidence: confident(),
criticality: Criticality::Medium,
derived_from: vec![],
}).await.unwrap();
assert_eq!(r_b.disposition, Disposition::CommittedCheap,
"B in 3-chain must be CommittedCheap (succession)");
let r_c = engine.ingest_claim(IngestClaimRequest {
agent_id: agent.clone(),
subject: "org".into(),
predicate: "cto".into(),
value: serde_json::json!("gamma"),
provenance: ProvenanceLabel::External(ExternalKind::UserAsserted),
cardinality: Cardinality::Functional,
valid_time: Some(vt("2023-01-01T00:00:00Z", None)),
confidence: confident(),
criticality: Criticality::Medium,
derived_from: vec![],
}).await.unwrap();
assert_eq!(r_c.disposition, Disposition::CommittedCheap,
"C in 3-chain must be CommittedCheap (succession)");
let qr = engine.query_memory(QueryMemoryRequest {
agent_id: agent.clone(),
subject: "org".into(),
predicate: "cto".into(),
as_of_tx_time: None,
valid_at: None,
}).await.unwrap();
assert_eq!(qr.belief.status, BeliefStatus::Resolved, "3-chain: NOW → Resolved");
assert_eq!(qr.belief.primary.unwrap().fact.value, serde_json::json!("gamma"),
"3-chain: NOW selects gamma (C's window)");
}
#[tokio::test]
async fn overlapping_is_conflict() {
let engine = open_default_in_memory().unwrap();
let agent = AgentId("succ-overlap".into());
engine.ingest_claim(IngestClaimRequest {
agent_id: agent.clone(),
subject: "bank".into(),
predicate: "ceo".into(),
value: serde_json::json!("alice"),
provenance: ProvenanceLabel::External(ExternalKind::UserAsserted),
cardinality: Cardinality::Functional,
valid_time: Some(vt("2020-01-01T00:00:00Z", Some("2025-01-01T00:00:00Z"))),
confidence: confident(),
criticality: Criticality::Medium,
derived_from: vec![],
}).await.unwrap();
let r_bob = engine.ingest_claim(IngestClaimRequest {
agent_id: agent.clone(),
subject: "bank".into(),
predicate: "ceo".into(),
value: serde_json::json!("bob"),
provenance: ProvenanceLabel::External(ExternalKind::UserAsserted),
cardinality: Cardinality::Functional,
valid_time: Some(vt("2024-01-01T00:00:00Z", None)),
confidence: confident(),
criticality: Criticality::Medium,
derived_from: vec![],
}).await.unwrap();
assert_eq!(r_bob.disposition, Disposition::Contested,
"overlapping windows (both confident) MUST be Contested (genuine conflict)");
let qr = engine.query_memory(QueryMemoryRequest {
agent_id: agent.clone(),
subject: "bank".into(),
predicate: "ceo".into(),
as_of_tx_time: None,
valid_at: None,
}).await.unwrap();
assert_eq!(qr.belief.status, BeliefStatus::Contested,
"overlapping confident windows MUST surface as Contested");
println!("[overlapping_is_conflict] PASS: overlapping → Contested");
}
#[tokio::test]
async fn low_confidence_is_conflict() {
let engine = open_default_in_memory().unwrap();
let agent = AgentId("succ-lowconf".into());
engine.ingest_claim(IngestClaimRequest {
agent_id: agent.clone(),
subject: "startup".into(),
predicate: "ceo".into(),
value: serde_json::json!("alice"),
provenance: ProvenanceLabel::External(ExternalKind::UserAsserted),
cardinality: Cardinality::Functional,
valid_time: Some(vt_low("2020-01-01T00:00:00Z", Some("2024-03-01T00:00:00Z"))),
confidence: Confidence { value_confidence: 0.9, valid_time_confidence: 0.5 },
criticality: Criticality::Medium,
derived_from: vec![],
}).await.unwrap();
let r_bob = engine.ingest_claim(IngestClaimRequest {
agent_id: agent.clone(),
subject: "startup".into(),
predicate: "ceo".into(),
value: serde_json::json!("bob"),
provenance: ProvenanceLabel::External(ExternalKind::UserAsserted),
cardinality: Cardinality::Functional,
valid_time: Some(vt_low("2024-03-01T00:00:00Z", None)),
confidence: Confidence { value_confidence: 0.9, valid_time_confidence: 0.5 },
criticality: Criticality::Medium,
derived_from: vec![],
}).await.unwrap();
assert_eq!(r_bob.disposition, Disposition::Contested,
"low-confidence valid_time (0.5 < 0.7) MUST be Contested (I2 fallback, not succession)");
let qr = engine.query_memory(QueryMemoryRequest {
agent_id: agent.clone(),
subject: "startup".into(),
predicate: "ceo".into(),
as_of_tx_time: None,
valid_at: None,
}).await.unwrap();
assert_eq!(qr.belief.status, BeliefStatus::Contested,
"low-confidence valid_time MUST surface as Contested");
println!("[low_confidence_is_conflict] PASS: confidence 0.5 < 0.7 threshold → Contested");
}
#[tokio::test]
async fn no_valid_time_regression() {
let engine = open_default_in_memory().unwrap();
let agent = AgentId("succ-novt".into());
engine.ingest_claim(IngestClaimRequest {
agent_id: agent.clone(),
subject: "co".into(),
predicate: "ceo".into(),
value: serde_json::json!("alice"),
provenance: ProvenanceLabel::External(ExternalKind::UserAsserted),
cardinality: Cardinality::Functional,
valid_time: None,
confidence: Confidence { value_confidence: 0.9, valid_time_confidence: 0.0 },
criticality: Criticality::Medium,
derived_from: vec![],
}).await.unwrap();
let r_bob = engine.ingest_claim(IngestClaimRequest {
agent_id: agent.clone(),
subject: "co".into(),
predicate: "ceo".into(),
value: serde_json::json!("bob"),
provenance: ProvenanceLabel::External(ExternalKind::UserAsserted),
cardinality: Cardinality::Functional,
valid_time: None,
confidence: Confidence { value_confidence: 0.9, valid_time_confidence: 0.0 },
criticality: Criticality::Medium,
derived_from: vec![],
}).await.unwrap();
assert_eq!(r_bob.disposition, Disposition::Contested,
"no valid_time → existing behavior unchanged → Contested (I2 regression guard)");
let qr = engine.query_memory(QueryMemoryRequest {
agent_id: agent.clone(),
subject: "co".into(),
predicate: "ceo".into(),
as_of_tx_time: None,
valid_at: None,
}).await.unwrap();
assert_eq!(qr.belief.status, BeliefStatus::Contested,
"no valid_time → Contested (existing behavior preserved)");
println!("[no_valid_time_regression] PASS: no valid_time → Contested unchanged");
}
#[tokio::test]
async fn n_gt_1_incumbent() {
let engine = open_default_in_memory().unwrap();
let agent = AgentId("succ-ngt1".into());
engine.ingest_claim(IngestClaimRequest {
agent_id: agent.clone(),
subject: "firm".into(),
predicate: "ceo".into(),
value: serde_json::json!("alice"),
provenance: ProvenanceLabel::External(ExternalKind::UserAsserted),
cardinality: Cardinality::Functional,
valid_time: None,
confidence: Confidence { value_confidence: 0.9, valid_time_confidence: 0.0 },
criticality: Criticality::Medium,
derived_from: vec![],
}).await.unwrap();
engine.ingest_claim(IngestClaimRequest {
agent_id: agent.clone(),
subject: "firm".into(),
predicate: "ceo".into(),
value: serde_json::json!("bob"),
provenance: ProvenanceLabel::External(ExternalKind::UserAsserted),
cardinality: Cardinality::Functional,
valid_time: None,
confidence: Confidence { value_confidence: 0.9, valid_time_confidence: 0.0 },
criticality: Criticality::Medium,
derived_from: vec![],
}).await.unwrap();
let r_carol = engine.ingest_claim(IngestClaimRequest {
agent_id: agent.clone(),
subject: "firm".into(),
predicate: "ceo".into(),
value: serde_json::json!("carol"),
provenance: ProvenanceLabel::External(ExternalKind::UserAsserted),
cardinality: Cardinality::Functional,
valid_time: Some(vt("2024-01-01T00:00:00Z", None)),
confidence: confident(),
criticality: Criticality::Medium,
derived_from: vec![],
}).await.unwrap();
assert_eq!(r_carol.disposition, Disposition::Contested,
"N>1 live incumbents → succession check SKIPPED → Contested (resolution #2)");
println!("[n_gt_1_incumbent] PASS: N>1 incumbents → succession skipped → Contested");
}