从孤儿 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]>
26 KiB
隔离备份恢复演练设计
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 读取,不执行任何远端生产写操作;开发机不接收真实备份。
环境选择
| 方案 | 数据移动与风险 | 对生产影响 | 结论 |
|---|---|---|---|
| ARM 一次性影子恢复 | 备份已在 ARM,只在同机复制到私有临时区;明文恢复范围最小 | 无生产容器操作 | 采用 |
| 开发机本地 Docker | 需要把真实 DB/附件下载到个人开发机,扩大敏感数据副本 | 无运行影响,但数据治理风险高 | 不采用 |
| 腾讯云同机影子 Compose | 不需跨机下载,但会在真实生产宿主创建容器、目录和资源负载 | 生产宿主存在误操作和资源竞争风险 | 不采用 |
ARM 在本设计中仍是备份与恢复演练节点,不是生产部署目标。一次性 PostgreSQL 不发布端口;本轮不启动 OA server。
数据流与信任边界
ARM 正式日归档(只读)
│ 选择最新 complete 批次并校验
▼
本轮 0700 临时副本
├── database.dump ──> 一次性 PostgreSQL 16 影子库 ──> migration/表计数检查
└── storage.tar.gz ─> 本轮空附件目录 ───────────────> 文件数/引用/哈希检查
│
▼
仅数量与状态的脱敏证据
正式归档不被解包、改名、删除或直接用作写入目标。临时副本、影子数据库和恢复附件目录都由本轮唯一 run id 标识,清理只允许命中该标识。
恢复点合同
- 从正式日归档中按 manifest
createdAt选择最新批次,而不是按目录 mtime 猜测。 - 候选目录只能包含三个普通文件;manifest 必须为 schema v1、
complete,并通过 run id、文件名、字节数和 SHA-256 自校验。 buildSha必须唯一解析为本仓库已知提交,并且是演练开始时公网/ready实测当前构建的祖先或相同;同时保留 source image digest 是否存在的布尔证据,不输出完整 digest。相等状态仅用于度量发布后何时形成同源恢复点,不再决定原始备份是否可恢复。- 演练开始时批次年龄不得超过 24 小时。候选不合格直接
HOLD,不自动降级到旧批次。 - 临时副本复制完成后再次验证三文件和 manifest,证明恢复输入与正式归档一致。
目录身份校验只适用于 ARM 正式归档源目录:其 basename 必须与 manifest runId 配对。临时副本允许使用本轮固定私有名称;对临时副本必须继续验证 manifest 合同、run id 格式、三文件集合、字节数、SHA-256、构建和时间,并与源目录首次校验得到的脱敏元数据完全一致,但不得要求临时目录 basename 冒充正式归档名称。
隔离恢复合同
数据库
- 使用随机一次性容器名和含
drill的数据库名;容器无ports,不加入现有业务网络,不挂载生产或 ARM 归档目录。 - 使用 PostgreSQL 16,与现有恢复基线和脚本一致。
- 调用
scripts/restore-db.sh,同时满足RESTORE_SCOPE=shadow、精确数据库确认和显式 Docker 容器身份。 - 恢复后只运行
SELECT:migration 成功/失败状态、固定关键表行数和附件引用聚合。不执行 Prisma migrate、seed、DDL 或修复语句。 - 从 manifest 来源提交动态取得 migration 目录名称集合,与恢复库成功且未回滚的
_prisma_migrations.migration_name做精确集合核对;不硬编码数量。再静态比较来源提交与当前构建的 migration 集合,记录“相同”或“当前有待升级 migration”。
固定 12 类表为:部门、员工、员工角色、采购申请、报销单、发票池、付款批次、合同、项目、采购附件、报销附件、付款回单附件。本次已知来源和当前构建现场均为 65 个 migration,但执行器必须使用动态名称集合,不能把 65 写成长期断言。
附件
- 调用
scripts/restore-storage.sh恢复到不存在或为空的本轮目录。 - 恢复前验证 tar 可读、无绝对路径、无
..、无软/硬链接;恢复后比较普通文件数。 - 对使用本地存储的发票、采购、报销、付款回单和请假附件记录,只在影子库内读取
storage_path、大小和哈希用于机器校验,输出仅保留总数和不一致数。 s3:引用不属于当前 tar 恢复范围;若最新备份中出现则FAIL并登记类别,不尝试访问外部对象存储。
RTO/RPO 口径
RPO = drill_started_at - manifest.createdAt。它表示使用该最新批次恢复时最多回退的数据时间窗口。data_restore_RTO = all_data_checks_passed_at - restore_copy_started_at。起点位于全部只读前置门禁通过后、复制正式批次到临时工作区之前;终点为 DB、附件及关联检查全部通过。data_restore_RTO不只输出总秒数,还必须按 runner 的实际串行边界输出八个非负整数阶段:batch_copy、postgres_container_start、dump_list、target_confirmation、restore_apply、database_verify(migration 与 12 类表)、storage_attachment_verify(附件恢复、文件数与业务关联)和final_integrity(源/副本末次身份复核)。总数应与八段之和一致;若因秒级取整存在误差,差值必须为0,不另设无法归属的隐藏开销。- 阶段计时以 runner 内连续边界为准:每一段结束时立即关闭本段并开始下一段。最长 60 秒的 TCP 目标确认轮询只计入
target_confirmation;1 秒轮询粒度是当前演练器实现成本,不与实际 restore apply 混算。失败时已完成阶段输出实测秒数,当前失败阶段输出从其起点到失败的秒数,未运行阶段为NOT_RUN。 - 阶段分解用于区分批次复制、容器与演练器等待、数据库/附件实际恢复及一致性校验成本;它们仍共同属于“数据恢复演练 RTO”。本轮未启动 OA 应用、未验证登录或业务 smoke,因此总数和任何阶段都不得称为完整 OA 灾难恢复 RTO,也不形成 SLA。
same_build_recovery_point_lag的正式口径需要可审计的发布完成时刻与 ARM 原子归档完成时刻;当前证据不具备后者,因此不得输出精确正式值。内部诊断可记录manifest_creation_after_contract_dropin = manifest.createdAt - automatic-backup service 构建 drop-in 最大 mtime,但必须明确它既不是应用发布时刻,也不是 ARM 到达时刻。首次严格构建门禁失败仍证明发布后存在一段没有当前构建同源恢复点的窗口;该画像独立于数据 RPO 和数据恢复 RTO,即使恢复没有开始也保留。- 前置访问准备、等待人工输入和任务文档整理不计入 RTO;镜像拉取、批次复制、实际恢复和验证命令计入。
- 本轮不启动应用,因此不得称为“完整 OA 恢复 RTO”;生产
/ready只证明演练未影响在线服务。
状态判定
GO:AC1–AC7 全部满足,可证明最新成对数据备份按其来源 schema 可恢复;若来源与当前 migration 集合不同,必须同时标注当前 schema 升级未验证,不能宣称完整灾难恢复 GO。PAUSE:访问权限、磁盘空间、Docker/PostgreSQL 16 运行条件或清理验证不足,但未发现备份内容损坏。NO_GO:最新批次超龄/损坏、来源提交未知或不是当前构建祖先/相同、DB restore 失败、来源 migration 集合或关键表异常、附件缺失/哈希不一致、DB 与附件不配对,或生产受到影响。
重试不得覆盖首次失败证据。若修正环境性问题后重试,必须使用新 run id 并保留首次脱敏 finding。
Attempt 1 后的只读身份探针
首次恢复入口因最新 manifest build_mismatch 失败关闭后,不直接重装恢复入口。该失败证据保留,但严格相等门禁已被证明会在发布后自然备份到达前产生必然假阻断。后续先安装一个固定无参数、运行后自删除的 ARM root-owned 只读探针,权限仅用于读取 receiver 的路径配置和最新正式日归档:
- 按 manifest
createdAt选择最新正式日批次,不自动改选旧批次。 - 只输出 manifest 构建短标识、与当前生产短标识的相等状态、批次年龄、run id/目录配对状态、source image digest 是否存在,以及两份产物的文件类型、大小和 SHA-256 聚合校验状态;祖先关系在本地仓库只读验证,不要求 ARM 持有 Git 历史。
- 不输出完整 run id、完整 digest、完整路径、字节数、文件名之外的业务信息或原始 manifest。
- 不复制、不解包、不启动 Docker、不读取数据库内容或附件内容,不修改正式归档。
- 成功或失败均删除探针自身和对应 sudoers;普通用户暂存文件在 SHA-256 精确匹配后另行清理。
探针结果不能替代恢复验收。最新 manifest 与当前生产不相等时,先判断其是否为当前构建祖先:是祖先且批次完整、新鲜时可进入原始恢复重试审批,同时记录同源恢复点滞后;未知或非祖先才维持 NO_GO 并转入备份链调查。手工触发生产备份始终是可撤回备选项,不是完成项。
Attempt 3 后的数据库恢复诊断合同
Attempt 3 只留下 database_restore_failed 聚合类别,失败清理同时删除了权限 0600 的私有原始日志。删除潜在敏感日志是正确安全边界,但下一轮 runner 必须在清理前生成只含 allowlist 状态的阶段诊断,不能再次盲重试。
- 数据库恢复拆成
dump_list、target_confirmation、restore_apply三个独立阶段;每个阶段只输出PASS、FAIL或NOT_RUN,并保留稳定错误类别,不输出 pg_restore 原始 stderr、对象名、路径、凭据或 SQL。诊断必须在失败路径进入清理前写到标准输出,清理仍由 trap/finally 等价路径保证执行;输出先于清理不表示保留私有原始日志。 dump_list使用与实际恢复相同的 PostgreSQL 16 容器和临时副本,只解析 custom dump 目录,不执行数据库写入。失败时分类为dump_list_failed并停止。target_confirmation只确认当前数据库名等于含drill标记的本轮目标,并确认容器连接可用;失败时分类为restore_target_confirmation_failed。- 只有前两项通过才允许调用现有
restore-db.sh。调用必须记录执行路径枚举DOCKER_STDIN或HOST_FILE、脚本退出码和真实 pg_restore apply 退出码;ARM runner 固定使用DOCKER_STDIN,两条路径都由本地集成回归直接驱动仓库中的scripts/restore-db.sh。此后非零分类为restore_apply_failed_after_list_pass,证明 dump 可解析但实际应用失败;它仍不能自动等同于备份内容损坏。 - 对 pg_restore 非零不设置“可忽略对象错误”白名单:即使私有日志末尾可脱敏提取到对象错误计数,任何非零仍为
FAIL/NO_GO。计数只输出非负整数或UNKNOWN,不得据此把部分恢复升级为PASS;未来若要允许部分成功,必须另行列出错误类别、证明对象集合完整并重新批准设计。 - 私有原始日志继续保持
0600且不得复制出 ARM;成功或失败都随本轮 runtime 删除。清理前将三项阶段状态写入最终标准输出,保证失败原因至少可定位到合同边界。 - 合成回归必须分别制造 dump 列表失败、目标确认失败和列表通过但 apply 失败,并断言后续阶段保持
NOT_RUN、输出不含 fixture Secret/路径/SQL、容器与 runtime 清理为零;至少一条注入失败用例必须同时证明诊断在清理前已输出、清理完成后该诊断仍可从调用方捕获。成功回归直接以 PostgreSQL 16 custom dump 驱动真实scripts/restore-db.sh的 Docker stdin 与宿主文件两条路径,不重写等价恢复逻辑。 - 该修复只改变演练器诊断能力,不放宽正式归档、源/副本身份、24 小时新鲜度、shadow 数据库、无端口、附件或清理门禁。任何 Attempt 4 实际恢复仍须使用新 run id、重新探测最新批次并另行批准。
Attempt 4 前的 runner 持久化门禁提案
个人 .trellis/workspace/ 中的 runner 和回归不会提交,不能作为 Attempt 4 的可重复证据。该限制不接受;实际恢复前应在另行批准的本地代码阶段完成以下持久化,不与 ARM 探针或恢复操作混在一起:
- 将当前一次性恢复 runner 收敛为受版本控制、只含占位符和校验逻辑的
scripts/qa/offhost-shadow-restore-runner.template.sh,将附件关联校验器作为scripts/qa/validate-offhost-shadow-restore-attachments.py。Attempt 4 候选必须由合同脚本使用私有 JSON 参数机械渲染,并与现有scripts/restore-db.sh、scripts/restore-storage.sh一起打包;不得从聊天或个人 workspace 重新生成等价实现。 - 新增
scripts/qa/run-offhost-shadow-restore-runner-contract.sh,直接校验候选 runner 的 Bash 语法、依赖 SHA-256、固定无参数入口、源/副本身份重绑、动态 migration 集合、三阶段脱敏诊断、退出码、失败先诊断后清理及敏感输出边界。 - 合同脚本直接以 PostgreSQL 16 custom dump 驱动真实
scripts/restore-db.sh的 Docker stdin 和 host-file 两条路径,并覆盖 dump-list 失败、目标确认失败、apply 非零、后续阶段NOT_RUN和零残留。测试失败不得上传候选。 - 远端地址、现场路径、远端账号、一次性容器/数据库命名、timer、构建前缀和 migration 集合指纹均不进入模板;由私有 JSON 提供并按字段类型、字符集和安全命名规则验证后替换。渲染器拒绝额外/缺失字段、换行、shell 语法、非绝对或逃逸路径和错误摘要格式,不从不受信环境变量覆盖。
- 私有 JSON 和渲染后的现场候选只能位于仓库外,或位于仓库内专用的
/.restore-drill-private/ignored 目录。渲染器必须对输入和输出分别执行 Git ignore 判定;任一路径在仓库内但未被忽略时,在创建候选前失败关闭,不能依赖人工避免git add -A。 - 权限
0600的私有原始日志只允许初始化时截断一次;容器启动、dump list、目标确认、restore apply 和附件恢复各写固定阶段标记并使用追加模式。这样后续阶段失败时仍能在本轮 runtime 内交叉核对之前阶段,最终清理和脱敏输出边界不变。 - 上传前由合同脚本对实际候选文件生成/校验 SHA-256;ARM 安装前再次校验同一摘要。证据记录合同脚本提交、候选摘要和 PASS,不记录地址、路径或业务数据。
Attempt 4 后的 PostgreSQL 稳定就绪修订提案
Attempt 4 在 dump_list=PASS/0 后、实际 restore apply 前以 restore_target_confirmation_failed 失败关闭。只读环境差异调查确认:runner 以第一次 pg_isready 成功作为容器就绪,而合同夹具因曾观察到 PostgreSQL 首次初始化临时服务关闭窗口,已改为连续两次真实查询成功;两者没有复用同一就绪实现。该问题按 local_contract_vs_arm_environment_difference_investigation 登记,高置信度根因为 postgres_initialization_readiness_race,不能归因于备份不可恢复。
修订只允许改变跟踪 runner 与其可复跑合同,不修改 scripts/restore-db.sh,不放宽目标身份、归档、shadow、无端口、恢复非零失败关闭或清理门禁:
- 删除 runner 的“首次
pg_isready成功即通过”判定。pg_isready最多可作为诊断,不能成为允许 restore 的充分条件。 dump_list保持不连接目标数据库的独立阶段;其成功只证明 custom dump 目录可解析,不计入数据库稳定就绪次数。target_confirmation在容器内显式通过 TCP 回环127.0.0.1执行真实SELECT current_database(),不用默认 Unix socket。官方 PostgreSQL 镜像的 initdb 临时服务器不监听 TCP,而正式服务器监听容器内回环;因此 TCP 成功是排除临时初始化服务器的结构性判据。--network none不影响容器自己的 loopback,实际 restore 仍可继续沿现有 socket 路径,不修改scripts/restore-db.sh。- TCP 目标确认使用最多 60 次、每次间隔 1 秒的有界窗口作为额外保险。每次查询退出码非零时把连续成功计数重置为
0;查询成功且数据库名精确等于本轮 shadow 目标时累计,只有连续两次成功才PASS;查询成功但名称不匹配立即失败,不继续重试。连续成功不是排除临时服务器的唯一依据,不能移除 TCP 回环判据后单独保留时序启发式。 - 脱敏输出增加
target_confirmation_exit_code、target_name_match=yes|no|NOT_RUN、target_confirmation_attempt_count和target_confirmation_success_streak。退出码取最后一次实际查询的非负整数;未执行为NOT_RUN。错误类别区分TARGET_QUERY_NONZERO与TARGET_NAME_MISMATCH,不得输出查询文本、数据库名、路径、凭据或原始 stderr。 - 稳定就绪函数必须定义在被渲染 runner 中;合同从 rendered runner 提取或直接驱动该同一实现,不得在 fixture 外围另写一套更宽松或更稳定的等待逻辑。fixture 自身如需等待,也必须通过同一实现进入后续真实脚本双路径回归。
- 实施前审计 runner 合同的每个覆盖机制,区分“直接执行跟踪脚本/校验器”“从 rendered runner 提取同一实现”“只做静态结构断言”和“合同自行重写等价逻辑”。最后一类必须消除或登记为明确限制,不能以组件测试通过推断未执行的 runner 编排行为。
- 回归至少覆盖:Unix socket 可用但 TCP 仍失败时不得通过;首次 TCP 查询失败后连续两次成功可继续;一次成功后失败会重置 streak;数据库名错配立即失败;查询持续非零在第 60 次后失败;每条失败路径的 apply 保持
NOT_RUN、诊断先于清理且残留为0;原有 dump、restore 双路径、身份、migration、附件和敏感输出合同继续全绿。 - 禁止用固定长 sleep、增加 ARM 特例、降低连续成功次数、跳过 TCP 回环或名称精确比较、只修改合同夹具来修复。若相同实现的本地合同通过而 ARM 仍在该阶段失败,保留环境差异 finding,依据新退出码和尝试计数继续调查,不现场放宽。
本节已于 2026-08-25 获准仅在本地实施和回归;授权不包含连接 ARM 或执行 Attempt 5。修复通过后仍须提交可追溯变更、重新选择当时最新批次、重跑只读身份探针、重新渲染并核对候选 SHA-256,再单独申请 Attempt 5 实际恢复。
清理与回滚
- 生产无变更,无生产回滚步骤。
- 成功或失败都只删除本轮 run id 对应的一次性容器、临时附件目录和临时批次副本;归档目录、receiver 管理目录和现有监控容器不在清理匹配范围。
- 清理前再次解析容器名和临时根目录;任何对象不满足本轮标识、类型或父目录约束时停止自动清理。
- 清理后确认无本轮容器、宿主端口、临时网络和临时目录残留;失败则结论至少为
PAUSE,交由管理员精确处置。
证据边界
执行证据写入当前 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 |
手工操作清单(实际执行另批)
- 在 ARM 获准管理员会话确认权限、镜像可用性、内存与磁盘余量。读取当时最新正式批次,而非复用 09-08 的摘要;核对 manifest 身份、PG/来源版本、DB/附件配对、字节数/摘要和24小时年龄。批次有歧义、超龄或损坏停止;现阶段没有选定备份,不声称上述前置已通过。
- 生成本轮唯一标识,登记本轮容器与临时卷/目录;容器无宿主端口、network=none、明确 shadow 库名,使用临时凭据且不回显。只读使用归档数据或通过 stdin 输入,不挂生产数据卷、不向容器注入生产数据库连接,不启动 OA/worker,也不执行 migration/seed。
- 容器内 TCP 回环查询 current_database,名称必须匹配本轮影子库;采用已有稳定就绪合同的连续两次成功和有界等待,不能以首次 pg_isready/socket 就绪直接开始。这里只是手工操作的就绪确认,不新增等待框架。失败保留阶段与安全错误后停止。
- 明确指定 DB_TOOL_MODE=docker、DB_CONTAINER、POSTGRES_DB/USER、临时密码、RESTORE_SCOPE=shadow、RESTORE_CONFIRM_DATABASE,调用上述版本的 restore-db.sh。参数从本轮受控会话生成,不从生产 env 复制;私有原始输出仅留受控临时位置。脚本任何非零不忽略、不自动重试,是否源备份损坏需进一步依据,不能由“恢复失败”一句话判定。
- 确认恢复整个命令退出 0;查询来源 migration 集合、失败状态以及原任务规定的12类关键表可读与数量摘要,不输出业务行。数量不是与实时生产一致的证明;恢复成功结论限于该具体批次。不要为了核对计数新增生产表数据读取。
- 数据库通过后可按另获准的范围继续以 restore-storage.sh 恢复配对附件到本轮空目录,复用既有附件关联校验器;检查归档路径安全、文件集合和适用引用/摘要。若只批准数据库,附件明确 NOT_TESTED,不顺手解包。
- 清理前生成短脱敏结果:批次指纹、开始/结束、退出码、DB/附件/应用验证状态、失败阶段和安全错误类别。原始敏感日志按既有受控清理规则处理,不能先删掉唯一诊断再留下“原因未知”。
- 按本轮登记身份精确删除临时容器、临时卷/目录和临时凭据并回查;不删正式备份、不清理旧回滚镜像、不变更监控/timer。目标不明确立即停止清理并报告,不宽泛 prune。未清理完的结果不得写成完全闭环。
结果字典
- DB_RESTORE_PASS / STORAGE_NOT_TESTED / APP_NOT_TESTED:本批次数据库恢复成功,附件和应用仍未验;不是原正式演练整体 PASS。
- DB_RESTORE_PASS / STORAGE_RESTORE_PASS / APP_NOT_TESTED:成对数据恢复及既定一致性检查成功,不证明应用启动、正式切换或完整恢复耗时。
- STOPPED + 实际失败阶段 + 安全错误类别:不自动认定备份损坏;不继续业务发布来掩盖失败。若备份完整性或真正恢复数据步骤报错,优先单独报告并保全恢复点,不将其降为工具债。
- CLEANUP_PENDING:保留本轮目标标识与待清对象,不声明环境已恢复原状。