fix(awooop): exclude audit-only ops from repair quality
All checks were successful
Code Review / ai-code-review (push) Successful in 11s
CD Pipeline / tests (push) Successful in 1m1s
CD Pipeline / build-and-deploy (push) Successful in 3m41s
CD Pipeline / post-deploy-checks (push) Successful in 1m32s

This commit is contained in:
Your Name
2026-05-13 17:24:08 +08:00
parent 55b28336e5
commit cecadb331b
2 changed files with 41 additions and 13 deletions

View File

@@ -131,8 +131,23 @@ def _is_no_action_operation(row: dict[str, Any]) -> bool:
)
def _is_audit_only_operation(row: dict[str, Any]) -> bool:
operation_type = str(row.get("operation_type") or "")
status = str(row.get("status") or "").lower()
if status == "dry_run":
return True
return operation_type in {
"ansible_candidate_matched",
"ansible_execution_skipped",
}
def _effective_execution_ops(automation_ops: list[dict[str, Any]]) -> list[dict[str, Any]]:
return [row for row in automation_ops if not _is_no_action_operation(row)]
return [
row
for row in automation_ops
if not _is_no_action_operation(row) and not _is_audit_only_operation(row)
]
def build_incident_reconciliation(
@@ -315,17 +330,17 @@ def _truth_status(
stage = "approval_required"
stage_status = "waiting"
needs_human = True
elif "APPROVED" in approval_statuses and not has_execution_records:
if approval_no_action or "NO_ACTION" in approval_actions:
elif not has_execution_records and (approval_no_action or "NO_ACTION" in approval_actions):
if approval_statuses:
stage = "manual_required"
stage_status = "blocked"
needs_human = True
blockers.append("approval_resolved_no_action_without_execution")
else:
stage = "execution_missing"
stage_status = "blocked"
needs_human = True
blockers.append("approved_without_execution_record")
elif "APPROVED" in approval_statuses and not has_execution_records:
stage = "execution_missing"
stage_status = "blocked"
needs_human = True
blockers.append("approved_without_execution_record")
op_statuses = {str(row.get("status") or "").lower() for row in effective_ops}
repair_successes = {row.get("success") for row in repair_rows}
@@ -410,6 +425,7 @@ def build_automation_quality(
legacy_total = int(legacy_mcp_summary.get("total") or 0)
effective_ops = _effective_execution_ops(automation_ops)
noop_ops = [row for row in automation_ops if _is_no_action_operation(row)]
audit_only_ops = [row for row in automation_ops if _is_audit_only_operation(row)]
automation_statuses = {str(row.get("status") or "").lower() for row in effective_ops}
auto_repair_successes = {row.get("success") for row in auto_repair_executions}
has_execution = bool(effective_ops or auto_repair_executions)
@@ -438,7 +454,7 @@ def build_automation_quality(
if any(status in {"PENDING", "WAITING_APPROVAL"} for status in approval_statuses):
gate("approval_state", "warning", "waiting_approval")
elif "APPROVED" in approval_statuses and (approval_no_action or "NO_ACTION" in approval_actions) and not has_execution:
elif approval_statuses and (approval_no_action or "NO_ACTION" in approval_actions) and not has_execution:
gate("approval_state", "failed", "approved_no_action_without_execution")
elif approvals:
gate("approval_state", "passed", ",".join(sorted(approval_statuses)))
@@ -472,7 +488,7 @@ def build_automation_quality(
verdict = "execution_failed"
elif has_execution:
verdict = "execution_unverified"
elif "APPROVED" in approval_statuses and (approval_no_action or "NO_ACTION" in approval_actions):
elif approval_statuses and (approval_no_action or "NO_ACTION" in approval_actions):
verdict = "manual_required_no_action"
elif any(status in {"PENDING", "WAITING_APPROVAL"} for status in approval_statuses):
verdict = "approval_required"
@@ -520,6 +536,7 @@ def build_automation_quality(
"automation_operation_records": len(automation_ops),
"effective_execution_records": len(effective_ops),
"noop_operation_records": len(noop_ops),
"audit_only_operation_records": len(audit_only_ops),
"auto_repair_execution_records": len(auto_repair_executions),
"verification_result": verification_result,
"knowledge_entries": len(km_entries),

View File

@@ -61,7 +61,7 @@ def test_truth_status_marks_no_action_approval_as_manual_required() -> None:
def test_truth_status_does_not_treat_no_action_audit_as_execution() -> None:
status = _truth_status(
incident={"incident_id": "INC-1", "status": "RESOLVED"},
approvals=[{"status": "APPROVED", "action": "未知操作 | NO_ACTION"}],
approvals=[{"status": "EXECUTION_SUCCESS", "action": "未知操作 | NO_ACTION"}],
evidence_rows=[{"sensors_attempted": 8, "sensors_succeeded": 6}],
automation_ops=[
{
@@ -71,6 +71,11 @@ def test_truth_status_does_not_treat_no_action_audit_as_execution() -> None:
"output_reason": "NO_ACTION",
"output_action": "未知操作 | NO_ACTION",
},
{
"operation_type": "ansible_candidate_matched",
"status": "dry_run",
"output_not_used_reason": "Ansible check-mode is not wired yet",
},
],
drift=None,
drift_repeat_count=0,
@@ -279,7 +284,7 @@ def test_automation_quality_marks_no_action_without_execution() -> None:
def test_automation_quality_ignores_no_action_audit_rows_as_execution() -> None:
quality = build_automation_quality(
incident={"incident_id": "INC-1", "status": "RESOLVED"},
approvals=[{"status": "APPROVED", "action": "未知操作 | NO_ACTION"}],
approvals=[{"status": "EXECUTION_SUCCESS", "action": "未知操作 | NO_ACTION"}],
evidence_rows=[{"sensors_attempted": 8, "sensors_succeeded": 6}],
automation_ops=[
{
@@ -289,6 +294,11 @@ def test_automation_quality_ignores_no_action_audit_rows_as_execution() -> None:
"output_reason": "NO_ACTION",
"output_action": "未知操作 | NO_ACTION",
},
{
"operation_type": "ansible_candidate_matched",
"status": "dry_run",
"output_not_used_reason": "Ansible check-mode is not wired yet",
},
],
auto_repair_executions=[],
gateway_mcp_summary={"total": 8},
@@ -300,9 +310,10 @@ def test_automation_quality_ignores_no_action_audit_rows_as_execution() -> None:
gates = {row["name"]: row["status"] for row in quality["gates"]}
assert quality["verdict"] == "manual_required_no_action"
assert quality["facts"]["automation_operation_records"] == 1
assert quality["facts"]["automation_operation_records"] == 2
assert quality["facts"]["effective_execution_records"] == 0
assert quality["facts"]["noop_operation_records"] == 1
assert quality["facts"]["audit_only_operation_records"] == 1
assert gates["execution_recorded"] == "missing"
assert gates["verification_recorded"] == "not_applicable"