Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LutgzyJqNhPpZy1nFsJtN3
7.9 KiB
7.9 KiB
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 的门禁路径清单的理由成立。
仍未通过,根子是同一个:
deploy/run-production-recovery.sh第 114 行pg_dump ... --no-owner --no-acl:恢复点不含属主、不含任何 GRANT / 默认权限。第 130 行的还原核对同样--no-owner --no-acl,只数行数,所以「核对通过」看不出权限丢了。- 后果 A(回退):runbook 约 106–114 行照此整库还原后,网页层切到
authenticated/service_role执行查询(frontend/src/lib/db/local-postgres-client-core.ts约 377 行),对所有表无权限,整站请求被拒;四个计数却全部对得上,看不出来。之后以schema_owner跑迁移也会 "must be owner"。 - 后果 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 的测试抓不到以上任何一条。
- 约 351–360 行改属主的 DO 块遍历
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 里:
- 用
npm run test:db同一个 PG17 fixture 建库,按frontend/supabase/migrations/应用到生产当前版本对应的迁移(以docs/operations/production-migrations-20261014.md里「生产已有」的最后一条为止),写入少量虚构数据(每张关键表几行)。 - 用新
run-production-recovery.sh的同款pg_dump参数导出、加密(本地测试密钥),按新还原核对流程还原并比对权限——必须一致。 - 用
rehearse-production-migrations.sh本地模式对这份备份演练全部待上线迁移,必须打出rehearsal_ok=1,且演练库被删除。 - 再按 runbook T3 的整库还原步骤还原到一个新库,以
authenticated读一行成功。 - 全过程命令与输出摘要(只写计数、退出码、
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. 硬红线
- 不点任何生产按钮;不连任何生产主机;所有演练只用本机 Docker 和虚构数据。
- 备份与核对文件里不得写入任何数据内容、密钥、口令。
- 不改 staging 工作流、
gated-paths.txt、is-docs-only-range.sh;生产工作流的改动只限 T1 要求的「恢复点格式版本」检查。 - 改既有断言写三栏。
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 都是首发前提。