purge: remove ALL hardcoded UI markers + model defaults from SAE

TDD cycle (RED → GREEN):

SAE perceive() — Zero Maintenance:
- Removed ALL hardcoded marker tuples (instagram_modal_markers,
  creation_flow_markers, dismiss_button_patterns, blocked_markers)
- Removed ALL hardcoded model names (llava:latest, qwen3.5:latest)
- Removed ALL hardcoded URLs (localhost:11434)
- Removed naked except blocks with model fallback defaults

New autonomous flow (zero hardcoded UI identifiers):
1. Package-based foreign app detection (Android-level, not app-specific)
2. Qdrant semantic cache (O(1) recall of learned screen types)
3. ScreenIdentity structural delegation (MODAL/FOREIGN_APP)
4. LLM autonomous classification (first-encounter learning then cached)

GOAP smart UI polling:
- Replaced static random.uniform(1.6, 2.8) sleep with MAX_POLLS=5
  polling loop that detects actual UI transitions

Model config centralized via _get_model_config():
- ALL model/URL lookups go through Config() SSOT
- Fail Fast - no silent hardcoded fallbacks

Tests: 9 new, 119 passing, 0 regressions
This commit is contained in:
2026-05-04 00:07:16 +02:00
parent 32731ed7ec
commit c98e2caaa1
18 changed files with 335 additions and 184 deletions

View File

