fix(alerts): 補齊處置結果與人工通知契約
This commit is contained in:
@@ -1361,6 +1361,8 @@ def test_awooop_status_chain_marks_verified_repair() -> None:
|
||||
assert chain["verification"] == "healthy"
|
||||
assert chain["needs_human"] is False
|
||||
assert chain["next_step"] == "monitor_for_regression"
|
||||
assert chain["operator_outcome"]["state"] == "completed_verified"
|
||||
assert chain["operator_outcome"]["needs_human"] is False
|
||||
assert chain["evidence"]["latest_route"] == "auto_repair_executor/rollout_restart/write"
|
||||
assert chain["mcp"]["gateway"]["success"] == 1
|
||||
assert chain["mcp"]["gateway"]["failed"] == 1
|
||||
@@ -1643,6 +1645,9 @@ def test_awooop_status_chain_does_not_treat_audit_ops_as_repair() -> None:
|
||||
|
||||
assert chain["repair_state"] == "diagnostic_or_audit_recorded"
|
||||
assert chain["next_step"] == "manual_review_or_collect_repair_evidence"
|
||||
assert chain["needs_human"] is True
|
||||
assert chain["operator_outcome"]["state"] == "diagnostic_only_manual_review"
|
||||
assert chain["operator_outcome"]["notification"]["mode"] == "action_required"
|
||||
assert chain["evidence"]["operation_records"] == 1
|
||||
assert chain["evidence"]["auto_repair_records"] == 0
|
||||
|
||||
|
||||
159
apps/api/tests/test_operator_outcome.py
Normal file
159
apps/api/tests/test_operator_outcome.py
Normal file
@@ -0,0 +1,159 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import inspect
|
||||
from types import SimpleNamespace
|
||||
from uuid import UUID
|
||||
|
||||
from src.services.approval_execution import ApprovalExecutionService
|
||||
from src.services.operator_outcome import build_operator_outcome
|
||||
|
||||
|
||||
def test_operator_outcome_marks_diagnostic_only_as_manual_action_required() -> None:
|
||||
outcome = build_operator_outcome(
|
||||
truth_status={
|
||||
"current_stage": "execution_succeeded",
|
||||
"stage_status": "success",
|
||||
"needs_human": False,
|
||||
"blockers": [],
|
||||
},
|
||||
automation_quality={
|
||||
"verdict": "auto_repaired_verification_degraded",
|
||||
"facts": {
|
||||
"automation_operation_records": 1,
|
||||
"effective_execution_records": 0,
|
||||
"auto_repair_execution_records": 0,
|
||||
"verification_result": "degraded",
|
||||
"mcp_gateway_total": 22,
|
||||
"knowledge_entries": 4,
|
||||
},
|
||||
"blockers": ["verification_recorded"],
|
||||
},
|
||||
source_id="INC-20260530-88D960",
|
||||
)
|
||||
|
||||
assert outcome["schema_version"] == "operator_outcome_v1"
|
||||
assert outcome["state"] == "diagnostic_only_manual_review"
|
||||
assert outcome["needs_human"] is True
|
||||
assert outcome["notification"]["mode"] == "action_required"
|
||||
assert "telegram_sre_war_room" in outcome["notification"]["channels"]
|
||||
assert outcome["next_action"] == "manual_review_or_collect_repair_evidence"
|
||||
|
||||
|
||||
def test_operator_outcome_marks_unverified_execution_as_human_review() -> None:
|
||||
outcome = build_operator_outcome(
|
||||
truth_status={
|
||||
"current_stage": "execution_succeeded",
|
||||
"stage_status": "success",
|
||||
"needs_human": False,
|
||||
"blockers": [],
|
||||
},
|
||||
automation_quality={
|
||||
"verdict": "execution_unverified",
|
||||
"facts": {
|
||||
"effective_execution_records": 1,
|
||||
"auto_repair_execution_records": 0,
|
||||
"verification_result": None,
|
||||
},
|
||||
"blockers": ["verification_recorded"],
|
||||
},
|
||||
)
|
||||
|
||||
assert outcome["state"] == "execution_unverified_manual_required"
|
||||
assert outcome["needs_human"] is True
|
||||
assert outcome["next_action"] == "run_or_review_post_execution_verification"
|
||||
|
||||
|
||||
def test_operator_outcome_marks_verified_repair_as_result_only() -> None:
|
||||
outcome = build_operator_outcome(
|
||||
truth_status={
|
||||
"current_stage": "execution_succeeded",
|
||||
"stage_status": "success",
|
||||
"needs_human": False,
|
||||
"blockers": [],
|
||||
},
|
||||
automation_quality={
|
||||
"verdict": "auto_repaired_verified",
|
||||
"facts": {
|
||||
"effective_execution_records": 1,
|
||||
"auto_repair_execution_records": 1,
|
||||
"verification_result": "success",
|
||||
},
|
||||
"blockers": [],
|
||||
},
|
||||
)
|
||||
|
||||
assert outcome["state"] == "completed_verified"
|
||||
assert outcome["needs_human"] is False
|
||||
assert outcome["notification"]["mode"] == "result_only"
|
||||
|
||||
|
||||
def test_execution_result_message_includes_operator_outcome_and_human_channels() -> None:
|
||||
service = ApprovalExecutionService()
|
||||
approval = SimpleNamespace(
|
||||
id=UUID("11111111-1111-4111-8111-111111111111"),
|
||||
incident_id="INC-20260531-ABC123",
|
||||
action="OBSERVE",
|
||||
)
|
||||
text = service._format_execution_result_message(
|
||||
approval=approval,
|
||||
success=True,
|
||||
error=None,
|
||||
no_action=True,
|
||||
km_info="",
|
||||
outcome=build_operator_outcome(
|
||||
truth_status={"needs_human": True, "blockers": ["manual_gate"]},
|
||||
automation_quality={
|
||||
"verdict": "manual_required_no_action",
|
||||
"facts": {},
|
||||
"blockers": [],
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
assert "處置結果" in text
|
||||
assert "人工: <code>yes</code>" in text
|
||||
assert "telegram_sre_war_room" in text
|
||||
assert "manual_review_no_action_decision" in text
|
||||
|
||||
|
||||
def test_execution_result_message_does_not_call_diagnostic_success_repair_done() -> None:
|
||||
service = ApprovalExecutionService()
|
||||
approval = SimpleNamespace(
|
||||
id=UUID("22222222-2222-4222-8222-222222222222"),
|
||||
incident_id="INC-20260531-DIAG01",
|
||||
action="ssh diagnose disk usage",
|
||||
)
|
||||
|
||||
text = service._format_execution_result_message(
|
||||
approval=approval,
|
||||
success=True,
|
||||
error=None,
|
||||
no_action=False,
|
||||
km_info="",
|
||||
outcome=build_operator_outcome(
|
||||
truth_status={"needs_human": False, "blockers": []},
|
||||
automation_quality={
|
||||
"verdict": "auto_repaired_verification_degraded",
|
||||
"facts": {
|
||||
"automation_operation_records": 1,
|
||||
"effective_execution_records": 0,
|
||||
"auto_repair_execution_records": 0,
|
||||
"verification_result": "degraded",
|
||||
},
|
||||
"blockers": ["diagnostic_only"],
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
assert "已記錄診斷,尚未證明修復" in text
|
||||
assert "執行成功" not in text
|
||||
assert "修復完成" not in text
|
||||
|
||||
|
||||
def test_execution_result_sender_has_standalone_fallback_when_original_card_missing() -> None:
|
||||
source = inspect.getsource(ApprovalExecutionService._push_execution_result_to_alert)
|
||||
|
||||
assert "push_execution_result_no_msg_id_standalone" in source
|
||||
assert "reply_to_message_id" in source
|
||||
assert "delivery" in source
|
||||
assert "standalone" in source
|
||||
@@ -130,6 +130,8 @@ def test_awooop_status_chain_lines_show_verified_auto_repair_stage() -> None:
|
||||
assert "auto_repair_executor/rollout_restart/write" in joined
|
||||
assert "人工: <code>no</code>" in joined
|
||||
assert "monitor_for_regression" in joined
|
||||
assert "處置結論" in joined
|
||||
assert "已驗證自動修復完成" in joined
|
||||
|
||||
|
||||
def test_awooop_status_chain_lines_show_read_only_manual_gate() -> None:
|
||||
@@ -206,6 +208,9 @@ def test_awooop_status_chain_lines_do_not_treat_audit_ops_as_repair() -> None:
|
||||
assert "diagnostic_or_audit_recorded" in joined
|
||||
assert "manual_review_or_collect_repair_evidence" in joined
|
||||
assert "executed_pending_verification" not in joined
|
||||
assert "處置結論" in joined
|
||||
assert "只完成診斷/觀察,尚未證明修復" in joined
|
||||
assert "通知:<code>telegram_sre_war_room,awooop_operator_console</code>" in joined
|
||||
|
||||
|
||||
def test_awooop_agent_evidence_lines_show_mcp_source_execution_playbook_km() -> None:
|
||||
|
||||
Reference in New Issue
Block a user