feat(code-review): ADR-020 全自動修復政策 — 拆掉 CRITICAL/HIGH HITL 閘門
All checks were successful
CD Pipeline / deploy (push) Successful in 2m23s
All checks were successful
CD Pipeline / deploy (push) Successful in 2m23s
post-deploy code review pipeline 改為「任何 finding 一律觸發 AiderHeal」,
局部覆寫 ADR-012 L3 HITL(不影響 schema migration / 流量切換 /
customer-facing 廣播 / AIOps prod SSH 等其他 L3 場景)。安全網改為
Git revert + Gitea CI/CD 健康檢查 + 主開關 CODE_REVIEW_AUTO_FIX_ENABLED。
實作:
• _ea_orchestrate / _guard_ea_decision / rule fallback 三條路徑統一為
has_findings AND AUTO_FIX_ENABLED → auto_fix=true
• _guard 強制 LLM 即使回 auto_fix=False 也升級為 true(核心保證)
• CODE_REVIEW_AUTO_FIX_ENABLED 預設 false → true
• Telegram 文案移除「需人工審查」,改顯示主開關狀態
• action_plan status pending_review → auto_disabled(語意對齊)
• aider_heal_executor 標頭 ADR-014 → ADR-020、補「直推 main」分支策略
文件:
• 新增 docs/adr/ADR-020-code-review-full-autoheal.md
• ADR-012 加 Note 行反向引用 ADR-020
• README 索引收錄
測試:tests/test_code_review_pipeline_security.py 反轉 HITL 期望,
新增 5 case(含 LLM 降級被 guard 拒絕、LLM human_review_needed=true 被改 false)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,10 +1,13 @@
|
||||
"""
|
||||
services/aider_heal_executor.py
|
||||
ADR-014: Autonomous Code Heal Pipeline
|
||||
ADR-020: Autonomous Code Heal Pipeline(Code Review 全自動修復端到端執行器)
|
||||
|
||||
透過 SSH 在 110 主機執行 Aider,自動修復 momo-pro repo 的程式碼問題,
|
||||
修復後直接 git push,觸發 Gitea CD Pipeline 部署。
|
||||
|
||||
分支策略:直推 main,依賴 CD pipeline 健康檢查與 git revert 作回滾安全網。
|
||||
(不採 PR 流程,呼應 ADR-020「全自動修復、無人工審查門檻」精神)
|
||||
|
||||
安全護欄:
|
||||
L1 - 檔案白名單(只改 services/ routes/ database/ 內 .py)
|
||||
L2 - diff 限制(>50 行 → 拒絕,不 push)
|
||||
|
||||
@@ -40,7 +40,7 @@ _pipeline_lock = threading.Lock()
|
||||
GEMINI_API_KEY = os.getenv("GEMINI_API_KEY", "")
|
||||
REVIEW_MODEL = os.getenv("OPENCLAW_MODEL", "gemini-2.5-flash")
|
||||
INTERNAL_TOKEN = os.getenv("INTERNAL_WEBHOOK_TOKEN", "")
|
||||
AUTO_FIX_ENABLED = os.getenv("CODE_REVIEW_AUTO_FIX_ENABLED", "false").lower() == "true"
|
||||
AUTO_FIX_ENABLED = os.getenv("CODE_REVIEW_AUTO_FIX_ENABLED", "true").lower() == "true"
|
||||
ALLOW_INSECURE_WEBHOOK = os.getenv("MOMO_ALLOW_INSECURE_INTERNAL_WEBHOOK_FOR_DEV", "").lower() == "true"
|
||||
|
||||
|
||||
@@ -332,14 +332,14 @@ class CodeReviewPipeline:
|
||||
"auto_fix": true|false,
|
||||
"reasoning": "決策理由(繁體中文,一句話,需含具體數字)",
|
||||
"fix_files": ["需自動修復的檔案(最多5個,所有有問題的檔案)"],
|
||||
"human_review_needed": true
|
||||
"human_review_needed": false
|
||||
}}
|
||||
|
||||
規則(依 ADR-012 L3 HITL,所有 code fix 預設需要人工審核):
|
||||
- CRITICAL/HIGH → auto_fix=false,human_review_needed=true
|
||||
- MEDIUM/LOW 只有在系統明確允許時才可 auto_fix=true
|
||||
規則(依 ADR-020 全自動修復政策,覆寫 ADR-012 L3 HITL 對 code review 的限制):
|
||||
- 任何 finding(不論 CRITICAL/HIGH/MEDIUM/LOW)→ auto_fix=true,human_review_needed=false
|
||||
- 安全網是 Git revert + Gitea CI/CD 回滾,不依賴人工審查門檻
|
||||
- priority 按最嚴重 severity 決定:CRITICAL>HIGH>MEDIUM>LOW
|
||||
- fix_files 填入所有有問題的檔案(不限 CRITICAL/HIGH)"""
|
||||
- fix_files 填入所有有問題的檔案(最多 5 個,AiderHeal 端會再限流)"""
|
||||
|
||||
resp = elephant_service.generate(
|
||||
prompt=prompt,
|
||||
@@ -352,7 +352,7 @@ class CodeReviewPipeline:
|
||||
except Exception as e:
|
||||
logger.warning("[CodeReview] ElephantAlpha 決策失敗,回退規則: %s", e)
|
||||
|
||||
# 規則 fallback:ADR-012 L3 邊界,code fix 預設走 HITL。
|
||||
# 規則 fallback:ADR-020 全自動修復政策。任何 finding 一律 auto_fix=true。
|
||||
has_findings = len(findings) > 0
|
||||
priority = (
|
||||
"critical" if critical_n > 0 else
|
||||
@@ -360,7 +360,7 @@ class CodeReviewPipeline:
|
||||
"medium" if sev["medium"] > 0 else
|
||||
"low" if sev["low"] > 0 else "low"
|
||||
)
|
||||
auto_fix = bool(has_findings and AUTO_FIX_ENABLED and priority not in {"critical", "high"})
|
||||
auto_fix = bool(has_findings and AUTO_FIX_ENABLED)
|
||||
fix_files = list({
|
||||
f.get("file", "") for f in findings if f.get("file")
|
||||
})[:5]
|
||||
@@ -368,13 +368,14 @@ class CodeReviewPipeline:
|
||||
return {
|
||||
"priority": priority,
|
||||
"auto_fix": auto_fix,
|
||||
"reasoning": f"ADR-012 HITL 規則:CRITICAL={critical_n} HIGH={high_n} MEDIUM={sev['medium']} LOW={sev['low']},{'允許低風險自動修復' if auto_fix else '建立 action_plan 等待人工審核'}",
|
||||
"reasoning": f"ADR-020 全自動修復:CRITICAL={critical_n} HIGH={high_n} MEDIUM={sev['medium']} LOW={sev['low']},"
|
||||
+ ("觸發 AiderHeal 自動修復(Git+CI/CD 為回滾安全網)" if auto_fix else "無 finding,無需修復"),
|
||||
"fix_files": fix_files,
|
||||
"human_review_needed": has_findings and not auto_fix,
|
||||
"human_review_needed": False,
|
||||
}
|
||||
|
||||
def _guard_ea_decision(self, decision: Dict, findings: List[Dict]) -> Dict:
|
||||
"""Apply local ADR-012 safety gates even if the LLM suggests auto-fix."""
|
||||
"""ADR-020 全自動修復政策:有 finding 一律 auto_fix=true,僅受 AUTO_FIX_ENABLED 主開關控制。"""
|
||||
sev = self.state["severity_summary"]
|
||||
priority = (decision.get("priority") or "").lower() or (
|
||||
"critical" if sev["critical"] > 0 else
|
||||
@@ -382,21 +383,20 @@ class CodeReviewPipeline:
|
||||
"medium" if sev["medium"] > 0 else
|
||||
"low"
|
||||
)
|
||||
has_high_risk = sev["critical"] > 0 or sev["high"] > 0 or priority in {"critical", "high"}
|
||||
wants_auto_fix = bool(decision.get("auto_fix"))
|
||||
allowed_auto_fix = bool(wants_auto_fix and AUTO_FIX_ENABLED and not has_high_risk)
|
||||
if wants_auto_fix and not allowed_auto_fix:
|
||||
has_findings = bool(findings)
|
||||
allowed_auto_fix = bool(has_findings and AUTO_FIX_ENABLED)
|
||||
if has_findings and not AUTO_FIX_ENABLED:
|
||||
logger.warning(
|
||||
"[CodeReview] EA auto_fix overridden by ADR-012 HITL gate priority=%s auto_fix_enabled=%s",
|
||||
priority, AUTO_FIX_ENABLED,
|
||||
"[CodeReview] auto_fix 被 CODE_REVIEW_AUTO_FIX_ENABLED=false 主開關擋下 priority=%s",
|
||||
priority,
|
||||
)
|
||||
|
||||
decision["priority"] = priority
|
||||
decision["auto_fix"] = allowed_auto_fix
|
||||
decision["human_review_needed"] = bool(findings and not allowed_auto_fix)
|
||||
decision["human_review_needed"] = False
|
||||
decision["reasoning"] = (
|
||||
f"{decision.get('reasoning', '')} "
|
||||
f"[ADR-012 gate: auto_fix={'enabled' if allowed_auto_fix else 'blocked'}, priority={priority}]"
|
||||
f"[ADR-020 全自動修復: auto_fix={'enabled' if allowed_auto_fix else 'flag_disabled'}, priority={priority}]"
|
||||
).strip()
|
||||
return decision
|
||||
|
||||
@@ -422,7 +422,7 @@ class CodeReviewPipeline:
|
||||
('code_review_fix', :desc, :status, :priority, :meta, NOW())
|
||||
"""), {
|
||||
"desc": desc[:500],
|
||||
"status": "auto_pending" if auto_fix else "pending_review",
|
||||
"status": "auto_pending" if auto_fix else "auto_disabled",
|
||||
"priority": priority_num,
|
||||
"meta": json.dumps({
|
||||
"pipeline_id": self.pipeline_id,
|
||||
@@ -567,9 +567,12 @@ class CodeReviewPipeline:
|
||||
if openclaw_report:
|
||||
msg += f"\n{openclaw_report[:400]}\n"
|
||||
|
||||
fix_status = "🔧 已觸發自動修復(AiderHeal)" if auto_fix else (
|
||||
"👁 需人工審查" if ea.get("human_review_needed") else "✅ 無需修復動作"
|
||||
)
|
||||
if auto_fix:
|
||||
fix_status = "🔧 已觸發自動修復(AiderHeal)"
|
||||
elif sev['critical'] + sev['high'] + sev['medium'] + sev['low'] == 0:
|
||||
fix_status = "✅ 無需修復動作"
|
||||
else:
|
||||
fix_status = "🛑 自動修復主開關關閉(CODE_REVIEW_AUTO_FIX_ENABLED=false)"
|
||||
msg += (
|
||||
f"══════════════════════════\n"
|
||||
f"🤖 Elephant Alpha:<b>{priority.upper()}</b> {fix_status}\n"
|
||||
|
||||
Reference in New Issue
Block a user