use super::*;
use tempfile::TempDir;
use trusty_common::memory_core::retrieval::seed_shared_embedder_with_mock;
use crate::core::sm::config::SmMemoryConfig;
fn sm_memory() -> (SmMemory, TempDir) {
seed_shared_embedder_with_mock();
let dir = TempDir::new().expect("tempdir");
let cfg = SmMemoryConfig::default();
let mem = SmMemory::open(dir.path(), &cfg).expect("open SM memory");
(mem, dir)
}
#[test]
fn palace_create_is_idempotent() {
seed_shared_embedder_with_mock();
let dir = TempDir::new().expect("tempdir");
let cfg = SmMemoryConfig::default();
let first = SmMemory::open(dir.path(), &cfg).expect("first open");
assert_eq!(first.persisted_palace_count().expect("count"), 1);
let second = SmMemory::open(dir.path(), &cfg).expect("second open");
assert_eq!(
second.persisted_palace_count().expect("count"),
1,
"creating the SM palace twice must yield exactly ONE palace"
);
assert_eq!(second.palace_id().as_str(), "session-manager");
}
#[tokio::test]
async fn ensure_palace_falls_back_to_open_when_palace_already_exists() {
seed_shared_embedder_with_mock();
let dir = TempDir::new().expect("tempdir");
let cfg = SmMemoryConfig::default();
{
let first = SmMemory::open(dir.path(), &cfg).expect("first open");
assert_eq!(first.persisted_palace_count().expect("count"), 1);
first
.remember("TOCTOU_MARKER existing palace content written by first SM")
.await
.expect("remember in first instance");
}
let second = SmMemory::open(dir.path(), &cfg).expect("second open");
assert_eq!(
second.persisted_palace_count().expect("count"),
1,
"create-when-exists fallback must open the existing palace, not add a second"
);
let hits = second
.recall("what marker did the first SM write")
.await
.expect("recall from second instance");
assert!(
hits.iter()
.any(|h| h.drawer.content.contains("TOCTOU_MARKER")),
"second SM must open the EXISTING palace (seeing prior content), not a fresh one; got {hits:?}"
);
}
#[tokio::test]
async fn remember_then_recall_round_trips() {
let (mem, _dir) = sm_memory();
mem.remember("The session manager delegates all engineering work to t-mpm sessions")
.await
.expect("remember");
let hits = mem
.recall("who does the session manager delegate work to")
.await
.expect("recall");
assert!(
hits.iter()
.any(|h| h.drawer.content.contains("delegates all engineering work")),
"recall must surface the remembered SM fact; got {hits:?}"
);
}
#[tokio::test]
async fn recall_deep_round_trips() {
let (mem, _dir) = sm_memory();
mem.remember("Decision: adopt worktree-per-ticket discipline for all SM work")
.await
.expect("remember");
let hits = mem
.recall_deep("what discipline did we adopt for tickets")
.await
.expect("recall_deep");
assert!(
hits.iter()
.any(|h| h.drawer.content.contains("worktree-per-ticket")),
"recall_deep must surface the remembered SM fact; got {hits:?}"
);
}
#[tokio::test]
async fn note_stores_short_fact() {
let (mem, _dir) = sm_memory();
mem.note("Goal: ship SM-4").await.expect("note short fact");
let hits = mem.recall("what is the goal").await.expect("recall");
assert!(
hits.iter().any(|h| h.drawer.content.contains("ship SM-4")),
"note must store a short curated fact retrievable via recall; got {hits:?}"
);
}
#[tokio::test]
async fn recall_is_scoped_to_sm_palace() {
seed_shared_embedder_with_mock();
let dir = TempDir::new().expect("tempdir");
let sm_cfg = SmMemoryConfig::default(); let sm = SmMemory::open(dir.path(), &sm_cfg).expect("open SM");
sm.remember("SM_PALACE_SECRET zebra orchestration delegation marker")
.await
.expect("remember in SM palace");
let other_cfg = SmMemoryConfig {
palace: "some-other-project".to_string(),
recall_top_k: 6,
};
let other = SmMemory::open(dir.path(), &other_cfg).expect("open other");
other
.remember("OTHER_PALACE_SECRET giraffe unrelated project marker")
.await
.expect("remember in other palace");
let sm_hits = sm.recall("secret marker").await.expect("sm recall");
assert!(
sm_hits
.iter()
.all(|h| !h.drawer.content.contains("OTHER_PALACE_SECRET")),
"SM recall must never surface another palace's content; got {sm_hits:?}"
);
let other_hits = other.recall("secret marker").await.expect("other recall");
assert!(
other_hits
.iter()
.all(|h| !h.drawer.content.contains("SM_PALACE_SECRET")),
"non-SM recall must never surface SM content; got {other_hits:?}"
);
}
#[tokio::test]
async fn data_survives_fresh_construction() {
seed_shared_embedder_with_mock();
let dir = TempDir::new().expect("tempdir");
let cfg = SmMemoryConfig::default();
{
let mem = SmMemory::open(dir.path(), &cfg).expect("first open");
mem.remember("Persistent SM outcome: SM-3 merged to main successfully")
.await
.expect("remember");
}
let reopened = SmMemory::open(dir.path(), &cfg).expect("reopen");
assert_eq!(
reopened.persisted_palace_count().expect("count"),
1,
"reopen must reuse the existing palace, not create a second"
);
let hits = reopened
.recall("which SM ticket merged to main")
.await
.expect("recall after restart");
assert!(
hits.iter()
.any(|h| h.drawer.content.contains("SM-3 merged to main")),
"remembered data must survive a fresh SmMemory construction; got {hits:?}"
);
}
#[tokio::test]
async fn writes_target_only_the_sm_palace() {
let (mem, _dir) = sm_memory();
assert_eq!(
mem.palace_id().as_str(),
"session-manager",
"SM must be bound to the configured palace name"
);
mem.remember("first SM write — a goal for the milestone")
.await
.expect("remember 1");
mem.note("terse SM decision").await.expect("note");
mem.remember("third SM write — an outcome record for the milestone")
.await
.expect("remember 2");
assert_eq!(
mem.persisted_palace_count().expect("count"),
1,
"no write may create or touch a palace other than the bound SM palace"
);
}
#[test]
fn construct_on_unwritable_root_is_error() {
seed_shared_embedder_with_mock();
let dir = TempDir::new().expect("tempdir");
let file_path = dir.path().join("not-a-dir");
std::fs::write(&file_path, b"x").expect("write blocking file");
let bad_root = file_path.join("child");
let cfg = SmMemoryConfig::default();
match SmMemory::open(&bad_root, &cfg) {
Ok(_) => panic!("must fail to create store under a file"),
Err(err) => assert!(
matches!(err, SmMemoryError::Palace { .. }),
"unavailable backend must degrade to SmMemoryError::Palace, got {err:?}"
),
}
}