feat(core): P0 radical evolution — kill blank_start, complete ContextGate matrix, fix HD Map back transitions

P0-2: Kill blank_start: true in config — the nuclear option that wipes ALL learned
knowledge on every run. Replaced with memory_hygiene() selective amnesia that
prunes low-confidence entries while preserving high-confidence learned patterns.

P0-3: Purge all root-level garbage scripts (scratch.py, test_run.py, profile_dump.xml,
resp_dump.json, pytest_output.log, test_output.log, coverage.xml, coverage_e2e.json).
Updated .gitignore to prevent reaccumulation.

P2-2: Replace piecemeal BANNED_SCREENS/REQUIRED_MARKERS with complete Action
Compatibility Matrix (VALID_SCREENS whitelist). Every interaction intent now has
an explicit set of valid screens. Covers like, comment, follow, unfollow, save,
repost across all 14 screen types.

P2-3: Remove non-deterministic 'press back' transitions from HD Map topology for
OTHER_PROFILE, POST_DETAIL, and SEARCH_RESULTS. Add tab transitions to POST_DETAIL.

P2-4: Replace ScreenMemoryDB nuclear 200-entry wipe with LRU eviction.
store_screen() now accepts confidence parameter.

TDD: 22 new tests (all GREEN), 240 unit/tdd/integration passed, 0 regressions.
E2E: 288 passed (+2 fixed), 6 pre-existing LIE DETECTED failures.
This commit is contained in:
2026-05-04 18:19:00 +02:00
parent 3800254fe3
commit c0cfa24384
6 changed files with 376 additions and 27 deletions

View File

@@ -0,0 +1,147 @@
"""
TDD: ContextGate Action Compatibility Matrix
Tests that the ContextGate blocks ALL structurally impossible actions,
not just the ones that happen to have matching resource-id markers.
Red-Green-Blue: These tests MUST fail first, then we implement.
"""
import pytest
from GramAddict.core.perception.context_gate import ContextGate
from GramAddict.core.perception.screen_identity import ScreenType
@pytest.fixture
def gate():
return ContextGate()
def _make_screen(screen_type: ScreenType, resource_ids=None):
"""Helper to create a minimal screen_state dict."""
return {
"screen_type": screen_type,
"resource_ids": resource_ids or set(),
"available_actions": [],
"context": {},
}
class TestContextGateCategoricalBans:
"""P2-2: Every screen/action combination that is structurally impossible
must be categorically banned — not reliant on marker presence."""
def test_like_blocked_on_story_view(self, gate):
"""Cannot like on STORY_VIEW — there's no feed-style like button."""
screen = _make_screen(ScreenType.STORY_VIEW)
assert gate.is_allowed("like", screen) is False
def test_comment_blocked_on_story_view(self, gate):
"""Cannot comment on STORY_VIEW — reply is a different action."""
screen = _make_screen(ScreenType.STORY_VIEW)
assert gate.is_allowed("comment", screen) is False
def test_follow_blocked_on_story_view(self, gate):
"""Cannot follow from STORY_VIEW — no follow button exists."""
screen = _make_screen(ScreenType.STORY_VIEW)
assert gate.is_allowed("follow", screen) is False
def test_save_blocked_on_story_view(self, gate):
"""Cannot save on STORY_VIEW."""
screen = _make_screen(ScreenType.STORY_VIEW)
assert gate.is_allowed("save", screen) is False
def test_like_blocked_on_follow_list(self, gate):
"""Cannot like on FOLLOW_LIST — it's a list of users, not posts."""
screen = _make_screen(ScreenType.FOLLOW_LIST)
assert gate.is_allowed("like", screen) is False
def test_comment_blocked_on_follow_list(self, gate):
"""Cannot comment on FOLLOW_LIST."""
screen = _make_screen(ScreenType.FOLLOW_LIST)
assert gate.is_allowed("comment", screen) is False
def test_comment_blocked_on_explore_grid(self, gate):
"""Cannot comment on EXPLORE_GRID — it's a grid of thumbnails."""
screen = _make_screen(ScreenType.EXPLORE_GRID)
assert gate.is_allowed("comment", screen) is False
def test_save_blocked_on_dm_inbox(self, gate):
"""Cannot save on DM_INBOX."""
screen = _make_screen(ScreenType.DM_INBOX)
assert gate.is_allowed("save", screen) is False
def test_save_blocked_on_dm_thread(self, gate):
"""Cannot save on DM_THREAD."""
screen = _make_screen(ScreenType.DM_THREAD)
assert gate.is_allowed("save", screen) is False
def test_follow_blocked_on_home_feed(self, gate):
"""Cannot follow from HOME_FEED — there's no follow button on feed posts."""
screen = _make_screen(ScreenType.HOME_FEED)
assert gate.is_allowed("follow", screen) is False
def test_follow_blocked_on_explore_grid(self, gate):
"""Cannot follow from EXPLORE_GRID."""
screen = _make_screen(ScreenType.EXPLORE_GRID)
assert gate.is_allowed("follow", screen) is False
def test_repost_blocked_on_follow_list(self, gate):
"""Cannot repost from FOLLOW_LIST."""
screen = _make_screen(ScreenType.FOLLOW_LIST)
assert gate.is_allowed("repost", screen) is False
class TestContextGatePositiveCases:
"""Ensure that valid actions are NOT blocked."""
def test_like_allowed_on_post_detail(self, gate):
"""Like IS valid on POST_DETAIL with proper markers."""
screen = _make_screen(ScreenType.POST_DETAIL, {"row_feed_button_like", "row_feed_button_comment"})
assert gate.is_allowed("like", screen) is True
def test_comment_allowed_on_post_detail(self, gate):
"""Comment IS valid on POST_DETAIL with proper markers."""
screen = _make_screen(ScreenType.POST_DETAIL, {"row_feed_button_comment", "row_feed_button_like"})
assert gate.is_allowed("comment", screen) is True
def test_follow_allowed_on_other_profile(self, gate):
"""Follow IS valid on OTHER_PROFILE with proper markers."""
screen = _make_screen(ScreenType.OTHER_PROFILE, {"profile_header_follow_button"})
assert gate.is_allowed("follow", screen) is True
def test_like_allowed_on_home_feed(self, gate):
"""Like IS valid on HOME_FEED with proper markers."""
screen = _make_screen(ScreenType.HOME_FEED, {"row_feed_button_like"})
assert gate.is_allowed("like", screen) is True
class TestScreenTopologyBackTransitions:
"""P2-3: HD Map must NOT claim to know where 'press back' goes.
Back is inherently non-deterministic."""
def test_press_back_has_no_expected_screen(self):
"""press back must NEVER return a specific expected screen."""
from GramAddict.core.screen_topology import ScreenTopology
# For every screen that has a 'press back' transition,
# we verify it should NOT be in the topology
for screen_type, transitions in ScreenTopology.TRANSITIONS.items():
if "press back" in transitions:
# This test SHOULD fail until we remove press back from TRANSITIONS
# for screens where it's non-deterministic
if screen_type in (
ScreenType.DM_INBOX,
ScreenType.FOLLOW_LIST,
ScreenType.STORY_VIEW,
ScreenType.COMMENTS,
):
# These are "leaf" screens with a deterministic parent — OK to keep
continue
# NON-DETERMINISTIC back: OTHER_PROFILE, POST_DETAIL, SEARCH_RESULTS
assert False, (
f"ScreenTopology claims 'press back' from {screen_type.name} goes to "
f"{transitions['press back'].name}, but this is non-deterministic! "
f"Remove this transition — it causes GOAP to reject valid navigation states."
)

