# 四层环境落地 runbook（已归档 2026-05-10）

> **状态**：**已完成 — 归档**
> **归档原因**：D7（test 环境基础设施）已由 PR #272 落地（test 拆到独立机 170.106.161.71，secret 改名 `FFWORKSPACE_TEST_CI_KEY_170_106_161_71`，deploy-test.yml 早已 active）；D8（Gitea 分支保护规则对齐）已基本对齐到当前 yml；C5/C6/C7 启用 checklist 已并入 `docs/ops/02-ci-cd-architecture.md` §10「待办 / 未启用」。
> **下文「当前状态」段落已严重过期**——保留作为 rollout 历史快照，**不要按这份做决策**。
> **当前 CI 实施现状以 [docs/ops/02-ci-cd-architecture.md](../docs/ops/02-ci-cd-architecture.md) 为准。**

---

本文件列出从当前两层（uat / production）扩展到四层（dev / test / uat / production）所需的运维动作，按优先级排序。

> **命名约定（2026-05 重构后）**：`dev` 指工程师本地开发机 / agent-pool / worktree；`test` 指 `43.153.69.73` 上 develop 分支自动部署的 L2 站点。本文档中"test 环境"全部指后者。

---

## D7 — Test 环境基础设施（L2 核心缺口）

### 当前状态
- ❌ 没有 test 服务器
- ❌ 没有 `.env.test` 配置
- ❌ 没有 test 域名（`ffworkspace.test.faradayfuturecn.com`）
- ❌ Gitea secret `TEST_DEPLOY_SSH_KEY` 未配置（或仍用旧名 `DEV_DEPLOY_SSH_KEY`，重构后建议重命名）

### 部署模式：test 和 UAT 共用一台机器（推荐过渡方案）

FFOA 当前体量下，test 和 UAT 共用一台机器是合理选择——UAT 不是 24×7 高负载，test 主要给 PM/AI 看，两者实际负载相加通常不满一台机器。等真出现撞车再拆。

**机器规格估算**

| 档位 | 规格 | 适用 |
| --- | --- | --- |
| 最低 | 4 核 / 8GB / 80GB SSD | 紧凑能跑 |
| **推荐** | **8 核 / 16GB / 200GB SSD** | 留余量给 nightly snapshot 之类的临时 CI 任务 |

**强隔离 5 点（必须遵守）**

1. **独立 Docker compose stack**：不同 `CONTAINER_PREFIX`（`ffws-test` / `ffws-uat`）、不同端口（test: 5000-5091 ；UAT: 7000-7091），互不冲突——具体端口配置已写在 `scripts/deploy/deploy.sh` 的 `ENV_CONFIG` 数组
2. **独立数据库容器**：test 和 UAT 各自独立 PostgreSQL container，**不共享 PG 实例**——test 频繁 reset 时连错库的概率才能降到零
3. **独立 nginx 站点**：`ffworkspace.test.faradayfuturecn.com` 和 `ffworkspace.test.faradayfuture.com` 各走各的 server block，不共享配置文件
4. **`.env.*` 严格分离**：`.env.test` 和 `.env.uat` 都不能引用对方的 PG host/port；`deploy.sh test` 只读 `.env.test`，`deploy.sh uat` 只读 `.env.uat`
5. **资源监控告警**：CPU > 80% 持续 5 分钟、或内存使用 > 90% 触发告警 → 评估拆机器

**拆机器触发条件**

任一发生，立刻规划拆 test 到独立机器：
- PM 反馈 UAT 卡顿（操作 > 2s）
- test 部署导致 UAT 服务重启或回滚
- 机器 CPU 长期 > 80% / 内存爆满
- nightly snapshot check 跑起来 IO 把 UAT 拖慢

### 需要做
1. **采购/复用一台机器**（按上面规格，**推荐 8C/16G**）
2. **安装基础环境**：参考 `scripts/deploy/setup-production.sh`（Docker / Node / PM2 / Nginx）
3. **创建 `.env.test` 和 `.env.uat`**：分别配置
   - `.env.test`：`CONTAINER_PREFIX=ffoa-test`、`POSTGRES_PORT=5002`、`BACKEND_PORT=5001`、`FRONTEND_PORT=5000`、`NODE_ENV=development`
   - `.env.uat`：`CONTAINER_PREFIX=ffoa-uat`、`POSTGRES_PORT=7002`、`BACKEND_PORT=7001`、`FRONTEND_PORT=7000`、`NODE_ENV=staging`
   - 两边都关闭真实邮件/短信发送，避免误发
4. **配置 nginx 双站点**：`ffworkspace.test.faradayfuturecn.com` → 5000+ 端口、`ffworkspace.test.faradayfuture.com` → 7000+ 端口；两个独立 server block；分别申请 Let's Encrypt 证书
5. **Gitea secret 添加**：
   - `TEST_DEPLOY_SSH_KEY` / `TEST_DEPLOY_HOST`（或沿用通用 `DEPLOY_SSH_KEY`）
   - `UAT_DEPLOY_SSH_KEY` / `UAT_DEPLOY_HOST`（如果还没加；可与 test 共用 SSH key + 同一 host）
