From aecb07104d9fd8255e5c3e846c2a07655007858d Mon Sep 17 00:00:00 2001 From: M3gA-Mind Date: Fri, 23 Oct 2026 05:05:34 +0530 Subject: [PATCH] test(layout_migration): pin that a partly stored legacy item blocks the scope-wide erase cleanup only erases a legacy tree wholesale when export reports nothing incomplete. No test covered that guard: dropping it let erase run on a tree still holding part of a chunked item. The new test makes the legacy engine report one incomplete item and panic on erase. --- .../src/layout_migration/cleanup_tests.rs | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/crates/tinymemory-tools/src/layout_migration/cleanup_tests.rs b/crates/tinymemory-tools/src/layout_migration/cleanup_tests.rs index 26f08630..d164b4ad 100644 --- a/crates/tinymemory-tools/src/layout_migration/cleanup_tests.rs +++ b/crates/tinymemory-tools/src/layout_migration/cleanup_tests.rs @@ -330,3 +330,81 @@ async fn after_an_unconfirmed_import_moved_items_are_forgotten_by_id_never_erase assert_eq!(state.phase, Phase::Cleaned); assert!(texts(legacy.as_ref()).await.is_empty()); } + +/// A legacy tree holding part of one chunked item: export names it in +/// `incomplete`, and its erase must never be called. +struct PartlyStored(ReferenceEngine); + +#[async_trait] +impl MemoryEngine for PartlyStored { + fn descriptor(&self) -> &EngineDescriptor { + self.0.descriptor() + } + async fn health(&self) -> EngineHealth { + self.0.health().await + } + async fn recall(&self, req: RecallRequest) -> Result { + self.0.recall(req).await + } + async fn fetch(&self, req: FetchRequest) -> Result { + self.0.fetch(req).await + } + async fn store(&self, item: StoreItem) -> Result { + self.0.store(item).await + } + async fn forget(&self, target: ForgetTarget) -> Result { + self.0.forget(target).await + } + async fn list(&self, req: ListRequest) -> Result { + self.0.list(req).await + } + async fn export(&self, req: ListRequest) -> Result { + let mut page = self.0.export(req).await?; + page.incomplete.push(ItemId("partly-stored".into())); + Ok(page) + } + async fn get(&self, req: GetRequest) -> Result> { + self.0.get(req).await + } + async fn erase(&self, _: EraseRequest) -> Result { + panic!("a legacy tree with a partly stored item is never erased scope-wide"); + } +} + +#[tokio::test] +async fn a_partly_stored_legacy_item_blocks_the_scope_wide_erase() { + let store = MemoryStateStore::default(); + let legacy = Arc::new(PartlyStored(ReferenceEngine::new())); + for i in 0..5 { + legacy + .store(StoreItem::learning( + format!("fact {i}"), + LearningKind::Fact, + 0.5, + MemoryMeta::default(), + )) + .await + .unwrap(); + } + let engines = Engines { + legacy: legacy.clone(), + tree: Arc::new(ReferenceEngine::new()), + }; + let mut state = copied(&store, &engines).await; + cleanup( + &store, + &engines, + &placement(), + false, + false, + &mut state, + || async { false }, + ) + .await + .unwrap(); + assert_eq!(state.phase, Phase::Cleaned); + assert!( + texts(legacy.as_ref()).await.is_empty(), + "moved items forgotten by id" + ); +}