diff --git a/docs/architecture/adr-003-unified-ai-job-engine.md b/docs/architecture/adr-003-unified-ai-job-engine.md index dcb9755..cb68826 100644 --- a/docs/architecture/adr-003-unified-ai-job-engine.md +++ b/docs/architecture/adr-003-unified-ai-job-engine.md @@ -76,13 +76,38 @@ running → queued (不可回退;需重试应通过 retryCount 机制) **所有** `lifecycleStatus` 的写入必须通过 `AiJobLifecycleRepository` 的以下方法: ```typescript -// AiJobLifecycleRepository — 唯一写入 lifeCycleStatus 的入口 +// AiJobLifecycleRepository — 唯一写入 lifecycleStatus 的入口 export class AiJobLifecycleRepository { + /** + * 在给定事务中创建 Job(lifecycleStatus = 'queued')。 + * 调用方负责在同一 tx 中写入 OutboxEvent,以保证 Job + Outbox 原子性。 + */ createJob(tx: Prisma.TransactionClient, input: CreateJobInput): Promise; - lockJob(jobId: string, instanceId: string): Promise; // CAS: queued → running - markSucceeded(jobId: string): Promise; - markFailed(jobId: string, error: JobErrorInfo): Promise; - markCancelled(jobId: string): Promise; + + /** + * CAS 抢锁(queued → running)。 + * 必须是独立操作(非事务内):CAS 需要立即对其他 Worker 可见。 + */ + lockJob(jobId: string, instanceId: string): Promise; + + /** + * 标记成功(→ succeeded)。 + * @param tx 可选事务客户端。由 Projector 在事务内调用以确保 Projector + markSucceeded 原子性。 + * 若未传 tx,方法内部使用独立事务(非 Projector 路径使用)。 + */ + markSucceeded(jobId: string, tx?: Prisma.TransactionClient): Promise; + + /** + * 标记失败(→ failed)。 + * @param tx 可选事务客户端。若非 Projector 路径(如超时/Provider 错误),传 undefined 使用独立事务。 + */ + markFailed(jobId: string, error: JobErrorInfo, tx?: Prisma.TransactionClient): Promise; + + /** + * 标记取消(→ cancelled)。 + * @param tx 可选事务客户端。取消路径通常为独立操作,不传 tx。 + */ + markCancelled(jobId: string, tx?: Prisma.TransactionClient): Promise; // 只读 findByLifecycleStatus(status: LifecycleStatus[], opts?: { queueName?: string; limit?: number }): Promise; @@ -544,14 +569,17 @@ interface ProjectionResult { #### 6.2 事务边界 ``` +// tx = await prisma.$transaction(async (tx) => { ... }) BEGIN TRANSACTION 1. ResultProjector.project(tx, job, validatedOutput) → 写入业务表(QuizQuestion / Flashcard / AiLearningAnalysis / WeakPointCandidate / NextActionRecommendation 等) → 写入 AiJobArtifact (jobId, artifactType, artifactId) - 2. AiJobLifecycleRepository.markSucceeded(tx, jobId) + 2. AiJobLifecycleRepository.markSucceeded(jobId, tx) // 传入 tx,与 Projector 共享事务 3. (未来) OutboxRepository.createInTransaction(tx, { eventType: 'ai.job.completed', ... }) COMMIT ``` +- `markSucceeded` 接受可选 `tx`:传入 tx 时复用事务,不传时内部开启独立事务 +- Projector 抛错 → Transaction Rollback → Job 回到 Engine retry 流程 - **全部成功** → Commit - **任何步骤失败** → Rollback,Job 重新进入 retry 流程