feat(wave6-8): P2.1 fusion + P2.2 governance + P2.4 consensus + Wave 7/8 BLOCKER 修復

承接 Wave 6/7/8 多 engineer 在 agent 限額前完成的代碼,補 commit 解 production
HEAD 隱性 import error(decision_fusion 已被 decision_manager 引用但檔案 untracked)。

新增(後端核心):
- decision_fusion.py (562 行) — P2.1 方法 III(OpenClaw + Hermes + Elephant 三 LLM 融合)
- aiops_timeline.py + aiops_timeline_service.py — critic B4 修復
  /api/v1/aiops/timeline endpoint,DB 存取抽到 service 層遵守 leWOOOgo 積木化
- migrations/p2_decision_fusion_columns.sql + rollback — approval_records fusion 欄位

修改(後端整合):
- decision_manager.py — fusion 三斷鏈修補(critic B1+B2+B3):
  · B1: 寫 _evidence_snapshot_ref 到 token.proposal_data
  · B2: fusion 前計算 complexity_score 並寫 token
  · B3: fusion composite 寫 token.proposal_data["decision_fusion"]
- auto_approve.py — fusion + consensus 認識(critic B3+B5):
  · composite > 0.7 → auto_execute_eligible bypass min_confidence
  · source=consensus_engine + score>=0.6 → 規則可信路徑
- consensus_engine.py — db-fix _save_consensus 重用 agent_sessions
- governance_agent.py — db-fix _alert PG 寫入 ai_governance_events
- approval_db.py — fusion 3 欄位 + 2 partial index + CheckConstraint
- db/models.py — schema 對齊 migration
- core/config.py — vuln #1 修復:OLLAMA_URL/_FALLBACK_URL field_validator
  拒絕公網 IP + 外部域名,僅允許私網/loopback/K8s SVC 白名單
- core/feature_flags.py — P2 fusion + consensus flags
- main.py — governance_agent lifespan 啟動
- failover_alerter.py — Wave8-X2: in-memory dedup fallback(Redis 拒絕後不 fail-open)
- ollama_*.py — metrics 整合 + recovery 改善
- auto_repair_service.py — verifier 接線

新增(測試 2438 行):
- test_decision_fusion.py / test_governance_agent.py / test_consensus_integration.py
- test_p2_db_fixes.py / test_wave8_fusion_fixes.py
- test_config_url_validation.py(vuln #1 12 tests)
- test_failover_alerter.py +Wave8-X2 in-memory dedup 補測

驗收: 116 tests pass (decision_fusion + wave8_fusion + config_url + consensus +
                      governance + p2_db_fixes + failover_alerter)

Conflict resolution:
- 3 檔(config.py + auto_approve.py + decision_manager.py)git stash pop 衝突
  保留 stashed (engineer 最終版),補回 ValueError 「公網 IP」字樣對齊 test

Note: 此 commit 解 production HEAD 隱性 import error
仍未修: vuln #4 prompt injection / debugger B14 quota fail-closed
       / B25-B26 drain_pending_tasks / B8 governance fail alert

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Multiple Engineers (Wave 6/7/8) <noreply@anthropic.com>
This commit is contained in:
Your Name
2026-04-27 08:10:28 +08:00
parent b0bf3783e4
commit cc547736ab
34 changed files with 4205 additions and 25 deletions

View File

@@ -622,16 +622,75 @@ class ConsensusEngine:
return result
async def _save_consensus(self, result: ConsensusResult) -> None:
"""儲存共識結果到 Redis"""
"""儲存共識結果到 Redis(熱快取)+ PG永久記錄
2026-04-26 P2-DB-Fix by Claude — db-expert P0 三修P0.2:
補 PG 寫入 agent_sessions符合 ADR-085 鐵律
Redis TTL 到期不再造成共識記憶消失
"""
# 1. 既有 Redis 寫(熱快取,保留)
redis_client = get_redis()
key = f"{CONSENSUS_PREFIX}{result.consensus_id}"
await redis_client.set(
key,
json.dumps(result.to_dict()),
ex=CONSENSUS_TTL,
)
# 2. 補 PG 永久寫入ADR-085 鐵律 — 失敗不阻斷主流程)
try:
from src.db.base import get_db_context
from src.db.models import AgentSession
from sqlalchemy import insert as _sa_insert
from hashlib import sha256 as _sha256
rows = []
# 每個 AgentOpinion 寫一行CISO 可稽核性要求)
for opinion in result.opinions:
_input_hash = _sha256(
json.dumps(opinion.to_dict(), sort_keys=True).encode()
).hexdigest()[:16]
rows.append({
"session_id": result.consensus_id,
"incident_id": result.incident_id,
"agent_role": opinion.agent_type.value, # sre/security/cost/performance ≤20 chars
"vote": "abstain", # AgentOpinion 無標準投票欄coordinator 行再覆蓋
"output_json": opinion.to_dict(),
"latency_ms": 0, # Phase 9.4 AgentOpinion 未計 latency
"degraded": False,
"input_hash": _input_hash,
})
# coordinator 行:整合決策結果
_coord_hash = _sha256(
json.dumps({"consensus_id": result.consensus_id}, sort_keys=True).encode()
).hexdigest()[:16]
rows.append({
"session_id": result.consensus_id,
"incident_id": result.incident_id,
"agent_role": "coordinator",
"vote": "approve" if result.consensus_score >= 0.6 else "abstain",
"output_json": result.to_dict(),
"latency_ms": 0,
"degraded": False,
"input_hash": _coord_hash,
})
async with get_db_context() as db:
await db.execute(_sa_insert(AgentSession), rows)
await db.commit()
logger.info(
"consensus_pg_write_ok",
consensus_id=result.consensus_id,
rows=len(rows),
)
except Exception as _pg_err:
logger.warning(
"consensus_pg_write_failed",
error=str(_pg_err),
consensus_id=result.consensus_id,
)
async def get_consensus(self, consensus_id: str) -> ConsensusResult | None:
"""取得共識結果"""
redis_client = get_redis()