use chrono::{DateTime, TimeZone, Utc};
use cortex_core::{
compose_policy_outcomes, AuditRecordId, BreakGlassAuthorization, BreakGlassReasonCode,
BreakGlassScope, MemoryId, PolicyContribution, PolicyOutcome,
};
use cortex_store::migrate::apply_pending;
use cortex_store::repo::memories::{
accept_candidate_policy_decision_test_allow, ACCEPT_OPEN_CONTRADICTION_RULE_ID,
ACCEPT_OPERATOR_TEMPORAL_USE_RULE_ID, ACCEPT_PROOF_CLOSURE_RULE_ID,
ACCEPT_SEMANTIC_TRUST_RULE_ID,
};
use cortex_store::repo::{MemoryAcceptanceAudit, MemoryCandidate, MemoryRepo};
use cortex_store::Pool;
use rusqlite::Connection;
use serde_json::json;
fn test_pool() -> Pool {
let pool = Connection::open_in_memory().expect("open in-memory sqlite");
apply_pending(&pool).expect("apply migrations");
pool
}
fn at(second: u32) -> DateTime<Utc> {
Utc.with_ymd_and_hms(2026, 1, 1, 12, 0, second).unwrap()
}
fn memory_id() -> MemoryId {
"mem_01ARZ3NDEKTSV4RRFFQ69G5FAV".parse().unwrap()
}
fn candidate() -> MemoryCandidate {
MemoryCandidate {
id: memory_id(),
memory_type: "semantic".into(),
claim: "Candidate awaits operator-attested acceptance.".into(),
source_episodes_json: json!([]),
source_events_json: json!(["evt_01ARZ3NDEKTSV4RRFFQ69G5FAV"]),
domains_json: json!(["accept-policy"]),
salience_json: json!({"score": 0.5}),
confidence: 0.7,
authority: "candidate".into(),
applies_when_json: json!([]),
does_not_apply_when_json: json!([]),
created_at: at(1),
updated_at: at(1),
}
}
fn audit_row() -> MemoryAcceptanceAudit {
MemoryAcceptanceAudit {
id: AuditRecordId::new(),
actor_json: json!({"kind": "test"}),
reason: "accept policy lattice test".into(),
source_refs_json: json!(["evt_01ARZ3NDEKTSV4RRFFQ69G5FAV"]),
created_at: at(2),
}
}
fn seed_candidate(pool: &Pool) {
MemoryRepo::new(pool)
.insert_candidate(&candidate())
.expect("seed candidate row");
}
fn candidate_status(pool: &Pool) -> Option<String> {
pool.query_row(
"SELECT status FROM memories WHERE id = ?1;",
[memory_id().to_string()],
|row| row.get(0),
)
.ok()
}
fn accept_audit_count(pool: &Pool) -> i64 {
pool.query_row(
"SELECT COUNT(*) FROM audit_records WHERE operation = 'memory.accept' AND target_ref = ?1;",
[memory_id().to_string()],
|row| row.get(0),
)
.expect("count memory.accept audit rows")
}
#[test]
fn accept_candidate_refuses_partial_proof_closure_without_mutation() {
let pool = test_pool();
let repo = MemoryRepo::new(&pool);
seed_candidate(&pool);
let quarantine_decision = compose_policy_outcomes(
vec![
PolicyContribution::new(
ACCEPT_PROOF_CLOSURE_RULE_ID,
PolicyOutcome::Quarantine,
"supporting memory has partial proof closure",
)
.unwrap(),
PolicyContribution::new(
ACCEPT_OPEN_CONTRADICTION_RULE_ID,
PolicyOutcome::Allow,
"no open durable contradiction on candidate slot",
)
.unwrap(),
PolicyContribution::new(
ACCEPT_SEMANTIC_TRUST_RULE_ID,
PolicyOutcome::Allow,
"semantic trust posture satisfied",
)
.unwrap(),
PolicyContribution::new(
ACCEPT_OPERATOR_TEMPORAL_USE_RULE_ID,
PolicyOutcome::Allow,
"operator temporal authority is currently valid",
)
.unwrap(),
],
None,
);
assert_eq!(quarantine_decision.final_outcome, PolicyOutcome::Quarantine);
let err = repo
.accept_candidate(&memory_id(), at(3), &audit_row(), &quarantine_decision)
.expect_err("Quarantine policy must fail closed");
assert!(
err.to_string().contains("Quarantine"),
"error must name the blocking outcome: {err}"
);
assert_eq!(
candidate_status(&pool).as_deref(),
Some("candidate"),
"candidate status must not flip when policy fails closed"
);
assert_eq!(
accept_audit_count(&pool),
0,
"no memory.accept audit row may be written when policy fails closed"
);
}
#[test]
fn accept_candidate_refuses_open_contradiction_without_mutation() {
let pool = test_pool();
let repo = MemoryRepo::new(&pool);
seed_candidate(&pool);
let reject_decision = compose_policy_outcomes(
vec![
PolicyContribution::new(
ACCEPT_PROOF_CLOSURE_RULE_ID,
PolicyOutcome::Allow,
"supporting memory proof closure verified",
)
.unwrap(),
PolicyContribution::new(
ACCEPT_OPEN_CONTRADICTION_RULE_ID,
PolicyOutcome::Reject,
"open durable contradiction touches candidate slot",
)
.unwrap(),
PolicyContribution::new(
ACCEPT_SEMANTIC_TRUST_RULE_ID,
PolicyOutcome::Allow,
"semantic trust posture satisfied",
)
.unwrap(),
PolicyContribution::new(
ACCEPT_OPERATOR_TEMPORAL_USE_RULE_ID,
PolicyOutcome::Allow,
"operator temporal authority is currently valid",
)
.unwrap(),
],
None,
);
assert_eq!(reject_decision.final_outcome, PolicyOutcome::Reject);
let err = repo
.accept_candidate(&memory_id(), at(3), &audit_row(), &reject_decision)
.expect_err("Reject policy must fail closed");
assert!(
err.to_string().contains("Reject"),
"error must name the blocking outcome: {err}"
);
assert_eq!(
candidate_status(&pool).as_deref(),
Some("candidate"),
"candidate status must not flip when policy fails closed"
);
assert_eq!(
accept_audit_count(&pool),
0,
"no memory.accept audit row may be written when policy fails closed"
);
}
#[test]
fn accept_candidate_refuses_missing_proof_closure_contributor() {
let pool = test_pool();
let repo = MemoryRepo::new(&pool);
seed_candidate(&pool);
let decision = compose_policy_outcomes(
vec![
PolicyContribution::new(
ACCEPT_OPEN_CONTRADICTION_RULE_ID,
PolicyOutcome::Allow,
"no open durable contradiction on candidate slot",
)
.unwrap(),
PolicyContribution::new(
ACCEPT_SEMANTIC_TRUST_RULE_ID,
PolicyOutcome::Allow,
"semantic trust posture satisfied",
)
.unwrap(),
PolicyContribution::new(
ACCEPT_OPERATOR_TEMPORAL_USE_RULE_ID,
PolicyOutcome::Allow,
"operator temporal authority is currently valid",
)
.unwrap(),
],
None,
);
let err = repo
.accept_candidate(&memory_id(), at(3), &audit_row(), &decision)
.expect_err("missing proof closure contributor must fail closed");
assert!(
err.to_string().contains(ACCEPT_PROOF_CLOSURE_RULE_ID),
"error must name the missing contributor: {err}"
);
assert_eq!(candidate_status(&pool).as_deref(), Some("candidate"));
}
#[test]
fn accept_candidate_refuses_missing_operator_temporal_use_contributor() {
let pool = test_pool();
let repo = MemoryRepo::new(&pool);
seed_candidate(&pool);
let decision = compose_policy_outcomes(
vec![
PolicyContribution::new(
ACCEPT_PROOF_CLOSURE_RULE_ID,
PolicyOutcome::Allow,
"proof closure verified",
)
.unwrap(),
PolicyContribution::new(
ACCEPT_OPEN_CONTRADICTION_RULE_ID,
PolicyOutcome::Allow,
"no open contradiction",
)
.unwrap(),
PolicyContribution::new(
ACCEPT_SEMANTIC_TRUST_RULE_ID,
PolicyOutcome::Allow,
"semantic trust satisfied",
)
.unwrap(),
],
None,
);
let err = repo
.accept_candidate(&memory_id(), at(3), &audit_row(), &decision)
.expect_err("missing operator-temporal-use contributor must fail closed");
assert!(
err.to_string()
.contains(ACCEPT_OPERATOR_TEMPORAL_USE_RULE_ID),
"error must name the missing contributor: {err}"
);
}
#[test]
fn accept_candidate_refuses_break_glass_substituting_for_temporal_authority() {
let pool = test_pool();
let repo = MemoryRepo::new(&pool);
seed_candidate(&pool);
let scope = BreakGlassScope {
operation_type: "memory.accept".into(),
artifact_refs: vec![memory_id().to_string()],
not_before: None,
not_after: None,
};
let break_glass = BreakGlassAuthorization {
permitted: true,
attested: true,
scope,
reason_code: BreakGlassReasonCode::OperatorCorrection,
};
let decision = compose_policy_outcomes(
vec![
PolicyContribution::new(
ACCEPT_PROOF_CLOSURE_RULE_ID,
PolicyOutcome::Allow,
"proof closure verified",
)
.unwrap(),
PolicyContribution::new(
ACCEPT_OPEN_CONTRADICTION_RULE_ID,
PolicyOutcome::Allow,
"no open contradiction",
)
.unwrap(),
PolicyContribution::new(
ACCEPT_SEMANTIC_TRUST_RULE_ID,
PolicyOutcome::Allow,
"semantic trust satisfied",
)
.unwrap(),
PolicyContribution::new(
ACCEPT_OPERATOR_TEMPORAL_USE_RULE_ID,
PolicyOutcome::Quarantine,
"operator temporal authority is historical only",
)
.unwrap()
.allow_break_glass_override(),
PolicyContribution::new(
"operator.override",
PolicyOutcome::BreakGlass,
"operator override requested",
)
.unwrap(),
],
Some(break_glass),
);
let err = repo
.accept_candidate(&memory_id(), at(3), &audit_row(), &decision)
.expect_err("BreakGlass cannot substitute for current-use temporal authority");
let message = err.to_string();
assert!(
message.contains(ACCEPT_OPERATOR_TEMPORAL_USE_RULE_ID)
|| message.contains("Quarantine")
|| message.contains("BreakGlass"),
"error must surface the §4 wall: {message}"
);
assert_eq!(candidate_status(&pool).as_deref(), Some("candidate"));
}
#[test]
fn accept_candidate_accepts_properly_composed_allow_decision() {
let pool = test_pool();
let repo = MemoryRepo::new(&pool);
seed_candidate(&pool);
let policy = accept_candidate_policy_decision_test_allow();
let accepted = repo
.accept_candidate(&memory_id(), at(3), &audit_row(), &policy)
.expect("Allow decision must be accepted");
assert_eq!(accepted.status, "active");
assert_eq!(candidate_status(&pool).as_deref(), Some("active"));
assert_eq!(
accept_audit_count(&pool),
1,
"exactly one memory.accept audit row must be written on success"
);
}