fix(awooop): Phase 2 初批 P0 修正 + Phase 1 Task 1.7 integration tests

## P0 安全 / 架構修正

### P0-08 telemetry.py — 移除硬碼 IP assert(ADR-121)
- config.py:新增 OTEL_ALLOWED_ENDPOINTS(預設 192.168.0.188)+ OTEL_FORBIDDEN_ENDPOINTS
- telemetry.py:_validate_endpoint() 改為 config-driven allowlist/forbidlist
- EwoooC 可用 env 覆寫 OTEL_ALLOWED_ENDPOINTS 指向自己的 SigNoz host

### P0-13 mcp_bridge.py — K8s namespace 由 settings 提供
- config.py:新增 AWOOOI_K8S_NAMESPACE(預設 "awoooi-prod")
- mcp_bridge.py:5 處 parameters.get("namespace", "awoooi-prod") → settings.AWOOOI_K8S_NAMESPACE
- EwoooC/Tsenyang 可設自己的 namespace

### P1-24 decision_manager.py — silence key 常數統一
- 新增 from src.services.telegram_gateway import SILENCE_KEY_PREFIX
- f"telegram_silence:{target}" → f"{SILENCE_KEY_PREFIX}{target}"
- 消除跨兩處重複定義(ADR-118 No Island Coding 原則)

## Phase 1 Task 1.7 Integration Tests
- tests/integration/test_awooop_phase1_schema.py:31 個測試案例
  - awooop_projects CHECK 約束(4 cases)
  - revision 不可變性 trigger(5 cases:draft 可改、published 鎖住、身份欄不可改、非法流轉、DELETE 禁止)
  - awooop_published_revisions VIEW draft/published 隔離(2 cases)
  - active_pointer_guard(3 cases:不可指向 draft、可指向 active、跨租戶 mismatch)
  - RLS fail-closed(3 cases:未設/錯設/正確設 project_id)
  - outbox FK + dedup(2 cases)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Your Name
2026-05-04 13:46:19 +08:00
parent 13e51802fe
commit 14bf86a462
5 changed files with 469 additions and 24 deletions

View File

@@ -23,6 +23,7 @@ from typing import Any
import httpx
from src.core.config import settings # P0-13: K8s namespace 由 settings.AWOOOI_K8S_NAMESPACE 提供
from src.utils.timezone import now_taipei
logger = logging.getLogger(__name__)
@@ -589,7 +590,7 @@ class MCPBridge:
if tool_name == "kubectl_get":
# 使用 kubectl 指令查詢
namespace = parameters.get("namespace", "awoooi-prod")
namespace = parameters.get("namespace", settings.AWOOOI_K8S_NAMESPACE)
resource = parameters.get("resource", "pods")
name = parameters.get("name", "")
cmd = f"kubectl get {resource} {name} -n {namespace} -o json".strip()
@@ -599,7 +600,7 @@ class MCPBridge:
return {"error": result.error}
elif tool_name == "kubectl_delete":
namespace = parameters.get("namespace", "awoooi-prod")
namespace = parameters.get("namespace", settings.AWOOOI_K8S_NAMESPACE)
resource = parameters.get("resource", "pod")
name = parameters.get("name", "")
if not name:
@@ -628,7 +629,7 @@ class MCPBridge:
}
elif tool_name == "kubectl_scale":
namespace = parameters.get("namespace", "awoooi-prod")
namespace = parameters.get("namespace", settings.AWOOOI_K8S_NAMESPACE)
deployment = parameters.get("deployment", "")
replicas = parameters.get("replicas", 1)
if not deployment:
@@ -644,7 +645,7 @@ class MCPBridge:
}
elif tool_name == "kubectl_restart":
namespace = parameters.get("namespace", "awoooi-prod")
namespace = parameters.get("namespace", settings.AWOOOI_K8S_NAMESPACE)
deployment = parameters.get("deployment", "")
if not deployment:
return {"error": "Missing 'deployment' parameter"}
@@ -678,7 +679,7 @@ class MCPBridge:
if not service_name:
return {"error": "Missing 'service_name' parameter"}
namespace = parameters.get("namespace", "awoooi-prod")
namespace = parameters.get("namespace", settings.AWOOOI_K8S_NAMESPACE)
time_window = parameters.get("time_window_minutes", 10)
metrics = await signoz.get_gold_metrics(