diff --git a/.jules/sentinel.md b/.jules/sentinel.md index 2889074f..95aeff51 100644 --- a/.jules/sentinel.md +++ b/.jules/sentinel.md @@ -2,3 +2,7 @@ **Vulnerability:** Hardcoded API key ("ippoc-secret-key") used as default in `src/cortex/cortex/server.py`. **Learning:** Default configurations for development often make their way into production or expose systems during testing if not explicitly overridden. The system relied on a specific hardcoded string for default auth, which is a Critical vulnerability (CWE-798). **Prevention:** Never provide a hardcoded default for secrets. If a secret is missing, either generate a secure random one at runtime (fail-safe) or refuse to start (fail-secure). +## 2025-02-28 - [Implement forget functionality] +**Vulnerability:** Not a vulnerability, but a partial/buggy uncommitted optimization blocked the proper feature. +**Learning:** Checking whether variables like bool vs int evaluate differently is crucial (`bool` is subclass of `int`). We need to explicitly check `type(res) is bool` vs `type(res) is int` when dealing with systems that mix the two. +**Prevention:** In heterogeneous subsystem interfaces, enforce standardized return types, or rely on explicit types instead of duck typing for counting. diff --git a/infra/src/mnemosyne/core.py b/infra/src/mnemosyne/core.py index b8fe46bb..75d806f4 100644 --- a/infra/src/mnemosyne/core.py +++ b/infra/src/mnemosyne/core.py @@ -8,7 +8,7 @@ - Graph Memory (relationships, causality) Usage: - from mnemosyne import memory + from ippoc.mnemosyne import memory # Store an experience await memory.store_episodic("User asked about Python", source="chat") @@ -234,7 +234,7 @@ async def _search_semantic(self, query: str, limit: int) -> List[MemoryFragment] type="semantic", content=doc.page_content, metadata=doc.metadata, - score=1.0, # TODO: get actual scores + score=doc.metadata.get("retrieval_score", 1.0), id=doc.metadata.get("id", f"semantic:{i}") ) for i, doc in enumerate(documents) ] @@ -320,9 +320,12 @@ async def forget(self, criteria: Dict[str, Any]) -> int: semantic_criteria = criteria["semantic"] if "ids" in semantic_criteria: semantic_ids = semantic_criteria["ids"] + if isinstance(semantic_ids, str): + semantic_ids = [semantic_ids] if semantic_ids: - if await self.semantic.delete_memories(semantic_ids): - total_deleted += len(semantic_ids) + res = await self.semantic.delete_memories(semantic_ids) + if res: + total_deleted += len(semantic_ids) if type(res) is bool else (res if type(res) is int else 1) except Exception as e: logger.error(f"Failed to delete semantic memories: {e}") @@ -332,9 +335,12 @@ async def forget(self, criteria: Dict[str, Any]) -> int: proc_criteria = criteria["procedural"] if "skills" in proc_criteria: skills = proc_criteria["skills"] + if isinstance(skills, str): + skills = [skills] for skill_name in skills: - if await self.procedural.delete_skill(skill_name): - total_deleted += 1 + res = await self.procedural.delete_skill(skill_name) + if res: + total_deleted += 1 if type(res) is bool else (res if type(res) is int else 1) except Exception as e: logger.error(f"Failed to delete procedural skills: {e}") @@ -344,15 +350,17 @@ async def forget(self, criteria: Dict[str, Any]) -> int: graph_criteria = criteria["graph"] if "entities" in graph_criteria: entities = graph_criteria["entities"] + if isinstance(entities, str): + entities = [entities] for entity in entities: count = await self.graph.delete_entity(entity) - total_deleted += count if isinstance(count, int) else (1 if count else 0) + total_deleted += count if type(count) is int else (1 if count else 0) except Exception as e: logger.error(f"Failed to delete graph entities: {e}") logger.info(f"Forget operation completed. Total removed: {total_deleted}") return total_deleted - + def health_check(self) -> Dict[str, Any]: """Check memory system health""" return { diff --git a/src/ippoc/mnemosyne/core.py b/src/ippoc/mnemosyne/core.py index 177806e6..75d806f4 100644 --- a/src/ippoc/mnemosyne/core.py +++ b/src/ippoc/mnemosyne/core.py @@ -320,9 +320,12 @@ async def forget(self, criteria: Dict[str, Any]) -> int: semantic_criteria = criteria["semantic"] if "ids" in semantic_criteria: semantic_ids = semantic_criteria["ids"] + if isinstance(semantic_ids, str): + semantic_ids = [semantic_ids] if semantic_ids: - if await self.semantic.delete_memories(semantic_ids): - total_deleted += len(semantic_ids) + res = await self.semantic.delete_memories(semantic_ids) + if res: + total_deleted += len(semantic_ids) if type(res) is bool else (res if type(res) is int else 1) except Exception as e: logger.error(f"Failed to delete semantic memories: {e}") @@ -332,9 +335,12 @@ async def forget(self, criteria: Dict[str, Any]) -> int: proc_criteria = criteria["procedural"] if "skills" in proc_criteria: skills = proc_criteria["skills"] + if isinstance(skills, str): + skills = [skills] for skill_name in skills: - if await self.procedural.delete_skill(skill_name): - total_deleted += 1 + res = await self.procedural.delete_skill(skill_name) + if res: + total_deleted += 1 if type(res) is bool else (res if type(res) is int else 1) except Exception as e: logger.error(f"Failed to delete procedural skills: {e}") @@ -344,15 +350,17 @@ async def forget(self, criteria: Dict[str, Any]) -> int: graph_criteria = criteria["graph"] if "entities" in graph_criteria: entities = graph_criteria["entities"] + if isinstance(entities, str): + entities = [entities] for entity in entities: count = await self.graph.delete_entity(entity) - total_deleted += count if isinstance(count, int) else (1 if count else 0) + total_deleted += count if type(count) is int else (1 if count else 0) except Exception as e: logger.error(f"Failed to delete graph entities: {e}") logger.info(f"Forget operation completed. Total removed: {total_deleted}") return total_deleted - + def health_check(self) -> Dict[str, Any]: """Check memory system health""" return {