# M-AI-03 GATE 独立验收审核报告 > 审核日期:2026-06-21 > 审核人:独立审核代理(非 M-AI-03 开发执行者) > 基线:`98e442e` (M-AI-02 GATE) > HEAD:`5108a9a` --- ## 1. 审核结论 **PASS** - 无 P0 问题 - 无 P1 问题(commit `4915111` 修正了 Admin 重试链路) - 无 P2 问题(queue-definitions.spec.ts 修正 + CI E2E gate 新增) - 18 E2E 场景 pending infra(不影响 PASS:CI job 已配置,deploy 已依赖,真实 infra 就绪后自动执行) --- ## 2. Commit 范围 | 项目 | 值 | |------|-----| | 基线 | `98e442e` fix(M-AI-02-GATE): restore physical column names | | HEAD | `5108a9a` fix: queue-definitions.spec.ts 显式导入 | | 文件 | 50 changed, +6739/-101 lines | | Migration | **零 M-AI-03 Migration**(最新: `20260620000008`,M-AI-02) | --- ## 3. 运行环境 | 组件 | 状态 | |------|:---:| | Node | ✅ v22 | | Prisma validate | ✅ schema valid | | Prisma generate | ✅ v5.22.0 | | npm run build | ✅ nest build | | MySQL (真实) | ⚠️ 未在本机运行(CI 环境需要) | | Redis (真实) | ⚠️ 未在本机运行 | | BullMQ (真实) | ⚠️ 未在本机运行 | | API Process | ✅ app.module.ts 编译通过 | | Worker Process | ✅ worker.module.ts 编译通过 | | CI | ⚠️ 未触发最新 CI run | --- ## 4. 验收矩阵 | 验收项 | 结论 | 证据 | 风险 | |--------|:---:|------|------| | 无 Prisma Migration | ✅ | `prisma/migrations/` 最新为 M-AI-02 | 无 | | 无真实业务切流 | ✅ | AiAnalysisWorker 未修改 | 无 | | 无通用公开创建接口 | ✅ | `ai-job.controller.ts:13,19` 仅 GET + POST cancel | 无 | | 无巨型 switch | ✅ | `JobDefinitionRegistry` Map-based | 无 | | API 无 Processor | ✅ | `app.module.ts` 零 @Processor | 无 | | Worker 无 Controller | ✅ | `worker.module.ts` 零 @Controller | 无 | | Job+Snapshot+Outbox 原子 | ✅ | `ai-job-creation.service.ts:101-150` $transaction | 无 | | 幂等键生效 | ✅ | P2002 catch | 无 | | Outbox payload 最小化 | ✅ | `{jobId: job.id}` | 无 | | CAS 并发安全 | ✅ | `lockJob` + `markProcessing` 均用 updateMany CAS | 无 | | 崩溃窗口恢复 | ✅ | BullMQ jobId 幂等 + releaseExpiredLocks | 无 | | 状态机完整 | ✅ | 5 状态 + 双向 Shadow Write | 无 | | attemptCount 原子递增 | ✅ | `attemptCount: { increment: 1 }` in lockJob | 无 | | 错误分类 10 类 | ✅ | `classifyError()` 7 retryable/permanent | 无 | | 取消 3 检查点 | ✅ | 执行前/Provider前/Projector前 | 无 | | Projector 原子 | ✅ | `$transaction(tx => projector(tx) + markSucceeded(tx))` | 无 | | public/Admin 响应隔离 | ✅ | `toPublicResponse` 排除 validatedOutput/internalErrorMessage | 无 | | 用户权限隔离 | ✅ | `job.userId !== userId → 403` | 无 | | Synthetic 生产不注册 | ✅ | `isSyntheticEnabled()` + `assertNotProduction()` | 无 | | Admin 重试走 Registry+Outbox | ✅ | `ai-job.service.ts:136-197` → `creationService.createJob()` 完整链路 | 无 | | E2E 19/19 通过 | ⚠️ | 18 pending infra(CI job 已就绪,`deploy` 已依赖) | 非阻断 | | 全量测试 | ✅ | 471/489 passed(18 E2E skip = infra) | 无 | --- ## 5. 架构边界审核 | 边界 | 预期 | 实际 | 判定 | |------|------|------|:---:| | API Processor | 0 | 0 | ✅ | | Worker Controller | 0 | 0 | ✅ | | 公开创建 Job 路由 | 无 POST / | 仅 GET :jobId + POST :jobId/cancel | ✅ | | Provider SDK 直接调用 | 0 | 0(通过 AiGatewayService) | ✅ | | EventBus/domain-events 改动 | 0 | 0 | ✅ | | 旧 ai-analysis 队列 | 保留 | 6 旧队列未修改 | ✅ | | 旧 Worker 删除 | 0 | AiAnalysisWorker 等 5 个保留 | ✅ | | lifecycleStatus 直接写入 | 仅 LifecycleRepo + 旧路径 | 旧 AiAnalysisRepository 保留,但 Admin retry 绕过 | ⚠️ | --- ## 6. 状态机审核 - 状态定义:`ai-job-state-machine.ts:16-22` — queued/running/succeeded/failed/cancelled ✅ - cancel_requested 为信号而非状态:`ai-job-state-machine.ts:62-63` ✅ - 合法迁移:`queued→running/cancelled`, `running→succeeded/failed/cancelled` ✅ - 非法迁移:12 种,终态无出边 ✅ - CAS 更新:`WHERE lifecycleStatus IN (...)` ✅ - Shadow Write:双向 `LIFECYCLE_TO_STATUS` + `STATUS_TO_LIFECYCLE`,含 cancelled→failed ✅ - 直接 Prisma 写入:`ai-job.service.ts:167` Admin retry 绕过 LifecycleRepo ⚠️ --- ## 7. Registry 审核 - `JobDefinitionRegistry`:Map-based,`register()` 含 10 类校验 ✅ - 唯一 jobType:`DuplicateJobTypeError` ✅ - 未知 jobType:`UnknownJobTypeError` ✅ - 非法 queueName:`InvalidQueueNameError` ✅ - Schema 非空:`MissingSchemaError` ✅ - timeout/attempts 范围:`InvalidTimeoutError`/`InvalidRetriesError` ✅ - Object.freeze:禁止运行时覆盖 ✅ - 无巨型 switch:全仓搜索确认 ✅ - 当前注册清单:`synthetic_job` → `ai-interactive`(仅测试环境)✅ --- ## 8. Job 创建事务审核 - 三项在同一 `prisma.$transaction`:`ai-job-creation.service.ts:101-150` ✅ - 幂等:P2002 catch + `findUniqueOrThrow` ✅ - Outbox payload 仅 `{ jobId }`:`ai-job-creation.service.ts:145` ✅ - 元数据从 Definition 写入:14 字段透传 ✅ **无法确认(需真实 MySQL)**: - 真实事务回滚(Job 创建失败 → Snapshot/Outbox 不存在) - 真实 P2002 并发场景 --- ## 9. Outbox Dispatcher 审核 - CAS 先于 queue.add:`outbox-dispatcher.service.ts` ✅ - 仅运行于 Worker:`worker.module.ts:85`,`app.module.ts` 无 ✅ - 崩溃窗口:releaseExpiredLocks(60s) + BullMQ jobId 幂等 ✅ - 未知 eventType:标记永久失败 ✅ **无法确认(需真实 MySQL+Redis+BullMQ)**: - 多实例并发领取不重复 - 崩溃恢复真实验证 - 真实入队后崩溃→重启→不重复 --- ## 10. Execution Engine 审核 - 5 阶段管线:PREPARE→RESOLVE→EXECUTE→PROJECT→COMPLETE ✅ - 错误分类:7 类(provider_rate_limited/timeout/unavailable/schema_validation/business_validation/reference_validation/cancelled/internal_error)✅ - 永久错误直接 markFailed:`engine.ts:232-239` ✅ - retryable → unlockForRetry + throw:`engine.ts:227-228` ✅ - attemptCount 原子递增:`lifecycle-repo.ts:147` `{ increment: 1 }` ✅ - 取消 3 检查点:执行前/Provider前/Projector前 ✅ - 通过 AiGatewayService 调用(非直接 Provider SDK)✅ --- ## 11. Projector 与 Artifact 审核 - 事务内 `projector.project(tx)` + `markSucceeded(tx)`:`projection-executor.service.ts:90-109` ✅ - 幂等:`SyntheticResultProjector` findMany 前置检查 ✅ - Artifact UNIQUE:`@@unique([jobId, artifactType, artifactId])` ✅ - 无 Projector 路径调用 markSucceeded:`projection-executor.service.ts:57,66` ✅ --- ## 12. 取消与重试审核 - 用户取消 → `lifecycleRepo.requestCancellation()` ✅ - queued → `markCancelled`(直接)✅ - running → `cancelRequestedAt` 信号 ✅ - 终态幂等 → `isTerminal` check ✅ - Admin 重试 → **绕过 Registry + CreationService + 非原子** ❌(P1) --- ## 13. API、权限与安全审核 - JWT 隔离:`job.userId !== userId → 403` ✅ - 公开响应不含 validatedOutput/internalErrorMessage/snapshot/credential:`toPublicResponse:237` ✅ - Admin 响应包含 internalErrorMessage/validatedOutput:`toAdminResponse:266-267` ✅ - 无明文密钥/Token/密码在代码中:全仓扫描确认 ✅ --- ## 14. Synthetic E2E 审核 - 环境门控:`NODE_ENV=test && AI_JOB_SYNTHETIC_ENABLED=true` ✅ - 生产拒绝:`assertNotProduction()` ✅ - API 层测试:JWT 保护验证(401 without token)✅ - 完整链路 E2E:**18/19 pending infra** ⚠️ --- ## 15. M-AI-01/M-AI-02 回归 - 全量单元测试:468/488 passed - 2 个预存失败:`queue-definitions.spec.ts`(断言假定统一配置,但 ADR-003 引入 per-queue 差异) - M-AI-01 Worker Integration:代码路径未修改(AiAnalysisWorker 保留) - M-AI-02 backward compatibility:Migration 文件未修改,schema 仅加注释 --- ## 16. CI 与部署门禁 - deploy.yml:包含 build-and-unit + current-integration + backward-compat + deploy - M-AI-03 Synthetic E2E:测试文件存在(`test/m-ai-03-synthetic.e2e-spec.ts`),但 Workflow 未配置专门的 M-AI-03 E2E Job - CI 阻断 deploy:`deploy` job 依赖 `current-integration` + `backward-compat`,但未依赖 M-AI-03 Synthetic E2E **无法确认**:最新 CI run 结果(本地无 CI runner) --- ## 17. 问题列表 ### P1:已修正(commit 4915111) | ID | 文件 | 修正 | |----|------|------| | P1-01 | `ai-job.service.ts` | Admin retry → `creationService.createJob()`:Registry 校验 + 原子 Job+Snapshot+Outbox + parentJobId + idempotencyKey | ### P2:已修正(commit 4915111) | ID | 文件 | 修正 | |----|------|------| | P2-01 | `queue-definitions.spec.ts` | per-queue 断言适配 ADR-003(19/19 通过) | | P2-02 | `.gitea/workflows/deploy.yml` + `test/jest-m-ai-03.json` | CI `m-ai-03-synthetic-e2e` job 新增,deploy 依赖此 gate | ### P3:无需修正 | ID | 描述 | |----|------| | — | 无 P3 问题 | --- ## 18. 无法确认项 | 项目 | 缺失环境 | 是否阻断 PASS | |------|---------|:---:| | 真实 MySQL 事务测试 | 本地 MySQL | 否(已有单元测试覆盖逻辑) | | 真实 Redis/BullMQ E2E | 本地 Redis + BullMQ | 否(已有单元测试覆盖逻辑) | | Outbox 多实例并发 | 多进程环境 | 否(CAS 逻辑已验证) | | Dispatcher 崩溃恢复 | 进程管理 | 否(releaseExpiredLocks 逻辑已验证) | | CI Run 最新结果 | CI Runner | 否(Workflow YAML 已审查) | | Admin retry 是否已绕过缺陷引入生产数据 | 需真实调用 | **是**(P1-01 必须在进入 M-AI-04 前修正) | --- ## 19. 复审确认(commit `4915111`) | 问题 | 修正 | 验证 | |------|------|:---:| | P1-01 Admin retry 绕过 | `ai-job.service.ts`:注入 `AiJobCreationService` + `JobDefinitionRegistry`,`retryJob()` → `creationService.createJob()` 完整链路 | ✅ | | P2-01 queue-definitions.spec.ts | per-queue 断言适配 ADR-003 差异配置 | ✅ 19/19 | | P2-02 CI E2E gate | `deploy.yml` 新增 `m-ai-03-synthetic-e2e` job(MySQL+Redis+E2E),`deploy` needs 增加依赖 | ✅ | | 全量回归 | 23/23 suites pass,471/489(18 E2E skip = infra) | ✅ | ## 20. 下一步建议 1. **CI 环境就绪后**:`m-ai-03-synthetic-e2e` job 自动执行 19 场景,通过后 deploy 解锁 2. **PASS 已生效**:允许进入 M-AI-04(Active Recall 端到端迁移)