merge origin/staging into the release-path branch

Bring in the second review brief for issue 74. Keep the existing progress links and add the fix2 task row.

Co-Authored-By: Grok (agent loop)
This commit is contained in:
jesse-ux
2026-10-14 20:28:40 +08:00
co-authored by Grok (agent loop)
2 changed files with 79 additions and 2 deletions
+3 -2
View File
@@ -334,8 +334,9 @@
| `TASK-upstream-sync7-20261014.md` | [PROGRESS-upstream-sync7-20261014.md](PROGRESS-upstream-sync7-20261014.md) | **上游同步第七轮(BUG-1318 起)**:婚恋评级断语改证据档位、DK「婚姻质量 X/100」改对;KP 宫头到宫头判宫;瑜伽宫主排序去重;api_server 大运年长 365.25;星历路径只核查。不动校正打分。Grok 执行(agent 循环) | 待验收 | `codex/upstream-sync7-20261014`(BUG-1318~1321 已改;BUG-1322 星历路径核查中,未改代码) |
| `TASK-beta-main-path-20261014.md` | `PROGRESS-beta-main-path-20261014.md` | **内测主路径**:BUG-1063 他人报告 worker 读档案(逐列只读授权 + test:db);对话与报告「AI 生成,仅供参考」标识与免责;BUG-1080/283 状态;状态板按事实清账;冒烟清单 `docs/testing/beta-smoke-20261014.md`。与发布单并行 | 第 1 次打回后已修,待验收 | `codex/beta-main-path-20261014` |
| `TASK-beta-main-path-fix-20261014.md` | `PROGRESS-beta-main-path-20261014.md` | **#73 第 1 次打回**:`fca62fda` 内容通过;与 #63 删除的旧报告视图有代码冲突,接受删除即可 | 待验收 | `codex/beta-main-path-20261014` |
| `TASK-release-path-production-20261014.md` | `PROGRESS-release-path-production-20261014.md` | **发布通路 + 生产首发**:产品 10-14 授权改生产工作流(发布 SHA = 最近门禁成功提交 + 其后纯文档);63 个迁移清单与演练脚本、首发 runbook;生产拒绝测试验证码。Grok 不 dispatch 生产工作流 | 第 1 次打回已修,待验收 | `codex/release-path-production-20261014`(BUG-1348、BUG-1349) |
| `TASK-release-path-production-fix-20261014.md` | `PROGRESS-release-path-production-fix-20261014.md` | **#74 第 1 次打回**:发布规则成立;测试写死 Windows 路径(Linux 全红)、主机演练依赖主机 node 且会留下生产数据副本、docs-only 头时回滚被拒;另补 CHECK_RELEASE_GATE 断言与 OTP 守卫规范化 | 待验收 | `codex/release-path-production-20261014` |
| `TASK-release-path-production-20261014.md` | `PROGRESS-release-path-production-20261014.md` | **发布通路 + 生产首发**:产品 10-14 授权改生产工作流(发布 SHA = 最近门禁成功提交 + 其后纯文档);63 个迁移清单与演练脚本、首发 runbook;生产拒绝测试验证码。Grok 不 dispatch 生产工作流 | 第 2 次打回后续修 | `codex/release-path-production-20261014`(BUG-1348、BUG-1349) |
| `TASK-release-path-production-fix-20261014.md` | `PROGRESS-release-path-production-fix-20261014.md` | **#74 第 1 次打回**:发布规则成立;测试写死 Windows 路径(Linux 全红)、主机演练依赖主机 node 且会留下生产数据副本、docs-only 头时回滚被拒;另补 CHECK_RELEASE_GATE 断言与 OTP 守卫规范化 | 已修,第 2 次仍未过 | `codex/release-path-production-20261014` |
| `TASK-release-path-production-fix2-20261014.md` | `PROGRESS-release-path-production-fix2-20261014.md` | **#74 修复单 2(产品选 A)**:恢复点要带属主与权限,旧格式不得回退;演练按备份里的授权比对,并在真实 PostgreSQL 17 上跑通一次 | 执行中 | `codex/release-path-production-20261014` |
| `TASK-beta-safety-net-20261014.md` | — | **内测安全网**:每日备份 + 恢复演练、探活告警、本地错误汇总、日志轮转、按 requestId 查日志。**等发布通路单合入、#58 合入后开工** | 待前置 | — |
| `TASK-beta-capacity-20261014.md` | — | **内测容量**:按用户限流、重计算短排队与对话重试、兑换码当邀请码、staging 10/20 并发压测。**等 #58 合入后开工,压测等 #57 合入** | 待前置 | — |
| `TASK-special-lagnas-native-20261008.md` | `PROGRESS-special-lagnas-native-20261008.md` | **特殊上升点原生计算(线上缺陷)**:sync6 U6 运行时调 PyJHora,生产镜像没有 PyJHora → staging 上 Hora/Ghati/Sree/Indu/Pranapada 全 blocked;原生实现、PyJHora 只作冻结对照、快速门禁止运行时 import jhora;最先做 | 已验收(已部署 staging `795b8845`,BUG-1273) | `codex/special-lagnas-native-20261008`(未上线;部署以 `/api/health` 为准) |
@@ -0,0 +1,76 @@
# TASK · 发布通路修复单 2:恢复点要带权限(issue #74,第 2 次未过后产品选 A)
- 日期:2026-10-14
- 前序:`TASK-release-path-production-20261014.md`、`TASK-release-path-production-fix-20261014.md`
- 被验 SHA:`32aa46b5731a32b9d7760738f51b983ab4dea041`(`codex/release-path-production-20261014`)
- 执行方:Grok(Windows + Docker),同一分支续做(先 merge 最新 `origin/staging`,不改写已推历史)
- **决策记录**:产品负责人 2026-10-14 在 issue #74 选 **A**——**授权扩大范围修改生产备份工具 `deploy/run-production-recovery.sh`**,让恢复点带上属主与权限,并要求一次在真实 PostgreSQL 17 上的端到端演练。原有授权(四个生产工作流)不变;仍不改 staging 工作流、`gated-paths.txt`、`is-docs-only-range.sh`;仍不点任何生产按钮。
## 1. 上一轮结论
已修好:B1(测试跨平台)、主机模式不依赖主机 node、清理失败非零退出并提示手动删库、库名守卫、本地 URL 拒绝查询参数、runbook 第 5 步与脚本输出一致、docs-only 头时回滚不再被拒且原有回滚保护不少、OTP 守卫按规范化 origin 比较、三处 `CHECK_RELEASE_GATE=true` 断言、recovery 用 H 的门禁路径清单的理由成立。
仍未通过,根子是同一个:
1. `deploy/run-production-recovery.sh` 第 114 行 `pg_dump ... --no-owner --no-acl`:**恢复点不含属主、不含任何 GRANT / 默认权限**。第 130 行的还原核对同样 `--no-owner --no-acl`,只数行数,所以「核对通过」看不出权限丢了。
2. 后果 A(回退):runbook 约 106–114 行照此整库还原后,网页层切到 `authenticated` / `service_role` 执行查询(`frontend/src/lib/db/local-postgres-client-core.ts` 约 377 行),对所有表无权限,整站请求被拒;四个计数却全部对得上,看不出来。之后以 `schema_owner` 跑迁移也会 "must be owner"。
3. 后果 B(演练):`deploy/rehearse-production-migrations.sh`
- 约 351–360 行改属主的 DO 块遍历 `pg_type` 里 `typtype IN ('e','c','d')`,`'c'` 包含每张表的行类型,PostgreSQL 对它报 "is a table's row type",`ON_ERROR_STOP` 直接中止;约 332–333 行改序列属主在序列先于所属表出现时也会被拒。
- 备份里没有 ACL,约 316 行还原后权限烟测看到的不是生产真实授权(`has_table_privilege` 不看列级授权),永远过不了,演练打不出 `rehearsal_ok=1`。
- 脚本头约 10 行、runbook 约 70 行说授权「来自备份里原来的授权 + 迁移」,与事实不符。
- 模拟 docker 的测试抓不到以上任何一条。
## 2. 任务
### T1 · 恢复点带属主与权限(`deploy/run-production-recovery.sh`)
- `pg_dump` 去掉 `--no-owner --no-acl`(保留 `--format=custom`)。角色是集群级对象,同一 PostgreSQL 实例内还原时 `schema_owner`、`authenticated`、`service_role` 等都已存在。
- 还原核对改为不带 `--no-owner --no-acl`,以 `POSTGRES_USER` 执行;在原有四个计数之外,**加权限核对**:对 `public` 下每张表比较源库与还原库的
`select grantee, privilege_type, table_name from information_schema.role_table_grants where table_schema='public'`
和列级 `information_schema.column_privileges`、以及 `pg_class.relowner` 的属主名,**三者必须完全相同**,不同即失败、不写 `recovery-baseline.env`。核对结果写进 `*-restore-verify.json` 的 `checks`(只写计数与是否一致,不写任何数据内容)。
- `RESTORE_VERIFIED=true` 只有在权限核对也通过时才写。
- 旧格式(8 月以来、不带权限)的恢复点**不得**再被当作可回退的恢复点:在 `recovery-baseline.env` 写一个格式版本号(如 `RECOVERY_FORMAT=2`),deploy / migrate 工作流读取恢复点时要求版本 ≥ 2,否则拒绝并提示先点 Create production recovery。合同测试锁住。
### T2 · 演练脚本按真实权限工作(`deploy/rehearse-production-migrations.sh`)
- 还原不再加 `--no-owner`(备份已带属主),**删掉**改属主的 DO 块与序列改属主循环;若仍需兜底,只能用 `ALTER TABLE ... OWNER`(对表)且不得遍历表的行类型。
- 权限烟测改为比较「迁移前还原库的授权」与「迁移后」——迁移不应收回任何已有授权;再检查本批迁移新增的授权确实存在(含 BUG-1063 的 `service_role` 对 `chart_profiles` 逐列 SELECT)。退出码含义如实写进脚本头与 runbook。
- runbook 第 5 步加一句:演练结束后执行 `\l rehearse_*` 确认没有残留库。
### T3 · runbook 的整库还原(`docs/operations/production-release-runbook-20261014.md`)
- 还原命令去掉 `--no-owner --no-acl`;写出删库重建的具体命令(只针对 `jyotisha`,`WITH (FORCE)` 前先停 `api`、`web`)。
- 还原后核对除四个计数外,加上与 `*-restore-verify.json` 里权限核对同口径的检查,**并实际以 `authenticated` 身份读一行**(如 `set role authenticated; select 1 from public.profiles limit 1;`)验证网站账号能读。
- 只允许使用 `RECOVERY_FORMAT=2` 的恢复点。
- 回退目标 `7b620c7a` 需要一次成功的手动发布门禁记录:runbook 写明首发前先确认 Gitea 里该记录仍在(runs 列表里能查到);查不到就先停,问产品负责人。
### T4 · 在真实 PostgreSQL 17 上端到端演练一次(必做,不得只跑模拟测试)
在 Grok 本机 Docker 里:
1. 用 `npm run test:db` 同一个 PG17 fixture 建库,按 `frontend/supabase/migrations/` 应用到生产当前版本对应的迁移(以 `docs/operations/production-migrations-20261014.md` 里「生产已有」的最后一条为止),写入少量**虚构**数据(每张关键表几行)。
2. 用新 `run-production-recovery.sh` 的同款 `pg_dump` 参数导出、加密(本地测试密钥),按新还原核对流程还原并比对权限——必须一致。
3. 用 `rehearse-production-migrations.sh` 本地模式对这份备份演练全部待上线迁移,必须打出 `rehearsal_ok=1`,且演练库被删除。
4. 再按 runbook T3 的整库还原步骤还原到一个新库,以 `authenticated` 读一行成功。
5. 全过程命令与输出摘要(只写计数、退出码、`rehearsal_ok`,不写数据内容与任何密钥)写进 `docs/testing/production-migration-rehearsal-20261014.md`。
### T5 · 小项
- `frontend/tests/staging-backend-workflows.test.ts` 约 1157 行:正向路径的 `CHECK_RELEASE_GATE=true` 正则会同时匹配回滚那一行,删掉正向那处仍会绿;把它锚在正向调用(例如带 `>>"$GITHUB_OUTPUT"` 的那一行)。
- 记录:`docs/BUG_HISTORY.md` 新增一条「生产恢复点不含属主与权限,整库还原后网站账号无权限」(状态写 fixed-pending-verify;复发 / 关联写清楚 8 月的恢复点工具),PROGRESS 记录本轮。
## 3. 硬红线
1. 不点任何生产按钮;不连任何生产主机;所有演练只用本机 Docker 和虚构数据。
2. 备份与核对文件里不得写入任何数据内容、密钥、口令。
3. 不改 staging 工作流、`gated-paths.txt`、`is-docs-only-range.sh`;生产工作流的改动只限 T1 要求的「恢复点格式版本」检查。
4. 改既有断言写三栏。
## 4. 验收
- Linux:`python3 -m pytest tests/test_resolve_release_revision.py tests/test_rehearse_production_migrations.py -q` 全绿;Claude 全量 pytest、前端全量 `npm test` 无新增失败;`bash -n` 全部脚本通过。
- `docs/testing/production-migration-rehearsal-20261014.md` 有 T4 的真实 PG17 运行记录:权限比对一致、`rehearsal_ok=1`、演练库已删、`authenticated` 读一行成功。Claude 会核对记录与脚本输出格式一致;必要时要求附上脱敏后的完整输出。
- runbook 每一步与脚本、工作流逐条一致。
## 5. 让步顺序
无。T1–T4 都是首发前提。