use std::sync::Arc;
use exocortex_kernel::{Memory, MemoryContext, MemoryId, Provenance, Visibility, LSN};
use exocortex_server::corpus_export::export_corpus;
use exocortex_storage::{InMemoryStorage, Storage};
fn ontology() -> Arc<exocortex_kernel::Ontology> {
Arc::new(
exocortex_kernel::Ontology::from_packs(vec![exocortex_pack_dev_v1::pack_def()]).unwrap(),
)
}
fn memory(
key: &str,
_type_name: &str,
recorded: chrono::DateTime<chrono::Utc>,
valid_from: chrono::DateTime<chrono::Utc>,
valid_until: Option<chrono::DateTime<chrono::Utc>>,
) -> Memory {
Memory {
id: MemoryId::new_v7(),
memory_type: 0,
title: key.into(),
content: format!("content {key}"),
summary: None,
tags: Default::default(),
visibility: Visibility::Org,
provenance: Provenance::Asserted {
author: "corpus-test".into(),
producer_kind: None,
},
context: MemoryContext {
timestamp: recorded,
project_id: None,
project_path: None,
team_id: None,
tenant_id: Some("org".into()),
session_id: None,
user_id: None,
created_by: None,
files_involved: Default::default(),
languages: Default::default(),
frameworks: Default::default(),
technologies: Default::default(),
git_commit: None,
git_branch: None,
working_directory: None,
entities: Default::default(),
additional_metadata: serde_json::Value::Null,
},
embedding: None,
importance: exocortex_kernel::memory::F01::new(0.5).unwrap(),
confidence: exocortex_kernel::memory::F01::new(0.8).unwrap(),
effectiveness: None,
usage_count: 0,
valid_from,
valid_until,
recorded_at: recorded,
invalidated_by: None,
lsn: LSN::new_local(1),
}
}
fn ts(year: i32) -> chrono::DateTime<chrono::Utc> {
chrono::DateTime::parse_from_rfc3339(&format!("{year}-06-01T00:00:00Z"))
.unwrap()
.with_timezone(&chrono::Utc)
}
#[tokio::test]
async fn corpus_cut_is_temporally_clean_and_carries_lineage() {
let onto = ontology();
let storage = InMemoryStorage::new(onto.clone());
let cut = ts(2026);
let live = memory("live", "Fix", ts(2024), ts(2024), None);
let future = memory("future", "Fix", ts(2027), ts(2027), None);
let stale = memory("stale", "Fix", ts(2023), ts(2023), Some(ts(2025)));
let pending = memory("pending", "Fix", ts(2025), ts(2027), None);
let fix_id = onto.memory_type_id("Fix").unwrap();
let mut live = live;
live.memory_type = fix_id;
let mut future = future;
future.memory_type = fix_id;
let mut stale = stale;
stale.memory_type = fix_id;
let mut pending = pending;
pending.memory_type = fix_id;
storage.upsert_memory(&live).await.unwrap();
storage.upsert_memory(&future).await.unwrap();
storage.upsert_memory(&stale).await.unwrap();
storage.upsert_memory(&pending).await.unwrap();
let mut external = memory("external-row", "Fix", ts(2024), ts(2024), None);
external.memory_type = fix_id;
external.provenance = Provenance::ExternalSnapshot(exocortex_kernel::ExternalSnapshot {
source_uri: "iceberg://lake/events".into(),
snapshot_id: "snap-1".into(),
schema_hash: [1u8; 32],
observed_at: ts(2024),
external_key: exocortex_kernel::ExternalKey {
table_uuid: "0102030405060708090a0b0c0d0e0f10".into(),
logical_pk: b"pk-7".to_vec(),
mapping_version: 1,
},
producer_id: "table-adapter".into(),
});
storage.upsert_memory(&external).await.unwrap();
let kind = onto.kind_id("RelatedTo").unwrap();
let both = exocortex_kernel::Relationship {
id: exocortex_kernel::RelationshipId::derive(live.id, kind, external.id, None),
kind,
from: live.id,
to: external.id,
visibility: Visibility::Org,
provenance: Provenance::Asserted {
author: "corpus-test".into(),
producer_kind: None,
},
properties: exocortex_kernel::relationship::RelationshipProperties {
strength: 0.5,
confidence: 0.5,
context: None,
evidence_count: 0,
success_rate: None,
validation_count: 0,
counter_evidence_count: 0,
last_validated: ts(2024),
},
description: None,
bidirectional: false,
valid_from: ts(2024),
valid_until: None,
recorded_at: ts(2024),
invalidated_by: None,
lsn: LSN::new_local(2),
};
let dangling = exocortex_kernel::Relationship {
id: exocortex_kernel::RelationshipId::derive(live.id, kind, future.id, None),
kind,
from: live.id,
to: future.id,
..both.clone()
};
storage.upsert_relationship(&both).await.unwrap();
storage.upsert_relationship(&dangling).await.unwrap();
let dir = tempfile::tempdir().unwrap();
let manifest = export_corpus(&storage, &onto, Some(cut), dir.path())
.await
.unwrap();
assert_eq!(manifest.memories, 2, "live + external-row made the cut");
assert_eq!(manifest.edges, 1, "only the fully-anchored edge exports");
assert_eq!(manifest.as_of.as_deref(), Some(cut.to_rfc3339()).as_deref());
let memories: Vec<Memory> = std::fs::read_to_string(dir.path().join("memories.jsonl"))
.unwrap()
.lines()
.map(|line| serde_json::from_str(line).unwrap())
.collect();
let titles: Vec<&str> = memories.iter().map(|m| m.title.as_ref()).collect();
assert!(titles.contains(&"live"), "{titles:?}");
assert!(titles.contains(&"external-row"), "{titles:?}");
assert!(!titles.contains(&"future"), "no future leakage: {titles:?}");
assert!(
!titles.contains(&"stale"),
"superseded before the cut: {titles:?}"
);
assert!(!titles.contains(&"pending"), "not yet believed: {titles:?}");
let edges: Vec<serde_json::Value> = std::fs::read_to_string(dir.path().join("edges.jsonl"))
.unwrap()
.lines()
.map(|line| serde_json::from_str(line).unwrap())
.collect();
assert_eq!(edges.len(), 1);
let lineage: Vec<serde_json::Value> = std::fs::read_to_string(dir.path().join("lineage.jsonl"))
.unwrap()
.lines()
.map(|line| serde_json::from_str(line).unwrap())
.collect();
assert_eq!(lineage.len(), 2);
let external_lineage = lineage
.iter()
.find(|row| row["provenance"] == "external-snapshot")
.expect("external row lineage");
assert!(
external_lineage["external_key"]
.as_str()
.unwrap()
.starts_with("0102030405060708090a0b0c0d0e0f10:pk-7"),
"raw external coordinates, not a digest"
);
assert_eq!(external_lineage["source"], "iceberg://lake/events");
let live_lineage = lineage
.iter()
.find(|row| row["provenance"] == "asserted")
.expect("asserted row lineage");
assert_eq!(live_lineage["source"], "corpus-test");
let manifest_doc: serde_json::Value =
serde_json::from_str(&std::fs::read_to_string(dir.path().join("manifest.json")).unwrap())
.unwrap();
assert_eq!(manifest_doc["format"], "exocortex-corpus");
assert_eq!(
manifest_doc["compatibility_fingerprint"]
.as_str()
.unwrap()
.len(),
64
);
assert!(
manifest_doc["egress"].as_str().unwrap().contains("D24"),
"the egress boundary is stated on every export"
);
}
#[tokio::test]
async fn corpus_cut_defaults_to_now() {
let onto = ontology();
let storage = InMemoryStorage::new(onto.clone());
let fix_id = onto.memory_type_id("Fix").unwrap();
let mut live = memory(
"live-now",
"Fix",
chrono::Utc::now() - chrono::Duration::days(1),
chrono::Utc::now() - chrono::Duration::days(1),
None,
);
live.memory_type = fix_id;
storage.upsert_memory(&live).await.unwrap();
let dir = tempfile::tempdir().unwrap();
let manifest = export_corpus(&storage, &onto, None, dir.path())
.await
.unwrap();
assert_eq!(manifest.memories, 1);
assert_eq!(manifest.as_of, None);
}