# ERR-20260501-003 — 重提路径 approval engine 端隐藏断裂（PR #208 P0 #131 hidden gap）

## 现象
PR #208 修复 P0 #131 "REJECTED/WITHDRAWN 状态可重新 submit"。前端 + form 端状态机都对：
- 用户点 Resubmit → POST `/api/v1/form-management/instances/<id>/submit`
- API 返回 200 OK，UI 跳转回列表
- form_instances.status: REJECTED → PENDING_APPROVAL ✓

但**审批引擎那条线根本没建立新流程**：
- approval_instances 表里仍只有那一条 REJECTED 的旧记录
- 没有新 approval task 被创建
- 审批人待办列表里看不到这单（重提的实例永远不会进入审批流程）

## 根因
`approval_instances` 表 schema：
```prisma
@@unique([businessType, businessId])
```

每个业务 ID 只能有一个 approval instance。

resubmit 流程：
1. `instance.service.submitInstance` 在事务里把 form status REJECTED → PENDING_APPROVAL
2. 调 `approvalService.startApproval()` → `prisma.approvalInstance.create({...})`
3. 旧 REJECTED 的 approval instance 仍存在 → **`Unique constraint failed on the fields: (business_type, business_id)`**
4. `submitInstance` 的 try/catch（`backend/src/engines/form/form-management/services/instance.service.ts:573-578`）吞掉这个错误：
   ```ts
   } catch (error) {
     this.logger.error(`Failed to start approval workflow: ${error.message}`);
     // 审批流程启动失败，但表单实例已提交，记录错误但不抛出异常
   }
   ```
5. 结果：form 端假装成功了，audit log 也是 SUCCESS，approval engine 是哑的

## 为何 L1 没抓到
`testing/backend/integration/form-management/instance-state-machine.api.test.ts` 的 resubmit 测试用的是 `requiresApproval=false` 的 form definition：
```ts
definition = await createTestFormDefinition(prisma, {
  ...
  requiresApproval: false,  // ← 跳过审批引擎
});
```

resubmit 时根本不会走到 `approvalService.startApproval`，所以 unique constraint 永远不会触发。**测试是绿的，但 production 路径是断的**。

`instance-with-approval.api.test.ts`（B 后续真实审批链路 L1）只测了"首次 submit"，没测 resubmit。

## 修复方向（不在本 PR 范围，是 follow-up）

3 条路：

### 方案 A — startApproval 检测旧终态 instance 时复用 ID 重启
```ts
// 在 startApproval 入口加：
const existing = await prisma.approvalInstance.findFirst({
  where: { businessType, businessId },
});
if (existing && (existing.status === 'REJECTED' || existing.status === 'WITHDRAWN')) {
  // 复用 ID + 重置字段 + 重新启动 workflow（用新 workflowId）
  // 把 endTime/endReason 清空，状态改 RUNNING
}
```
最小改动，但旧 approval_tasks / records 留在那里看着乱

### 方案 B — schema 加 attempt_no
```prisma
@@unique([businessType, businessId, attemptNo])
```
最干净，但 schema 改动 → 必须独立 PR + 迁移；且现有 (businessType, businessId) 索引需重做

### 方案 C — resubmit 时硬删旧 instance（cascade）
最简但最暴力，丢失审计

**短期 + 长期推荐**：
- 短期：方案 A，附 L1 测试锁定（`instance-with-approval.api.test.ts` 加 resubmit 用例，requiresApproval=true）
- 长期：方案 B，schema 演进时配套

## 这个 session 的处置 — **已修复（PR #214）**
采用方案 A 修复：
- `backend/src/engines/approval/approval.service.ts::startApproval` 入口加 `isResubmit` 分支
- 检测到旧 ApprovalInstance（任何非 RUNNING 状态）→ 复用 ID + `update` 重置所有字段（清 endTime/endReason/endComment + status=RUNNING + 新 workflowId 避免 Temporal workflow 重名）
- 不删旧 NodeInstance/Task/Log（保留审计；新 workflow 起来后会产生新的，不冲突）
- L1 测试 `instance-with-approval.api.test.ts` 加 2 用例：REJECTED → resubmit + WITHDRAWN → resubmit，锁定 ApprovalInstance ID 复用 + 状态重置 + 新 workflowId

## 适用范围
任何 form-management 重提流程 + 任何带 `@@unique([businessType, businessId])` 的 approval engine 业务关联表。
