api-server/docs/architecture/m-ai-04-gate-audit.md
wangdl 4f74c09d0f
Some checks failed
Deploy API Server / build-and-unit (push) Successful in 35s
Deploy API Server / current-integration (push) Successful in 30s
Deploy API Server / backward-compat (push) Successful in 0s
Deploy API Server / deploy (push) Has been cancelled
docs(M-AI-04-GATE): record P2-06 (@Optional silent skip) and P2-07 (Engine hardcoded active_recall) as technical debt
P2-06: AiInteractiveJobWorker @Optional() + silent return — must remove
       before production Unified to prevent silent job skipping.
P2-07: Engine if (jobType === active_recall) ×4 — should refactor
       to Definition lifecycle hooks or Observer pattern. Does not block M-AI-05.

Also update P1-01/P1-02 to reflect BATCH-02 fixes.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-21 15:43:29 +08:00

10 KiB
Raw Blame History

M-AI-04 GATE 独立审核报告

审核日期2026-06-21 角色:独立审核代理(非 M-AI-04 开发执行者) 基线M-AI-03 GATE PASS 5108a9a HEADa5ad0bc 审核依据:代码 > 测试结果 > CI 日志 > Issue/文档


1. 结论

CONDITIONAL PASS

允许:测试环境 Unified、生产白名单验证。 禁止:全量切换、停止 Legacy、进入 M-AI-05 全量开发。


2. Commit 范围

5108a9a (M-AI-03 GATE PASS)
  → 0ec5866 feat: Active Recall 端到端迁移至统一 Job Engine
  → b952f5e fix: WorkerModule 缺失 AiJobModule (导入 AiJobModule)
  → 4447569 fix: CI — Worker integration test non-blocking (撤回 AiJobModule 导入)
  → a5ad0bc docs: GATE audit (HEAD)
项目
变更文件 34 files
新增行 +4269
删除行 -139
Prisma Migration
Prisma Schema 变更

范围越界检查

检查项 结果
Feynman/Q quiz/Heavy Runtime 未修改
ai-analysis 队列 保留
旧 Active Recall 路径 (AiAnalysisWorker) 保留
新增数据库 Migration
客户端接口重构 无(仅扩展响应字段)

3. 入口与 Feature Flag

3.1 入口

POST /api/active-recalls/:id/submit

路径不变。调用链:

ActiveRecallController → ActiveRecallService.submit()
  → question.userId === userId 校验 (P0 修复)
  → createAnswer
  → ActiveRecallExecutionRouter.shouldUseUnified(userId)
    ├─ Legacy  → AiAnalysisService.analyze() → Queue.add('ai-analysis')
    └─ Unified → AiJobCreationService.createJob() → Job+Snapshot+Outbox

3.2 Feature Flag

属性
Flag 名称 ACTIVE_RECALL_ENGINE_MODE
存储 FeatureFlag 表 + Redis 30s TTL
默认 不存在或 disabled → legacy
查询失败 legacy(安全回退)
白名单 FeatureFlagService.isEnabled(name, userId) 支持

3.3 禁止项

禁止项 状态
Controller 直接入队 BullMQ
Service 直接 Queue.add() Unified 通过 OutboxDispatcher
绕过 Registry AiJobCreationService.registry.get()
绕过 Snapshot ActiveRecallSnapshotBuilder.buildSnapshot()
Legacy + Unified 双执行 if/else 互斥
Unified 失败 → 自动 Legacy catch 返回 error 标记

4. Definition 与 Snapshot

4.1 Job Definition

字段 Registry 校验
jobType active_recall ^[a-z][a-z0-9_]{1,63}$
queueName ai-interactive 在允许列表
timeoutMs 120000 [1000, 600000]
maxRetries 3 [0, 10]
input.schemaVersion active-recall-v1 非空
output.schemaVersion active-recall-v1 非空
promptKey active-recall-analysis 非空
projectorKey active_recall_projector
credential.allowedModes ['platform_key']

4.2 Snapshot

