feat(workflow): add risk-based release gates

This commit is contained in:
john
2026-07-27 10:38:24 +08:00
parent c88623855f
commit dfab78c75a
17 changed files with 794 additions and 69 deletions
+76 -20
View File
@@ -2,16 +2,16 @@
> 适用目标:生产 `103` Portal runtime 及其共享链路。
> 规则状态:已确认为生产发布的强制验收规范。
> 当前实施状态:187 场景机器目录、fail-closed 执行器、artifact 哈希、机器报告校验和发布脚本上传前硬阻断已经落地;业务场景自动化仍在分批补齐。任何未实现项保持 `unknown` 并阻断发布,在 187 项全部通过或合法豁免前,不得宣称生产发布已经通过本守门员
> 当前实施状态:187 场景机器目录保留为完整回归资产;常规生产发布使用 16 项 Core Gate 加 Git diff 自动选择的影响域 Gate。关键共享路径、无法映射的运行时代码或高风险变更自动升级为完整 187 项 Gate。任何被选择但未实现、未执行或证据不完整的场景保持 `unknown` 并阻断发布
## 1. 目标
本守门员将生产发布从“运行若干测试后人工判断”升级为绑定同一源码和同一产物的可审计硬闸门:
1. 只有完整、干净、可追溯的 `main` 可以成为候选版本。
2. 同一个 `main` commit 构建出的同一个 runtime artifact,必须完成本文规定的全部适用场景
2. 同一个 `main` commit 构建出的同一个 runtime artifact,必须完成 Core Gate 和影响选择器选出的全部场景;满足全量触发条件时执行完整 187 项
3. 结果必须满足 `failed=0``skipped=0``cleanup_failed=0`
4. 确实不受影响的场景只能标记为经过审核的 `not_applicable`,不能用 `skip`、口头说明或发布参数绕过
4. 常规风险分层报告不要求为未选场景逐项填写 `not_applicable`;选择器必须记录 changed paths、影响域和选择证据,被选中的场景不能豁免
5. 即使机器闸门全部通过,仍须取得用户针对“发布 103”动作本身的明确批准。
6. 未获得批准时,禁止上传、切换 runtime、重启服务或执行任何生产写操作。
@@ -22,6 +22,9 @@
| Candidate commit | 已进入远端 `main`、CI 通过、准备构建发布包的完整 Git SHA |
| Candidate artifact | 由 Candidate commit 构建的 Portal runtime 包及其 SHA256 |
| Scenario family | 本文定义的一条业务或运行时场景;可包含多个参数化 case |
| Core Gate | 每次生产候选都必须执行的 16 项最小安全闭包 |
| Impact Gate | 根据线上 commit 到候选 commit 的 Git diff 自动选择的业务域场景 |
| Full Gate | 高风险、共享入口或影响范围不明时执行的完整 187 项回归 |
| Deterministic case | 不依赖真实外部模型输出、可以稳定重复的单元或集成测试 |
| Live canary | 在隔离或预生产环境调用真实 LLM、图片、搜索等服务的最小验证 |
| `not_applicable` | 有完整影响证明并经审核后,确认本次发布不适用的场景状态 |
@@ -37,9 +40,10 @@
- Candidate commit 位于完整 `main`,工作区干净,不落后 `origin/main`
- Candidate commit 已在远端 CI 通过,且没有未合并的关键变更。
- Candidate artifact 的 SHA256 与 Gate report 完全一致。
- 所有适用场景均为 `passed`
- Core Gate 和影响选择器选出的全部场景均为 `passed`
- 没有 `failed``skipped``blocked``unknown` 或未执行场景。
- 所有 `not_applicable` 均具有有效的影响评估和审核记录
- 风险分层报告的 base commit、changed paths、影响域和 selected IDs 能够从 Git diff 重算且完全一致
- 全量报告如果使用 `not_applicable`,所有记录均具有有效的影响评估和审核信息。
- 测试数据和临时资源清理完成。
- Gate report 未过期;默认有效期不超过 4 小时。
- 103 只读预检通过。
@@ -47,7 +51,32 @@
任何一项不满足,发布脚本必须在上传 artifact 之前退出。
### 3.2 永远不可豁免的场景
### 3.2 常规发布的 Core Gate
以下 16 项每次生产候选都必须执行:
- `REL-01``REL-02``REL-04``REL-06``REL-09``REL-11`
- `AUTH-01``AUTH-05`
- `CHAT-01``CHAT-06``CHAT-07`
- `AGENT-01``AGENT-03`
- `MS-01`
- `COMP-09` 脱敏生产回归语料回放。
Core Gate 之外,选择器以当前 103 manifest 中的 `git_head` 为 base commit,比较候选
commit 的 changed paths,并按 `release-gate/impact.mjs` 的版本化规则选择业务域和依赖闭包。
正常风险分层报告中的被选场景必须真实执行,不能标记为 `not_applicable`
### 3.3 自动升级为 Full Gate
以下情况必须执行完整 187 项:
- `server.mjs`、鉴权/会话基础设施、数据库 schema/migration、runtime 构建和生产启动脚本变化。
- 依赖清单或 lockfile、goosed/worker/代理、公共配置和发布闸门自身变化。
- 影响选择器无法映射任一运行时代码路径。
- 线上 base commit 不可用、不是候选祖先,或选择结果无法从 Git diff 重现。
- 上次灰度或生产发布失败,或明确要求完整回归。
### 3.4 Full Gate 中永远不可豁免的场景
下列场景不允许标记为 `not_applicable`
@@ -66,7 +95,7 @@
- `CFG-08` 模型配置兼容场景。
- `COMP-08``COMP-09`
### 3.3 `not_applicable` 的判定
### 3.5 Full Gate 中 `not_applicable` 的判定
只有同时满足以下条件,才允许将场景标记为 `not_applicable`
@@ -479,6 +508,7 @@ npm run test:release-gate:browser -- --artifact .runtime/portal
npm run test:release-gate:providers -- --artifact .runtime/portal
npm run test:release-gate:upgrade -- --artifact .runtime/portal
npm run test:release-gate:all -- --artifact .runtime/portal
node scripts/run-release-gate-impact.mjs --artifact .runtime/portal --deployed-commit <103-stable-sha>
npm run verify:release-gate-report -- --artifact .runtime/portal
```
@@ -497,7 +527,8 @@ npm run verify:release-gate-report -- --artifact .runtime/portal
└── partials/
```
当前执行器从本文解析并校验 187 个连续唯一编号。现有自动化 suite 已为 184
当前执行器从本文解析并校验 187 个连续唯一编号。187 项是完整回归目录,不是常规发布
必须逐项执行的固定数量。现有自动化 suite 已为 184
个场景提供逐项业务断言,仓库/候选检查另直接判定 `REL-01``REL-02`
`REL-04`,共 187 个场景都有自动判定能力。未执行 mode 仍为 `unknown`
缺少 active 脱敏回归 fixture 时 `COMP-09` 明确失败;候选不等于 `origin/main`
@@ -505,11 +536,12 @@ npm run verify:release-gate-report -- --artifact .runtime/portal
这是发布保护而不是测试故障。精确分组覆盖、命令和扩展方式见
[自动发布闸门实现说明](release-gate-automation.md)。
`report.json` 至少包含:
常规风险分层 `report.json` 至少包含:
```json
{
"schema_version": 1,
"mode": "impact",
"commit_sha": "<full sha>",
"branch": "main",
"artifact_sha256": "<sha256>",
@@ -517,9 +549,22 @@ npm run verify:release-gate-report -- --artifact .runtime/portal
"completed_at": "<ISO-8601>",
"expires_at": "<ISO-8601>",
"environment_fingerprint": "<sha256>",
"selection": {
"policy_version": 1,
"strategy": "core|impact|full",
"base_commit": "<103 stable sha>",
"catalog_total": 187,
"selected_total": 32,
"core_ids": [],
"impact_groups": [],
"changed_paths": [],
"unmapped_paths": [],
"full_gate_reasons": [],
"selected_ids": []
},
"summary": {
"required": 187,
"passed": 187,
"required": 32,
"passed": 32,
"not_applicable": 0,
"failed": 0,
"skipped": 0,
@@ -533,7 +578,14 @@ npm run verify:release-gate-report -- --artifact .runtime/portal
}
```
如果存在合法 `not_applicable``required` 仍为 187,且必须满足:
风险分层报告必须满足:
```text
passed = required = selected_total
failed = skipped = blocked = unknown = cleanup_failed = 0
```
完整 187 项报告如果存在合法 `not_applicable`,必须满足:
```text
passed + not_applicable = required
@@ -565,12 +617,11 @@ commit、artifact 和 Gate report,并完成备份、只读预检及人工批
2. 获取完整 `main` SHA 和远端 CI 状态
3. 构建 Candidate artifact
4. 计算 artifact SHA256
5. 读取对应 Gate report
6. 校验 commit、artifact、环境有效期和场景汇总
7. 校验全部 `not_applicable` 记录
5. 读取对应 Gate report;不存在或过期时自动读取 103 稳定 commit 并执行 Core + Impact Gate
6. 从 Git diff 重算并校验 base commit、changed paths、影响域、selected IDs、artifact、环境有效期
7. 满足全量触发条件时确认报告覆盖完整 187 项
8. 执行 103 只读预检
9. 请求本次发布的明确人工批准
10. 批准后才允许上传
9. 请求本次发布的明确人工批准,批准后才允许上传
以下情况必须直接退出:
@@ -578,12 +629,17 @@ commit、artifact 和 Gate report,并完成备份、只读预检及人工批
- 使用范围绕过变量
- report 不存在、过期或解析失败
- commit 或 artifact SHA 不一致
- 任一场景失败、跳过、未知或清理失败
- 豁免记录缺少审核信息
- 任一被选场景失败、跳过、未知、未执行或清理失败
- 影响选择结果不可重现、存在未处理路径,或应全量时报告不完整
- 全量报告中的豁免记录缺少审核信息
- 103 有活动任务且未完成排空
- 备份、磁盘空间、数据库锁或回滚预检失败
当前脚本已在任何 103 SSH、上传或切换之前执行 `verify-release-gate-report.mjs`,并硬拒绝 `--skip-tests``ALLOW_PORTAL_RELEASE_SCOPE_BYPASS``ALLOW_MINDSPACE_PUBLIC_LINK_ISSUES`。Gate report 绑定 `.runtime/portal` 的排序目录树 SHA256;打包后的传输压缩包另行记录自身 SHA256。
当前脚本会先尝试验证已有报告;报告缺失或过期时,只读获取 103 稳定 manifest 的
`git_head` 并自动执行风险分层 Gate。任何 artifact 上传、runtime 切换或生产写操作前都必须
再次通过 `verify-release-gate-report.mjs`,并硬拒绝 `--skip-tests`
`ALLOW_PORTAL_RELEASE_SCOPE_BYPASS``ALLOW_MINDSPACE_PUBLIC_LINK_ISSUES`。Gate report
绑定 `.runtime/portal` 的排序目录树 SHA256;打包后的传输压缩包另行记录自身 SHA256。
## 11. 发布后的最小保护
+3 -2
View File
@@ -51,7 +51,8 @@ LaunchAgent、Portal provider 同步和路由器依赖健康配置是否属于
## 发布入口
候选必须来自干净且等于 `origin/main` 的完整 `main`CI 成功,并具有与
`.runtime/portal` 完全一致、未过期的 187 项 Gate report。
`.runtime/portal` 完全一致、未过期且可从线上 base commit 重算选择结果的风险分层
Gate report;满足全量触发条件时报告必须覆盖完整 187 项。
```bash
bash scripts/release-portal-canary-prod.sh --yes
@@ -59,7 +60,7 @@ bash scripts/release-portal-canary-prod.sh --yes
该脚本按顺序执行:
1. 验证源码、分支、远端主线、Gate report 和 artifact hash
1. 验证源码、分支、远端主线、artifact hash;缺少有效报告时自动执行 Core + Impact Gate
2. 执行 103 只读预检。
3. 上传经过验证的候选包。
4. 创建稳定 live 全量备份、持久目录备份和 105 两份活动 nginx 配置备份,并校验完整性。
+33 -16
View File
@@ -2,17 +2,21 @@
## 当前状态
自动发布闸门采用 fail-closed 设计。机器目录从 `production-release-guardian.md` 加载并校验 187 个连续、唯一场景编号;执行器不能把没有实现、没有执行或没有证据的场景视为成功。
自动发布闸门采用 fail-closed 设计。机器目录从 `production-release-guardian.md` 加载并校验
187 个连续、唯一场景编号,但常规发布只执行 16 项核心场景和 Git diff 选择的影响域。
关键、共享或无法映射的运行时路径自动升级为完整 187 项;执行器不能把被选择但没有实现、
没有执行或没有证据的场景视为成功。
已经实现:
- `release-gate/catalog.mjs`:187 场景机器目录和永远不可豁免清单。
- `release-gate/safety.mjs`:拒绝生产 IP、`*.tkmind.cn`、生产路径和生产关联环境变量。
- `release-gate/artifact.mjs`runtime 排序目录树 SHA256、依赖闭包和持久数据扫描。
- `release-gate/impact.mjs`Core Gate、changed-path 业务域映射、依赖闭包和全量触发规则。
- `release-gate/runner.mjs`:suite 执行、日志归档和失败关闭。
- `release-gate/report.mjs``report.json`、Markdown、JUnit、环境指纹及四小时有效期。
- `release-gate/regression-corpus.mjs`:脱敏生产回归 fixture 和 active manifest 校验。
- `scripts/verify-release-gate-report.mjs`:校验完整 `main` SHA、artifact SHA、187 项状态和合法豁免
- `scripts/verify-release-gate-report.mjs`:校验完整 `main` SHA、artifact SHA,并从 Git diff 重算风险分层选择结果
- `scripts/release-portal-runtime-prod.sh`:任何 103 连接或上传之前强制验证 Gate report。
当前已经有自动判定证据的场景如下。这里的“覆盖”必须同时满足
@@ -45,20 +49,25 @@
分别执行生产配置冷启动和脱敏旧库升级,并断言运行前后 artifact 哈希不变;`REL-11`
在无网络 Linux ARM64 容器中实际加载 Portal、WeChat、worker 和每个 MCP 模块。
全部 187 项都已有自动判定路径未执行的 mode 仍保持 `unknown`。UI-01 至 UI-08
全部 187 项都已有自动判定路径。完整 Gate 中未执行的 mode 仍保持 `unknown`;风险分层
Gate 只为自动选中的场景生成结果,所有选中项都必须执行并通过。UI-01 至 UI-08
在隔离本地 Portal、390×844 移动视口和公开页 fixture 上运行;COMP-09 由独立执行器
要求 active 脱敏回归 manifest 并逐条回放。没有 manifest 时 COMP-09 明确 `failed`
不会因“没有用例”而通过。`REL-02` 在候选等于 `origin/main` 且 Gitea commit status 为
`success` 时通过;也可显式注入 `MEMIND_RELEASE_CI_STATUS=success`
当 runtime artifact SHA256 与已发布版本一致时,可使用增量 Gate
常规发布使用风险分层 Gate
```bash
node scripts/run-release-gate-incremental.mjs --artifact .runtime/portal --deployed-commit <103-stable-sha>
node scripts/run-release-gate-impact.mjs --artifact .runtime/portal --deployed-commit <103-stable-sha>
```
增量 Gate 会复用基线 report 中的非 `REL-*` 场景,仅重跑发布相关场景。
稳定发布脚本在完整 report 缺失或过期时会自动尝试增量 Gate。
它固定执行 16 项核心场景,再根据 `<103-stable-sha>..HEAD` 的 changed paths 选择业务域及
依赖闭包。`server.mjs`、鉴权/会话基础设施、schema/migration、依赖、runtime 构建、
生产启动/发布脚本、Gate 自身或未映射运行时代码会自动选择完整 187 项。发布脚本在有效报告
缺失或过期时自动执行该入口,不再要求人工先跑多个 mode 或逐项填写 129 条豁免。
离线 `--dry-run` 不连接 103;如需模拟风险分层,可设置
`MEMIND_RELEASE_BASE_COMMIT=<known-stable-sha>`,未提供时安全退化为本地 Full Gate。
2026-07-26 本地补齐验证中,历史完整报告为 180/187 通过;`REL-01` 因当前仍在功能
分支且工作区不干净而失败。PAGE-01/02 与 DATA-01/02/03/04 的隔离栈现在使用后台
@@ -76,14 +85,22 @@ node scripts/run-release-gate-incremental.mjs --artifact .runtime/portal --deplo
npm run test:release-gate:unit
```
构建候选 runtime 后运行完整闸门:
构建候选 runtime 后,普通发布运行风险分层闸门:
```bash
npm run build:portal-runtime
node scripts/run-release-gate-impact.mjs \
--artifact .runtime/portal \
--deployed-commit <103-stable-sha>
```
高风险变更、选择器自身变化或定期回归运行完整闸门:
```bash
npm run test:release-gate:all -- --artifact .runtime/portal
```
完整报告路径:
风险分层或完整报告路径:
```text
.release-gate/<full-main-sha>/report.json
@@ -95,7 +112,7 @@ npm run test:release-gate:all -- --artifact .runtime/portal
.release-gate/<full-main-sha>/partials/<mode>/
```
只验证已有完整报告与当前 artifact
只验证已有报告与当前 artifact:
```bash
npm run verify:release-gate-report -- --artifact .runtime/portal
@@ -116,11 +133,11 @@ npm run verify:release-gate-report -- --artifact .runtime/portal
`RELEASE_GATE_SCENARIO_TIMEOUT_MS`
`RELEASE_GATE_SCENARIO_PROCESS_TIMEOUT_MS` 在 CI 资源范围内收紧预算,不得用它们
放宽为无限超时。
- 完整闸门只为干净 `main` 的精确 commit + artifact 生成一次四小时有效报告;
- 风险分层或完整闸门只为干净 `main` 的精确 commit + artifact 生成一次四小时有效报告;
发布脚本复用并校验这份报告,不重复跑慢场景。commit、artifact 或环境指纹变化后
证据立即失效并重新执行。
- 本地开发先跑相关确定性 suite只有形成发布候选时才聚合全部 187 项。任何未执行、
超时或缺证据项仍保持 `unknown/failed` 并阻断发布。
- 本地开发先跑相关确定性 suite普通发布候选执行 Core + Impact,完整 187 项用于高风险
候选和定期回归。任何被选择但未执行、超时或缺证据项仍保持 `unknown/failed` 并阻断发布。
报告出现以下任意状态都会返回非零:
@@ -129,7 +146,7 @@ npm run verify:release-gate-report -- --artifact .runtime/portal
- `blocked`
- `unknown`
- `cleanup_failed`
- 缺少场景
- 缺少被选择场景,或影响选择结果无法从 Git diff 重现
- 非法 `not_applicable`
- commit、branch 或 artifact SHA 不匹配
- 报告超过四小时
@@ -172,8 +189,8 @@ scenarios/production-regressions/manifests/active.json
1. 检查干净、完整且不落后远端的 `main`
2. 运行既有本地 verify。
3. 构建并检查 `.runtime/portal`
4. 验证与当前 commit 和 runtime tree SHA256 完全一致的完整 Gate report。
5. Gate report 通过后才允许执行 103 只读预检。
4. 验证与当前 commit 和 runtime tree SHA256 完全一致的风险分层或完整 Gate report;缺失时自动执行风险分层 Gate
5. Gate report 通过后才允许执行 103 只读预检和任何生产写操作
6. 再次请求本次发布的明确人工确认。
7. 确认后才允许上传和切换。
+1 -1
View File
@@ -45,7 +45,7 @@ npm run check:mindspace-public-links
## 2. Portal runtime 发布唯一流程
生产发布守门员、完整 Gate report 和本次明确人工批准全部满足后,首次生产动作只使用:
生产发布守门员、风险分层或全量 Gate report 和本次明确人工批准全部满足后,首次生产动作只使用:
```bash
bash scripts/release-portal-canary-prod.sh --yes