fix(perception): 3 production bug regressions from 2026-05-01 run

Bug 1 — VLM Profile Tab Hallucination:
  - VLM confused 'Profile' nav tab with post author username
  - Added Author Tab Guard to filter_navigation_conflicts()
  - Fixed mock LLM to use structural row_feed_photo_profile matching
    instead of hardcoded username (was the test lie)

Bug 2 — Like Button 1-Byte Delta Kill:
  - Toggle actions (like/save) produce 1-byte XML deltas
  - GOAP's MIN_UI_CHANGE_BYTES=50 threshold killed them as 'no change'
  - Split interaction gate: interactions use ANY xml diff, navigations
    keep the 50-byte threshold

Bug 3 — Empty Username Silently Accepted:
  - PostDataExtraction returned 'Post by @' with no warning
  - Added 'username_missing' reliability flag to result dict
  - Downstream consumers can now detect degraded data quality

Cleanup:
  - Removed all debug print() statements from production code
  - Replaced with structured logger.debug() calls
This commit is contained in:
2026-05-01 21:57:56 +02:00
parent df18a48a84
commit 2e1edec56a
15 changed files with 1824 additions and 7 deletions

View File

@@ -689,7 +689,7 @@ def mock_llm_network_calls(monkeypatch, request):
if not prompt and len(args) > 0:
prompt = args[0]
print(f"MOCK LLM PROMPT:\n{prompt}\n---------------------")
# Debug mock routing available at DEBUG log level if needed
if "OBSTACLE_LOCKED_SCREEN" in prompt and "FOREIGN_APP" in prompt:
return json.dumps({"situation": "OBSTACLE_FOREIGN_APP"})
@@ -739,6 +739,10 @@ def mock_llm_network_calls(monkeypatch, request):
match = re.search(r"\[(\d+)\][^\[]*story ring avatar", prompt.lower())
if match:
return json.dumps({"selected_index": int(match.group(1))})
if "author" in intent or "profile" in intent or "username" in intent:
match = re.search(r"\[(\d+)\][^\[]*row_feed_photo_profile", prompt.lower())
if match:
return json.dumps({"selected_index": int(match.group(1))})
if "follow" in intent:
match = re.search(r"\[(\d+)\][^\[]*follow", prompt.lower())
if match:
@@ -783,6 +787,13 @@ def mock_llm_network_calls(monkeypatch, request):
match = re.search(r"\[(\d+)\][^\n]*?story ring avatar", prompt.lower())
if match:
return json.dumps({"box": int(match.group(1))})
if "author" in intent or "username" in intent:
# STRUCTURAL MATCH — match by resource_id pattern, not by hardcoded username.
# This is the fix for the 2026-05-01 lie where the mock searched for 'ninjatrader'
# but the real VLM picked 'Profile' tab instead of the actual author.
match = re.search(r"\[(\d+)\][^\n]*?row_feed_photo_profile", prompt.lower())
if match:
return json.dumps({"box": int(match.group(1))})
if "follow" in intent:
match = re.search(r"\[(\d+)\][^\n]*?follow", prompt.lower())
if match: