fix(memory): Initialize GoalExecutor singleton with username and validate Qdrant deletes (TDD)

This commit is contained in:
2026-04-25 22:28:54 +02:00
parent 42eabb7bda
commit 5b53a7e4c0
4 changed files with 80 additions and 5 deletions

View File

@@ -184,6 +184,9 @@ def start_bot(**kwargs):
active_inference = ActiveInferenceEngine(username)
# Core Autonomous Engines
from GramAddict.core.goap import GoalExecutor
GoalExecutor.get_instance(device, username)
zero_engine = ZeroLatencyEngine(device)
nav_graph = QNavGraph(device)
growth_brain = GrowthBrain(username, persona_interests=persona_interests)

View File

@@ -205,11 +205,13 @@ class QdrantBase:
point_id = self.generate_uuid(seed_string)
try:
self.client.delete(collection_name=self.collection_name, points_selector=[point_id])
logger.info(
f"🗑️ [Qdrant] Purged poisoned memory vector from {self.collection_name} (UUID: {point_id[:8]}...)",
extra={"color": "\x1b[31m"},
)
res = self.client.retrieve(collection_name=self.collection_name, ids=[point_id])
if res:
self.client.delete(collection_name=self.collection_name, points_selector=[point_id])
logger.info(
f"🗑️ [Qdrant] Purged poisoned memory vector from {self.collection_name} (UUID: {point_id[:8]}...)",
extra={"color": "\x1b[31m"},
)
return True
except Exception as e:
self._handle_error(e, f"Failed to delete point {point_id}")

View File

@@ -0,0 +1,28 @@
from unittest.mock import MagicMock, patch
import pytest
def test_bot_flow_goal_executor_init_order():
# We want to prove that without explicit get_instance(device, username),
# QNavGraph(device) initializes GoalExecutor with an empty username.
# We have to reset GoalExecutor singleton first
from GramAddict.core.goap import GoalExecutor
from GramAddict.core.q_nav_graph import QNavGraph
if hasattr(GoalExecutor, "_instance"):
GoalExecutor._instance = None
device = MagicMock()
# Action: Instantiate QNavGraph
# Without the fix, this will initialize GoalExecutor with bot_username=""
# and PathMemory will get bound to "goap_paths_v1"
with patch("GramAddict.core.qdrant_memory.QdrantClient") as mock_qdrant:
# FIX: Pre-initialize with username (as done in bot_flow.py)
executor_pre = GoalExecutor.get_instance(device, "marisaundmarc")
nav_graph = QNavGraph(device)
executor = GoalExecutor.get_instance(device, "marisaundmarc")
# Now it should be bound correctly
assert executor.path_memory._db.collection_name == "goap_paths_v1_marisaundmarc", "PathMemory collection name does not contain the username suffix! Initialization leak occurred."

View File

@@ -0,0 +1,42 @@
from unittest.mock import MagicMock, patch, PropertyMock
from GramAddict.core.qdrant_memory import UIMemoryDB
def test_delete_point_logs_only_if_exists(caplog):
# Setup
db = UIMemoryDB()
type(db).is_connected = PropertyMock(return_value=True)
db.client = MagicMock()
db.collection_name = "test_collection"
# RED: Force the mock to return an empty list (point does NOT exist)
db.client.retrieve.return_value = []
# Action
result = db.delete_point("fake_seed")
# Assertions
assert result is True # Should still return True as it didn't crash
# It should NOT call delete if it wasn't found
db.client.delete.assert_not_called()
# It should NOT log the "Purged poisoned memory" line
assert "Purged poisoned memory vector" not in caplog.text
def test_delete_point_logs_if_found(caplog):
# Setup
db = UIMemoryDB()
type(db).is_connected = PropertyMock(return_value=True)
db.client = MagicMock()
db.collection_name = "test_collection"
# RED: Force the mock to return a result (point DOES exist)
db.client.retrieve.return_value = [{"id": "some_uuid"}]
# Action
with patch("GramAddict.core.qdrant_memory.logger") as mock_logger:
result = db.delete_point("fake_seed")
# Assertions
assert result is True
db.client.delete.assert_called_once()
mock_logger.info.assert_called_once()
assert "Purged poisoned memory vector" in mock_logger.info.call_args[0][0]