From 5cd67d372f2e2cbfa4e6855ed7df1d3eb859c96b Mon Sep 17 00:00:00 2001 From: OG T Date: Sun, 5 Apr 2026 14:17:13 +0800 Subject: [PATCH] =?UTF-8?q?docs(spec):=20ADR-059=20Gitea=20Webhook=20?= =?UTF-8?q?=E9=81=B7=E7=A7=BB=E8=A8=AD=E8=A8=88=E8=A6=8F=E6=A0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 從 GitHub Webhook (Phase 13.1) 遷移至 Gitea Webhook 最少改動策略:Header 常數替換,業務邏輯層不動 廢棄 workflow_run CI 診斷(CD pipeline 已有 TG 通知覆蓋) 整合首席架構師護欄:防禦性 payload 解析 + Content-Type 設定 Co-Authored-By: Claude Sonnet 4.6 --- ...26-04-05-gitea-webhook-migration-design.md | 185 ++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 docs/superpowers/specs/2026-04-05-gitea-webhook-migration-design.md diff --git a/docs/superpowers/specs/2026-04-05-gitea-webhook-migration-design.md b/docs/superpowers/specs/2026-04-05-gitea-webhook-migration-design.md new file mode 100644 index 00000000..fe4bb65a --- /dev/null +++ b/docs/superpowers/specs/2026-04-05-gitea-webhook-migration-design.md @@ -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 全面遷移至 Gitea,GitHub 只剩唯讀備份 +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=` | `sha256=` ← **相同** | +| 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=` + +--- + +## 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 設定清除(可手動處理)