# Phase 路线图

> **module**: ops/backup-and-dr
> **doc_type**: roadmap
> **status**: Active
> **owner**: lijian
> **upstream_docs**: [01-prd.md](./01-prd.md), [02-drill-matrix.md](./02-drill-matrix.md)
> **last_verified**: 2026-05-14
>
> **事实源**：本文档定义"分几个阶段交付 + 每个阶段什么时候算完"。每个 Phase 的退出条件**钉死**到 [02-drill-matrix.md](./02-drill-matrix.md) 的一个具体验收条款，**不允许靠手感判断 Done**。

---

## Phase 1 — MVP：关掉最大窟窿（4 个 DB + Secrets + Temporal + LUKS + 演练扩展）

### 范围

| 维度 | 交付 |
|---|---|
| 4 套应用 DB（FFAI prod/UAT, AIxC prod/UAT） | 接入枢纽，日级 `pg_dumpall --clean --if-exists` |
| Configs & Secrets（应用机 + offsite-1 自身交叉备份）| `.env.*` / `certs/` / `/etc/nginx/sites-enabled/` / pm2 ecosystem.config / 关键 systemd unit 入枢纽；offsite-1 自身 keys + bin 交叉备份到 FFAI prod 机 |
| Temporal | 与应用 DB 同 `pg_dumpall` 一起覆盖；演练显式恢复 Temporal DB + 起 worker 验证 |
| **附件 inventory** | **穷举**全后端 `prisma/schema/**` 中**所有**指向 FS 路径的字段（不限于 platform_tickets）；产出 `docs/ops/backup-and-dr/attachment-inventory.md` 列：模块 / 表 / 字段 / FS 路径约定 / 估算量级。**这是 P2 接入前的必备前置**，漏列一个模块 = 永久静默丢附件 |
| 枢纽 `/backups/` | LUKS 启用 + systemd-cryptenroll/TPM 自动解锁（先验 vTPM 可用性）|
| 演练扩展 | §B 月度浅层覆盖到所有 P1 source |

### 不变量复用

