Production-grade: webhook execution, Postgres backup, rate limit, worker timeout/retry
GitHub webhook: delivery-id dedup persisted in webhook_deliveries, /fix-review creates a queued task with audit (no model run in the webhook itself), check_run/check_suite parsing without mutating task state (CI backfill still via pr-checks). Control plane: Postgres pg_dump backup branch + POST /admin/backup (maintainer+, password only via PGPASSWORD env), per-key/IP rate limit (default 120/min, 429), health returns backend + db_ok, metrics by_status always has all seven status keys. Worker: per-task timeout (default 1800s, --task-timeout), timeout is failed not interrupted, limited retries (default 1, backoff, fallback failures not re-retried), stale codex- container cleanup on loop/CLI start. Tests: cloud 271 (267 pass, 4 skipped are pre-existing real- Postgres tests), host 27 pass.
This commit is contained in:
1 parent
86160ad175
commit
33841355b5
9 files changed
+1366
-52
No files matched your search
@@ -26,6 +26,8 @@ from cloud.db import connect as _db_connect
|
||||
__all__ = [
|
||||
"verify_github_signature",
|
||||
"parse_github_pr_comment",
|
||||
"parse_github_check_run",
|
||||
"WebhookDeliveryStore",
|
||||
"verify_slack_signature",
|
||||
"parse_slack_command",
|
||||
"SlackUserMap",
|
||||
@@ -94,6 +96,104 @@ def parse_github_pr_comment(payload):
|
||||
}
|
||||
|
||||
|
||||
def parse_github_check_run(payload):
|
||||
"""解析 GitHub check_run / check_suite 事件,返回结构化结果或 None。
|
||||
|
||||
只在结论已定的时候返回:check_run / check_suite 的 conclusion
|
||||
为 "success" 或 "failure" 时返回::
|
||||
|
||||
{"type": "check_run", "repo": ..., "head_sha": ...,
|
||||
"conclusion": ..., "check_name": ...}
|
||||
|
||||
其中 repo 取 repository full_name,head_sha 取 check_run /
|
||||
check_suite 的 head_sha,check_name 取 check_run 的 name
|
||||
(check_suite 没有单项名时为 None)。结论为 None / 进行中
|
||||
(如 queued / in_progress / neutral / cancelled 等)时返回 None,
|
||||
调用方不应据此建任务或改任务状态——CI 状态回填由 pr-checks
|
||||
主动拉取,webhook 只做通知解析。
|
||||
"""
|
||||
if not isinstance(payload, dict):
|
||||
return None
|
||||
for key in ("check_run", "check_suite"):
|
||||
run = payload.get(key)
|
||||
if not isinstance(run, dict):
|
||||
continue
|
||||
conclusion = run.get("conclusion")
|
||||
if conclusion not in ("success", "failure"):
|
||||
return None
|
||||
repository = payload.get("repository") or {}
|
||||
return {
|
||||
"type": "check_run",
|
||||
"repo": repository.get("full_name"),
|
||||
"head_sha": run.get("head_sha"),
|
||||
"conclusion": conclusion,
|
||||
"check_name": run.get("name"),
|
||||
}
|
||||
return None
|
||||
|
||||
|
||||
class WebhookDeliveryStore:
|
||||
"""GitHub webhook delivery 去重(持久化,不只放内存)。
|
||||
|
||||
表 webhook_deliveries:delivery_id(主键,X-GitHub-Delivery 头)、
|
||||
event、repo、created_at(epoch 秒)。claim() 第一次返回 True,
|
||||
同一 delivery_id 第二次返回 False,调用方应直接回 200 且标记
|
||||
duplicate,不重复建任务、不重复记审计。INSERT 冲突时先按
|
||||
IntegrityError 处理,再回查一次确认(PG 适配层抛的完整性错误
|
||||
类型与 SQLite 不同,这里按“插入失败后回查存在即重复”处理,
|
||||
不依赖具体异常类名)。
|
||||
"""
|
||||
|
||||
def __init__(self, db_path):
|
||||
self.db_path = db_path
|
||||
self._conn = _db_connect(db_path)
|
||||
self._conn.execute(
|
||||
"CREATE TABLE IF NOT EXISTS webhook_deliveries("
|
||||
"delivery_id TEXT PRIMARY KEY, "
|
||||
"event TEXT, "
|
||||
"repo TEXT, "
|
||||
"created_at REAL)"
|
||||
)
|
||||
self._conn.commit()
|
||||
|
||||
def claim(self, delivery_id, event=None, repo=None):
|
||||
"""认领一次 delivery:首次 True,重复 False。空 id 不认领。"""
|
||||
if not delivery_id:
|
||||
return True
|
||||
row = self._conn.execute(
|
||||
"SELECT delivery_id FROM webhook_deliveries WHERE delivery_id = ?",
|
||||
(delivery_id,),
|
||||
).fetchone()
|
||||
if row is not None:
|
||||
return False
|
||||
try:
|
||||
self._conn.execute(
|
||||
"INSERT INTO webhook_deliveries"
|
||||
"(delivery_id, event, repo, created_at)"
|
||||
" VALUES(?, ?, ?, ?)",
|
||||
(delivery_id, event, repo, time.time()),
|
||||
)
|
||||
self._conn.commit()
|
||||
except Exception:
|
||||
try:
|
||||
self._conn.rollback()
|
||||
except Exception:
|
||||
pass
|
||||
row = self._conn.execute(
|
||||
"SELECT delivery_id FROM webhook_deliveries"
|
||||
" WHERE delivery_id = ?",
|
||||
(delivery_id,),
|
||||
).fetchone()
|
||||
if row is not None:
|
||||
return False
|
||||
raise
|
||||
return True
|
||||
|
||||
def close(self):
|
||||
"""关闭数据库连接(可选,单测/退出时用)。"""
|
||||
self._conn.close()
|
||||
|
||||
|
||||
def verify_slack_signature(signing_secret, timestamp, body, signature_header):
|
||||
"""校验 Slack 请求签名(防重放)。
|
||||
|
||||
|
||||
Reference in new issue
Block a user