feat(governance): persist remediation dry run history
This commit is contained in:
@@ -51,6 +51,24 @@ class _FakeVerifier:
|
||||
return self.state
|
||||
|
||||
|
||||
class _FakeAlertOperationLogRepository:
|
||||
def __init__(self) -> None:
|
||||
self.calls: list[dict[str, Any]] = []
|
||||
|
||||
async def append(self, event_type: str, **kwargs: Any):
|
||||
self.calls.append({"event_type": event_type, **kwargs})
|
||||
return type("AlertOperationRecord", (), {"id": "aol-1"})()
|
||||
|
||||
|
||||
class _FakeTimelineService:
|
||||
def __init__(self) -> None:
|
||||
self.calls: list[dict[str, Any]] = []
|
||||
|
||||
async def add_event(self, **kwargs: Any) -> dict[str, Any]:
|
||||
self.calls.append(kwargs)
|
||||
return {"id": "timeline-1"}
|
||||
|
||||
|
||||
class _NoopPlaybookService:
|
||||
async def get_recommendations(self, *_args, **_kwargs): # noqa: ANN002, ANN003
|
||||
return []
|
||||
@@ -111,6 +129,9 @@ def _service(
|
||||
item: dict[str, Any],
|
||||
incident: Incident | None = None,
|
||||
state: dict[str, Any] | None = None,
|
||||
timeline_service: Any | None = None,
|
||||
alert_operation_log_repository: Any | None = None,
|
||||
record_history: bool = False,
|
||||
) -> Adr100RemediationService:
|
||||
return Adr100RemediationService(
|
||||
slo_service=_FakeSloService([item]),
|
||||
@@ -120,6 +141,9 @@ def _service(
|
||||
cooldown_checker=_no_cooldown,
|
||||
),
|
||||
verifier=_FakeVerifier(state or {"k8s_get_pod_status": {"phase": "Running"}}),
|
||||
timeline_service=timeline_service,
|
||||
alert_operation_log_repository=alert_operation_log_repository,
|
||||
record_history=record_history,
|
||||
)
|
||||
|
||||
|
||||
@@ -156,6 +180,7 @@ async def test_dry_run_reverify_collects_state_without_writes():
|
||||
assert result["post_state_summary"]["tool_count"] == 1
|
||||
assert result["mcp_route"]["agent_id"] == "post_execution_verifier"
|
||||
assert result["mcp_route"]["required_scope"] == "read"
|
||||
assert result["history"]["recorded"] is False
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -187,6 +212,36 @@ async def test_dry_run_blocks_when_incident_missing():
|
||||
assert any(check["name"] == "incident_loaded" and not check["passed"] for check in result["checks"])
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_dry_run_records_alert_operation_and_timeline_history():
|
||||
alert_repo = _FakeAlertOperationLogRepository()
|
||||
timeline = _FakeTimelineService()
|
||||
svc = _service(
|
||||
item=_queue_item(),
|
||||
timeline_service=timeline,
|
||||
alert_operation_log_repository=alert_repo,
|
||||
record_history=True,
|
||||
)
|
||||
|
||||
result = await svc.dry_run("verification:INC-20260514-TEST01:are-1")
|
||||
|
||||
assert result["history"] == {
|
||||
"recorded": True,
|
||||
"alert_operation_id": "aol-1",
|
||||
"timeline_event_id": "timeline-1",
|
||||
}
|
||||
assert alert_repo.calls[0]["event_type"] == "PRE_FLIGHT_PASSED"
|
||||
assert alert_repo.calls[0]["incident_id"] == "INC-20260514-TEST01"
|
||||
assert alert_repo.calls[0]["success"] is True
|
||||
assert alert_repo.calls[0]["context"]["schema_version"] == (
|
||||
"adr100_remediation_dry_run_history_v1"
|
||||
)
|
||||
assert alert_repo.calls[0]["context"]["writes_incident_state"] is False
|
||||
assert timeline.calls[0]["event_type"] == "verifier"
|
||||
assert timeline.calls[0]["status"] == "success"
|
||||
assert timeline.calls[0]["actor_role"] == "replay"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_missing_work_item_raises_not_found():
|
||||
svc = _service(item=_queue_item())
|
||||
|
||||
Reference in New Issue
Block a user