docs(spec): ADR-059 Gitea Webhook 遷移設計規格

從 GitHub Webhook (Phase 13.1) 遷移至 Gitea Webhook
最少改動策略:Header 常數替換,業務邏輯層不動
廢棄 workflow_run CI 診斷(CD pipeline 已有 TG 通知覆蓋)
整合首席架構師護欄:防禦性 payload 解析 + Content-Type 設定

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
OG T
2026-04-05 14:17:13 +08:00
parent 6937238174
commit 5cd67d372f

View File

@@ -0,0 +1,185 @@
# Gitea Webhook 整合設計規格
**日期**: 2026-04-05 (台北時區)
**作者**: Claude Code + 首席架構師 Review
**狀態**: 已批准,待實作
**ADR**: ADR-059 (待建立)
**關聯**: ADR-039 (Gitea CI/CD 遷移), ADR-029 (CI/CD AI 整合架構)
---
## 背景
Phase 13.1 實作了 GitHub Webhook → OpenClaw AI 代碼審查整合,但:
1. CI/CD 已於 ADR-039 全面遷移至 GiteaGitHub 只剩唯讀備份
2. `GITHUB_WEBHOOK_SECRET` 從未注入 K8s Secrets功能實際上處於死路狀態
3. 代碼審查的觸發源應改為 Gitea實際開發倉庫
**決策:遷移至 Gitea Webhook廢棄 GitHub Webhook 整合。**
---
## 設計原則
**最少改動策略 (Minimal Viable Change)**
Gitea 與 GitHub 的 webhook payload 欄位幾乎相同,差異只在 HTTP header 名稱。
因此策略是:重命名 + Header 常數替換業務邏輯層OpenClaw 呼叫、Redis 儲存、Telegram 通知、Approval 建立)完全不動。
---
## Header 差異對照
| 項目 | GitHub (廢棄) | Gitea (新) |
|------|-------------|-----------|
| 事件類型 Header | `X-GitHub-Event` | `X-Gitea-Event` |
| 簽章 Header | `X-Hub-Signature-256` | `X-Gitea-Signature` |
| 投遞 ID Header | `X-GitHub-Delivery` | `X-Gitea-Delivery` |
| 簽章格式 | `sha256=<hex>` | `sha256=<hex>`**相同** |
| Payload 欄位 | — | — ← **幾乎相同** |
> Gitea 會同時送出 `X-GitHub-Event` 相容 header但我們應讀取原生的 `X-Gitea-Event`
> 語意清晰,未來不依賴相容層。
---
## 遷移範圍
### 支援事件
| 事件 | GitHub (舊) | Gitea (新) | 說明 |
|------|------------|-----------|------|
| `pull_request` | ✅ | ✅ 遷移 | PR 代碼審查 |
| `push` | ✅ | ✅ 遷移 | 主分支推送審查 |
| `workflow_run` | ✅ | ❌ 廢棄 | CD pipeline 已有 TG 通知,重複覆蓋無必要 |
| `ping` | ✅ | ✅ 遷移 | 連線測試 |
### 檔案變更清單
| 檔案 | 動作 | 說明 |
|------|------|------|
| `src/api/v1/github_webhook.py` | 重命名 → `gitea_webhook.py` | Header 常數改 Gitea移除 `workflow_run` handler |
| `src/services/github_webhook_service.py` | 重命名 → `gitea_webhook_service.py` | 移除 `diagnose_ci_failure()`class 改名 |
| `src/main.py` | 修改 | 更新 import掛載路徑改 `/webhooks/gitea` |
| `src/core/config.py` | 修改 | `GITHUB_WEBHOOK_SECRET``GITEA_WEBHOOK_SECRET``GITHUB_ALLOWED_REPOS``GITEA_ALLOWED_REPOS` |
| `k8s/awoooi-prod/03-secrets.yaml` | 修改 | 新增 `GITEA_WEBHOOK_SECRET`,移除 `GITHUB_WEBHOOK_SECRET` |
| `tests/conftest.py` | 修改 | 更新 env var 名稱 |
| `tests/test_github_webhook.py` | 重命名 → `test_gitea_webhook.py` | Header 常數改 Gitea |
| `docs/adr/ADR-059-gitea-webhook-integration.md` | 新增 | 決策記錄 |
---
## API 端點
```
舊: POST /api/v1/webhooks/github
新: POST /api/v1/webhooks/gitea
舊: GET /api/v1/webhooks/github/reviews/{review_id}
新: GET /api/v1/webhooks/gitea/reviews/{review_id}
```
---
## Config 變更
```python
# 舊 (廢棄)
GITHUB_WEBHOOK_SECRET: str = Field(default="")
GITHUB_ALLOWED_REPOS: str = Field(default="")
# 新
GITEA_WEBHOOK_SECRET: str = Field(
default="",
description="Gitea Webhook secret for HMAC-SHA256 signature verification (X-Gitea-Signature)",
)
GITEA_ALLOWED_REPOS: str = Field(
default="wooo/awoooi",
description="Comma-separated list of allowed Gitea repositories (e.g., 'wooo/awoooi')",
)
```
---
## 防禦性 Payload 解析(首席架構師護欄 #1
Gitea 部分欄位為 nullable 或可能缺失。所有欄位存取使用 `.get()` chain禁止直接 key access
```python
# ❌ 禁止
repo_name = payload["repository"]["full_name"]
# ✅ 正確
repo_name = payload.get("repository", {}).get("full_name", "")
pr_number = payload.get("pull_request", {}).get("number", 0)
head_sha = payload.get("pull_request", {}).get("head", {}).get("sha", "")
```
現有 `github_webhook_service.py` 中已部分採用此模式,遷移時需全面審查確認。
---
## Gitea UI 設定步驟(首席架構師護欄 #2
部署完成後在 Gitea 設定 Webhook
1. 進入 `http://192.168.0.110:3001/wooo/awoooi` → Settings → Webhooks → Add Webhook → Gitea
2. **Target URL**: `https://awoooi.wooo.work/api/v1/webhooks/gitea`
3. **HTTP Method**: POST
4. **Content Type**: `application/json`**必須明確選擇,否則 FastAPI 無法解析**
5. **Secret**: 與 `GITEA_WEBHOOK_SECRET` K8s Secret 值相同
6. **Trigger On**: Pull Request events + Push events
7. **Active**: 勾選
> ⚠️ Content-Type 若選 `application/x-www-form-urlencoded`FastAPI 的 `Request.json()`
> 將無法解析,導致所有 webhook 請求失敗。
---
## 簽章驗證邏輯
驗證邏輯與 GitHub 完全相同,只需更換 header 名稱:
```python
# 舊 header
x_hub_signature_256: str | None = Header(default=None, alias="X-Hub-Signature-256")
# 新 header
x_gitea_signature: str | None = Header(default=None, alias="X-Gitea-Signature")
```
HMAC-SHA256 計算方式不變:`sha256=<hmac_sha256(secret, body).hexdigest()>`
---
## K8s Secrets 注入
`k8s/awoooi-prod/03-secrets.yaml` 新增:
```yaml
GITEA_WEBHOOK_SECRET: "CHANGE_ME" # CD 自動注入
```
`cd.yaml``Inject K8s Secrets` step 需新增注入:
```bash
kubectl patch secret awoooi-secrets \
--patch "{\"stringData\": {\"GITEA_WEBHOOK_SECRET\": \"${{ secrets.GITEA_WEBHOOK_SECRET }}\"}}"
```
Gitea Actions secrets 需新增 `GITEA_WEBHOOK_SECRET`
---
## 測試策略
`test_gitea_webhook.py` 複用現有 8 個測試案例,更新:
1. Header 名稱:`X-Hub-Signature-256``X-Gitea-Signature``X-GitHub-Event``X-Gitea-Event`
2. Router import`from src.api.v1.gitea_webhook import router as gitea_webhook_router`
3. `conftest.py``GITHUB_WEBHOOK_SECRET``GITEA_WEBHOOK_SECRET``GITHUB_ALLOWED_REPOS``GITEA_ALLOWED_REPOS`
---
## 不在此 Scope
- Gitea 端實際 webhook secret 的生成與管理(統帥決定)
- `workflow_run` 等值替代方案(未來 Phase 規劃)
- GitHub mirror repo 的 webhook 設定清除(可手動處理)