Files
Jyotisha/docs/tasks/TASK-release-path-production-fix2-20261014.md
T

7.9 KiB
Raw Blame History

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 都是首发前提。