mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-08 18:57:43 +02:00
[eric] events: self-heal verifies its fix answers before applying (never trust the model's claim)
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user