# [LEARN-20260501-002] 测试 DB 重置脚本吞错误 → silent state corruption → CI 偶发大面积红

## 触发场景

PR #207 的 CI `backend-integration` 在同一份 commit 上**反复失败**（9 个 suite 红 / 63 tests 红，错误是 `organization:create` 403 + `user_role_rel_organization_id_fkey` FK violation）。但**本地全量 366/366 全过**，无法复现。

## 真正根因（CI specific）

`testing/scripts/lib-test-db.sh::reset_test_db_schema` 用 `>/dev/null 2>&1` 吞 SQL 错误：

```bash
# 旧代码
docker exec ... psql -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname='${TEST_DB_NAME}' AND pid<>pg_backend_pid();" >/dev/null 2>&1 || true
docker exec ... psql -c "DROP DATABASE IF EXISTS ${TEST_DB_NAME};" >/dev/null 2>&1
docker exec ... psql -c "CREATE DATABASE ${TEST_DB_NAME};" >/dev/null 2>&1
```

CI runner 是**共享 self-hosted 跑多个 PR**，docker 容器跨 PR 复用。如果上一轮 PR 的 backend 没干净退出（孤儿连接 / 未释放锁）：

1. `pg_terminate_backend` 失败（权限 / 时序）→ 静默
2. `DROP DATABASE` 失败（"is being accessed by other users"）→ 静默
3. `CREATE DATABASE` 失败（"already exists"）→ 静默
4. 后面 `prisma db push --accept-data-loss` 跑在**旧数据库上**
   - 表 schema 被覆盖更新
   - **种子数据残留**——permission/role/rolePermission 仍是上次 PR seed 后的状态
5. 测试启动后混合了旧种子 + 新表结构
   - Administrator 的 rolePermission 可能映射到不存在的 permission id
   - userRole.organizationId 引用的 org 已过期
   - 大面积 403 / FK violation

本地无法复现是因为 dev 机的 `ffws-wt-pg-3502` 容器只服务一个 worktree，没有跨 PR 污染。

## 修法

`-v ON_ERROR_STOP=1` + 不再 redirect stderr：

```bash
docker exec ... psql -v ON_ERROR_STOP=1 -c "SELECT pg_terminate_backend(...);" >/dev/null
docker exec ... psql -v ON_ERROR_STOP=1 -c "DROP DATABASE IF EXISTS ${TEST_DB_NAME};"
docker exec ... psql -v ON_ERROR_STOP=1 -c "CREATE DATABASE ${TEST_DB_NAME};"
```

让 fatal 错误立刻 fail-fast。下次 CI 撞同样问题时直接红 with 明确日志，不再 silent 跑脏库。

## 通用规则（CI 相关）

1. **基础设施 reset 脚本不要吞错**——和 [LRN-20260501-001] 是孪生原则
2. **共享 runner 跨 PR 复用容器是污染源**——任何 setup 步骤都要假设上一轮可能不干净，并显式 fail-fast
3. **本地通过 ≠ CI 一定通过**——尤其涉及共享基础设施时
4. **撞 CI flake 时**先比对：本地容器 vs CI 容器是否独立 / 是否每 PR 重建

## 反诊断陷阱

我最初的假设：
- 假设 1：`ai-tools.api.test.ts:59` 的 `cleanupDatabase(prisma)` 不传 sessionId 触发 `cleanupAllData` 把种子 nuke 掉 → ❌ `cleanupAllData` 注释明确说 "保留种子"，实际只删非内置数据
- 假设 2：改 `cleanupDatabase` 默认用 sessionId fallback → ❌ 实测让本地从 366/366 变 8 suite 红，反而打破依赖旧行为的测试，**已撤回**
- 假设 3：CI runner 容器跨 PR 残留 → ✅ 真因

教训：**测试基础设施 silent error 比"测试逻辑错"更难定位**。如果一个 fail 在 CI 重现而本地 OK，第一直觉应该是"基础设施 reset 是否真的 reset 了"，而不是去看测试代码。

## 后续行动

1. ✅ 修 `reset_test_db_schema` 用 ON_ERROR_STOP
2. ⏳ 评估给 self-hosted runner 加"每 PR 重建容器"hook（更彻底的隔离）
3. ⏳ 给 backend-integration job 加一段"DB freshness check"（CI 启动时验证 `_prisma_migrations` 表为空 / 几张种子表为预期 row count）
