fix(agent99): dispatch durable alerts before LLM

This commit is contained in:
ogt
2026-07-15 01:55:48 +08:00
parent e3acc321a7
commit fa61d4e07a
2 changed files with 215 additions and 18 deletions

View File

@@ -2,16 +2,20 @@ import ast
import asyncio
import inspect
from datetime import datetime
from types import SimpleNamespace
from unittest.mock import AsyncMock, Mock
import pytest
from src.api.v1 import webhooks as webhooks_module
from src.api.v1.webhooks import (
_analyze_alertmanager_with_timeout,
_process_new_alert_background,
_should_bypass_alertmanager_llm,
_should_use_alertmanager_rule_first,
)
from src.repositories.alert_operation_log_repository import ALERT_EVENT_TYPES
from src.services.alert_approval_guard import ApprovalActionGuardResult
from src.services.alertmanager_llm_guard import (
ALERTMANAGER_LLM_INFLIGHT_LOCK_TTL_SECONDS,
alertmanager_llm_inflight_key,
@@ -175,6 +179,125 @@ async def test_alertmanager_analysis_error_returns_fallback():
assert result == (None, "fallback_error", "", None, "", 0, 0.0)
@pytest.mark.asyncio
async def test_agent99_durable_route_dispatches_before_llm(monkeypatch):
class ApprovalService:
created_request = None
async def create_approval_with_fingerprint(self, *, request, fingerprint):
self.created_request = request
assert fingerprint == "fp-agent99-durable-route"
return SimpleNamespace(id="approval-agent99-fast-path", incident_id=None)
async def update_incident_id(self, approval_id, incident_id):
assert str(approval_id) == "approval-agent99-fast-path"
assert incident_id == "INC-AGENT99-FAST-PATH"
service = ApprovalService()
openclaw_factory = Mock(
side_effect=AssertionError("durable Agent99 route must not create OpenClaw")
)
handoff = AsyncMock(
return_value={
"status": "agent99_dispatch_accepted_verifier_pending",
"queued": True,
}
)
telegram = AsyncMock()
monkeypatch.setattr(webhooks_module, "get_approval_service", lambda: service)
monkeypatch.setattr(webhooks_module, "get_openclaw", openclaw_factory)
monkeypatch.setattr(
webhooks_module,
"get_trusted_alert_canonical_route_context",
lambda *_args, **_kwargs: {},
)
monkeypatch.setattr(
webhooks_module,
"match_rule",
lambda _context: {
"rule_id": "generic_fallback",
"risk_level": "critical",
"blast_radius": {},
"kubectl_command": "",
"description": "generic fallback must not block Agent99",
"confidence": 0.0,
},
)
monkeypatch.setattr(
webhooks_module,
"resolve_playbook_id_for_alert",
AsyncMock(return_value=None),
)
monkeypatch.setattr(
webhooks_module,
"guard_alert_approval_action",
AsyncMock(
side_effect=lambda **kwargs: ApprovalActionGuardResult(
action=kwargs["action"]
)
),
)
monkeypatch.setattr(
webhooks_module,
"get_auto_approve_policy",
lambda: SimpleNamespace(
evaluate=lambda _proposal: SimpleNamespace(
should_auto_approve=False,
reason=SimpleNamespace(value="bounded_durable_route"),
)
),
)
monkeypatch.setattr(
webhooks_module,
"create_incident_for_approval",
AsyncMock(return_value="INC-AGENT99-FAST-PATH"),
)
monkeypatch.setattr(
webhooks_module,
"record_alertmanager_event",
AsyncMock(),
)
monkeypatch.setattr(webhooks_module, "_try_auto_repair_background", handoff)
monkeypatch.setattr(webhooks_module, "_push_to_telegram_background", telegram)
monkeypatch.setattr(webhooks_module, "record_alert_chain_success", Mock())
await _process_new_alert_background(
alert_context={
"alertname": "RebootAutoRecoveryActiveBlocker",
"annotations": {"summary": "cold-start gate blocked"},
"source_url": "http://prometheus.invalid/graph",
},
alert_id="alert-agent99-fast-path",
fingerprint="fp-agent99-durable-route",
target_resource="reboot-auto-recovery-slo",
namespace="default",
alert_type="custom",
message="cold-start-gate service recovery verifier",
alertname="RebootAutoRecoveryActiveBlocker",
severity="critical",
alert_labels={"service": "cold-start-gate"},
notification_type="TYPE-3",
alert_category="general",
can_auto_repair=True,
)
openclaw_factory.assert_not_called()
handoff.assert_awaited_once()
assert handoff.await_args.kwargs["source_alertname"] == (
"RebootAutoRecoveryActiveBlocker"
)
assert service.created_request.risk_level.value == "medium"
assert service.created_request.action == (
"AGENT99_CONTROLLED_ROUTE agent99:host_recovery:Recover"
)
assert service.created_request.metadata["source"] == "agent99_durable_route"
telegram.assert_awaited_once()
assert telegram.await_args.kwargs["automation_state"] == (
"agent99_dispatch_accepted_verifier_pending"
)
def test_resolved_guard_stamp_without_timestamp_is_clean():
assert _format_resolved_guard_stamp(None) == "✅ 此事件已解決"