文件
hyb-oa/.trellis/tasks/archive/2026-07/07-27-offhost-backup-final-fixes/prd.md
T
wangsli cf63d3cb1b
ci-e2e / e2e (push) Waiting to run
ci-minimal / check-minimal (push) Waiting to run
归档异机备份最终收尾任务(07-27-offhost-backup-final-fixes)
2026-07-27 18:08:44 +08:00

7.3 KiB

异机备份最终收尾修复

Goal

在不扩建运维平台的前提下,完成自动异机备份最后一轮有界修复:让生产热路径和 Docker 备份/恢复入口不再把数据库凭证放入进程 argv;让紧急恢复卡与唯一系统管理员的事实一致;让 ARM finalize 在被强杀后能够安全恢复明确的孤儿事务,并在歧义状态下保留全部现场、失败告警而非静默停摆。

Background

  • origin/main 与当前生产活动构建均为 a0006f0;2526052 已推送,生产已有 62 项成功 migration,手机号 nullable migration 不需要重复部署。
  • 当前待推送提交只有 de35ab8、95b51e6、f1e8cf1;本任务完成前不单独推送,避免本地与远端继续形成多个收尾批次。
  • scripts/run-automatic-backup.sh:344 使用 env KEY=value 调用成对备份,完整 DATABASE_URL 和数据库口令会进入 env 进程 argv。
  • scripts/backup-db.sh:74、scripts/restore-db.sh:101、scripts/restore-db.sh:113 使用 docker exec -e PGPASSWORD=value,其中 nightly 自动备份会经过 backup-db.sh,因此只修 wrapper 不能关闭热路径暴露。
  • 生产 Docker 20.10.24 已用无敏感合成变量只读实测:VAR=value docker exec -e VAR ... 能把客户端环境值继承给容器命令;测试只记录 inherited=yes,未读取或输出数据库凭证。
  • deploy/trialrun/runbook.md 的紧急恢复卡仍写“复制到公司知识库”和“第二名内部人员”,与已经批准的唯一系统管理员口径矛盾。
  • receiver 已有 flock 和正常错误回滚,但 SIGKILL 无法执行 shell 清理;强杀窗口可能留下 .daily-replacement-*、.new.* 或 .rollback.*,下一次 finalize 当前没有启动恢复逻辑。

Scope

  • 修复 wrapper 与三处 Docker DB 工具调用的凭证 argv 暴露,并增加合成回归断言。
  • 增加 receiver 锁内日归档/周检查点孤儿事务恢复及 fail-closed 测试矩阵。
  • 同步单管理员紧急恢复卡和部署规约;完成本地验证、提交与后续受控部署后归档运维主线。

Requirements

  • 四处已确认的凭证调用必须改为环境继承,敏感值不得出现在调用命令 argv:wrapper 在隔离子 shell 中导出所需变量后调用 capture_command;Docker 入口使用 PGPASSWORD="$POSTGRES_PASSWORD" docker exec -e PGPASSWORD ...,不得使用 env DATABASE_URL=... 或 -e PGPASSWORD=...。
  • 契约测试中的 env KEY=value 是合成夹具,不含真实凭证,不做无意义的批量改写;新增断言必须使用合成标记证明敏感值不出现在 mock 捕获的 argv。
  • 紧急恢复卡标题、负责人和季度动作必须统一为单管理员口径:恢复卡保存在现有 runbook,由唯一系统管理员维护并按季度在 ARM 隔离目标执行影子恢复;不虚构知识库副本或第二人门禁。
  • finalize 必须在取得现有独占锁之后、处理本次 partial 之前,扫描严格命名的日归档和周检查点孤儿事务;正式点和 rollback 只有在普通目录、非符号链接、位于受控根目录且通过 manifest/文件集/大小/SHA-256 自校验后才可作为权威来源。.new 永远不是权威来源,只能在严格路径/类型校验且另一个有效权威来源已经确定后清理。
  • .daily-replacement-* 的判定必须遵守 design.md 的日归档状态表:孤儿与有效当日归档同时存在时删除孤儿;孤儿存在但当日归档缺失时从孤儿 manifest 还原原归档名;当日归档校验失败、存在多个候选或任何身份歧义时 fail closed,两个对象均保留。
  • 周检查点 .new.*/.rollback.* 必须遵守 design.md 的对称状态表:rollback 在正式周检查点缺失时优先恢复;正式周检查点有效时临时对象才可清理;只有 staging 且正式点缺失时,必须先找到同 ISO 周唯一、有效的日归档来源再重建;歧义或校验失败一律保留现场并失败。
  • 自动清理必须逐目标执行严格路径、类型和命名校验;不得用宽泛 glob 删除,不得触碰正式日归档、正式周检查点、手工批次或最后一个有效恢复点。
  • orphan 歧义导致 receiver 非零时,源端必须继续在 offhost_finalize 阶段失败、保留源批次和 0600 诊断并走独立群机器人告警;ARM dead-man 继续在超龄窗口独立告警。本任务复用已真实验证的链路,不再人为制造第二次真实断链。
  • 恢复卡复制到 ARM 和离线副本属于可选纯运维缓解项,不是代码验收阻断;任何 ARM 写入、推送、生产部署或真实备份执行仍需另行批准。
  • 本任务完成后硬收口运维加固,不继续新增备份平台、告警能力、保留层级或恢复自动化,后续返回产品 backlog。

