fix(runtime): preserve lifecycle closure receipts

This commit is contained in:
ogt
2026-07-15 04:32:15 +08:00
parent 6c1a12e388
commit 8f741ce100
2 changed files with 97 additions and 24 deletions

View File

@@ -15,6 +15,9 @@ from src.jobs.incident_lifecycle_reconciler import (
@pytest.mark.asyncio
async def test_reconcile_stuck_incidents_resolves_strong_evidence(monkeypatch):
service = SimpleNamespace(resolve_incident=AsyncMock(return_value=object()))
timeline = SimpleNamespace(
add_event=AsyncMock(side_effect=[{"id": "tl-1"}, {"id": "tl-2"}])
)
monkeypatch.setattr(
"src.jobs.incident_lifecycle_reconciler._fetch_candidates",
@@ -37,6 +40,10 @@ async def test_reconcile_stuck_incidents_resolves_strong_evidence(monkeypatch):
"src.services.incident_service.get_incident_service",
lambda: service,
)
monkeypatch.setattr(
"src.services.approval_db.get_timeline_service",
lambda: timeline,
)
resolved, errors = await reconcile_stuck_incidents(limit=2)
@@ -53,6 +60,64 @@ async def test_reconcile_stuck_incidents_resolves_strong_evidence(monkeypatch):
"resolution_type": "timeout",
"emit_postmortem": False,
}
assert timeline.add_event.await_count == 2
assert len(timeline.add_event.await_args_list[0].kwargs["event_type"]) <= 20
assert timeline.add_event.await_args_list[0].kwargs == {
"event_type": "lifecycle_reconciled",
"status": "success",
"title": "Incident lifecycle reconciled from durable source truth",
"description": (
"reason=approval_execution_success; resolution_type=auto_repair; "
"postmortem=suppressed_batch_reconcile; telegram=not_sent"
),
"actor": "incident_lifecycle_reconciler",
"actor_role": "ai_agent",
"risk_level": "low",
"incident_id": "INC-EXEC-SUCCESS",
"project_id": "awoooi",
}
@pytest.mark.asyncio
async def test_inactive_alert_uses_service_and_writes_timeline(monkeypatch):
service = SimpleNamespace(resolve_incident=AsyncMock(return_value=object()))
timeline = SimpleNamespace(add_event=AsyncMock(return_value={"id": "tl-inactive"}))
candidate = LifecycleCandidate(
incident_id="INC-INACTIVE",
resolution_type="timeout",
reason="inactive_alert_stale",
)
monkeypatch.setattr(
"src.jobs.incident_lifecycle_reconciler._fetch_candidates",
AsyncMock(return_value=[]),
)
monkeypatch.setattr(
"src.jobs.incident_lifecycle_reconciler._fetch_active_alertnames",
AsyncMock(return_value=set()),
)
monkeypatch.setattr(
"src.jobs.incident_lifecycle_reconciler._fetch_inactive_or_duplicate_alert_candidates",
AsyncMock(return_value=[candidate]),
)
monkeypatch.setattr(
"src.services.incident_service.get_incident_service",
lambda: service,
)
monkeypatch.setattr(
"src.services.approval_db.get_timeline_service",
lambda: timeline,
)
resolved, errors = await reconcile_stuck_incidents(limit=1)
assert (resolved, errors) == (1, 0)
service.resolve_incident.assert_awaited_once_with(
"INC-INACTIVE",
resolution_type="timeout",
emit_postmortem=False,
)
timeline.add_event.assert_awaited_once()
@pytest.mark.asyncio