use std::sync::Arc;
use exocortex_ingest::IngestServer;
use exocortex_kernel::{MemoryId, Provenance};
use exocortex_pack_dev_v1::pack_def;
use exocortex_storage::{InMemoryStorage, Storage};
use exocortex_wire::ingest::v1::{
ingest_service_server::IngestService, ExternalKey, ExternalSnapshotInfo, IngestBatch,
MemoryDraft, ProducerIdentity, RejectCode,
};
fn server() -> IngestServer<InMemoryStorage> {
let onto = Arc::new(exocortex_kernel::Ontology::from_packs(vec![pack_def()]).unwrap());
IngestServer::new(
Arc::new(InMemoryStorage::new(onto.clone())),
onto,
[5u8; 32],
)
}
fn draft(key: &str, title: &str) -> MemoryDraft {
MemoryDraft {
rights: None,
draft_key: key.into(),
id: String::new(),
memory_type: "General".into(),
title: title.into(),
content: "external row".into(),
tags: vec![],
visibility: 3,
valid_from: None,
valid_until: None,
external_key: Some(ExternalKey {
table_uuid: [
0x8fu8, 0x3a, 0xfe, 0xff, 0xfe, 0xff, 0xfe, 0xff, 0xfe, 0xff, 0xfe, 0xff, 0xfe,
0xff, 0xfe, 0xff,
]
.to_vec(),
logical_pk: key.into(),
mapping_version: 1,
}),
}
}
fn signed(mut b: IngestBatch) -> IngestBatch {
exocortex_wire::signing::prepare_batch(&[5u8; 32], &mut b);
b
}
fn batch(snapshot: &ExternalSnapshotInfo, memories: Vec<MemoryDraft>) -> IngestBatch {
let fp = Arc::new(exocortex_kernel::Ontology::from_packs(vec![pack_def()]).unwrap());
signed(IngestBatch {
org_id: "org".into(),
source_uri: "iceberg://cat/db/orders".into(),
producer_id: "external-sync".into(),
batch_id: format!("b8b9-{}", std::process::id()),
mapping_version: "orders:1.0.0".into(),
ontology_fingerprint: fp.fingerprint.0.to_vec(),
ceiling: 3,
checksum: String::new(),
observed_at: None,
recorded_at: None,
snapshot: Some(snapshot.clone()),
memories,
relationships: vec![],
producer: Some(ProducerIdentity {
node_id: "n".into(),
agent_id: String::new(),
adapter_id: String::new(),
hmac_signature: vec![],
client_metadata: None,
}),
})
}
async fn registered(srv: &IngestServer<InMemoryStorage>) {
srv.register_source(tonic::Request::new(exocortex_wire::signing::registration(
&[5u8; 32],
"org",
"iceberg://cat/db/orders",
"external-sync",
3,
"custom",
"test-node",
exocortex_wire::ingest::v1::ProducerKind::CodingAgent,
)))
.await
.unwrap();
}
fn snap(schema_hash: Vec<u8>) -> ExternalSnapshotInfo {
ExternalSnapshotInfo {
snapshot_id: "s1".into(),
schema_hash,
source_flavor: "custom".into(),
}
}
#[tokio::test]
async fn schema_hash_round_trips_through_commit() {
let srv = server();
registered(&srv).await;
let mut hash = [0u8; 32];
for (i, b) in hash.iter_mut().enumerate() {
*b = (0xa3 + i) as u8;
}
let b = batch(
&snap(hash.to_vec()),
vec![draft("order-7", "payments owned by team-payments")],
);
let ack = srv
.submit(tonic::Request::new(b))
.await
.unwrap()
.into_inner();
assert_eq!(ack.accepted, 1, "{:?}", ack.rejections);
let committed = {
use futures::StreamExt;
let storage = srv.storage.clone();
let mut ms = storage.stream_all_memories().await;
let mut found = None;
while let Some(Ok(m)) = ms.next().await {
if m.title.contains("payments owned") {
found = Some(m);
}
}
found.expect("committed row")
};
match committed.provenance {
Provenance::ExternalSnapshot(ext) => {
assert_eq!(
ext.schema_hash, hash,
"B9: submitted schema_hash persists byte-exact"
);
assert!(
ext.schema_hash != [0u8; 32],
"the pre-B9 hard-coded zeros are gone"
);
}
other => panic!("external batch must commit ExternalSnapshot: {other:?}"),
}
}
#[tokio::test]
async fn identity_derives_from_raw_uuid_bytes() {
let srv = server();
registered(&srv).await;
let uuid = [
0x8fu8, 0x3a, 0xfe, 0xff, 0xfe, 0xff, 0xfe, 0xff, 0xfe, 0xff, 0xfe, 0xff, 0xfe, 0xff, 0xfe,
0xff,
];
let b = batch(
&snap([0u8; 32].to_vec()),
vec![draft("order-8", "raw uuid row")],
);
let ack = srv
.submit(tonic::Request::new(b))
.await
.unwrap()
.into_inner();
assert_eq!(ack.accepted, 1, "{:?}", ack.rejections);
let expected = MemoryId::from_external("org", "iceberg://cat/db/orders", &uuid, b"order-8", 1);
let storage = srv.storage.clone();
assert!(
storage.get_memory(&expected).await.unwrap().is_some(),
"committed row lives at the raw-byte-derived id"
);
let lossy = String::from_utf8_lossy(&uuid).to_string();
let lossy_id = MemoryId::from_external(
"org",
"iceberg://cat/db/orders",
lossy.as_bytes(),
b"order-8",
1,
);
assert!(
storage.get_memory(&lossy_id).await.unwrap().is_none(),
"no row at the lossy-string-derived id"
);
}
#[tokio::test]
async fn malformed_external_coordinates_are_rejected() {
let srv = server();
registered(&srv).await;
let mut b = batch(&snap([0u8; 32].to_vec()), vec![draft("k", "short uuid")]);
if let Some(d) = b.memories.get_mut(0) {
if let Some(k) = d.external_key.as_mut() {
k.table_uuid = vec![1u8; 8];
}
}
let ack = srv
.submit(tonic::Request::new(re_sign(b)))
.await
.unwrap()
.into_inner();
assert!(
ack.rejections
.iter()
.any(|r| r.code == RejectCode::InvalidExternalKey as i32),
"short table_uuid rejected: {:?}",
ack.rejections
);
let b2 = batch(&snap(vec![1u8; 31]), vec![draft("k2", "short hash")]);
let ack2 = srv
.submit(tonic::Request::new(b2))
.await
.unwrap()
.into_inner();
assert!(
ack2.rejections
.iter()
.any(|r| r.code == RejectCode::InvalidExternalKey as i32),
"short schema_hash rejected: {:?}",
ack2.rejections
);
}
fn re_sign(mut b: IngestBatch) -> IngestBatch {
if let Some(p) = b.producer.as_mut() {
p.hmac_signature = vec![];
}
signed(b)
}
#[tokio::test]
async fn bad_checksum_is_rejected() {
let srv = server();
registered(&srv).await;
let mut b = batch(
&snap([0u8; 32].to_vec()),
vec![draft("order-9", "corrupt checksum row")],
);
b.checksum = "deadbeef".into();
exocortex_wire::signing::sign_batch(&[5u8; 32], &mut b);
let ack = srv
.submit(tonic::Request::new(b))
.await
.unwrap()
.into_inner();
assert!(
ack.rejections
.iter()
.any(|r| r.code == RejectCode::BadChecksum as i32),
"corrupted checksum rejected: {:?}",
ack.rejections
);
let mut b2 = batch(
&snap([0u8; 32].to_vec()),
vec![draft("order-10", "empty checksum row")],
);
b2.checksum = String::new();
exocortex_wire::signing::sign_batch(&[5u8; 32], &mut b2);
let ack2 = srv
.submit(tonic::Request::new(b2))
.await
.unwrap()
.into_inner();
assert!(
ack2.rejections
.iter()
.any(|r| r.code == RejectCode::BadChecksum as i32),
"empty checksum rejected: {:?}",
ack2.rejections
);
}
#[tokio::test]
async fn pinned_org_rejects_foreign_batches() {
let srv = server().with_org("org");
registered(&srv).await;
let mut b = batch(
&snap([0u8; 32].to_vec()),
vec![draft("order-x", "foreign org row")],
);
b.org_id = "evil-org".into();
let ack = srv
.submit(tonic::Request::new(re_sign(b)))
.await
.unwrap()
.into_inner();
assert!(
ack.rejections
.iter()
.any(|r| r.code == RejectCode::UnknownSource as i32),
"foreign org rejected: {:?}",
ack.rejections
);
let err = srv
.register_source(tonic::Request::new(exocortex_wire::signing::registration(
&[5u8; 32],
"evil-org",
"custom://x",
"p",
3,
"custom",
"test-node",
exocortex_wire::ingest::v1::ProducerKind::CodingAgent,
)))
.await;
assert!(err.is_err(), "foreign org registration rejected");
let committed = {
use futures::StreamExt;
let storage = srv.storage.clone();
let mut ms = storage.stream_all_memories().await;
let mut any = false;
while let Some(Ok(m)) = ms.next().await {
if m.title.contains("foreign org") {
any = true;
}
}
any
};
assert!(!committed, "no cross-org row committed");
}