diff --git a/apps/api/src/services/platform_operator_service.py b/apps/api/src/services/platform_operator_service.py index 9b21bc24..126a23ed 100644 --- a/apps/api/src/services/platform_operator_service.py +++ b/apps/api/src/services/platform_operator_service.py @@ -2753,6 +2753,20 @@ def _safe_int(value: Any) -> int: return 0 +def _has_repair_execution_evidence(facts: dict[str, Any]) -> bool: + return ( + _safe_int(facts.get("auto_repair_execution_records")) > 0 + or _safe_int(facts.get("effective_execution_records")) > 0 + ) + + +def _has_nonrepair_operation_evidence(facts: dict[str, Any]) -> bool: + return ( + _safe_int(facts.get("automation_operation_records")) > 0 + and not _has_repair_execution_evidence(facts) + ) + + def _latest_remediation_history_item( history: dict[str, Any] | None, ) -> dict[str, Any]: @@ -3509,6 +3523,8 @@ def _build_awooop_status_chain( ) auto_repair_records = _safe_int(facts.get("auto_repair_execution_records")) operation_records = _safe_int(facts.get("automation_operation_records")) + has_repair_execution = _has_repair_execution_evidence(facts) + has_nonrepair_operation = _has_nonrepair_operation_evidence(facts) gateway_total = _safe_int(facts.get("mcp_gateway_total")) km_entries = _safe_int(facts.get("knowledge_entries")) needs_human = bool(truth_status.get("needs_human")) @@ -3516,13 +3532,16 @@ def _build_awooop_status_chain( if verdict == "auto_repaired_verified": repair_state = "auto_repaired_verified" next_step = "monitor_for_regression" - elif auto_repair_records > 0 or operation_records > 0: + elif has_repair_execution: repair_state = ( "executed_pending_verification" if str(verification) == "missing" else "executed" ) next_step = "verify_execution_result" + elif has_nonrepair_operation: + repair_state = "diagnostic_or_audit_recorded" + next_step = "manual_review_or_collect_repair_evidence" elif remediation_state == "read_only": repair_state = "read_only_dry_run" next_step = "approve_or_escalate_from_awooop" diff --git a/apps/api/tests/test_awooop_operator_timeline_labels.py b/apps/api/tests/test_awooop_operator_timeline_labels.py index 8d60efd1..1d4d2419 100644 --- a/apps/api/tests/test_awooop_operator_timeline_labels.py +++ b/apps/api/tests/test_awooop_operator_timeline_labels.py @@ -1614,6 +1614,39 @@ def test_awooop_status_chain_marks_read_only_manual_gate() -> None: assert chain["blockers"] == ["pending_human_approval"] +def test_awooop_status_chain_does_not_treat_audit_ops_as_repair() -> None: + chain = _build_awooop_status_chain( + incident_ids=["INC-20260530-88D960"], + source_id="INC-20260530-88D960", + truth_chain={ + "truth_status": { + "current_stage": "execution_succeeded", + "stage_status": "success", + "needs_human": False, + "blockers": [], + }, + "automation_quality": { + "verdict": "auto_repaired_verification_degraded", + "facts": { + "auto_repair_execution_records": 0, + "automation_operation_records": 1, + "effective_execution_records": 0, + "verification_result": "degraded", + "mcp_gateway_total": 22, + "knowledge_entries": 4, + }, + "blockers": ["verification_recorded"], + }, + }, + remediation_history={"total": 0}, + ) + + assert chain["repair_state"] == "diagnostic_or_audit_recorded" + assert chain["next_step"] == "manual_review_or_collect_repair_evidence" + assert chain["evidence"]["operation_records"] == 1 + assert chain["evidence"]["auto_repair_records"] == 0 + + def test_legacy_mcp_timeline_summary_surfaces_tool_context() -> None: record = { "incident_id": "INC-20260514-F85F21", diff --git a/docs/LOGBOOK.md b/docs/LOGBOOK.md index 598a2465..0e1a36f0 100644 --- a/docs/LOGBOOK.md +++ b/docs/LOGBOOK.md @@ -124,6 +124,7 @@ DB / worker evidence: - `incident_timeline_service` 讀取 `approval_records.extra_metadata.execution_kind / repair_executed`;`execution_success + repair_executed=false` 改成 `info`,標題為 observation recorded,不再顯示 repair success。 - Telegram 首屏 `_automation_status_summary()` 與 AwoooP status-chain / callback snapshot 改以 `auto_repair_execution_records` 或 `effective_execution_records` 判斷「真的有 repair execution」。 +- `/api/v1/platform/status-chain` 的前台共用 API 同步改用 `effective_execution_records`;只有 raw operation log 時回 `repair_state=diagnostic_or_audit_recorded`,不再回 `executed`。 - 若只有 `automation_operation_records` 但 `effective_execution_records=0`,顯示 `diagnostic_recorded` / `diagnostic_or_audit_recorded`,文案改為「已記錄診斷/觀察,尚未證明修復」。 - `build_incident_reconciliation()` 也改用 `_effective_execution_ops()` 計算 executed ops,避免 NO_ACTION / audit-only row 觸發 `incident_open_after_successful_execution`。 - Production backfill 兩筆舊 incident: @@ -140,6 +141,8 @@ ruff check --select E9,F401,F821,F841 modified services/tests -> pass pytest test_incident_timeline_service.py test_awooop_truth_chain_service.py test_telegram_ai_automation_block.py test_telegram_message_templates.py -q -> 101 passed +pytest test_awooop_operator_timeline_labels.py -q + -> status-chain diagnostic/audit-only repair_state guard covered production DB readback: INC-20260530-88D960 approval extra_metadata.execution_kind=diagnostic repair_executed=false INC-20260531-88394F approval extra_metadata.execution_kind=no_action repair_executed=false diff --git a/docs/superpowers/specs/2026-04-15-MASTER-ai-autonomous-flywheel-v2.md b/docs/superpowers/specs/2026-04-15-MASTER-ai-autonomous-flywheel-v2.md index 2a06dfc3..35dc277f 100644 --- a/docs/superpowers/specs/2026-04-15-MASTER-ai-autonomous-flywheel-v2.md +++ b/docs/superpowers/specs/2026-04-15-MASTER-ai-autonomous-flywheel-v2.md @@ -2679,9 +2679,9 @@ Phase 6 完成後 **T154b Telegram display truth + historical backfill(2026-05-31 台北)**: - 觸發:T154 補了新流量 execution metadata,但前台/Telegram 顯示層仍以 raw `automation_operation_records > 0` 顯示「已自動執行」,會把 diagnostic / audit-only operation 誤認成 repair。`incident_timeline_service` 也會把 `execution_success` 一律畫成 executor success,舊 incident 即使補 metadata 仍會誤導 operator。 -- 修正:`incident_timeline_service` 讀 `approval_records.extra_metadata.execution_kind / repair_executed`;`execution_success + repair_executed=false` 改成 info / observation recorded。Telegram 首屏、AwoooP status-chain、callback snapshot 改用 `auto_repair_execution_records` 或 `effective_execution_records` 判斷 repair execution;只有 raw operation records 時顯示 `diagnostic_recorded` / `diagnostic_or_audit_recorded`,文案為「已記錄診斷/觀察,尚未證明修復」。`build_incident_reconciliation()` 改用 `_effective_execution_ops()`,NO_ACTION / audit-only 不再觸發 successful execution mismatch。 +- 修正:`incident_timeline_service` 讀 `approval_records.extra_metadata.execution_kind / repair_executed`;`execution_success + repair_executed=false` 改成 info / observation recorded。Telegram 首屏、AwoooP status-chain、callback snapshot、`/api/v1/platform/status-chain` 共用前台 API 改用 `auto_repair_execution_records` 或 `effective_execution_records` 判斷 repair execution;只有 raw operation records 時顯示 `diagnostic_recorded` / `diagnostic_or_audit_recorded`,文案為「已記錄診斷/觀察,尚未證明修復」。`build_incident_reconciliation()` 改用 `_effective_execution_ops()`,NO_ACTION / audit-only 不再觸發 successful execution mismatch。 - Production backfill:`INC-20260530-88D960` approval 標記 `execution_kind=diagnostic, repair_executed=false`;`INC-20260531-88394F` approval 標記 `execution_kind=no_action, repair_executed=false`。兩筆均新增 `alert_operation_log.EXECUTION_COMPLETED`、postmortem `knowledge_entries(entry_type=POSTMORTEM,path_type=postmortem)`、`KM_CONVERTED`,`truth_backfill_id=telegram_execution_truth_backfill_20260531_t154b`。未重跑任何修復動作。 -- Verification:`py_compile` pass;targeted `ruff --select E9,F401,F821,F841` pass;`test_incident_timeline_service.py` + `test_awooop_truth_chain_service.py` + `test_telegram_ai_automation_block.py` + `test_telegram_message_templates.py` -> 101 passed。Production DB readback confirms both approvals have `repair_executed=false` plus execution/postmortem/KM backfill rows. +- Verification:`py_compile` pass;targeted `ruff --select E9,F401,F821,F841` pass;`test_incident_timeline_service.py` + `test_awooop_truth_chain_service.py` + `test_telegram_ai_automation_block.py` + `test_telegram_message_templates.py` -> 101 passed;`test_awooop_operator_timeline_labels.py` covers status-chain diagnostic/audit-only repair_state。Production DB readback confirms both approvals have `repair_executed=false` plus execution/postmortem/KM backfill rows. - 判讀:可宣稱 repair 的條件是 `effective_execution_records > 0` 或 `auto_repair_execution_records > 0`,不是 operation log 數量。舊資料修正後,operator 看到「已記錄診斷/觀察」時不能再視為自動修復完成。 **T152 Ansible runtime readiness surfaced(2026-05-24 台北)**: