fix(navigation): purge permanent trap dead-ends & clear UNKNOWN traps

ROOT CAUSE:
The bot would permanently softlock itself on UNKNOWN screens (like Trending Audio/Reels)
because 'is_trap' had NO time-decay, and UNKNOWN screen traps persisted forever.
Once 'press back' was trapped on an UNKNOWN screen, the bot was permanently blocked
from escaping any future UNKNOWN screen, causing endless restart loops.

FIXES:
1. Trap Time-Decay: 'is_trap' now forgives Qdrant-persisted traps older than 30 minutes.
   This mirrors ContextMemory and prevents permanent dead-ends.
2. UNKNOWN Guard: 'learn_trap' now REFUSES to persist traps for UNKNOWN screens to Qdrant.
   They only live in-memory to prevent breaking global navigation.
3. In-Memory Purge: 'force start instagram' now properly calls 'clear_traps()' to wipe
   the planner's in-memory traps. Previously they survived restarts, immediately
   re-trapping the bot.

Purged 6 ancient traps from Qdrant (one was 8 days old!).
TDD: 3 new tests, full suite 113/114 (1 infra flake)
This commit is contained in:
2026-05-05 16:42:34 +02:00
parent 8a2c93fb64
commit cf21dd3f76
3 changed files with 105 additions and 1 deletions

View File

@@ -0,0 +1,62 @@
"""
TDD: Navigation Trap Recovery & UNKNOWN Screen Handling
1. After force-restart, all in-memory traps must be cleared to allow fresh routing.
2. Traps on UNKNOWN screens must NEVER persist — UNKNOWN is a catch-all,
permanent traps here block ALL unidentified screens.
3. Trap system must have time-decay — old traps expire after a threshold.
"""
import pytest
class TestNavigationTrapRecovery:
"""Trap system must not create permanent dead-ends."""
def test_app_restart_clears_in_memory_traps(self):
"""
META-TEST: When 'force start instagram' succeeds in GOAP,
the planner's learned traps MUST be cleared.
"""
import inspect
from GramAddict.core.goap import GoalExecutor
source = inspect.getsource(GoalExecutor.achieve)
# Find the force start instagram block
assert "_learned_traps" in source or "clear_traps" in source or "wipe_traps" in source, (
"GOAP.navigate_to must clear the planner's _learned_traps after a force restart. "
"Currently only action_failures and explored_nav_actions are cleared, "
"leaving traps active — which causes immediate re-trapping on restart."
)
def test_unknown_screen_traps_never_persist_to_qdrant(self):
"""
META-TEST: Traps learned on UNKNOWN screens must NOT be persisted to Qdrant.
UNKNOWN is a catch-all — persisting traps here blocks ALL unidentified screens forever.
"""
import inspect
from GramAddict.core.navigation.knowledge import NavigationKnowledge
source = inspect.getsource(NavigationKnowledge.learn_trap)
source_lower = source.lower()
# Must have a guard against persisting UNKNOWN traps
assert "unknown" in source_lower, (
"learn_trap must guard against persisting traps on UNKNOWN screens. "
"UNKNOWN is a catch-all — permanent traps here block ALL unidentified screens."
)
def test_trap_system_has_time_decay(self):
"""
META-TEST: is_trap must consider the age of the trap entry.
Old traps (persisted in Qdrant) must expire to prevent permanent dead-ends.
"""
import inspect
from GramAddict.core.navigation.knowledge import NavigationKnowledge
source = inspect.getsource(NavigationKnowledge.is_trap)
# Must reference time-based expiry or timestamp comparison
assert "timestamp" in source or "time" in source or "age" in source or "expire" in source, (
"is_trap must implement time-based expiry for Qdrant-persisted traps. "
"Permanent traps cause permanent dead-ends."
)