[eric] events: self-heal verifies its fix answers before applying (never trust the model's claim)

This commit is contained in:
ciregenz
2026-07-28 17:59:20 -07:00
parent a0fe84ca94
commit 536f3b04e5
2 changed files with 44 additions and 0 deletions
@@ -44,6 +44,16 @@ def parse_heal_reply(text: str) -> Tuple[Optional[str], str]:
return fix, reason
async def probe_url(url: str) -> bool:
"""A heal is only real if the fix demonstrably answers: SSRF-guarded GET, 2xx/3xx required. Model claims never applied unverified."""
try:
from backend.apps.agents.tools.web import USER_AGENT, safe_fetch
resp = await safe_fetch(url, method="GET", headers={"User-Agent": USER_AGENT}, timeout=12.0)
return resp.status_code < 400
except Exception:
return False
async def attempt_heal(workflow_id: str, trigger: EventTriggerConfig) -> bool:
"""True when the trigger config was repaired (caller should re-poll now)."""
from backend.apps.events import stores
@@ -63,6 +73,12 @@ async def attempt_heal(workflow_id: str, trigger: EventTriggerConfig) -> bool:
logger.warning("heal turn failed for trigger %s: %s", trigger.id, e)
return False
if fix and fix.startswith("http") and fix != url:
if not await probe_url(fix):
stores.append_log(workflow_id, EventLogEntry(
trigger_id=trigger.id, kind="error",
summary=f"Self-heal proposed {fix} but it didn't answer; not applied",
))
return False
wf = storage.get_workflow(workflow_id)
if wf is None:
return False
+28
View File
@@ -176,6 +176,10 @@ def test_self_heal_fixes_url_or_escalates(make_wf, monkeypatch):
assert "https://old.example/feed" in prompt and "410 Gone" in prompt
return "Investigated.\nFIX_URL: https://new.example/feed"
async def p_probe_ok(url):
return url == "https://new.example/feed"
monkeypatch.setattr(ht, "probe_url", p_probe_ok)
monkeypatch.setattr(ac, "run_check_turn", p_fix_turn)
assert p_run(ht.attempt_heal(wf.id, trig)) is True
healed = storage.get_workflow(wf.id).event_triggers[0]
@@ -190,3 +194,27 @@ def test_self_heal_fixes_url_or_escalates(make_wf, monkeypatch):
assert p_run(ht.attempt_heal(wf.id, healed)) is False
assert storage.get_workflow(wf.id).event_triggers[0].source.url == "https://new.example/feed" # untouched
assert any("requires a sign-in" in e.summary for e in stores.read_log(wf.id))
def test_heal_never_applies_an_unverified_fix(make_wf, monkeypatch):
"""The model can claim any URL; only one that actually answers gets applied."""
from backend.apps.events.adapters import agent_check as ac
from backend.apps.events.adapters import heal_trigger as ht
from backend.apps.events import stores
from backend.apps.workflows import storage
trig = EventTriggerConfig(source=StreamSource(url="https://old.example/feed"))
wf = make_wf(event_triggers=[trig])
storage.save_workflow(wf)
async def p_fix_turn(model, prompt, **kwargs):
return "FIX_URL: https://hallucinated.example/feed"
async def p_probe_dead(url):
return False
monkeypatch.setattr(ac, "run_check_turn", p_fix_turn)
monkeypatch.setattr(ht, "probe_url", p_probe_dead)
assert p_run(ht.attempt_heal(wf.id, trig)) is False
assert storage.get_workflow(wf.id).event_triggers[0].source.url == "https://old.example/feed"
assert any("didn't answer; not applied" in e.summary for e in stores.read_log(wf.id))