6. **首次部署**：手动跑 `bash scripts/deploy/deploy.sh test deploy`，再 `bash scripts/deploy/deploy.sh uat deploy`，验证两个站点同时起来且互不干扰
7. **打开 deploy-test.yml 触发**：把 workflow 中注释的 `push: branches: [develop]` 取消注释
8. **数据策略**：
   - test：每周一次自动 reset（cron + `db:seed`），保持数据干净
   - UAT：脱敏快照定期注入（不自动 reset，避免冲掉验收数据）

### 验证标准
- 推一个无害 commit 到 develop → 自动部署到 `ffworkspace.test.faradayfuturecn.com` → smoke test 通过
- PM 能打开 `ffworkspace.test.faradayfuturecn.com` 看到 develop 分支的状态
- **同时**，UAT 站点 `ffworkspace.test.faradayfuture.com` 不受影响（响应时间 < 1s）
- 两个 PostgreSQL container 都在跑，端口不冲突，数据互不可见

---

## D8 — Gitea 分支保护规则对齐

### 当前状态
- `develop` 的必需检查（已合并 quality-gates 后的真实清单）：
  `quality-gates / verify-agent-assets`、`quality-gates / build-check`、
  `quality-gates / migration-file-count`、`quality-gates / contract-check`、
  `quality-gates / backend-integration`（含 L0c 响应快照内联）
- `staging` / `production`：禁止直推但缺细化

### 需要做
登录 Gitea → 仓库 → Settings → Branches → 各保护分支：

**`develop`** — 必需检查（quality-gates 5 个 job + ai-review 转正后第 6 个）：
- [x] `quality-gates / verify-agent-assets`
- [x] `quality-gates / build-check`（已合 lint+typecheck，nest build / next build 自带 tsc）
- [x] `quality-gates / migration-file-count`
- [x] `quality-gates / contract-check`
- [x] `quality-gates / backend-integration`（L1 集成 + L0c 响应快照同 job 内跑）
- [ ] `ai-review / ai-review`（**当前 dry-run 观察期，不阻断**；准确率达标后去 dry-run 再加进 required）

> **注意**：原 `agent-assets-check.yml` 已删，verify-agent-assets 现在是 quality-gates.yml 的第一个 job。
> 原 `lint-and-type-check` 已删，类型检查由 build-check 内部 nest build / next build 自带。

**`staging`** — 必需检查：
- [ ] 人工 approve（必需）
- [ ] `ai-review / ai-review`（前置：dry-run 观察期通过 + 转正）
- 不重跑 L2 的检查（quality-gates 重型 job 已在 yml 里加 `if: base_ref == 'develop'` 自动 skip）

**`production`** — 必需检查：
- [ ] 人工 approve（必需，且 reviewer ≠ 作者；Gitea 设置：require approval from someone other than author）
- [ ] `ai-review / ai-review`
- [ ] `nightly-snapshot-check / snapshot-data-quality`（手动触发后通过即可）

### 验证标准
- 在 develop 上故意推一个 lint 错误 → CI 失败 → PR 不能合并
- 自己开 PR 到 production 自己批 → 被 Gitea 拒绝（require ≠ author）

---

## C5 / C6 / C7 启用前置条件汇总

| Workflow | 启用条件 | 关联 D 项 |
|----------|---------|-----------|
| `deploy-test.yml` | test 服务器就绪 + .env.test + secret + smoke-test.sh 实测 | D7 |
| `nightly-snapshot-check.yml` | 脱敏快照 pipeline + S3 / 解密密钥 + snapshot-loader.sh 实测 | 新增：脱敏快照基础设施 |
| `ai-review.yml` | ✅ `ai-review-runner.sh` 已实现（用 Claude Code CLI）；✅ Runner 主机已装 `claude` CLI 并以 `act_runner` 用户登录（auth 在 `/var/lib/act_runner/.claude/`）；✅ secret `AI_REVIEW_GITEA_TOKEN` 已配置（注：原 `GITEA_API_TOKEN_FOR_AI` 因 Gitea 保留 `GITEA_*` 前缀已改名）；✅ `pull_request` 触发已启用；**当前阶段：dry-run 观察期**（`AI_REVIEW_DRY_RUN=1`，仅日志、不写 Gitea） | 新增：AI Review 基础设施 |

---

## 启用顺序建议

1. **先 D7（test 环境）+ deploy-test.yml** — 解决 L2 最大缺口，立即提价值
2. **再 D8（分支保护规则）** — 保证 CI 真的卡住 PR
3. **再 C7（AI Review）** — 解锁 PR 阶段的 AI 自动化（**当前已进入 dry-run 阶段**：1-2 周观察期，调 prompt + 看 BLOCK 准确率，达标后去 dry-run + 加进 required checks）
4. **最后 C6（脱敏快照）** — 最复杂，前置依赖多，可推迟

---

## Owner

- D7 / D8：运维（admin）
- C5/C6/C7 启用：开发 + 运维联合
- 校验：PM 在 test 环境能看到改动 = 落地成功
