use chrono::Utc;
use mempill_core::application::{IngestClaimRequest, QueryMemoryRequest};
use mempill_sqlite::open_default_in_memory;
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, }
}
fn vt_low(start: &str, end: Option<&str>) -> ValidTime {
ValidTime {
start: Some(dt(start)),
end: end.map(dt),
valid_time_confidence: 0.5, }
}
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,
}).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 engine = open_default_in_memory().unwrap();
let agent = AgentId("succ-past".into());
engine.ingest_claim(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![],
}).await.unwrap();
engine.ingest_claim(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![],
}).await.unwrap();
let qr_past = engine.query_memory(QueryMemoryRequest {
agent_id: agent.clone(),
subject: "widget".into(),
predicate: "owner".into(),
as_of_tx_time: Some(dt("2022-06-01T00:00:00Z")),
}).await.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) in Alice's window → Resolved");
assert_eq!(
qr_past.belief.primary.unwrap().fact.value,
serde_json::json!("alice"),
"past instant in Alice's window [2020, 2024) → primary is Alice"
);
}
#[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,
}).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,
}).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,
}).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,
}).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,
}).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,
}).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");
}