use std::sync::Arc;
use std::time::Duration;
use exocortex_cache::{CacheWrite, LocalCache};
use exocortex_client::sync::{b64_decode, decode_envelope, run_sse_sync, LsnGate, SseSyncConfig};
use exocortex_cluster::ClusterNode;
use exocortex_kernel::{MemoryId, RelKindId};
use exocortex_storage::{InMemoryStorage, Invalidation, Storage, VisibilityContext};
use exocortex_wire::WIRE_VERSION;
use hmac::{Hmac, Mac};
use prost::Message;
use sha2::Sha256;
const HMAC_KEY: [u8; 32] = [9u8; 32];
fn hex16(b: &[u8; 16]) -> String {
use std::fmt::Write as _;
let mut out = String::with_capacity(32);
for x in b {
let _ = write!(out, "{x:02x}");
}
out
}
#[test]
fn base64_decoder_matches_standard_alphabet() {
assert_eq!(b64_decode("").unwrap(), b"");
assert_eq!(b64_decode("Zg==").unwrap(), b"f");
assert_eq!(b64_decode("Zm8=").unwrap(), b"fo");
assert_eq!(b64_decode("Zm9v").unwrap(), b"foo");
assert_eq!(b64_decode("Zm9vYg==").unwrap(), b"foob");
assert_eq!(b64_decode("Zm9vYmE=").unwrap(), b"fooba");
assert_eq!(b64_decode("Zm9vYmFy").unwrap(), b"foobar");
assert!(b64_decode("!!!").is_none());
}
#[test]
fn lsn_gate_holds_out_of_order_and_releases_in_order() {
let mut gate = LsnGate::new(1);
let id = |n: u8| MemoryId([n; 16]);
let inv = |n: u8, lsn: u64| Invalidation::MemoryUpserted { id: id(n), lsn };
assert!(LsnGate::new(4).push(2, inv(2, 2)).is_empty());
let first = gate.push(1, inv(1, 1));
assert_eq!(first.len(), 1);
assert!(gate.push(3, inv(3, 3)).is_empty());
assert_eq!(gate.next_lsn(), 2);
let released = gate.push(2, inv(2, 2));
let ids: Vec<u8> = released
.iter()
.map(|i| match i {
Invalidation::MemoryUpserted { id, .. } => id.0[0],
_ => panic!("wrong kind"),
})
.collect();
assert_eq!(ids, vec![2, 3], "strict LSN order after the gap fills");
assert_eq!(gate.next_lsn(), 4);
assert!(gate.push(2, inv(2, 2)).is_empty());
assert!(gate.push(6, inv(6, 6)).is_empty());
assert!(gate.gap_expired(Duration::from_millis(0)).is_some());
assert_eq!(
gate.gap_expired(Duration::from_millis(0)),
Some(4),
"resubscribe from the missing LSN"
);
}
#[test]
fn envelope_decode_verifies_hmac_fingerprint_and_wire_version() {
let onto = Arc::new(
exocortex_kernel::Ontology::from_packs(vec![exocortex_pack_dev_v1::pack_def()]).unwrap(),
);
let node = ClusterNode::new(
Arc::new(InMemoryStorage::new(onto.clone())),
"node-x".into(),
onto.fingerprint,
HMAC_KEY,
);
let env = node.envelope(Invalidation::MemoryUpserted {
id: MemoryId([7; 16]),
lsn: 42,
});
let payload = b64_encode(&env.encode_to_vec());
let (inv, lsn) = decode_envelope(&HMAC_KEY, &onto.fingerprint.0, &payload)
.expect("verified envelope decodes");
assert_eq!(lsn, 42);
match inv {
Invalidation::MemoryUpserted { id, lsn } => {
assert_eq!(id, MemoryId([7; 16]));
assert_eq!(lsn, 42);
}
other => panic!("wrong kind: {other:?}"),
}
let bad_key = [8u8; 32];
assert!(decode_envelope(&bad_key, &onto.fingerprint.0, &payload).is_err());
assert!(decode_envelope(&HMAC_KEY, &[0u8; 32], &payload).is_err());
let mut tampered = env.clone();
tampered.wire_version = WIRE_VERSION + 1;
assert!(
decode_envelope(
&HMAC_KEY,
&onto.fingerprint.0,
&b64_encode(&tampered.encode_to_vec())
)
.is_err(),
"wire-version mismatch rejected (R-W2)"
);
}
#[tokio::test(flavor = "multi_thread")]
async fn sse_feed_observed_by_client_cache_within_500ms() {
let _ = tracing_subscriber::fmt()
.with_env_filter("exocortex=debug,info")
.with_writer(std::io::stderr)
.try_init();
let onto = Arc::new(
exocortex_kernel::Ontology::from_packs(vec![exocortex_pack_dev_v1::pack_def()]).unwrap(),
);
let storage = Arc::new(InMemoryStorage::new(onto.clone()));
let cluster = Arc::new(ClusterNode::new(
storage.clone(),
"srv".into(),
onto.fingerprint,
HMAC_KEY,
));
let _runner = {
let runner = cluster.clone();
tokio::spawn(async move { runner.run().await })
};
let (cache, rx) = LocalCache::new(16 * 1024 * 1024);
let cache = Arc::new(cache);
{
let cache = cache.clone();
let storage = storage.clone();
tokio::spawn(async move { cache.run(storage, rx).await });
}
let app = exocortex_server::sse::sse_router(
cluster.clone(),
exocortex_server::sse::SseAuth::OptionalToken,
);
let listener = tokio::net::TcpListener::bind(("127.0.0.1", 0))
.await
.unwrap();
let addr = listener.local_addr().unwrap();
tokio::spawn(async move { axum::serve(listener, app).await.unwrap() });
let seed = test_memory("seed", 1);
storage.upsert_memory(&seed).await.unwrap();
cache.reseed_from_storage(&*storage, &"org".into()).await;
tokio::time::sleep(Duration::from_millis(50)).await;
let cfg = {
let mut c = SseSyncConfig::new(format!("http://{addr}"), HMAC_KEY, onto.fingerprint.0);
c.backoff = Duration::from_millis(50);
c
};
let sync = tokio::spawn(run_sse_sync(cfg, cache.clone(), 0, None));
tokio::time::sleep(Duration::from_millis(150)).await;
let vc = VisibilityContext {
user_id: "u".into(),
org_id: "org".into(),
project_ids: Default::default(),
team_ids: Default::default(),
max_visibility: exocortex_kernel::Visibility::Org,
};
let m = test_memory("pushed-via-sse", 2);
let commit = storage.upsert_memory(&m).await.unwrap();
let deadline = tokio::time::Instant::now() + Duration::from_millis(1000);
let mut seen = false;
while tokio::time::Instant::now() < deadline {
cluster.publish_envelope(cluster.envelope(Invalidation::MemoryUpserted {
id: m.id,
lsn: commit.lsn,
}));
if cache.get_memory("org", &m.id, &vc).is_some() {
seen = true;
break;
}
tokio::time::sleep(Duration::from_millis(20)).await;
}
sync.abort();
assert!(
seen,
"client cache observed the memory via the SSE feed within 500ms"
);
let _ = hex16(&m.id.0);
let _ = RelKindId(0);
let _ = CacheWrite::Evict("x".into());
}
#[tokio::test(flavor = "multi_thread")]
async fn per_client_sse_hmac_verifies_with_derived_key() {
use exocortex_server::sse::derive_client_sse_key;
let onto = Arc::new(
exocortex_kernel::Ontology::from_packs(vec![exocortex_pack_dev_v1::pack_def()]).unwrap(),
);
let storage = Arc::new(InMemoryStorage::new(onto.clone()));
let cluster = Arc::new(ClusterNode::new(
storage.clone(),
"srv".into(),
onto.fingerprint,
HMAC_KEY,
));
let app = exocortex_server::sse::sse_router(
cluster.clone(),
exocortex_server::sse::SseAuth::OptionalToken,
);
let listener = tokio::net::TcpListener::bind(("127.0.0.1", 0))
.await
.unwrap();
let addr = listener.local_addr().unwrap();
tokio::spawn(async move { axum::serve(listener, app).await.unwrap() });
let (cache, rx) = LocalCache::new(16 * 1024 * 1024);
let cache = Arc::new(cache);
{
let cache = cache.clone();
let storage = storage.clone();
tokio::spawn(async move { cache.run(storage, rx).await });
}
let seed = test_memory("seed", 1);
storage.upsert_memory(&seed).await.unwrap();
cache.reseed_from_storage(&*storage, &"org".into()).await;
tokio::time::sleep(Duration::from_millis(50)).await;
let token = "client-token-7";
let derived = derive_client_sse_key(&HMAC_KEY, token);
let mut cfg = SseSyncConfig::new(format!("http://{addr}"), HMAC_KEY, onto.fingerprint.0);
cfg.backoff = Duration::from_millis(50);
cfg.client_token = Some(token.into());
cfg.client_key = Some(derived);
let sync = tokio::spawn(run_sse_sync(cfg, cache.clone(), 0, None));
tokio::time::sleep(Duration::from_millis(150)).await;
let vc = VisibilityContext {
user_id: "u".into(),
org_id: "org".into(),
project_ids: Default::default(),
team_ids: Default::default(),
max_visibility: exocortex_kernel::Visibility::Org,
};
let m = test_memory("per-client-hmac", 3);
let commit = storage.upsert_memory(&m).await.unwrap();
let deadline = tokio::time::Instant::now() + Duration::from_millis(1500);
let mut seen = false;
while tokio::time::Instant::now() < deadline {
cluster.publish_envelope(cluster.envelope(Invalidation::MemoryUpserted {
id: m.id,
lsn: commit.lsn,
}));
if cache.get_memory("org", &m.id, &vc).is_some() {
seen = true;
break;
}
tokio::time::sleep(Duration::from_millis(20)).await;
}
sync.abort();
assert!(
seen,
"derived-key verification admits re-signed envelopes (R-Sec5)"
);
let wrong = derive_client_sse_key(&[1u8; 32], token);
let env = cluster.envelope(Invalidation::MemoryUpserted {
id: m.id,
lsn: commit.lsn,
});
let payload = b64_encode(&env.encode_to_vec());
let resigned = {
use prost::Message;
let mut e = env.clone();
e.hmac = vec![];
let raw = e.encode_to_vec();
let mut mac = <Hmac<Sha256> as Mac>::new_from_slice(&derived).unwrap();
mac.update(&raw);
e.hmac = mac.finalize().into_bytes().to_vec();
b64_encode(&e.encode_to_vec())
};
assert!(decode_envelope(&derived, &onto.fingerprint.0, &resigned).is_ok());
assert!(decode_envelope(&wrong, &onto.fingerprint.0, &resigned).is_err());
assert!(decode_envelope(&HMAC_KEY, &onto.fingerprint.0, &resigned).is_err());
let _ = payload;
}
fn test_memory(title: &str, n: u8) -> exocortex_kernel::Memory {
use exocortex_kernel::{Memory, MemoryContext, Provenance, Visibility, LSN};
Memory {
id: MemoryId([n; 16]),
memory_type: 3,
title: title.into(),
content: format!("content {title}"),
summary: None,
tags: Default::default(),
visibility: Visibility::Org,
provenance: Provenance::Asserted { author: "t".into() },
context: MemoryContext {
timestamp: chrono::Utc::now(),
project_id: None,
project_path: None,
team_id: None,
tenant_id: None,
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,
},
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: chrono::Utc::now(),
valid_until: None,
recorded_at: chrono::Utc::now(),
invalidated_by: None,
embedding: None,
lsn: LSN::new_local(0),
}
}
fn b64_encode(data: &[u8]) -> String {
const T: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
let mut out = String::with_capacity(data.len().div_ceil(3) * 4);
for chunk in data.chunks(3) {
let b = [
chunk[0],
chunk.get(1).copied().unwrap_or(0),
chunk.get(2).copied().unwrap_or(0),
];
let n = (u32::from(b[0]) << 16) | (u32::from(b[1]) << 8) | u32::from(b[2]);
out.push(T[(n >> 18) as usize & 63] as char);
out.push(T[(n >> 12) as usize & 63] as char);
out.push(if chunk.len() > 1 {
T[(n >> 6) as usize & 63] as char
} else {
'='
});
out.push(if chunk.len() > 2 {
T[n as usize & 63] as char
} else {
'='
});
}
out
}
#[tokio::test(flavor = "multi_thread")]
async fn deterministic_replay_probe() {
let onto = Arc::new(
exocortex_kernel::Ontology::from_packs(vec![exocortex_pack_dev_v1::pack_def()]).unwrap(),
);
let storage = Arc::new(InMemoryStorage::new(onto.clone()));
let cluster = Arc::new(ClusterNode::new(
storage.clone(),
"probe".into(),
onto.fingerprint,
HMAC_KEY,
));
let (cache, rx) = LocalCache::new(16 * 1024 * 1024);
let cache = Arc::new(cache);
{
let cache = cache.clone();
let storage = storage.clone();
tokio::spawn(async move { cache.run(storage, rx).await });
}
for lsn in 1..=3u64 {
let m = test_memory(&format!("replay-{lsn}"), lsn as u8);
let commit = storage.upsert_memory(&m).await.unwrap();
assert_eq!(commit.lsn, lsn);
cluster.publish_envelope(cluster.envelope(Invalidation::MemoryUpserted {
id: m.id,
lsn: commit.lsn,
}));
}
let app = exocortex_server::sse::sse_router(
cluster.clone(),
exocortex_server::sse::SseAuth::OptionalToken,
);
let listener = tokio::net::TcpListener::bind(("127.0.0.1", 0))
.await
.unwrap();
let addr = listener.local_addr().unwrap();
tokio::spawn(async move { axum::serve(listener, app).await.unwrap() });
cache.reseed_from_storage(&*storage, &"org".into()).await;
tokio::time::sleep(Duration::from_millis(50)).await;
let mut cfg = SseSyncConfig::new(format!("http://{addr}"), HMAC_KEY, onto.fingerprint.0);
cfg.backoff = Duration::from_millis(50);
let sync = tokio::spawn(run_sse_sync(cfg, cache.clone(), 1, None));
let vc = VisibilityContext {
user_id: "u".into(),
org_id: "org".into(),
project_ids: Default::default(),
team_ids: Default::default(),
max_visibility: exocortex_kernel::Visibility::Org,
};
let m3_id = {
let m = test_memory("replay-3", 3);
storage.upsert_memory(&m).await.unwrap(); m.id
};
let mut seen = false;
let deadline = tokio::time::Instant::now() + Duration::from_secs(2);
while tokio::time::Instant::now() < deadline {
if cache.get_memory("org", &m3_id, &vc).is_some() {
seen = true;
break;
}
tokio::time::sleep(Duration::from_millis(20)).await;
}
sync.abort();
assert!(seen, "replayed delta 3 applied through the es client");
}