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
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:
@@ -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 風險)
|
||||
|
||||
Reference in New Issue
Block a user