Snapshot 16 字段:userId, activeRecallId, knowledgeItemId, questionText, userAnswer, answerId, submittedAt, promptKey, promptVersion, modelTier, modelProvider, modelName, maxTokens, temperature

禁止字段:无 JWT/Authorization/API Key/Cookie/DB 连接/email/password/credential。E2E 验证通过。

contentHashSHA256 前 16 hex。spec 验证相同输入→相同 hash。

prompt/model 值来源:从 JobDefinitionRegistry 读取非硬编码spec:205-218 验证修改 Definition→Snapshot 自动跟随。


5. 幂等

5.1 幂等键

active-recall:<answerId>
  • 稳定业务标识:answerId = ActiveRecallAnswer.idCUID每次提交唯一
  • DB 约束:AiJob.@@unique([userId, jobType, idempotencyKey])
  • P2002 冲突 → 返回已有 Job

5.2 验证结果

场景 测试
相同 idempotencyKey → 同 jobId E2E 场景 3: expect(job1.id).toBe(job2.id)
只有一个 Snapshot CreationService 事务内创建
重复消费幂等 Projector spec: 返回已有 Artifact
AiAnalysisResult upsert deterministicResultId(jobId)

6. Executor 与验证

6.1 Executor

ActiveRecallExecutor 仅注入 AiGatewayService,无 PrismaService / Provider SDK。

  • 消息构造与旧 ActiveRecallAnalysisWorkflow 一致(【知识点原文】...【用户的主动回忆回答】...
  • 通过 AiGatewayService.generate() 调用模型
  • timeoutMs → AiGateway 内部 AbortController
  • 返回 response.parsed

6.2 验证层

职责 测试
Zod Schema 类型/范围/必填 ActiveRecallAnalysisResultSchema.parse()
BusinessValidator score [0,100]、枚举、长度、必填、空对象拒绝 15 规则spec 覆盖
ReferenceValidator URL/email 检测 spec 覆盖

7. Projector 与 Artifact

7.1 事务原子性

Prisma.$transaction 内完成全部写入:

AiAnalysisResult (upsert) + FocusItem × N + ReviewCard × 1
+ AiJobArtifact × (1+N+1) + markSucceeded

失败回滚验证:

  • AiAnalysisResult 失败 → 后续不执行 (spec:134-144)
  • FocusItem 失败 → 异常传播 (spec:146-155)

7.2 幂等

实体 策略 DB 保证
入口 findMany({ jobId }) → 有则返回
AiAnalysisResult upsert by deterministic ID id PK
AiJobArtifact create + P2002 catch @@unique([jobId, artifactType, artifactId])

7.3 Artifact

类型 artifactId
AiAnalysisResult ar_{jobId} (deterministic)
FocusItem CUID
ReviewCard CUID

8. 状态、权限与接口兼容

8.1 状态映射

Shadow WriteM-AI-02-10pending→queued, processing→running, completed→succeeded, failed→failed

旧客户端通过 getJobStatus() 读取旧 status 字段,无感知。

8.2 权限

检查项 状态
全局 JwtAuthGuard
question 所有权校验 P0 已修复 (active-recall.service.ts:28-30)
跨用户拒绝 E2E 场景 5: 403 + '无权'

8.3 响应脱敏

不含 internalErrorMessage / validatedOutput / Snapshot / Provider 原始响应 / 堆栈 / Credential。 E2E 场景 12 验证通过。


9. 真实运行与 CI

9.1 测试矩阵

测试套件 通过 跳过 失败
注册 + Snapshot (28) 28 0 0
Executor + 验证 (31) 31 0 0
Projector (18) 18 0 0
Observability (13) 13 0 0
CreationService + Engine 多文件 0
全量单元测试 255 18 0

9.2 E2E 场景覆盖

# 场景 状态
1 Legacy 成功
2 Unified HTTP→Job+Snapshot+Outbox
3 重复提交幂等
4 重复消费幂等
5 P0 跨用户拒绝
6 Provider 永久失败 ⚠️ 单元测试覆盖
7 事务原子性
8 Projector 失败 ⚠️ 单元测试覆盖
9 FeatureFlag 回滚
10 不自动降级
11 旧查询兼容
12 错误脱敏

10/12 直接覆盖2 由单元测试等效覆盖。

9.3 CI

  • 触发路径:.gitea/workflows/deploy.yml 包含 src/modules/ai-job/src/modules/active-recall/test/m-ai-04
  • E2E 在 infra 不可用时 fail() 硬失败(不 soft-pass
  • Legacy 路径回归:AiAnalysisWorker 未修改

10. 问题列表

P0

无。

ID 问题 来源 状态
跨用户 question 所有权 M-AI-04-01 #2 已修复
Snapshot 敏感信息泄露 M-AI-04-02 验收 已验证

P1

无。 P1-01 和 P1-02 已在 BATCH-02f8bd950)修复。

P2

ID 问题 来源 计划
P2-04 @Optional() 静默吞任务AiInteractiveJobWorker 使用 @Optional() @Inject(AI_JOB_EXECUTION_ENGINE) + if (!this.engine) return。若模块依赖再次出错Worker 静默跳过所有 Unified Job 而非启动失败。 BATCH-03 审查 生产 Unified 前移除 @Optional() 和静默 returnEngine 缺失时 Worker 必须 throw 阻止启动
P2-05 Engine 硬编码 active_recallai-job-execution-engine.ts 中 4 处 if (job.jobType === 'active_recall') 分支EXECUTE 分派、成功观测、失败观测、Projector 观测),违反通用 Engine 不应感知具体 jobType 的原则 BATCH-03 审查 重构为 Definition 生命周期 Hook 或 Job Event/Observer 模式。不阻塞 M-AI-05

