---
date: 2026-04-23 21:30
slug: release-deploy-sop
title: 研发发布 SOP 骨架（单 main + tag 触发 / alpha 自动 / epsilon 手动 / PyPI+Skill 绑定）
status: active
tags: [sop, release, ci, deploy]
---

# 研发发布 SOP 骨架

## 场景

xiangqin 同时管理两条发布线（PyPI 包 + ClawHub skill）和两条部署线（alpha 测试机 + epsilon 生产机），还要夹本地开发 / GHA / CF Pages 文档。之前 SOP 只有 epsilon 一台机器的 `deploy.sh` + `publish-pypi.sh`，没有整体流程约定，加了 alpha（issue #001）+ version-check（issue #002）后到了必须拉齐的阶段。

## 决策（A-G 七点）

### A. 分支模型：单 main + tag 触发 release

只有 `main` 一条长期分支。feature 在本地或短命分支开发，PR 合 main。tag `vX.Y.Z` 触发正式发布链路。**不走 git-flow** —— 两人团队 develop/release/hotfix 分支是负担。

### B. alpha 同步时机：每次合 main 自动

合 main 后 GHA 自动 deploy alpha。alpha 永远反映 main HEAD，用来本地之外做端到端验收。不打 tag 的普通 commit 也会到 alpha。

### C. PyPI + ClawHub 节奏：绑在一起，一个 tag 同时发

`git tag vX.Y.Z && git push --tags` 触发 GHA on-tag workflow，一次发两边：
1. PyPI 发 `acong-tech-xiangqin==X.Y.Z`
2. 同步 SKILL.md 到 `acong-tech/skill-xiangqin` 仓库 + push tag（ClawHub 从 GitHub 同步）

两个产物用同一版本号（`pyproject.toml` 的 version），不分开打 tag。

### D. 版本号来源：pyproject.toml 是唯一源

`pyproject.toml` 的 `version` 字段是唯一权威。`src/xiangqin/__init__.py.__version__` 要跟着它走（目前手动同步；未来可以用 `setuptools_scm` 或自动脚本校验两处一致）。skill 仓里的 SKILL.md 不写版本号。

### E. epsilon 生产 deploy 触发：手动 workflow_dispatch

tag 被打后不自动部署生产。需要人在 GHA UI 点 `Deploy to epsilon` workflow，输入要部署的 tag（默认最新）。理由：给老板一个"我真的要按了"的瞬间；alpha 已经验过，但生产机变更永远值得一次手动确认。

### F. Hotfix：main 上直接小修 + patch tag

不拉 hotfix 分支。紧急 bug → 在 main 改 → 打 `vX.Y.(Z+1)` → 走完整 release-flow（PyPI + skill + epsilon）。alpha 这一圈跳过冒烟直接点 epsilon deploy（hotfix 前提就是等不及）。前置门槛：pytest 必须绿，不能跳。

### G. 回滚：新 patch + 重跑老 tag deploy

PyPI 不能删版本，用户降级用 `pip install acong-tech-xiangqin==<旧>`；新版本里首选是发 **patch 修正版**（+1）而不是依赖用户降级。

epsilon 回滚 = `git checkout <旧 tag>` 后重跑 `Deploy to epsilon` workflow（指定 tag）。数据库不可逆的 migration（比如 DROP TABLE）不能靠代码回滚，走 `sop/services/restore.md` 恢复 OSS 备份。

skill 仓：`git revert` + push tag。ClawHub 侧是软删不是硬删（保留历史）。

## 落地形态

三份 SOP 文件：

- `brain/sop/release-flow.md` —— 总览图 + 时间线，入口文档
- `brain/sop/services/deploy.md`（刷）—— alpha + epsilon 两机器的 deploy 剧本
- `brain/sop/services/publish.md`（刷）—— PyPI + Skill 绑定发布剧本
- `brain/sop/services/hotfix.md`（新）—— 跳 alpha 直上 epsilon 的紧急流程
- `brain/sop/emergency/rollback.md`（新）—— 回滚分 3 种产物的剧本

GHA workflow 骨架（后续独立 PR）：

- `.github/workflows/ci.yml`（现有）—— test + lint + docs 部署，不动
- `.github/workflows/deploy-staging.yml`（新）—— on push main 自动 rsync + restart
- `.github/workflows/release.yml`（新）—— on tag 发 PyPI + 同步 skill 仓
- `.github/workflows/release.yml 的 deploy-production job`（新）—— workflow_dispatch 手动触发

## 代价 / 放弃

- **主干直推**：不走保护分支 / PR review 强制 —— 两人团队不值得，但老板合 main 前必须本地 pytest 绿
- **tag 不回撤**：一旦 push tag 就发出去了，GHA 会直接发 PyPI；要撤就打 patch 修正版（回滚规则 G）
- **skill 仓手动建过一次**：`acong-tech/skill-xiangqin` 仓库首次创建不在 SOP 范围内（一次性动作），GHA 只 push 已存在的仓

## 未决

1. **真付**：目前 `xq envelope buy --mock` 是 mock，真付支付宝接入后 PyPI 版本号跳到多少？（推荐 `0.5.0` minor bump + CHANGELOG 标 BREAKING if any）
2. **skill 仓 CI**：skills.sh 侧暂未见强 schema 校验；如果 ClawHub 上线 strict validation，GHA release.yml 的 skill push 可能需要先跑 lint
3. **数据库 migration 工具**：目前是 `db.py` 里手写 `_migrate_*` 函数；下一个真 breaking schema change 前考虑 alembic

## 记录源

- 老板 2026-04-23 21:30 "按你的来" —— 整体认可 A-G 推荐方案
- 前置：issue #001（alpha 建机）+ issue #002（version-check）刚完成，触发 SOP 拉齐需求

---

## 2026-04-23 23:30 补充：E 项覆盖 + ClawHub 纳入

**E 项决策覆盖**：原设计"epsilon 生产 deploy 手动 workflow_dispatch"，落地跑了一轮后发现：
- 手动点按钮违反老板"自动化"偏好
- 缺少 ClawHub 发布自动化（本地手动 `clawhub publish` 制造了版本号脱节事故 —— ClawHub 0.4.1 vs PyPI 0.4.0）

**改**：

- `deploy-production.yml` 废弃（文件删除）
- 功能合并到 `release.yml` 作为 `deploy-production` job，`needs: [publish-pypi]` chain 触发
- `release.yml` 同时加 `publish-clawhub` job（并行和 PyPI / skills.sh）
- 所有 publish/deploy job 前置 `test` gate job（pytest + ruff），绿了才 publish
- `deploy-staging.yml` 也前置 `test` gate

**新 workflow 架构**：

| workflow | 触发 | job 链 |
|---|---|---|
| `ci.yml` | push/PR | test + lint + mkdocs + CF Pages |
| `deploy-staging.yml` | push main | test → deploy alpha |
| `release.yml` | push tag `v*.*.*` | test → (publish-pypi ∥ sync-skills-sh ∥ publish-clawhub) → deploy-production |

打 tag = 一键全链路上线（PyPI + skills.sh + ClawHub + epsilon），**不再点按钮**。

**新增 secret**：`CLAWHUB_TOKEN`（从本地 `~/Library/Application Support/clawhub/config.json` 的 `clh_xxx` token 复用，yarnovo 身份）。

**跳号 0.4.1**：因上面 ClawHub 抢先发了 0.4.1 作 display name metadata patch，主仓从 0.4.0 直接 bump 0.4.2 对齐三端。ClawHub 0.4.1 留作历史（"混沌的往前进"）。
