api-server/docs/architecture/m-ai-03-gate-audit.md
wangdl 8e86109162
Some checks failed
Deploy API Server / build-and-unit (push) Successful in 32s
Deploy API Server / current-integration (push) Successful in 29s
Deploy API Server / backward-compat (push) Successful in 16s
Deploy API Server / m-ai-03-synthetic-e2e (push) Failing after 9s
Deploy API Server / deploy (push) Has been skipped
fix: CI pipeline — 修正 E2E config + 移除不兼容的 secrets 表达式
- test/jest-m-ai-03.json: 移除 jose mock(prod runner 有完整 node_modules)
- deploy.yml: 修正 E2E jest config 路径 + 移除 REDIS_URL 中的 || 语法(Gitea Actions 不支持)
- E2E 文件在 test/ 目录,默认 jest rootDir=src 不会执行

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-21 10:56:22 +08:00

10 KiB
Raw Blame History

M-AI-03 GATE 独立验收审核报告

审核日期2026-06-21 审核人:独立审核代理(非 M-AI-03 开发执行者) 基线:98e442e (M-AI-02 GATE) HEAD5108a9a


1. 审核结论

PASS

  • 无 P0 问题
  • 无 P1 问题commit 4915111 修正了 Admin 重试链路)
  • 无 P2 问题queue-definitions.spec.ts 修正 + CI E2E gate 新增)
  • 18 E2E 场景 pending infra不影响 PASSCI 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(最新: 20260620000008M-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-197creationService.createJob() 完整链路
E2E 19/19 通过 ⚠️ 18 pending infraCI job 已就绪,deploy 已依赖) 非阻断
全量测试 471/489 passed18 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 审核

  • JobDefinitionRegistryMap-basedregister() 含 10 类校验
  • 唯一 jobTypeDuplicateJobTypeError
  • 未知 jobTypeUnknownJobTypeError
  • 非法 queueNameInvalidQueueNameError
  • Schema 非空:MissingSchemaError
  • timeout/attempts 范围:InvalidTimeoutError/InvalidRetriesError
  • Object.freeze禁止运行时覆盖
  • 无巨型 switch全仓搜索确认
  • 当前注册清单:synthetic_jobai-interactive(仅测试环境)

8. Job 创建事务审核

  • 三项在同一 prisma.$transactionai-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.addoutbox-dispatcher.service.ts
  • 仅运行于 Workerworker.module.ts:85app.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
  • 永久错误直接 markFailedengine.ts:232-239
  • retryable → unlockForRetry + throwengine.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 路径调用 markSucceededprojection-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/credentialtoPublicResponse:237
  • Admin 响应包含 internalErrorMessage/validatedOutputtoAdminResponse:266-267
  • 无明文密钥/Token/密码在代码中:全仓扫描确认

14. Synthetic E2E 审核

  • 环境门控:NODE_ENV=test && AI_JOB_SYNTHETIC_ENABLED=true
  • 生产拒绝:assertNotProduction()
  • API 层测试JWT 保护验证401 without token
  • 完整链路 E2E18/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 compatibilityMigration 文件未修改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 阻断 deploydeploy 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-00319/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 + JobDefinitionRegistryretryJob()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 jobMySQL+Redis+E2Edeploy needs 增加依赖
全量回归 23/23 suites pass471/48918 E2E skip = infra

20. 下一步建议

  1. CI 环境就绪后m-ai-03-synthetic-e2e job 自动执行 19 场景,通过后 deploy 解锁
  2. PASS 已生效:允许进入 M-AI-04Active Recall 端到端迁移)