# Prisma migrate dev shadow DB 在历史迁移 drift 上失败

## 现象

`npx prisma migrate dev --name xxx --create-only` 失败，报错：

```
Error: P3006
Migration `20260320000731_cleanup_deprecated_models` failed to apply cleanly to the shadow database.
Error: column "assignment_status" of relation "kpi_assignment" does not exist
```

报错的迁移**不是**你刚改的那个，而是一个存量历史迁移。

## 原因

Prisma 创建 migration 时会起一个 shadow 数据库，从零按顺序重放所有历史迁移来做 diff。如果历史迁移里有 drift（某条 migration 引用了后来才存在的列，或者某个对象已经被后面的 migration 改了）—— shadow DB 重放就会挂。

本仓库至少有一条 migration（`20260320000731_cleanup_deprecated_models`）处于这种状态，所有人都复现，和你改了什么没关系。

## 绕行方案

不要尝试修存量迁移（会影响生产部署）。改用两步走：

1. **开发库同步**：用 `npm run db:push`（底层是 `prisma db push`）把新 schema 直接同步到开发库。它不经过 shadow DB、不生成 migration 文件。

2. **手写 migration SQL**：自己在 `backend/prisma/migrations/YYYYMMDDHHMMSS_<name>/migration.sql` 里写建表/改列 SQL。命名规则：`%Y%m%d%H%M%S` + 下划线 + 描述（例如 `20260415000000_add_robot_field_def`）。这份 SQL 会在未来 UAT / 生产部署时被 `prisma migrate deploy` 按顺序执行。

参考本次 P0 的产出：

```
backend/prisma/migrations/20260415000000_add_robot_field_def/migration.sql
```

写 SQL 时注意：
- schema 用 `"robot_manager"` 双引号前缀（项目用 multi-schema）
- 列名用 snake_case（`label_en / sort_order / dict_category / show_in_list`），和 Prisma `@map` 对齐
- 索引名跟 Prisma 的命名约定：`<table>_<col>_key`（唯一）、`<table>_<col1>_<col2>_idx`（普通）

## 什么时候用 db push vs 手写 migration

| 场景 | 选择 |
|---|---|
| 纯本地 dev，不打算提交 | 只用 `db push` |
| 要合并进 feature 分支，将来要部署 | `db push` **同时** 手写 migration SQL 归档 |
| shadow DB 没坏 | 用 `prisma migrate dev` 正常流程 |

## 如何确认 shadow DB 是否坏了

```bash
set -a && . ./.env && set +a
npx prisma migrate dev --name test --create-only
```

如果它在某条和你无关的历史迁移上挂了，就是坏的——走本文方案。

## 相关

- Prisma 官方文档：<https://www.prisma.io/docs/concepts/components/prisma-migrate/shadow-database>
- 本仓库 `CLAUDE.md` 里已经把 `npm run db:push` 列为本地开发的默认启动步骤