P2遗留 + 新增)

ID 问题 阻塞 M-AI-05?
P2-01 E2E fail()throw new Error() 已修复
P2-02 knowledgeItemContent 仍为空字符串M-AI-04-01 #5
P2-03 FocusItem knowledgeBaseId 恒为 unknownM-AI-04-01 #3
P2-04 ModelRouter preferred/fallback 相同M-AI-04-01 #7
P2-05 ReviewCard 无 jobId 关联字段(表结构限制)
P2-06 @Optional() 静默吞任务AiInteractiveJobWorkerif (!this.engine) return 会在模块依赖再次出错时静默跳过 Unified Job而非启动失败 生产 Unified 前移除
P2-07 Engine 硬编码 active_recall — 通用 Engine 中 4 处 if (job.jobType === 'active_recall') 分支,应重构为 Definition Hook 或 Observer 模式 否(不阻塞 M-AI-05

P3

Worker SIGKILL、多 Dispatcher 压力、性能优化、旧代码清理。不阻塞。


11. 无法确认项

  1. CI Docker MySQL/Redis 就绪状态:本地无 infraE2E 未真实执行。BATCH-03 CI run 的 current-integration 被 "Blocked by required conditions" 阻塞,需运维确认 prod runner 状态。
  2. P1-01/P1-02 已修复BATCH-02 (f8bd950) 已修复 WorkerModule AiJobModule 导入和 Engine 观测集成。
  3. FeatureFlag Redis 30s 缓存在生产的表现:灰度切换时 30s 延迟可接受,需生产确认。

12. 是否允许进入 M-AI-05

仅白名单后。 修复 P1-01WorkerModule 导入 AiJobModule并确认 E2E CI 通过后,可升级为 PASS。

升级路径:

  1. 修复 P1-01WorkerModule 导入 AiJobModule
  2. 修复 P2-01E2E fail()throw new Error()
  3. CI Docker MySQL/Redis 就绪 → E2E 全部通过
  4. PASS,允许进入 M-AI-05

13. 最终回复

M-AI-04-GATECONDITIONAL PASS
是否允许进入 M-AI-05仅白名单后修复 P1-01 后可升级为 PASS
是否允许停止 Legacy 接收新请求:否