Acceptance Criteria

  • 合成 argv 测试证明 wrapper、nightly Docker 备份、Docker 影子确认和 Docker 影子恢复四条路径的命令参数均不包含合成密码或完整连接串,同时容器命令仍收到 PGPASSWORD。
  • run-automatic-backup.sh 不再出现生产路径 capture_command ... env DATABASE_URL=...;backup-db.sh/restore-db.sh 不再出现 docker exec ... -e PGPASSWORD=value。
  • 恢复卡正文、标题、负责人和季度动作与《管理员手册》单管理员口径一致,且仍保留真实生产恢复的单独批准门禁。
  • 日归档测试覆盖“孤儿+有效正式点则清理孤儿”“孤儿+缺正式点则恢复旧点”“孤儿+无效正式点则两个都保留并失败”“重复/非法/链接候选则失败且不删除”。
  • 周检查点测试覆盖 .new、.rollback、正式点存在/缺失、可唯一重建和歧义失败;任何成功恢复后只保留一个正式周检查点,任何歧义失败后现场对象不减少。
  • orphan fail closed 通过本地合成链路证明 finalize 非零、源端不写成功状态、不执行源端保留清理且诊断为 0600;不重复执行真实企微断链验收。
  • 现有同日取代、失败回滚、7 日 + 4 周保留、dead-man、备份/恢复往返、环境合同和敏感扫描均无回退。
  • task、Trellis governance、文档注册/流程合同和 git diff --check 通过;只有经确认的任务文件进入提交。

Out of Scope

  • 不新增数据库 migration,不重复部署 2526052,不修改生产业务数据。
  • 不重测已经通过的真实企微失败、重复降噪和恢复通知链路。
  • 不自动执行恢复,不删除 ARM 正式归档或手工批次,不改变 7 日 + 4 周保留策略。
  • 不改写仅含合成值的测试夹具 env 调用,不做全仓无关安全重构。
  • 不在本任务直接推送、部署、更新 ARM 脚本或复制恢复卡;这些动作分别保持外部门禁。
  • 不继续新增运维能力;本任务之后不因本轮审查再派生新的运维加固 task。

Test Plan

  • Shell 语法检查和 bash scripts/qa/run-automatic-backup-contract.sh,扩展 mock argv/环境继承与 orphan 恢复矩阵。
  • bash scripts/qa/run-backup-restore-drill.sh,覆盖 Docker 备份与影子恢复入口无回退。
  • node deploy/trialrun/validate-env-contract.js。
  • python3 ./.trellis/scripts/task.py validate .trellis/tasks/07-27-offhost-backup-final-fixes。
  • Trellis governance、敏感扫描、文档注册、流程合同和 git diff --check。