feat: add guarded portal canary release
Memind CI / Test, build, and release guards (push) Failing after 2m14s

This commit is contained in:
john
2026-07-26 19:51:44 +08:00
parent 058d646b32
commit 286069449b
33 changed files with 2161 additions and 53 deletions
+13
View File
@@ -35,6 +35,19 @@ Local Colima may exist for other workloads, but local Memind/goosed is not suppo
Portal must be released as a runtime artifact. Do not edit source directly on 103.
### User-level canary (when enabled)
| Item | Value |
|------|-------|
| Identity router | `127.0.0.1:18080` |
| Candidate Portal | `127.0.0.1:18081` |
| Candidate goosed | `127.0.0.1:18015` |
| Release entry | `scripts/release-portal-canary-prod.sh` |
| Rollback entry | `scripts/rollback-portal-canary-prod.sh` |
The stable Portal stays on 8081. The 105 reverse tunnel points to 18080 only after the
candidate, router, backups, immutable identity checks, and fallback checks pass.
## MindSpace Service
| Item | Value |
+11 -2
View File
@@ -544,9 +544,18 @@ failed = skipped = blocked = unknown = cleanup_failed = 0
### 10.0 用户级灰度前置条件
当前 Portal 发布脚本会替换 8081 live 目录,不具备版本级用户路由能力,因此在用户级灰度代理和候选 runtime 并行运行方案完成前,禁止调用该脚本执行生产发布。灰度身份必须使用不可变用户 ID/用户名/微信绑定 ID;禁止按显示昵称(包括“唐”)匹配。未命中或身份解析失败必须回落稳定版本,候选不可用必须自动回退。
用户级灰度入口由 `scripts/release-portal-canary-prod.sh` 安装:稳定 Portal 保持 8081
候选 Portal 使用 18081,候选 goosed 使用 18015,身份路由器使用 18080。灰度身份必须
使用不可变用户 ID、唯一用户名或微信绑定账号标识;禁止按显示昵称(包括“唐”)匹配。未命中、
身份解析失败或候选不可用必须自动回落稳定版本。
本地路由契约见 [docs/release-canary-103.md](release-canary-103.md),对应测试为 `release-gate/canary-routing.test.mjs`。该测试通过只代表身份匹配契约通过,不代表 103 已具备灰度入口。
完整契约和回滚入口见 [103 用户级灰度发布](release-canary-103.md),对应测试至少包括
`release-gate/canary-routing.test.mjs``memind-canary-proxy.test.mjs`
`release-gate/release-script.test.mjs`。测试通过不代表已经部署;103 灰度仍须绑定同一
commit、artifact 和 Gate report,并完成备份、只读预检及人工批准。
`scripts/release-portal-runtime-prod.sh` 会替换 8081 live 目录。它只用于同一候选完成灰度
验收后的全量晋升;在晋升证据校验完成前继续硬阻断非 dry-run,不能拿灰度入口替代晋升批准。
`scripts/release-portal-runtime-prod.sh` 必须按以下顺序执行:
+66 -8
View File
@@ -1,13 +1,71 @@
# 103 用户级灰度发布设计
# 103 用户级灰度发布
当前 `scripts/release-portal-runtime-prod.sh` 是整包替换脚本,不能用于“只让 john 和精确微信用户唐”灰度。因此在闸门通过前禁止调用它进行生产发布
Portal 的首个生产动作必须是用户级灰度,不能直接整包替换稳定版本
候选版本必须与稳定版本并行运行,由入口代理依据登录后的不可变身份选择版本:
## 运行拓扑
- `john`:按唯一用户名或数据库用户 ID 匹配。
- 微信用户“唐”:按绑定的不可变用户 ID `wx_ul610et8` 匹配;禁止按昵称匹配,避免与其他同名用户串流。
- 未命中、身份缺失、解析异常:全部回落到稳定版本。
| 组件 | 地址 | 说明 |
|---|---|---|
| 稳定 Portal | `127.0.0.1:8081` | 保持当前 live 目录和进程,不在灰度发布中替换 |
| 灰度路由器 | `127.0.0.1:18080` | 105 的 `19081` 反向隧道在全部检查通过后切到这里 |
| 候选 Portal | `127.0.0.1:18081` | 独立、无源码 runtime;使用被动运行时模式 |
| 候选 goosed | `127.0.0.1:18015` | 独立容器并挂载候选 runtime,不重建稳定 9 个容器 |
候选版本必须使用独立监听端口和独立 runtime 目录,但共享经审计的持久数据数据库连接;灰度入口必须支持 SSE/附件/公开页请求的完整转发,并保留稳定版本回退。
候选 Portal 通过 `MEMIND_PORTAL_H5_ROOT` 共享经审计的持久数据数据库,但
`MEMIND_RUNTIME_ROLE=candidate` 会禁止重复启动 MindSpace、Plaza、提醒、订阅续期和
Agent 恢复扫描等单例后台任务。
上线前必须在本地证明:命中用户只到候选版本、其他用户只到稳定版本、同名非目标用户不命中、身份解析失败回落稳定、候选不可用自动回退。生产发布仍需在 `main` 干净提交、187 场景全通过并获得单独的生产灰度确认后执行;本设计不会触发生产动作。
## 身份和回落规则
- `john`:按数据库中的唯一用户名或用户 ID 匹配。
- 微信用户“唐”:只按绑定微信身份的唯一账号标识 `wx_ul610et8` 匹配;该值在生产库中是唯一 `username/slug`,不是显示昵称。
- 显示昵称永远不参与路由。
- Cookie 会话通过 `h5_login_sessions` 的 token hash 只读解析。
- 微信回调通过 `FromUserName``h5_user_wechat_identities` 只读解析到绑定账号,再用唯一账号标识匹配。
- 未命中、无身份、过期/撤销会话、数据库异常:走稳定版本。
- 候选 `/api/status` 不健康:自动走稳定版本。
- SSE、附件、公开页和普通 API 均由同一反向代理转发;微信 XML body 必须原样保留。
## 发布入口
候选必须来自干净且等于 `origin/main` 的完整 `main`CI 成功,并具有与
`.runtime/portal` 完全一致、未过期的 187 项 Gate report。
```bash
bash scripts/release-portal-canary-prod.sh --yes
```
该脚本按顺序执行:
1. 验证源码、分支、远端主线、Gate report 和 artifact hash。
2. 执行 103 只读预检。
3. 上传经过验证的候选包。
4. 创建稳定 live 全量备份和持久目录备份,并校验 SHA256、gzip 和关键目录。
5. 启动独立候选 goosed、候选 Portal 和灰度路由器。
6. 验证不可变身份配置、稳定/候选健康和路由器诊断。
7. 短暂阻止新 Agent Run,排空活动任务。
8. 只把反向隧道从 8081 切到 18080。
9. 主动停止候选,证明目标身份会自动回落稳定版,再恢复候选。
任一步失败都会把隧道恢复到稳定 8081,停止候选服务并保留候选目录和备份证据。
## 回滚
```bash
bash scripts/rollback-portal-canary-prod.sh --yes
```
回滚只执行以下动作:
- 把 105 反向隧道恢复到稳定 Portal 8081。
- 停止灰度路由器、候选 Portal 和候选 goosed。
- 清除发布排水标记。
- 验证稳定 Portal 和 105 隧道健康。
不会删除候选目录、备份或稳定运行目录。
## 全量晋升
`scripts/release-portal-runtime-prod.sh` 仍是整包替换脚本,不能把“灰度路由器已实现”
解释为自动获得全量切换权限。全量晋升必须使用同一 commit 和 artifact,保留灰度验收
证据,并再次取得明确批准;在晋升入口完成对应证据校验前,该脚本继续硬阻断非 dry-run。
+5 -3
View File
@@ -45,13 +45,15 @@ npm run check:mindspace-public-links
## 2. Portal runtime 发布唯一流程
生产发布守门员落地、完整 Gate report 通过且获得本次明确人工批准后,只使用:
生产发布守门员、完整 Gate report 本次明确人工批准全部满足后,首次生产动作只使用:
```bash
bash scripts/release-portal-runtime-prod.sh --yes
bash scripts/release-portal-canary-prod.sh --yes
```
生产 `103` 禁止使用 `--skip-tests` 或任何范围绕过变量。本文历史记录中的旧绕过命令只用于事故复盘,不构成当前发布授权。
生产 `103` 禁止使用 `--skip-tests` 或任何范围绕过变量。整包替换脚
`release-portal-runtime-prod.sh` 只用于同一候选完成灰度验收后的全量晋升,在晋升证据
校验完成前仍会硬阻断。本文历史记录中的旧绕过命令只用于事故复盘,不构成当前发布授权。
这个脚本必须完成这些动作: