fix: ADR-003 cancel_requested 信号语义 + BullMQ retry 范围 + CreateJobInput 补全
- §1.1.1: 明确 cancel_requested 不是独立生命周期状态,是 running 上的信号字段 - §5.5: Engine 内部 retry(业务级)vs BullMQ retry(进程崩溃)的分工表 - §2.4: CreateJobInput 对齐 #289 规格(新增 triggerType/parentJobId) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
88b35d0d18
commit
8b6c7cef72
@ -48,6 +48,31 @@ M-AI-03 的目标是在 M-AI-02 Schema Expand 之上,纯代码层实现统一
|
||||
| `failed` | 执行失败且重试耗尽 | 是 | `AiJobLifecycleRepository.markFailed()` |
|
||||
| `cancelled` | 用户/系统取消 | 是 | `AiJobLifecycleRepository.markCancelled()` |
|
||||
|
||||
#### 1.1.1 `cancel_requested` 不是独立状态
|
||||
|
||||
`cancel_requested` **不是** `lifecycleStatus` 枚举值。它是 `AiJob.cancelRequestedAt` 字段,作为 `running` 状态上的**取消信号**而非状态迁移目标。
|
||||
|
||||
流程:
|
||||
|
||||
```
|
||||
用户调用 cancel API
|
||||
│
|
||||
├─ lifecycleStatus = 'queued'
|
||||
│ → markCancelled(jobId) → cancelled(直接终态)
|
||||
│
|
||||
└─ lifecycleStatus = 'running'
|
||||
→ 设置 cancelRequestedAt = now()(lifecycleStatus 保持 running)
|
||||
→ Worker Heartbeat / Engine 执行间隙检测到 cancelRequestedAt != null
|
||||
→ 抛出 JobCancelledError
|
||||
→ Engine catch → markCancelled(jobId) → cancelled
|
||||
```
|
||||
|
||||
**设计理由**:
|
||||
- 不引入中间状态 `cancel_requested` 避免状态机膨胀(5 状态 + 1 信号 vs 6 状态)
|
||||
- `cancelRequestedAt` 是 `running` 上的 boolean flag,语义更轻量
|
||||
- Engine 在每次 Provider 调用前后检查此字段,实现协作式取消
|
||||
- §1.5 的 `STATUS_TO_LIFECYCLE` 中 `cancel_requested` 仅用于过渡期 Shadow Write(legacy `status` 列可能有此值,映射到新 `lifecycleStatus = 'cancelled'`)
|
||||
|
||||
#### 1.2 允许迁移
|
||||
|
||||
```
|
||||
@ -367,11 +392,20 @@ export class AiJobService {
|
||||
async createJob(input: CreateJobInput): Promise<AiJob>;
|
||||
}
|
||||
|
||||
// 对齐 Issue #289:事务化 AiJob 创建、Snapshot 与 Enqueue Outbox
|
||||
interface CreateJobInput {
|
||||
/** 用户 ID */
|
||||
userId: string;
|
||||
/** Job 类型(对应 JobDefinition.jobType) */
|
||||
jobType: string;
|
||||
/** 触发方式 */
|
||||
triggerType: 'user_api' | 'admin_manual' | 'system_scheduled' | 'parent_job';
|
||||
/** 分析目标类型 */
|
||||
targetType: string;
|
||||
/** 分析目标 ID */
|
||||
targetId: string;
|
||||
/** 父 Job ID(Job 链场景) */
|
||||
parentJobId?: string;
|
||||
/** 幂等 Key。提供时启用幂等;不提供时每次创建新 Job */
|
||||
idempotencyKey?: string;
|
||||
/** 覆盖 Definition 默认值 */
|
||||
@ -638,7 +672,16 @@ try {
|
||||
| Execution Engine | 内部重试循环 | `JobDefinition.execution.maxRetries` |
|
||||
| Outbox | attemptCount < 3 → pending;>= 3 → dead | OutboxRepository |
|
||||
|
||||
**约束**:BullMQ retry 与 Engine 内部 retry 不叠加。Engine 内部错误不抛出(不触发 BullMQ retry),而是在 Engine 内完成所有重试后,一次性 `markFailed` 或 `markSucceeded`。
|
||||
**约束**:BullMQ retry 与 Engine 内部 retry 分工明确,不叠加。
|
||||
|
||||
| 层级 | 覆盖范围 | 触发条件 |
|
||||
|------|---------|---------|
|
||||
| **Engine 内部 retry** | 业务级错误(Provider 超时/5xx、输出校验失败) | `JobDefinition.execution.maxRetries` |
|
||||
| **BullMQ retry** | 进程级故障(SIGKILL、OOM、Worker 崩溃、Redis 断连) | §3.1 `attempts` |
|
||||
|
||||
Engine 内部完成所有业务重试后,一次性 `markFailed` 或 `markSucceeded`。Engine 内部错误**不向 BullMQ 抛出**——BullMQ 仅看到 Worker 进程退出(SIGKILL/OOM)或未响应(stalled),此时触发 BullMQ retry,Worker 重启后从 `markFailed` 或 `markSucceeded` 的幂等语义恢复。
|
||||
|
||||
**这解释了 §3.1 中 `ai-interactive` attempts=2 的设计**:交互式场景只需要 2 次机会覆盖进程崩溃;业务重试已在 Engine 内部闭环。
|
||||
|
||||
#### 5.6 ValidatedOutput 与 OutputHash
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user