@@ -0,0 +1,217 @@
"""
🔴 RED: Zero-Maintenance Compliance for SituationalAwarenessEngine
These tests enforce that the SAE contains ZERO hardcoded UI element identifiers
and ZERO hardcoded model/URL defaults. The bot must:
1. Detect obstacles via autonomous LLM+Qdrant pipeline — NOT via hardcoded resource-ids or text patterns.
2. Pull ALL model names/URLs from Config() — NO fallback literals scattered across the codebase.
3. Rely on structural package checks (app-agnostic) and ScreenIdentity delegation — NOT marker tuples.
Violating any of these rules means the bot breaks when Instagram updates its UI.
That is the opposite of Zero Maintenance.
"""
import inspect
import re
# ══════════════════════════════════════════════════════════
# 1. NO HARDCODED INSTAGRAM UI ELEMENT IDENTIFIERS IN SAE
# ══════════════════════════════════════════════════════════
class TestSAEContainsNoHardcodedUIElements:
"""
The SAE perceive() must NOT contain any hardcoded Instagram resource-id
fragments, button text patterns, or content-desc strings.
ALL obstacle detection must flow through: package check → Qdrant cache → LLM fallback.
"""
FORBIDDEN_RESOURCE_ID_FRAGMENTS = [
"survey_overlay",
"survey_title",
"interstitial_container",
"mystery_interstitial",
"nux_overlay",
"rating_prompt",
"feedback_dialog",
"action_bar_browser",
"browser_action_bar",
"quick_capture",
"gallery_cancel_button",
"creation_flow",
"reel_camera",
]
FORBIDDEN_BUTTON_TEXT_PATTERNS = [
"Not Now",
"not now",
"Nicht jetzt",
"Take Survey",
"Bewerten",
"rate \\d+ stars",
]
FORBIDDEN_CONTENT_DESC_PATTERNS = [
"Close browser",
]
def _get_perceive_source(self):
from GramAddict.core.situational_awareness import SituationalAwarenessEngine
return inspect.getsource(SituationalAwarenessEngine.perceive)
def test_no_hardcoded_resource_id_markers_in_perceive(self):
"""perceive() must not contain hardcoded Instagram resource-id fragments."""
source = self._get_perceive_source()
for fragment in self.FORBIDDEN_RESOURCE_ID_FRAGMENTS:
assert fragment not in source, (
f"SAE.perceive() contains hardcoded resource-id fragment '{fragment}'! "
f"This must be detected autonomously via LLM+Qdrant, not hardcoded."
)
def test_no_hardcoded_button_text_patterns_in_perceive(self):
"""perceive() must not contain hardcoded dismiss button text patterns."""
source = self._get_perceive_source()
for pattern in self.FORBIDDEN_BUTTON_TEXT_PATTERNS:
assert pattern not in source, (
f"SAE.perceive() contains hardcoded button text pattern '{pattern}'! "
f"Modal detection must be autonomous, not text-matching."
)
def test_no_hardcoded_content_desc_in_perceive(self):
"""perceive() must not contain hardcoded content-desc strings."""
source = self._get_perceive_source()
for pattern in self.FORBIDDEN_CONTENT_DESC_PATTERNS:
assert pattern not in source, (
f"SAE.perceive() contains hardcoded content-desc '{pattern}'! " f"Must be discovered autonomously."
)
def test_no_marker_tuples_in_perceive(self):
"""perceive() must not define any '_markers' or '_patterns' tuples."""
source = self._get_perceive_source()
marker_defs = re.findall(r"\b\w+_markers\s*=\s*\(", source)
pattern_defs = re.findall(r"\b\w+_patterns\s*=\s*\(", source)
all_defs = marker_defs + pattern_defs
assert len(all_defs) == 0, (
f"SAE.perceive() defines hardcoded marker/pattern tuples: {all_defs}. "
f"All obstacle detection must be autonomous."
)
# ══════════════════════════════════════════════════════════
# 2. NO HARDCODED MODEL NAMES / URLS IN SAE
# ══════════════════════════════════════════════════════════
class TestSAEContainsNoHardcodedModelDefaults:
"""
The SAE must never contain hardcoded model names or URLs as fallback strings.
ALL model config must come from Config() — the single source of truth.
A naked `except: model = "llava:latest"` is a maintenance bomb.
"""
FORBIDDEN_MODEL_LITERALS = [
"llava:latest",
"qwen3.5:latest",
"llava",
]
FORBIDDEN_URL_LITERALS = [
"localhost:11434",
"http://localhost:11434/api/generate",
]
def _get_sae_source(self):
from GramAddict.core.situational_awareness import SituationalAwarenessEngine
return inspect.getsource(SituationalAwarenessEngine)
def test_no_hardcoded_model_names(self):
"""SAE must not contain any hardcoded model name strings."""
source = self._get_sae_source()
for literal in self.FORBIDDEN_MODEL_LITERALS:
# Only check actual string literals (quoted), not comments
pattern = rf"""['"]({re.escape(literal)})['"]"""
matches = re.findall(pattern, source)
assert len(matches) == 0, (
f"SAE contains hardcoded model name '{literal}' ({len(matches)} occurrence(s)). "
f"Model config must come exclusively from Config()."
)
def test_no_hardcoded_urls(self):
"""SAE must not contain any hardcoded Ollama/API URLs."""
source = self._get_sae_source()
for literal in self.FORBIDDEN_URL_LITERALS:
pattern = rf"""['"]([^'"]*{re.escape(literal)}[^'"]*)['"]"""
matches = re.findall(pattern, source)
assert len(matches) == 0, (
f"SAE contains hardcoded URL '{literal}' ({len(matches)} occurrence(s)). "
f"All URLs must come from Config()."
)
def test_no_naked_except_with_model_defaults(self):
"""SAE must not have except blocks that hardcode model fallbacks."""
source = self._get_sae_source()
# Pattern: `except` followed within 5 lines by a model/url assignment
except_blocks = re.findall(
r"except.*?:\s*\n(?:.*\n){0,5}.*(?:model|url)\s*=\s*['\"]",
source,
)
assert len(except_blocks) == 0, (
f"SAE has {len(except_blocks)} except block(s) with hardcoded model/URL fallbacks. "
f"Config() must be the SSOT — if it fails, crash loudly (Fail Fast)."
)
# ══════════════════════════════════════════════════════════
# 3. GOAP SMART UI STABILIZATION (Poll instead of static sleep)
# ══════════════════════════════════════════════════════════
class TestGOAPSmartUIStabilization:
"""
After clicking an element, GOAP must poll for UI changes instead of
using a static sleep. This prevents false 'inconclusive' results on
slow devices/networks where UI transitions take >2s.
"""
def test_goap_polls_dump_hierarchy_multiple_times_after_click(self):
"""
The GOAP _execute_action must call dump_hierarchy multiple times
(polling loop) instead of a single post-click dump.
We verify this by inspecting the source code for the poll pattern.
"""
import inspect
from GramAddict.core.goap import GoalExecutor
source = inspect.getsource(GoalExecutor._execute_action)
# Must have a polling loop with MAX_POLLS
assert "MAX_POLLS" in source, "GOAP._execute_action must use a MAX_POLLS polling loop, not a static sleep."
# Must NOT have the old static random.uniform sleep
assert "random.uniform" not in source, (
"GOAP._execute_action still uses random.uniform for static sleep! " "Must use smart UI polling instead."
)
# Must poll dump_hierarchy inside a loop
assert "dump_hierarchy()" in source, "GOAP._execute_action must call dump_hierarchy() inside the poll loop."
def test_goap_has_no_static_sleep_after_click(self):
"""The old pattern was: click → sleep(random) → single dump. This must be gone."""
import inspect
from GramAddict.core.goap import GoalExecutor
source = inspect.getsource(GoalExecutor._execute_action)
# The click line and the next significant operation must NOT be a raw sleep with random
click_idx = source.index("self.device.click(")
code_after_click = source[click_idx : click_idx + 500]
assert "random.uniform" not in code_after_click, (
"GOAP still uses random.uniform sleep after click! "
"Replace with smart UI polling (poll dump_hierarchy until XML changes)."
)

View File

@@ -100,5 +100,4 @@ def test_telepathic_grid_selection_uses_structural_id(monkeypatch):
engine.evaluate_grid_visuals(MockDevice(), ["travel"])
assert grid_node in candidates_captured
assert background_node not in candidates_captured, "Background recycler view should not be a grid candidate"