Archived
欠账:CD 接上了,五处「信号全绿而事情没办成」各配了一道机检
入口守卫的字符串比较、install-activities 的 continue、savePackage 的 onConflictDoNothing、 drizzle 只认 max(created_at) 的水位线、/api/health 不传 status —— 五个会静默成功的机制各配机检。 另记六条还欠(无版本可见性 / 无 graceful shutdown / 换版空窗 / activities:install 的样例副作用 / reset-runs 被打进生产 dist / 每版一份 node_modules)。
This commit is contained in:
@@ -1057,3 +1057,46 @@ dubious ownership 之类),`catch` 里印一句「非 git 仓库,跳过比对」
|
||||
`db/init/` 挂进 `/docker-entrypoint-initdb.d`、在**首次起卷**时灌的 —— 换任何一条不经
|
||||
compose 的路子建库,都得自己先灌一遍。`tools/ci.mjs db-create` 已经这么做(灌的是
|
||||
`db/init/` 那份文件本身,不另抄一份)。
|
||||
|
||||
## 2026-08-25 · CD 接上了,五处「信号全绿而事情没办成」各配了一道机检
|
||||
|
||||
**已接**(`.gitea/workflows/ci.yml` 后半段 14 步 + `tools/cd.mjs` 八个子命令 + `docs/部署.md`):
|
||||
门禁全绿 → 前置断言 → 备份 → 确保库在 → 发布到 `releases/<sha>` → 迁移 → 迁移到位断言 →
|
||||
判首次/升级 → 播种或装活动包 → 回查 → 发前端 → pm2 重启 → 探活 → 收尾。
|
||||
部署在 runner 本机(host 模式),路径类配置从 Gitea Variables 读,机密从 Secrets 读。
|
||||
|
||||
接的过程中逐条对上了五个**会静默成功**的机制,每个都配了一道机检:
|
||||
|
||||
| 机制 | 它会怎么骗人 | 机检 |
|
||||
|---|---|---|
|
||||
| `main.ts` 的入口守卫是 `import.meta.url === \`file://${process.argv[1]}\`` 字符串比较 | 经软链启动两边不等 → `boot()` 永不执行 → **进程零输出 exit 0**(实测过) | pm2 一律用 `releases/<sha>` 真实路径起;探活兜底 |
|
||||
| `install-activities` 所有失败路径都是「打印一行然后 continue」 | 一个包都没装成也 exit 0 | 装完回查 `sys.activity_package`(`activities-verify`) |
|
||||
| `savePackage` 是 `onConflictDoNothing`,主键 `(activity_id, version)` | 版本没升就什么都不更新,日志照说「已装」 | 版本没变但产物文本变了 → **当场红**(`activities-plan`) |
|
||||
| drizzle 只取 `max(created_at)` 当水位线,`hash` 列只写不读 | 新迁移的 `when` ≤ 水位线会被永久静默跳过,而 migrate 照样打印「完成」 | 已应用条数 == journal 条数(`migrate-verify`) |
|
||||
| `/api/health` 用 `c.json()` 没传 status | 库挂了照样返 200,只是 body 里 `db:"down"` | 探活验 body(`health`) |
|
||||
|
||||
**还欠**:
|
||||
|
||||
① **无版本可见性**。`/api/health` 的 `version` 是写死的 `CONTRACTS_VERSION = '0.1.0-m1'`,
|
||||
从 M1 至今没动过;全仓没有任何地方注入 git sha(`tsup.config.ts` 无 `define`,`vite.config.ts` 也没有)。
|
||||
部署完只能靠 `releases/<sha>/RELEASE` 文件与 `sys.activity_package.installed_at` 间接确认「新版真上去了」。
|
||||
要补的话最小侵入是 tsup 加一个 `define: { __GIT_SHA__ }` 再塞进 health 的出参 ——
|
||||
但那是契约变更(`/api/health` 还是**免鉴权**的,暴露 commit sha 要先想清楚)。
|
||||
|
||||
② **`main.ts` 没有 graceful shutdown**。`serve()` 的返回值没被接住,HTTP server 从不 `close()`;
|
||||
SIGTERM 直接 `process.exit(0)` —— 在途请求与**所有 SSE 长连接**硬切。
|
||||
换版本时必然掉一次连接(前端 EventSource 会自己重连,所以影响小,但不是零)。
|
||||
|
||||
③ **换版本有几秒空窗**。pm2 是 `delete` + `start`(脚本路径每版都变,`restart` 会留在旧路径),
|
||||
中间那几秒 502。要做到零停机得上两个端口 + nginx upstream 切换,MVP 阶段先不做。
|
||||
|
||||
④ **升级时 `activities:install` 的副作用还在**:它会按主键 upsert 覆盖 8 行样例主数据
|
||||
(含 `CU-0142` 的 `coverage_score`),并发出 8 个 data 事件 —— 生产库里若有启用的 data 触发器,
|
||||
会凭空起 8 张单。当前是靠「版本没变就不装」把它挡在门外,而不是根治。
|
||||
根治要一个「只装包不写样例」的入口,那是改代码。
|
||||
|
||||
⑤ **`reset-runs.js` 会被 tsup 打进 dist 一起发到生产机**(`tsup.config.ts` 里它是显式 entry)。
|
||||
没人调它,但它就在 `apps/api/dist/reset-runs.js` 躺着,一条命令就能 TRUNCATE 33 张表。
|
||||
|
||||
⑥ **每版一份 node_modules**(`pnpm install --prod` 在发布目录里跑,实测 214M)。
|
||||
pnpm 硬链到全局 store,真实占盘远小于这个数,但 `du` 看着吓人。留最近 5 版。
|
||||
|
||||
Reference in New Issue
Block a user