feat(ai): enforce single-writer automation router
All checks were successful
CD Pipeline / workflow-shape (push) Successful in 1s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 2m21s
CD Pipeline / build-and-deploy (push) Successful in 6m53s
CD Pipeline / post-deploy-checks (push) Successful in 1m55s
AWOOOI Harbor 110 Local Repair / workflow-shape (push) Successful in 0s
AWOOOI Harbor 110 Local Repair / harbor-110-local-repair (push) Successful in 49s

This commit is contained in:
ogt
2026-07-11 05:45:48 +08:00
parent 01605b4947
commit f453359e34
20 changed files with 1674 additions and 570 deletions

View File

@@ -216,44 +216,27 @@ class FailureWatcherService(IFailureWatcher):
result["risk_level"] = "MEDIUM"
if risk_level != "CRITICAL" and RISK_LEVELS.get(risk_level, {}).get("auto_repair"):
# 自動修復 (Phase 18.3: 傳入完整 failure_data)
success, repair_result = await self.execute_auto_repair(
# Executor failures must not recursively invoke a second writer.
# Queue the repair context for the controlled broker path instead.
await self._queue_ai_repair_followup(
audit_log_id=audit_log_id,
repair_strategy=analysis["suggested_repair"],
failure_data=failure_data,
analysis=analysis,
reason=(
"executor failure routed to single-writer retry/repair queue; "
"a new check-mode receipt is required before apply"
),
)
result["repair_attempted"] = True
result["repair_result"] = repair_result
result["next_action"] = "auto_repaired" if success else "escalate"
result["repair_attempted"] = False
result["repair_result"] = "single_writer_retry_or_repair_queued"
result["next_action"] = "ai_retry_queued"
# 更新 AuditLog
await self._update_audit_log_classification(
audit_log_id=audit_log_id,
classification=analysis["classification"],
auto_repair_attempted=True,
auto_repair_result=repair_result,
auto_repair_attempted=False,
auto_repair_result="single_writer_retry_or_repair_queued",
)
if success:
# P0-1 補充: 記錄全域修復動作 (ADR-040)
from src.services.global_repair_cooldown import record_global_repair_action
await record_global_repair_action()
# 推送揭露通知 (自動修復成功)
await self._push_repair_notification(
audit_log_id=audit_log_id,
repair_result=repair_result,
auto=True,
)
else:
# 失敗後排入 AI controlled retry / rollback不再直接轉人工。
result["risk_level"] = "MEDIUM"
await self._queue_ai_repair_followup(
audit_log_id=audit_log_id,
analysis=analysis,
reason="AI 受控自動修復失敗,已排入 rollback / transport / PlayBook 修復重試",
)
result["next_action"] = "ai_retry_queued"
else:
# CRITICAL: break-glass / hard blocker不執行寫入。
await self._queue_ai_repair_followup(
@@ -322,6 +305,18 @@ class FailureWatcherService(IFailureWatcher):
audit_log_id: str,
repair_strategy: str,
failure_data: dict | None = None,
) -> tuple[bool, str]:
"""Fail closed when legacy code tries to bypass the single writer."""
raise RuntimeError(
"failure_watcher_direct_execution_disabled_use_single_writer_broker"
)
async def _legacy_execute_auto_repair_disabled(
self,
audit_log_id: str,
repair_strategy: str,
failure_data: dict | None = None,
) -> tuple[bool, str]:
"""
執行自動修復 (僅限 LOW 風險)