From 0624be08dfe45cd52a7b8b2e65d2f5c9acaff9c4 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 27 Jun 2026 21:51:47 +0800 Subject: [PATCH] fix(awooop): persist controlled apply receipt snapshots --- .../services/awooop_truth_chain_service.py | 6 ++- apps/api/src/services/telegram_gateway.py | 5 +++ .../tests/test_awooop_truth_chain_service.py | 24 ++++++++++++ .../tests/test_telegram_message_templates.py | 37 +++++++++++++++++++ 4 files changed, 71 insertions(+), 1 deletion(-) diff --git a/apps/api/src/services/awooop_truth_chain_service.py b/apps/api/src/services/awooop_truth_chain_service.py index 03d8105f..9662247e 100644 --- a/apps/api/src/services/awooop_truth_chain_service.py +++ b/apps/api/src/services/awooop_truth_chain_service.py @@ -435,6 +435,9 @@ def _truth_status( approval_suppressed = _approval_suppresses_repair_execution(approvals) effective_ops = [] if approval_suppressed else _effective_execution_ops(automation_ops) has_execution_records = bool(effective_ops or repair_rows) + latest_verification = str( + _latest_verification_result(incident, evidence_rows) or "" + ).lower() stage = "received" stage_status = incident_status.lower() if incident_status in {"RESOLVED", "CLOSED"}: @@ -504,7 +507,8 @@ def _truth_status( if incident_status == "INVESTIGATING" and approvals: if execution_succeeded: blockers.append("incident_open_after_successful_execution") - needs_human = True + if latest_verification != "success": + needs_human = True elif not has_execution_records: blockers.append("incident_still_investigating_after_approval") diff --git a/apps/api/src/services/telegram_gateway.py b/apps/api/src/services/telegram_gateway.py index fa0af992..aa9cf67a 100644 --- a/apps/api/src/services/telegram_gateway.py +++ b/apps/api/src/services/telegram_gateway.py @@ -9237,6 +9237,10 @@ class TelegramGateway: incident_id=incident_id, truth_chain=truth_chain, ) + km_completion_summary = await _fetch_km_stale_completion_summary_for_incident( + incident_id=incident_id, + project_id=project_id or "awoooi", + ) source_extra = _callback_reply_source_envelope_extra( incident_id=incident_id, failure_context="controlled_apply_result", @@ -9245,6 +9249,7 @@ class TelegramGateway: chunk_count=1, callback_action="controlled_apply_result", parse_mode="HTML", + km_stale_completion_summary=km_completion_summary, awooop_status_chain=status_snapshot, ) lines = [ diff --git a/apps/api/tests/test_awooop_truth_chain_service.py b/apps/api/tests/test_awooop_truth_chain_service.py index d2e0a9a7..0009ea14 100644 --- a/apps/api/tests/test_awooop_truth_chain_service.py +++ b/apps/api/tests/test_awooop_truth_chain_service.py @@ -232,6 +232,30 @@ def test_truth_status_marks_open_incident_after_successful_execution() -> None: assert "incident_open_after_successful_execution" in status["blockers"] +def test_truth_status_keeps_verified_controlled_apply_autonomous_when_incident_open() -> None: + status = _truth_status( + incident={ + "incident_id": "INC-OPEN-VERIFIED", + "status": "INVESTIGATING", + "verification_result": "success", + }, + approvals=[{"status": "EXECUTION_SUCCESS", "action": "ansible controlled apply"}], + evidence_rows=[{"sensors_attempted": 8, "sensors_succeeded": 6}], + automation_ops=[], + drift=None, + drift_repeat_count=0, + gateway_mcp_total=3, + legacy_mcp_total=2, + outbound_visible_total=1, + auto_repair_executions=[{"success": True}], + ) + + assert status["current_stage"] == "execution_succeeded" + assert status["stage_status"] == "success" + assert status["needs_human"] is False + assert "incident_open_after_successful_execution" in status["blockers"] + + def test_truth_status_marks_repeated_pending_drift_as_human_needed() -> None: status = _truth_status( incident=None, diff --git a/apps/api/tests/test_telegram_message_templates.py b/apps/api/tests/test_telegram_message_templates.py index 30396502..2c44adab 100644 --- a/apps/api/tests/test_telegram_message_templates.py +++ b/apps/api/tests/test_telegram_message_templates.py @@ -1092,12 +1092,45 @@ async def test_controlled_apply_result_receipt_marks_callback_reply_evidence(mon }, } + async def fake_fetch_km_completion_summary(*, incident_id, project_id): + assert incident_id == "INC-20260627-64472B" + assert project_id == "awoooi" + return { + "schema_version": "km_stale_owner_review_completion_telegram_summary_v1", + "status": "no_related_owner_review", + "project_id": "awoooi", + "incident_id": "INC-20260627-64472B", + "missing_reason": "no_matching_completion_item", + "ready_count": 10, + "blocked_count": 0, + "completed_count": 1, + "failed_count": 0, + "related_total": 0, + "writes_on_read": False, + "batch_writes_allowed": False, + "manual_review_required": True, + "triage": { + "schema_version": "km_stale_callback_owner_review_triage_v1", + "flow_stage": "callback_observed_owner_review_link_missing", + "ai_lead_agent": "Hermes", + "supporting_agents": ["OpenClaw"], + "automation_state": "manual_owner_review_required", + "safe_to_auto_repair": False, + "blocking_reason": "no_matching_completion_item", + "matching_strategy": "related_incident_id_exact_match", + }, + } + monkeypatch.setattr(TelegramGateway, "alert_chat_id", property(lambda _self: "chat")) monkeypatch.setattr(gateway, "_send_request", fake_send_request) monkeypatch.setattr( "src.services.awooop_truth_chain_service.fetch_truth_chain", fake_fetch_truth_chain, ) + monkeypatch.setattr( + "src.services.telegram_gateway._fetch_km_stale_completion_summary_for_incident", + fake_fetch_km_completion_summary, + ) result = await gateway.send_controlled_apply_result_receipt( incident_id="INC-20260627-64472B", @@ -1123,6 +1156,10 @@ async def test_controlled_apply_result_receipt_marks_callback_reply_evidence(mon assert source_extra["callback_reply"]["action"] == "controlled_apply_result" assert source_extra["callback_reply"]["status"] == "callback_reply_sent" assert source_extra["source_refs"]["incident_ids"] == ["INC-20260627-64472B"] + km_snapshot = source_extra["km_stale_completion_summary"] + assert km_snapshot["status"] == "no_related_owner_review" + assert km_snapshot["ready_count"] == 10 + assert km_snapshot["triage"]["ai_lead_agent"] == "Hermes" snapshot = source_extra["awooop_status_chain"] assert snapshot["repair_state"] == "auto_repaired_verified" assert snapshot["operator_outcome"]["state"] == "completed_verified"