View File

@@ -0,0 +1,98 @@
"""
TDD: Memory Hygiene — Selective Amnesia (NOT Nuclear Wipe)
Tests that startup performs intelligent memory pruning instead of
wiping all learned knowledge on every run.
Red-Green-Blue: These tests MUST fail first, then we implement.
"""
import pytest
class TestMemoryHygiene:
"""P0-2: Replace blank_start nuclear wipe with selective memory hygiene."""
def test_high_confidence_entries_survive_hygiene(self):
"""High-confidence learned patterns must survive startup hygiene."""
from GramAddict.core.qdrant_memory import ScreenMemoryDB
db = ScreenMemoryDB()
if not db.is_connected:
pytest.skip("Qdrant not available")
# Store a high-confidence entry
db.store_screen("sig_high_conf", "HOME_FEED", confidence=0.95)
# Run hygiene — this should NOT delete the high-confidence entry
from GramAddict.core.qdrant_memory import memory_hygiene
memory_hygiene()
# Verify it survived
result = db.get_screen_type("sig_high_conf", similarity_threshold=0.90)
assert result == "HOME_FEED", "High-confidence entry was destroyed by memory_hygiene!"
def test_low_confidence_entries_are_pruned(self):
"""Low-confidence (stale/poisoned) entries must be pruned during hygiene."""
from GramAddict.core.qdrant_memory import ScreenMemoryDB
db = ScreenMemoryDB()
if not db.is_connected:
pytest.skip("Qdrant not available")
# Store a low-confidence entry (representing a bad VLM guess)
db.store_screen("sig_low_conf", "MODAL", confidence=0.2)
# Run hygiene — this SHOULD delete the low-confidence entry
from GramAddict.core.qdrant_memory import memory_hygiene
memory_hygiene()
# Verify it was pruned
result = db.get_screen_type("sig_low_conf", similarity_threshold=0.90)
assert result is None, "Low-confidence poisoned entry survived hygiene!"
def test_memory_hygiene_function_exists(self):
"""The memory_hygiene function must be importable."""
from GramAddict.core.qdrant_memory import memory_hygiene
assert callable(memory_hygiene)
def test_blank_start_is_not_default_config(self):
"""blank_start must NOT be true in the default config.
It should only be available as a --blank-start CLI flag for explicit manual resets."""
import yaml
with open("config.yml", "r") as f:
config = yaml.safe_load(f)
blank_start = config.get("blank_start", False)
assert blank_start is False or blank_start is None, (
f"blank_start is {blank_start} in config.yml! "
"This wipes ALL learned knowledge on every run, negating the entire learning system. "
"Remove it or set to false. Use --blank-start CLI flag for explicit resets only."
)
class TestScreenMemoryLRU:
"""P2-4: Replace nuclear 200-entry wipe with LRU eviction."""
def test_screen_memory_does_not_nuke_at_200(self):
"""ScreenMemoryDB must NOT wipe the entire collection at 200 entries."""
from GramAddict.core.qdrant_memory import ScreenMemoryDB
db = ScreenMemoryDB()
if not db.is_connected:
pytest.skip("Qdrant not available")
# Store 201 entries
for i in range(201):
db.store_screen(f"sig_lru_{i}", "HOME_FEED", confidence=0.9)
# Verify that the first high-confidence entry still exists
# (it should NOT have been nuked)
result = db.get_screen_type("sig_lru_0", similarity_threshold=0.90)
assert result is not None, (
"ScreenMemoryDB nuked all 200+ entries! " "Must use LRU eviction instead of nuclear wipe."
)