use cortex_reflect::{
parse_principle_candidates, parse_reflection, principle_candidate_batch_json_schema,
quarantine_record, session_reflection_json_schema, MemoryType, ReflectError,
REFLECTION_QUARANTINE_OPERATION,
};
use cortex_store::migrate::apply_pending;
use cortex_store::repo::AuditRepo;
use rusqlite::Connection;
const VALID_REFLECTION: &str = r#"{
"trace_id": "trc_01ARZ3NDEKTSV4RRFFQ69G5FAV",
"episode_candidates": [
{
"summary": "User corrected reflection behavior",
"source_event_ids": ["evt_01ARZ3NDEKTSV4RRFFQ69G5FAV"],
"domains": ["agents"],
"entities": ["Cortex"],
"candidate_meaning": null,
"confidence": 0.7
}
],
"memory_candidates": [
{
"memory_type": "correction",
"claim": "Invalid reflection JSON must be quarantined.",
"source_episode_indexes": [0],
"applies_when": ["parsing model output"],
"does_not_apply_when": ["input is trusted typed data"],
"confidence": 0.8,
"initial_salience": {
"reusability": 0.9,
"consequence": 0.7,
"emotional_charge": 0.1
}
}
],
"contradictions": [],
"doctrine_suggestions": []
}"#;
#[test]
fn valid_reflection_parses() {
let reflection = parse_reflection(VALID_REFLECTION).expect("valid reflection parses");
assert_eq!(reflection.episode_candidates.len(), 1);
assert_eq!(reflection.memory_candidates.len(), 1);
assert_eq!(
reflection.memory_candidates[0].memory_type,
MemoryType::Correction
);
}
#[test]
fn invalid_json_is_quarantine_ready() {
let raw = r#"{"trace_id":"trc_01ARZ3NDEKTSV4RRFFQ69G5FAV""#;
let err = parse_reflection(raw).expect_err("invalid JSON is rejected");
assert!(matches!(err, ReflectError::InvalidJson { .. }));
assert_eq!(err.quarantine_payload(), raw);
assert_eq!(err.quarantine_reason(), "invalid_json");
}
#[test]
fn invalid_reflection_writes_one_quarantine_audit_row() {
let pool = Connection::open_in_memory().expect("open in-memory sqlite");
apply_pending(&pool).expect("apply store migrations");
let raw = r#"{"trace_id":"trc_01ARZ3NDEKTSV4RRFFQ69G5FAV""#;
let err = parse_reflection(raw).expect_err("invalid JSON is rejected");
let entry = quarantine_record(err.quarantine_payload(), err.quarantine_reason(), &pool)
.expect("append quarantine audit row");
let entries = AuditRepo::new(&pool)
.list_by_target_ref(&entry.target_ref)
.expect("read quarantine audit rows");
assert_eq!(entries.len(), 1);
assert_eq!(entries[0].operation, REFLECTION_QUARANTINE_OPERATION);
assert_eq!(entries[0].reason, "invalid_json");
assert!(entries[0].target_ref.starts_with("reflection_output:"));
assert_eq!(entries[0].after_hash.len(), 64);
assert_ne!(entries[0].source_refs_json.to_string(), raw);
}
#[test]
fn invalid_schema_is_quarantine_ready() {
let raw = VALID_REFLECTION.replace(
"evt_01ARZ3NDEKTSV4RRFFQ69G5FAV",
"trc_01ARZ3NDEKTSV4RRFFQ69G5FAV",
);
let err = parse_reflection(&raw).expect_err("wrong source ID type is rejected");
assert!(matches!(err, ReflectError::InvalidSchema { .. }));
assert_eq!(err.quarantine_payload(), raw);
assert_eq!(err.quarantine_reason(), "invalid_schema");
assert!(err.detail().contains("EventId") || err.detail().contains("expected prefix"));
}
#[test]
fn out_of_range_confidence_is_quarantine_ready() {
let raw = VALID_REFLECTION.replace("\"confidence\": 0.8", "\"confidence\": 1.8");
let err = parse_reflection(&raw).expect_err("out-of-range confidence is rejected");
assert!(matches!(err, ReflectError::InvalidSchema { .. }));
assert_eq!(err.quarantine_payload(), raw);
assert!(err.detail().contains("confidence"));
}
#[test]
fn missing_episode_index_is_quarantine_ready() {
let raw = VALID_REFLECTION.replace(
"\"source_episode_indexes\": [0]",
"\"source_episode_indexes\": [1]",
);
let err = parse_reflection(&raw).expect_err("missing episode index is rejected");
assert!(matches!(err, ReflectError::InvalidSchema { .. }));
assert_eq!(err.quarantine_payload(), raw);
assert!(err.detail().contains("source_episode_indexes"));
}
#[test]
fn principle_candidate_batch_parses() {
let raw = r#"{
"candidate_principles": [
{
"statement": "Prefer evidence-bound parser failures.",
"supporting_memory_ids": ["mem_01ARZ3NDEKTSV4RRFFQ69G5FAV"],
"contradicting_memory_ids": [],
"domains_observed": ["agents"],
"applies_when": ["building parser boundaries"],
"does_not_apply_when": ["runtime evidence is unavailable"],
"alternative_interpretations": ["This is only a local parser preference."],
"confidence": 0.6,
"overgeneralisation_risk": 0.2
}
]
}"#;
let batch = parse_principle_candidates(raw).expect("valid principles parse");
assert_eq!(batch.candidate_principles.len(), 1);
}
#[test]
fn schema_export_matches_checked_in_json_schema() {
let expected: serde_json::Value =
serde_json::from_str(include_str!("../session_reflection.schema.json"))
.expect("checked-in SessionReflection schema is valid JSON");
assert_eq!(session_reflection_json_schema(), expected);
}
#[test]
fn principle_schema_export_contains_candidate_principles() {
let schema = principle_candidate_batch_json_schema();
assert_eq!(schema["title"], "PrincipleCandidateBatch");
assert!(schema["properties"]
.as_object()
.expect("properties object")
.contains_key("candidate_principles"));
}