> For the complete documentation index, see [llms.txt](https://tinyhumans.gitbook.io/openhuman/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://tinyhumans.gitbook.io/openhuman/zh/kai-fa/release-policy.md).

# 发布政策

OpenHuman 如何发布版本，包括分支模型、CI 通道、最低版本门槛和密钥轮换。

本运行手册涵盖发布如何出货。它还解释了当最新版本是受支持流程时，我们如何阻止用户在过时的桌面安装程序上完成 OAuth（包括 Gmail）。

## 分发

* 用于 [tinyhumansai/openhuman](https://github.com/tinyhumansai/openhuman/releases) 是桌面构建的主要来源。
* Tauri 更新器端点（见 `scripts/prepareTauriConfig.js` 和发布工作流）应将用户指向当前发布产物。
* 当你淘汰某条发布线时，要停用旧的稳定产物：
  * 移除或隐藏 GitHub Releases 上已过时的安装程序资源。
  * 将网站和 CDN 下载链接指向 `releases/latest`.
  * 刷新更新器清单（例如 Gist 或 `latest.json`），使其不再指向已弃用的构建。
  * 检查旧的直接 URL 是否重定向，或返回 404/410。尝试文档或书签中已知的旧资源 URL，并确认它们不再提供主要安装路径。

## OAuth 的最低应用版本

生产 Web 构建会在构建时嵌入最低受支持应用版本，因此 OAuth 深度链接无法在已弃用的二进制文件上完成。每个安装程序都携带该构建生成时设定的最低版本。要为从不升级的用户提高门槛，需要他们安装一个新版本，或进行应用内更新。

| 变量                                   | 用途                                                                                    |
| ------------------------------------ | ------------------------------------------------------------------------------------- |
| `VITE_MINIMUM_SUPPORTED_APP_VERSION` | 例如 `0.51.0`。桌面应用必须至少是此版本才能完成 `openhuman://oauth/success`.                             |
| `VITE_LATEST_APP_DOWNLOAD_URL`       | 可选；默认为 `https://github.com/tinyhumansai/openhuman/releases/latest`。当门控阻止 OAuth 时会打开它。 |

将这些配置为 GitHub Actions 变量。在独立的 `pnpm build` 步骤以及 `tauri-apps/tauri-action` 步骤中都设置它们， `.github/workflows/build-desktop.yml` （可复用的矩阵 `release-production.yml` 和 `release-staging.yml` 调用），这样已出货安装程序中的 Vite bundle 就会包含该门控。留空 `VITE_MINIMUM_SUPPORTED_APP_VERSION` 用于本地开发，这会禁用该门控。

代码位于 `app/src/utils/oauthAppVersionGate.ts`, `app/src/utils/desktopDeepLinkListener.ts`.

## Gmail / Google Cloud OAuth

* Google Cloud Console 中的重定向 URI 必须与当前后端 + 隧道回调路径匹配。
* 桌面 scheme（`openhuman://`）是稳定的。当 `VITE_MINIMUM_SUPPORTED_APP_VERSION` 设置时，已安装二进制文件必须满足最低版本要求。

## 发布检查清单

1. 提升 `app/package.json` 和 `crates/openhuman-app/tauri.conf.json` （以及根目录 `Cargo.toml` / core）以符合现有版本工作流。
2. 在放弃对旧安装的支持时，请在该发布之前或同时将 `VITE_MINIMUM_SUPPORTED_APP_VERSION` 设置为新的最低门槛（仓库 Actions 变量 + 上面的两个工作流步骤）。
3. 从面向用户的表面中移除、重定向或停用旧的稳定安装程序和过时的更新器条目（GitHub Release 资源、网站、CDN、更新源）。确认已弃用的产物无法从默认安装/更新流程中访问。
4. 在 releases/latest 的全新安装上对 Gmail 连接做冒烟测试。
5. 完成 [手动冒烟检查清单](https://github.com/tinyhumansai/openhuman/blob/main/docs/RELEASE-MANUAL-SMOKE.md)。然后将已完成的签字确认块原样粘贴——每个已勾选项都保持勾选——作为 GitHub 提交评论，发到 QA 已验证的 `v<version>-staging` 带标签提交上。晋升流程没有发布 PR。在批准生产运行之前， `Release-Approval` 审核人要检查两件事。第一，签字确认评论存在于带 staging 标签的提交上。第二，生产运行的目标内容就是这份已验证内容：要么将 staging 标签的 SHA 作为 `commit_sha`传入，要么确认只有 `[skip ci]` 版本提升提交将其与运行目标分隔开。更多内容都属于 QA 从未冒烟测试过的新内容，因此要先重新运行 staging。

## 分支模型与 CI 线路

两个长期分支，两条 CI 线路：

* `main`：所有功能和修复 PR 都合并到这里。每个 PR 都运行 CI Fast（[`ci-fast.yml`](https://github.com/tinyhumansai/openhuman/blob/main/.github/workflows/ci-fast.yml)），对变更区域进行质量检查和完整单元测试套件，差异覆盖率至少 80% 才放行。
* `release`：维护者晋升后的 `main` 快照，正式版本就是从这里切出。目标为 `release` 以及每次推送到 `release` 的 PR 都运行 CI Full（[`ci-full.yml`](https://github.com/tinyhumansai/openhuman/blob/main/.github/workflows/ci-full.yml)）：完整单元测试套件、Rust mock-backend E2E、Playwright Web E2E，以及 Linux 上的桌面 E2E。macOS 和 Windows 桌面 E2E 是手动触发，默认关闭，直到每个平台都有原生驱动为止，因此跨平台桌面信号是可选的。 `CI Full Gate` 检查会汇总除 Playwright spec 运行之外的所有线路。由于目前在 CI 争用下它不稳定，所以该运行暂时不阻塞（`continue-on-error`），因此绿色 gate 并不能证明 Playwright specs 已通过。在切版之前请检查那条线路的结果。只有 Playwright 产物构建会被 gate 限制。

流程：

1. 维护者触发 [`promote-main-to-release.yml`](https://github.com/tinyhumansai/openhuman/blob/main/.github/workflows/promote-main-to-release.yml)，它会从 `main` 到 `release` 推送一个合并提交，而无需 PR。重新触发会用最新的 `release` 刷新 `main` ，并保留已在 `release`上的修复提交。如果 `release` 已经包含 `main`，它就不会做任何事。
2. CI Full 会在晋升推送上运行。如果发现破坏，任何拥有写权限的人都可以直接针对 `release`开一个修复 PR。修复 PR 会跑两条线路：CI Fast 用于快速获取 lint 和覆盖率反馈，CI Full 作为阻塞合并的 `CI Full Gate` 检查。合并后的推送会在合并结果上重新运行 CI Full。
3. 一旦 `release` HEAD `release-production.yml`上的 CI Full 通过，就用 `main` 切出生产版。若 QA 需要先验证 `main` ，也可以改为从 `CI Full Gate`触发 staging。发布工作流不会检查
4. ，因此操作员必须在切版前验证 CI 证据。 `release` 从 `release` 到 `main` 替换为 `脚本/release/merge-release-into-main.sh`回合并。能快进就快进，否则会生成类似这样的带版本合并提交： `chore(release): merge release v1.2.4 back into main`。提升和修复提交会按这个方向流回。来自 `main` 的 staging 切版不需要回合并。版本提升提交带有 `[skip ci]`.

此模型所需的 GitHub 设置（仓库 Settings > Rules）： `main` 要求 `PR CI Gate` 状态检查用于 PR； `release` 要求非绕过角色的 PR 具有 `CI Full Gate` 状态检查必需（它运行在指向 release 的 PR 上）；release GitHub App 在这两个规则集的绕过名单中，因此 promote 和 release 工作流可以直接推送。

## 工作流：staging 与 production

每个环境对应一个 GitHub Actions 工作流。按意图选择，而不是切换标志。staging 会跟随所选的 `main` 或 `release` dispatch ref；production 始终签出 `release`，不管 GitHub 工作流 UI 显示的 dispatch ref 是什么。

| Workflow                                                                                                                 | 分支                 | 提升                                     | 推送的标签                | 并发组                  | 何时使用                                       |
| ------------------------------------------------------------------------------------------------------------------------ | ------------------ | -------------------------------------- | -------------------- | -------------------- | ------------------------------------------ |
| [`release-staging.yml`](https://github.com/tinyhumansai/openhuman/blob/main/.github/workflows/release-staging.yml)       | `main` 或 `release` | `补丁` 仅                                 | `v<version>-staging` | `release-staging`    | 从所选分支为 QA 切出 staging 构建。                   |
| [`release-production.yml`](https://github.com/tinyhumansai/openhuman/blob/main/.github/workflows/release-production.yml) | `release`          | `补丁` / `次要` / `主要` (`release_type` 输入） | `v<version>`         | `release-production` | 从已验证的 `release` HEAD（或一个固定的 `commit_sha`). |

构建、签名、Sentry 调试文件和产物上传管线都位于 [`.github/workflows/build-desktop.yml`](https://github.com/tinyhumansai/openhuman/blob/main/.github/workflows/build-desktop.yml) 中，作为可复用的 `workflow_call` 工作流。上面的两个工作流负责 ref 解析、版本提升、打标签、发布和清理。构建本身是共享的。

### Android / Google Play

Android 发布由单独的 [`.github/workflows/android-compile.yml`](https://github.com/tinyhumansai/openhuman/blob/main/.github/workflows/android-compile.yml) 工作流处理。它会构建一个发布用 Android App Bundle（`.aab`），用 Play 上传密钥签名，并在启用发布时上传到 Google Play。未签名和已签名的 AAB 会作为 Actions 产物保留，便于审计和调试。

手动 Android 上传使用同一个工作流：

```bash
pnpm --dir app release:android:play -- --track internal
pnpm --dir app release:android:play -- --ref main --track production --status draft
```

所需的 GitHub Actions 密钥：

| 密钥                                 | 用途                                                            |
| ---------------------------------- | ------------------------------------------------------------- |
| `ANDROID_UPLOAD_KEYSTORE_BASE64`   | Base64 编码的 Play 上传 keystore（`.jks`）。使用上传密钥，而不是 Google 应用签名密钥。 |
| `ANDROID_UPLOAD_KEY_ALIAS`         | 上传密钥的 keystore 别名。                                            |
| `ANDROID_UPLOAD_KEYSTORE_PASSWORD` | keystore 密码。                                                  |
| `ANDROID_UPLOAD_KEY_PASSWORD`      | 密钥密码。                                                         |
| `GOOGLE_PLAY_SERVICE_ACCOUNT_JSON` | Play Console 服务账号的原始 JSON，需对 `com.openhuman.app`.             |

可选的 GitHub Actions 变量：

| 变量                    | 默认值        | 用途                                                         |
| --------------------- | ---------- | ---------------------------------------------------------- |
| `ANDROID_PLAY_TRACK`  | `internal` | 要上传到的 Play 轨道（`internal`, `alpha`, `beta`，或 `production`). |
| `ANDROID_PLAY_STATUS` | `已完成`      | Play 发布状态（`已完成`, `draft`, `inProgress`, `halted`).         |

Google Play 要求每次上传都使用更高的 Android `versionCode` 。发布提升脚本会更新 `app/src-tauri-mobile/tauri.conf.json`, `app/src-tauri-mobile/Cargo.toml` 和 `app/src-tauri-mobile/Cargo.lock` 以及桌面文件，所以生成的 Android `tauri.properties` 会随着每次发布一起递增。

### 切出 staging 构建

1. 通过 `workflow_dispatch` 来自 `release` 运行 Release (Staging)（可选地固定一个可达发布的 `commit_sha`). `create_tag = false` 提升并提交，但不打标签或构建。
2. 工作流会提升 `补丁` 于 `release`，提交 `chore(staging): vX.Y.Z [skip ci]`，推送，并在该提交处创建一个不可变的 `vX.Y.Z-staging` 标签。
3. 构建矩阵从标签而不是 `release` HEAD `release` 运行，因此即使
4. 已向前移动，重跑也会重建相同内容。 `release`上的提升提交（以及其他任何内容）会合并回 `main`.
5. 失败时，staging 标签会自动删除。 `release` 上的提升提交会保留，因此下一次切版会从 `vX.Y.(Z+1)`.

开始。没有单独的 `staging` 分支。staging 切版和 production 发布都位于 `release`上，只有标签后缀（`-staging` 或无后缀）以及创建该标签的工作流能把它们区分开。

### 发布 production 版本

1. 通过 `workflow_dispatch` 运行 Release Production，使用所需的 `release_type` (`补丁` / `次要` / `主要`），从 `release` HEAD 或一个固定的可达发布的 `commit_sha`.
2. 运行首先等待 `review-approval` 作业（`environment: Release-Approval`）。在任何推送发生之前， [必需审阅者](#release-app-token-approval-gate-and-rotation) 必须批准。然后 `prepare-build` 在 `release`，提交 `chore(release): vX.Y.Z [skip ci]`上提升版本、推送、打标签 `vX.Y.Z`，构建并发布。
3. `release` 会在切版后立即合并回 `main` 。

### 标签策略与回滚

* 命名：staging 标签使用 SemVer 预发布后缀 `-staging` (`v1.2.4-staging`），因此它们会排在对应的 production 标签之前。
* 冲突：如果目标标签在本地或在 `origin`上已经存在，这两个工作流都会快速失败。删除旧标签（仅限组织维护者）或提升跳过它。
* 生产回滚：失败的构建矩阵会触发 `cleanup-failed-release`，它会删除草稿 GitHub Release 和 `v<version>` 标签。
* staging 回滚：失败的 staging 构建会删除 `v<version>-staging` 标签。来源分支上的提升提交会保留。下一次 staging 切版会从新的补丁号继续，这会在补丁号中留下一个小间隔，而不是与并发合并竞争。
* 删除标签：你需要与 `main`相同的写权限。 `通过`，工作流清理使用工作流的令牌运行。GitHub App 令牌仅由 `prepare-build` 用于提升提交和标签推送。手动删除（`git push --delete origin <tag>`）需要等效的维护者权限。

## Release App 令牌：审批门控与轮换

`release-production.yml` 提升版本、提交到 `release` 并回合并到 `main`，使用 GitHub App 令牌（推送这些提交和标签`secrets.XGITHUB_APP_ID` / `secrets.XGITHUB_APP_PRIVATE_KEY`）来推送，从而绕过分支保护。同一个 App 还会将 staging 提升推送到所选的 `main` 或 `release` 源（`release-staging.yml`）以及晋升合并提交（`promote-main-to-release.yml`）。私钥泄露（通过日志、被攻破的 action 或配置错误的 runner）会让攻击者能够向受保护分支推送任意提交（[CWE-250](https://cwe.mitre.org/data/definitions/250.html)）。有两个控制措施可限制损害。

### 人工审批门控

该 `review-approval` 作业会在 `prepare-build` 之前运行，并把每次生产运行都停在 `Release-Approval` GitHub 环境上，因此在发生任何推送前都必须由人工批准。

一次性设置（仓库 Settings > Environments）：

1. 创建一个名为 `Release-Approval` 的环境（必须是精确名称，因为工作流会逐字引用它）。
2. 在 Deployment protection rules 下，启用 Required reviewers，并添加允许批准向 `main`进行生产推送的维护者。若关闭 Prevent self-review，审阅者不能批准自己的运行。对于发布门控，请保持开启，这样必须由第二个人批准。
3. 可选择将等待计时器设为 0。该门控是人工决策，而不是延迟。

当生产运行开始时， `review-approval` 作业会显示“Waiting”。批准者打开运行并点击 Review deployments > Approve。拒绝或取消会跳过 `prepare-build`，因此不会发生任何推送。

### 每季度轮换密钥

轮换 `XGITHUB_APP_PRIVATE_KEY` ，每个季度轮换一次（若怀疑泄露则立即轮换）。在 3 月、6 月、9 月和 12 月月底执行。

1. 在 GitHub App 设置中（Org > Settings > Developer settings > GitHub Apps > 该 release App），在 Private keys 下点击 Generate a private key。下载新的 `.pem`.
2. 更新仓库密钥：Settings > Secrets and variables > Actions > `XGITHUB_APP_PRIVATE_KEY`，然后粘贴完整的新密钥（包括 `-----BEGIN/END-----` 行）。 `XGITHUB_APP_ID` 不变。
3. 触发一个低风险验证运行，例如 Release (Staging)，并确认 Generate GitHub App token 步骤成功且推送已完成认证。除非你确实要切出真实的提升提交，否则不要为此使用 Release Production。即使 `create_release = false`, `prepare-build` 仍然会提升版本并提交到 `release`。staging 配合 `create_tag = false` 也会提交，但会跳过标签和构建，因此它是风险较低的探针。
4. 回到 App 设置中的 Private keys，删除旧密钥，使只有新颁发的密钥保持有效。
5. 在 PR 描述或运维日志中记录轮换日期，这样下一位负责人就能看到上次是什么时候发生的。

轮换会使旧密钥的任何泄露副本失效，从而将暴露窗口限制为一个季度。