- 完全沿用 [`../10-backup-strategy.md § 3`](../10-backup-strategy.md) SOP：每个 source 补 `(name, dump-cmd, runtime-user)` 三元组 + dispatcher
- 不动 Gitea 枢纽配置（已在 [#310](http://43.130.59.228/FFAIWorkspace/workspace/issues/310) 落地）

### 退出条件（钉死）

✅ [02-drill-matrix.md §A](./02-drill-matrix.md#a--验收条款-a4h-内空白-vm-整体恢复) 跑通一次全部 10 项 ≤ 4h

退出后状态：
- RPO ≤ 24h（受日级 dump 限制）
- RTO ≤ 4h（沙箱实测 + 生产 buffer）
- Issue [#342](http://43.130.59.228/FFAIWorkspace/workspace/issues/342) **不关**，仅切 label `Status/开发中` → `Status/待验收`（等 §A 演练完）→ 然后保留 OPEN 进入 Phase 2

> **状态机豁免说明**：[`docs/standards/14-feature-request-workflow.md`](../../standards/14-feature-request-workflow.md) 完整状态机是 `PRD 中 → 契约设计中 → 开发中 → 待验收`。本模块为运维模块，**无业务契约面**（无 API / data-model / UI / 状态机文档），doc-review 已在 PRD 阶段完成。因此 PRD merge 后直接 `PRD 中 → 开发中`，**跳过** `契约设计中` 状态。后续 ops 模块（如 #339/#338/#332 接入备份）也按此惯例。

### 预估工作量

- 4 source × dispatcher + sudoers + keypair: ~半天 / source
- Secrets dispatcher（tar + rsync，含 offsite-1 自身交叉备份段）: ~1 天
- 附件 inventory（grep schema + 跨模块清点 + 写 inventory.md）: ~0.5 天
- LUKS + systemd-cryptenroll（含 vTPM 可用性验证）: ~1 天
- 演练 §A 跑通（按 T0→T1→T2→T3 顺序）: ~1-2 天
- **总：≈ 1 周**

### 不在 Phase 1

- PITR（pgbackrest）→ Phase 2
- 上传附件 → Phase 2
- CBS 快照自动化 → Phase 3

---

## Phase 2 — 增强：PITR + 附件 + 误删 30min 恢复

### 范围

| 维度 | 交付 |
|---|---|
| **Step 0 dispatcher 白名单扩展** | 各 source 的 sudoers + SSH ForceCommand case 分支增加 pgbackrest 命令（`pgbackrest --stanza=* archive-push`、`backup` 等），与 P1 的 `pg_dumpall` **并存过渡 ≥ 2 周稳定后**才下逻辑 dump |
| 应用 DB PITR | pgbackrest 全套：postgresql.conf 改 `archive_mode=on` + archive_command + stanza-create + 全量+增量+WAL，先 UAT 再 prod |
| 上传附件接入 | **基于 P1 产出的 inventory** 接入枢纽 rsync dispatcher，每个 inventory 列出的模块都要覆盖；P2 启动前 inventory 必须已存在 |
| 附件清扫 job（启动后跑） | 后端模块负责：扫描 inventory 列出的所有引用字段，把指向不存在文件的行打 `attachment_missing` 标记（不删行）。运维只声明约束，由后端实施 |
| 监控告警 | `pg_stat_archiver.failed_count` 增量 + `pg_wal/` 大小阈值 → 接入告警 webhook |
| 演练矩阵 | §C 误删 30min 恢复演练 |

### 高风险约束（PRD 不变量 #7）

> **PITR 配置变更必须先 UAT ≥ 2 周稳定 + 一次演练通过，再上 production**。

具体卡点：

1. **UAT 启用 PITR 后**：观察 ≥ 2 周，每天检查 `pg_stat_archiver` 计数 + 主库 P95 写入延迟基线
2. **沙箱推迟问题补跑**（[`.learnings/2026-05-14-pgbackrest-sandbox.md`](../../../.learnings/2026-05-14-pgbackrest-sandbox.md) Q4/Q5/Q6/Q7）：
   - Q4 repo 拓扑：UAT 上验证"DB 本地 repo + 枢纽 pull"组合工作正常
   - Q5 14 天保留存储估算：拿 UAT 实际 DB 大小线性外推，决定枢纽磁盘是否需扩容
   - Q6 LUKS 性能：UAT 上跑 baseline vs LUKS 对比，确认开销 < 10%
   - Q7 备份损坏检测：把 `pgbackrest verify` 加入 §B 月度 drill 前置
3. **UAT 演练 §C 全通**才允许碰 production
4. 上 production 走 [`deploy-ops`](../../../.agents/skills/deploy-ops) skill；变更 postgresql.conf 视为高风险路径，单独 PR

### 退出条件（钉死）

✅ [02-drill-matrix.md §C](./02-drill-matrix.md#c--验收条款-c误删-30min-内恢复到误删前-10s) 跑通一次全部 6 项 ≤ 30 min

退出后状态：
- RPO ≤ 5 min（WAL 连续推送，受 `archive_timeout=60` 影响）
- RTO ≤ 30 min（误删场景）+ ≤ 4h（整机灾难）

### 预估工作量

- 沙箱推迟问题补跑：~4 天
- UAT 启用 PITR + 2 周观察：≥ 2 周
- 附件调研 + 接入：~3 天
- 监控告警接入：~2 天
- 演练 §C 跑通：~1 天
- production 上 PITR：~1 天（PR + 部署 + 监控）
- **总：≈ 3-4 周（含 UAT 观察期）**

---

## Phase 3 — 自动化：CBS 快照 + 季度全维度 drill

### 范围

| 维度 | 交付 |
|---|---|
| 腾讯云 CBS 系统盘快照 | 5 台机（4 应用 + offsite-1）控制台/CLI 自动快照策略，7 天滚动 |
| 季度全维度演练 | §A 完整演练每季度自动触发（Gitea cron / runner mode）+ 结果自动写 issue |
| 演练失败自动告警 | drill 失败 → 自动开 issue + label `Severity/High` + 通知 |
| `../03-ops-policy.md` 同步 | 备份策略表按本 PRD 结论同步修订（保留时间、加密、PITR、3-2-1 描述；现状写"阿里云 OSS / AWS S3"是历史描述，改为"腾讯云 COS（暂不实际启用，offsite-1 本身已是异地）"） |
| `.agents/skills/db-backup/SKILL.md` 同步 | 更新为 pgbackrest PITR 操作流程；保留 legacy `deploy.sh db:backup` 段作为应急（无 pgbackrest 时的兜底）；新增"误删恢复 runbook"段引用 [02-drill-matrix.md §C](./02-drill-matrix.md#c--验收条款-c误删-30min-内恢复到误删前-10s) |
| `.agents/skills/db-backup/` references | 同步分发到 `.claude/skills/db-backup/` |

### 退出条件（钉死）

✅ [02-drill-matrix.md §B](./02-drill-matrix.md#b--验收条款-b月度自动-drill--3-次无-false-failure) 连续 3 次月度自动 drill 全 ✅，无 false failure

退出后状态：
- 全维度备份机制 + 演练**完全自动化**
- 月度演练失败 → 自动告警，不依赖人工巡检
- 工单 [#342](http://43.130.59.228/FFAIWorkspace/workspace/issues/342) 切 `Status/已关闭`

### 预估工作量

- CBS 快照策略配置 + 检查脚本：~2 天
- §A 自动化（Gitea runner）：~3 天
- 失败自动开 issue + 通知：~1 天
- 文档同步（03-ops-policy.md / 07-env-and-secrets.md / db-backup skill 等）：~1 天
- 等待月度 drill 3 次 ≈ 3 个月
- **总：≈ 1 周工作量 + 3 个月观察期**

---

## 阶段总览

```
Phase 1 (≈1周)        Phase 2 (≈3-4周)           Phase 3 (1周+3个月观察)
─────────────         ──────────────────         ────────────────────────
DB×4 入枢纽            PITR (UAT 2周 + prod)       CBS 快照自动化
Secrets/Configs       附件接入                    季度全维度 drill 自动
Temporal 验证          监控告警                    失败自动告警
LUKS                  演练 §C 误删恢复             文档同步
演练扩展 §B           
                                                  
退出 = §A ✅          退出 = §C ✅               退出 = §B 连续 3 次 ✅
RPO≤24h / RTO≤4h     RPO≤5min                  全自动化稳态
```

---

## 跨 Phase 不变项

无论在哪个 Phase，以下规则始终生效：

1. **任何演练失败 → 禁用对应 cron**（[02-drill-matrix.md §"不通过的处理"](./02-drill-matrix.md#不通过的处理)）
2. **不引入跨云厂商**（PRD §6 共识）
3. **生产改动遵循 production 只读铁律**：所有 PG / 枢纽 / 应用配置变更走 PR → UAT → production（CLAUDE.md §"5a. 生产环境只读"）
4. **每接入新 source 都验证安全屏障**：`ssh ... "ls /"` 应被 dispatcher 拒绝 = `denied: command not allowed`（[`../10-backup-strategy.md § 3.5`](../10-backup-strategy.md)）

---

## 风险触发"提前退出 / 重新评估"的情况

任意一条触发 → 暂停后续 Phase，回到 PRD 重新评估：

- 沙箱推迟问题补跑（Q4-Q7）发现重大未预料行为
- PITR 在 UAT 出现主库写入延迟 P95 翻倍以上的回归
- 任何一次演练 §A / §B / §C 失败原因是"备份机制本身有 bug"而不是"演练脚本 bug"
- 提出人 / 管理层要求扩范围（如要求增加跨云、增加 RPO 要求到秒级等）

---

## 状态汇报锚点

每个 Phase 结束时，必须更新：

- [ ] [01-prd.md](./01-prd.md) 第 9 节"变更与版本"加一行
- [ ] [02-drill-matrix.md "历次演练记录"](./02-drill-matrix.md#历次演练记录) 加一行
- [ ] Gitea issue [#342](http://43.130.59.228/FFAIWorkspace/workspace/issues/342) 评论 Phase 完成 + 演练结果链接
- [ ] [`../10-backup-strategy.md`](../10-backup-strategy.md) 按本 Phase 新接入的 source 补章节
