回填隔离备份恢复演练执行记录 [08-23-isolated-backup-restore-drill]

从孤儿 worktree(artifacts/isolated-worktrees/20260909-ops-preparation、
20260910-reimbursement-payment-integration)合并回填两轮从未提交的真实
执行记录:

- 2026-09-09 ARM 管理员会话完成首次隔离数据库恢复,回执 DB_RESTORE_PASS,
  66 项迁移精确匹配、12 类关键表可读。
- 2026-09-11 第二次配对恢复,AC1/AC3/AC6 勾选完成;对发票池一条历史缺失
  附件做 SHA-256 比对、来源元数据核对、生产/ARM 归档/周检查点全量搜索,
  均未命中,按用户决定非破坏性标记、暂不追查(AC4 仍未过)。

无产品代码改动,无生产/ARM 操作;仅补齐 Trellis 任务文档与既有证据记录,
使 in_progress 状态反映真实已发生的执行历史。

Co-Authored-By: Claude Sonnet 5 <[email protected]>
这个提交包含在:
2026-09-25 19:45:18 +08:00
共同撰写人 Claude Sonnet 5
父节点 b1d25d3c99
当前提交 5be9f44445
共修改 4 个文件,包含 308 行新增和 4 行删除
@@ -1,5 +1,15 @@
# 隔离备份恢复演练设计
## 2026-09-11 简化执行优先
附件失败诊断不改变准入:逐表保留原校验器,额外只输出缺文件、大小/摘要、路径/编码/行格式、非普通文件、外部存储和读取失败等白名单计数,并交叉核对原校验器的总数。不排除测试、归档、留痕或重复引用。所有表核对结束再执行同一失败门禁,避免只知道首张表失败;解包和普通文件数量验证、已完成 DB 验证须在附件失败前写入回执,不被笼统 NOT_TESTED 掩盖。无任何实际路径、记录 ID、文件摘要或内容进入诊断输出。
本轮遵循 PRD 最新收敛:固定摘要的一次性脚本内嵌已发布的 DB/附件恢复脚本和附件校验器,普通账号暂存,管理员在自己的终端输入一次 sudo 密码后执行;校验的是读入内存并将被执行的同一份字节,不采用先校验再以 root 重新读取文件的两次读取方式。没有长期安装器、sudoers 修改或通用 root shell。
仅本轮随机标签的无网络 PostgreSQL 容器、专属卷和私有目录可写;最终 TCP 查询连续成功再进入 restore,避免历史初始化临时服务竞态。来源提交动态冻结迁移名和 SQL 摘要;附件仍复用既有五类引用校验,base64 换行在 SQL 中去除。实际失败输出阶段、退出码和白名单错误类别,不回传原始 stderr。清理先校验本轮标签,身份不匹配不得删除;源归档前后摘要和监控/公网健康另行只读核验。
下文八段 RTO、历史 runner 部署/授权装置保留用于追溯,不作为本轮执行前提。本轮报告总恢复耗时、RPO、阶段和清理结果;应用与其他附件模型的端到端恢复不冒充已覆盖。
## 设计结论
采用“ARM 最新正式日归档 + 本轮私有临时副本 + 无宿主端口的一次性 PostgreSQL 16 + 空附件目录”的数据恢复方案。腾讯云生产只做演练前后公网 `/ready` 读取,不执行任何远端生产写操作;开发机不接收真实备份。
@@ -143,3 +153,36 @@ Attempt 4 在 `dump_list=PASS/0` 后、实际 restore apply 前以 `restore_targ
## 证据边界
执行证据写入当前 task 的 `research/restore-drill-evidence.md`,随后只在现有 OPS7 Runbook 或人工试运行报告中补一条脱敏状态摘要;不新建 `docs/` 平行文档。不得保存主机地址、SSH 别名、完整远端路径、Secret、连接串、真实账号、表内容、附件名或原始日志。
## 2026-09-09 最小人工恢复路径
### 与旧装置的关系
本阶段直接调用已跟踪的恢复脚本,不新增执行 runner、安装器、确定性渲染、sudoers 规则或 RTO 分段平台。现有 shadow/目标名称确认是恢复脚本内置安全条件,不删除、不改参数绕过。原正式演练指标继续保留未验,本阶段不改写五次历史结果。
本地已固定脚本身份:
| 文件 | SHA-256 |
| --- | --- |
| `scripts/restore-db.sh` | `3707d81db02364c5a4917f230ddde721731c04aa66ab746bd23a7bdcd475b95d` |
| `scripts/restore-storage.sh` | `de9e9340e16383d05b9821cdc73b65a0c28fbb2784a5ecb93d70c2e456ca651b` |
| `scripts/qa/validate-offhost-shadow-restore-attachments.py` | `2c2981d0e3f522145ffa1770f5ff519b3ffdfea74267e4d8ea9e30c130f7f0c2` |
### 手工操作清单(实际执行另批)
1. 在 ARM 获准管理员会话确认权限、镜像可用性、内存与磁盘余量。读取当时最新正式批次,而非复用 09-08 的摘要;核对 manifest 身份、PG/来源版本、DB/附件配对、字节数/摘要和24小时年龄。批次有歧义、超龄或损坏停止;现阶段没有选定备份,不声称上述前置已通过。
2. 生成本轮唯一标识,登记本轮容器与临时卷/目录;容器无宿主端口、network=none、明确 shadow 库名,使用临时凭据且不回显。只读使用归档数据或通过 stdin 输入,不挂生产数据卷、不向容器注入生产数据库连接,不启动 OA/worker,也不执行 migration/seed。
3. 容器内 TCP 回环查询 current_database,名称必须匹配本轮影子库;采用已有稳定就绪合同的连续两次成功和有界等待,不能以首次 pg_isready/socket 就绪直接开始。这里只是手工操作的就绪确认,不新增等待框架。失败保留阶段与安全错误后停止。
4. 明确指定 DB_TOOL_MODE=docker、DB_CONTAINER、POSTGRES_DB/USER、临时密码、RESTORE_SCOPE=shadow、RESTORE_CONFIRM_DATABASE,调用上述版本的 restore-db.sh。参数从本轮受控会话生成,不从生产 env 复制;私有原始输出仅留受控临时位置。脚本任何非零不忽略、不自动重试,是否源备份损坏需进一步依据,不能由“恢复失败”一句话判定。
5. 确认恢复整个命令退出 0;查询来源 migration 集合、失败状态以及原任务规定的12类关键表可读与数量摘要,不输出业务行。数量不是与实时生产一致的证明;恢复成功结论限于该具体批次。不要为了核对计数新增生产表数据读取。
6. 数据库通过后可按另获准的范围继续以 restore-storage.sh 恢复配对附件到本轮空目录,复用既有附件关联校验器;检查归档路径安全、文件集合和适用引用/摘要。若只批准数据库,附件明确 NOT_TESTED,不顺手解包。
7. 清理前生成短脱敏结果:批次指纹、开始/结束、退出码、DB/附件/应用验证状态、失败阶段和安全错误类别。原始敏感日志按既有受控清理规则处理,不能先删掉唯一诊断再留下“原因未知”。
8. 按本轮登记身份精确删除临时容器、临时卷/目录和临时凭据并回查;不删正式备份、不清理旧回滚镜像、不变更监控/timer。目标不明确立即停止清理并报告,不宽泛 prune。未清理完的结果不得写成完全闭环。
### 结果字典
- DB_RESTORE_PASS / STORAGE_NOT_TESTED / APP_NOT_TESTED:本批次数据库恢复成功,附件和应用仍未验;不是原正式演练整体 PASS。
- DB_RESTORE_PASS / STORAGE_RESTORE_PASS / APP_NOT_TESTED:成对数据恢复及既定一致性检查成功,不证明应用启动、正式切换或完整恢复耗时。
- STOPPED + 实际失败阶段 + 安全错误类别:不自动认定备份损坏;不继续业务发布来掩盖失败。若备份完整性或真正恢复数据步骤报错,优先单独报告并保全恢复点,不将其降为工具债。
- CLEANUP_PENDING:保留本轮目标标识与待清对象,不声明环境已恢复原状。