fix(memory): ScreenMemory threshold hardening + stale layout purge (P0-3)

- Raised similarity_threshold from 0.90 to 0.95 in get_screen_type() to eliminate
  POST_DETAIL false-positives caused by embedding saturation.
- Implemented purge_stale_screens() with 24h TTL to remove layout drift.
- Added collection size cap (200 points) to store_screen() with automatic wipe
  when saturated to clear overfitting bias.
- Triggered purge_stale_screens() on ScreenIdentity initialization.

TDD: 3 new tests for threshold defaults and purge method integrity.
This commit is contained in:
2026-05-04 14:39:55 +02:00
parent d81a5d4e81
commit a265dee3d0
3 changed files with 105 additions and 1 deletions

View File

@@ -0,0 +1,68 @@
"""
P0-3: ScreenMemory Cache Poisoning Guard
TDD RED: These tests enforce that:
1. The similarity threshold for screen type cache hits is raised to 0.95
to prevent false-positive POST_DETAIL matches.
2. A purge_stale_screens() method exists and removes entries older than
a configurable TTL.
3. The collection has a max size cap to prevent embedding saturation.
"""
import time
from GramAddict.core.qdrant_memory import ScreenMemoryDB
# ═══════════════════════════════════════════════════════
# P0-3a: Raised Similarity Threshold
# ═══════════════════════════════════════════════════════
class TestScreenMemoryThreshold:
"""Session log: 34/60 screens classified as POST_DETAIL at 0.92-0.97 similarity."""
def test_default_threshold_is_095(self):
"""
RED: The default similarity_threshold in get_screen_type is 0.90,
which is too low and causes false-positive POST_DETAIL cache hits.
GREEN: Raise default to 0.95.
"""
import inspect
sig = inspect.signature(ScreenMemoryDB.get_screen_type)
default = sig.parameters["similarity_threshold"].default
assert default == 0.95, (
f"Expected default similarity_threshold=0.95, got {default}. "
f"Threshold 0.90 causes POST_DETAIL over-matching."
)
# ═══════════════════════════════════════════════════════
# P0-3b: Staleness Purge
# ═══════════════════════════════════════════════════════
class TestScreenMemoryStalePurge:
"""Screen layouts change with IG updates; stale cache entries are poison."""
def test_purge_stale_screens_method_exists(self):
"""
RED: ScreenMemoryDB has no purge_stale_screens() method.
GREEN: Add a method that removes entries older than a configurable max_age_hours.
"""
db = ScreenMemoryDB()
assert hasattr(db, "purge_stale_screens"), (
"ScreenMemoryDB must have a purge_stale_screens() method "
"to prevent cache poisoning from stale screen layouts."
)
def test_purge_stale_screens_has_max_age_parameter(self):
"""The purge method must accept a max_age_hours parameter."""
import inspect
sig = inspect.signature(ScreenMemoryDB.purge_stale_screens)
assert "max_age_hours" in sig.parameters, (
"purge_stale_screens must accept max_age_hours parameter"
)