fix(api): distinguish ansible dry run from repair
Some checks failed
Code Review / ai-code-review (push) Successful in 14s
CD Pipeline / tests (push) Successful in 1m35s
CD Pipeline / build-and-deploy (push) Successful in 5m0s
CD Pipeline / post-deploy-checks (push) Successful in 1m33s
Ansible / Reboot Recovery Contract / validate (push) Has been cancelled

This commit is contained in:
Your Name
2026-06-25 20:52:22 +08:00
parent 8080606112
commit d7b3997b4a
9 changed files with 362 additions and 23 deletions

View File

@@ -1732,6 +1732,82 @@ def test_awooop_status_chain_surfaces_controlled_ansible_apply_proof() -> None:
assert ansible["approval_source"] == "user_chat_approved_continue"
def test_awooop_status_chain_does_not_treat_ansible_check_mode_as_repair() -> None:
chain = _build_awooop_status_chain(
incident_ids=["INC-20260625-977E5F"],
source_id="INC-20260625-977E5F",
truth_chain={
"truth_status": {
"current_stage": "execution_succeeded",
"stage_status": "success",
"needs_human": True,
"blockers": ["incident_open_after_successful_execution"],
},
"automation_quality": {
"verdict": "execution_unverified",
"facts": {
"auto_repair_execution_records": 0,
"automation_operation_records": 2,
"effective_execution_records": 1,
"verification_result": None,
"mcp_gateway_total": 8,
"knowledge_entries": 0,
},
"blockers": ["verification_recorded"],
"operator_outcome": {
"state": "execution_unverified_manual_required",
"needs_human": True,
"next_action": "run_or_review_post_execution_verification",
},
},
"execution": {
"automation_operation_log": [
{
"operation_type": "ansible_check_mode_executed",
"status": "success",
"actor": "ansible_check_mode_worker",
"input_executor": "ansible",
"input_catalog_id": "ansible:188-ai-web",
"input_playbook_path": "infra/ansible/playbooks/188-ai-web-readonly.yml",
}
],
"ansible": {
"considered": True,
"records": [
{
"operation_type": "ansible_check_mode_executed",
"status": "success",
"actor": "ansible_check_mode_worker",
"catalog_id": "ansible:188-ai-web",
"playbook_path": "infra/ansible/playbooks/188-ai-web-readonly.yml",
"execution_mode": "check_mode",
"check_mode": True,
"apply_executed": False,
"returncode": 0,
}
],
"candidate_catalog": {"candidates": []},
},
},
},
remediation_history={"total": 0},
)
assert chain["verdict"] == "ansible_check_mode_only"
assert chain["repair_state"] == "ansible_check_mode_only"
assert chain["next_step"] == "owner_review_apply_gate_or_create_verifier_plan"
assert chain["needs_human"] is True
assert chain["evidence"]["ansible_dry_run_only"] is True
assert chain["operator_outcome"]["state"] == "dry_run_only_owner_review_required"
assert (
chain["operator_outcome"]["execution_result"]["completion_status"]
== "dry_run_completed_no_apply"
)
assert chain["execution"]["ansible"]["check_mode_total"] == 1
assert chain["execution"]["ansible"]["apply_total"] == 0
assert chain["execution"]["ansible"]["applied"] is False
def test_awooop_status_chain_includes_source_provider_correlation() -> None:
chain = _build_awooop_status_chain(
incident_ids=["INC-20260520-4D1124"],

View File

@@ -66,6 +66,35 @@ def test_operator_outcome_marks_unverified_execution_as_human_review() -> None:
assert outcome["next_action"] == "run_or_review_post_execution_verification"
def test_operator_outcome_marks_ansible_check_mode_as_dry_run_only() -> None:
outcome = build_operator_outcome(
truth_status={
"current_stage": "execution_succeeded",
"stage_status": "success",
"needs_human": False,
"blockers": [],
},
automation_quality={
"verdict": "ansible_check_mode_only",
"facts": {
"effective_execution_records": 1,
"auto_repair_execution_records": 0,
"ansible_check_mode_total": 1,
"ansible_apply_total": 0,
"verification_result": None,
},
"blockers": [],
},
)
assert outcome["state"] == "dry_run_only_owner_review_required"
assert outcome["needs_human"] is True
assert outcome["next_action"] == "owner_review_apply_gate_or_create_verifier_plan"
assert outcome["execution_result"]["completion_status"] == "dry_run_completed_no_apply"
assert outcome["execution_result"]["command_status"] == "check_mode_succeeded"
assert outcome["execution_result"]["repair_status"] == "not_executed"
def test_operator_outcome_marks_verified_repair_as_result_only() -> None:
outcome = build_operator_outcome(
truth_status={

View File

@@ -708,6 +708,74 @@ def test_awooop_status_chain_lines_do_not_treat_audit_ops_as_repair() -> None:
assert "通知:<code>telegram_sre_war_room,awooop_operator_console</code>" in joined
def test_awooop_status_chain_lines_mark_ansible_check_mode_as_dry_run_only() -> None:
lines = telegram_gateway_module._format_awooop_status_chain_lines(
truth_chain={
"truth_status": {
"current_stage": "execution_succeeded",
"stage_status": "success",
"needs_human": True,
"blockers": ["incident_open_after_successful_execution"],
},
"automation_quality": {
"verdict": "execution_unverified",
"facts": {
"auto_repair_execution_records": 0,
"automation_operation_records": 2,
"effective_execution_records": 1,
"verification_result": None,
"mcp_gateway_total": 8,
"knowledge_entries": 0,
},
"blockers": ["verification_recorded"],
"operator_outcome": {
"state": "execution_unverified_manual_required",
"needs_human": True,
"next_action": "run_or_review_post_execution_verification",
},
},
"execution": {
"automation_operation_log": [
{
"operation_type": "ansible_check_mode_executed",
"status": "success",
"actor": "ansible_check_mode_worker",
"input_executor": "ansible",
"input_catalog_id": "ansible:188-ai-web",
}
],
"ansible": {
"considered": True,
"records": [
{
"operation_type": "ansible_check_mode_executed",
"status": "success",
"actor": "ansible_check_mode_worker",
"catalog_id": "ansible:188-ai-web",
"playbook_path": "infra/ansible/playbooks/188-ai-web-readonly.yml",
"execution_mode": "check_mode",
"check_mode": True,
"apply_executed": False,
"returncode": 0,
}
],
"candidate_catalog": {"candidates": []},
},
},
},
remediation_history={"total": 0},
)
joined = "\n".join(lines)
assert "ansible_check_mode_only" in joined
assert "owner_review_apply_gate_or_create_verifier_plan" in joined
assert "dry_run_only_owner_review_required" in joined
assert "dry_run_completed_no_apply" in joined
assert "只完成 Ansible check-mode 乾跑,尚未執行修復" in joined
assert "executed_pending_verification" not in joined
assert "run_or_review_post_execution_verification" not in joined
def test_awooop_agent_evidence_lines_show_mcp_source_execution_playbook_km() -> None:
"""Telegram 詳情/歷史要像前端一樣顯示五段 AI Agent 證據鏈。"""
lines = telegram_gateway_module._format_awooop_agent_evidence_lines(