feat(core): complete P1 hardening phase - ContextGate, Localization Purge, SoM Safety Clamp

This commit is contained in:
2026-05-04 14:51:54 +02:00
parent 5c9ea0e5e2
commit 79e5784b93
9 changed files with 281 additions and 53 deletions

View File

@@ -0,0 +1,65 @@
"""
P1-5: ContextGate Interaction Guards
TDD RED: These tests enforce that:
1. Actions are blocked if they are structurally impossible on the current screen.
2. 'follow' is blocked on OWN_PROFILE.
3. 'comment' is blocked if no comment button is present in the XML.
4. 'like' is blocked if no heart/like button is present.
"""
from GramAddict.core.perception.context_gate import ContextGate
from GramAddict.core.perception.screen_identity import ScreenType
class TestContextGate:
def test_block_follow_on_own_profile(self):
"""RED: Following yourself is impossible and usually a sign of navigation drift."""
gate = ContextGate()
# Mock screen state
screen = {
"screen_type": ScreenType.OWN_PROFILE,
"resource_ids": {"profile_tab", "profile_edit_button"},
"xml": "<node resource-id='profile_edit_button' />",
}
assert gate.is_allowed("follow", screen) is False, "Should block 'follow' on OWN_PROFILE"
def test_block_comment_without_button(self):
"""RED: Blocking comment if the button isn't there (e.g. on a profile view)."""
gate = ContextGate()
screen = {
"screen_type": ScreenType.OTHER_PROFILE,
"resource_ids": {"button_follow", "action_bar_overflow_icon"},
"xml": "<node resource-id='button_follow' />",
}
# Commenting on a profile page is impossible (must be on post detail or feed)
assert gate.is_allowed("comment", screen) is False, "Should block 'comment' if no comment button is present"
def test_allow_like_on_feed(self):
"""GREEN (Target): Allow if the button exists."""
gate = ContextGate()
screen = {
"screen_type": ScreenType.HOME_FEED,
"resource_ids": {"row_feed_button_like", "row_feed_button_comment"},
"xml": "<node resource-id='row_feed_button_like' />",
}
assert gate.is_allowed("like", screen) is True
def test_block_post_interaction_on_dm_thread(self):
"""RED: Prevent accidental likes/comments in DM threads."""
gate = ContextGate()
screen = {
"screen_type": ScreenType.DM_THREAD,
"resource_ids": {"direct_text_input", "direct_thread_header"},
"xml": "<node resource-id='direct_text_input' />",
}
assert gate.is_allowed("like", screen) is False
assert gate.is_allowed("comment", screen) is False

View File

@@ -0,0 +1,45 @@
"""
P1-7: IntentResolver Box Index Clamping
TDD RED: These tests enforce that:
1. VLM responses with string box indices are correctly parsed as integers.
2. Hallucinated indices (OOB) are gracefully rejected without crashing.
3. Common VLM hallucination patterns (e.g. {"box": "Box 5"}) are handled.
"""
from GramAddict.core.perception.intent_resolver import IntentResolver
from GramAddict.core.perception.spatial_parser import SpatialNode
class TestIntentResolverHardening:
def test_parse_box_index_variants(self):
"""RED: Resolver should handle various VLM JSON formats and types."""
resolver = IntentResolver()
# Test basic int
assert resolver._parse_box_index({"box": 5}) == 5
# Test string int
assert resolver._parse_box_index({"box": "5"}) == 5
# Test prefixed string
assert resolver._parse_box_index({"box": "Box 5"}) == 5
# Test alternate keys
assert resolver._parse_box_index({"selected_index": 3}) == 3
assert resolver._parse_box_index({"box_index": "7"}) == 7
# Test invalid
assert resolver._parse_box_index({"box": "none"}) is None
assert resolver._parse_box_index({"box": "null"}) is None
def test_box_clamping_rejection(self):
"""RED: Reject indices that are not in the box_map."""
resolver = IntentResolver()
box_map = {0: SpatialNode(0, 0, 10, 10), 1: SpatialNode(10, 10, 20, 20)}
# Index 99 is OOB
assert resolver._validate_and_get_node(99, box_map) is None
# Index 1 is valid
assert resolver._validate_and_get_node(1, box_map) is not None