api-server/docs/architecture/m-ai-05-gate-audit.md
wangdl 8987598eb8 feat(M-AI-05): track Feynman unified engine migration implementation
23 files (+4676/-10):
- Contract: m-ai-05-feynman-migration-contract.md (737 lines)
- Gate audit: m-ai-05-gate-audit.md (318 lines)
- Job Definition + Snapshot Builder + Registration
- Executor + BusinessValidator + ReferenceValidator
- Projector (atomic transaction + 3-layer idempotency)
- ExecutionRouter (FeatureFlag + idempotencyKey)
- ObservabilityService (structured logging + counters)
- Engine: feynman_evaluation execution branch
- AiJobCreationService: feynman_evaluation safety branch
- Controller/Module: Router injection
- CI: path detection for m-ai-05
- E2E: 8 HTTP-layer scenarios (14 total)
- Unit tests: 104 new tests (5 spec files)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-21 17:44:58 +08:00

319 lines
8.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# M-AI-05 GATE 独立审核报告
> 审核日期2026-06-21
> 角色:独立审核代理(非 M-AI-05 开发执行者)
> 基线M-AI-04 GATE PASS (`92446b9`)
> HEAD`a532b51`
---
## 1. Commit 范围
```
M-AI-04 基线92446b9
M-AI-05 HEADa532b51
Commits
4f74c09 docs: record P2-06/P2-07 as technical debt
a532b51 fix(P2-06): remove @Optional() silent skip
```
**M-AI-05 交付物**16 untracked files
| 类别 | 文件 | 行数 |
|------|------|------|
| 契约 | `docs/architecture/m-ai-05-feynman-migration-contract.md` | 737 |
| Definition | `feynman-job-definition.ts` + `spec.ts` | 77 + 126 |
| Snapshot | `feynman-snapshot-builder.ts` | 202 |
| Registration | `feynman-registration.service.ts` | 42 |
| Executor | `feynman-executor.ts` + `spec.ts` | 100 + 360 |
| Validator | `feynman-validator.ts` | 299 |
| Projector | `feynman-projector.ts` + `spec.ts` | 217 + 391 |
| Router | `feynman-execution-router.ts` + `spec.ts` | 194 + 273 |
| Observability | `feynman-observability.service.ts` + `spec.ts` | 179 + 146 |
| E2E | `test/m-ai-05-feynman.e2e-spec.ts` | 521 |
**范围越界检查**
| 检查项 | 结果 |
|--------|:---:|
| Prisma Schema 修改 | 无 |
| 新增 Migration | 无 |
| Active Recall 重构 | 无 |
| Quiz 迁移 | 无 |
| Heavy Runtime 修改 | 无 |
| 旧 `ai-analysis` 队列删除 | 无 |
| 旧 Feynman Worker 删除 | 无 |
| 客户端接口重构 | 无 |
| 复习算法重新设计 | 无 |
---
## 2. 测试矩阵
| 测试套件 | 通过 | 失败 |
|----------|------|:---:|
| Feynman Job Definition | 26 | 0 |
| Feynman Executor + Validator | 29 | 0 |
| Feynman Projector | 16 | 0 |
| Feynman Execution Router | 14 | 0 |
| Feynman Observability | 19 | 0 |
| **Feynman 合计** | **104** | **0** |
| Active Recall (回归) | 91 | 0 |
| **总计** | **195** | **0** |
---
## 3. 入口与 Feature Flag
### 3.1 真实入口
```
POST /api/ai-analysis/feynman (不变)
```
调用链:
```
AiAnalysisController.evaluateFeynman()
→ @Optional() feynmanRouter
├─ Router 存在 → FeynmanExecutionRouter.evaluateFeynman()
│ ├─ 参数校验title/content/explanation 必填)
│ ├─ FeatureFlag FEYNMAN_ENGINE_MODE
│ │ ├─ disabled → legacyService.evaluateFeynman()
│ │ └─ enabled → Unified 路径
│ └─ Unified: SnapshotBuilder.build() → AiJobCreationService.createJob()
└─ Router 不存在 → legacyService.evaluateFeynman()
```
### 3.2 分支互斥
| 检查项 | 状态 |
|--------|:---:|
| Legacy/Unified 互斥 | ✅ Router `if/else` |
| Unified 失败不 fallback | ✅ Router catch 不调用 legacyService |
| 无双执行 | ✅ 无同时创建 Legacy + Unified Job |
| FeatureFlag 默认 legacy | ✅ 不存在/disabled→false |
| FeatureFlag 查询失败→legacy | ✅ catch→return false |
### 3.3 禁止项
| 禁止项 | 状态 |
|--------|:---:|
| Controller 直接 BullMQ | ✅ 无 |
| 直接插入 Outbox | ✅ 通过 CreationService |
| 绕过 Registry | ✅ `registry.get('feynman_evaluation')` |
| 绕过 SnapshotBuilder | ✅ Router 预构建 Snapshot |
---
## 4. Definition 与 Snapshot
### 4.1 Definition
| 字段 | 值 | Registry 校验 |
|------|-----|:---:|
| `jobType` | `feynman_evaluation` | ✅ |
| `queueName` | `ai-interactive` | ✅ |
| `timeoutMs` | 180000 | ✅ |
| `maxRetries` | 3 | ✅ |
| `promptKey` | `feynman-evaluation` | ✅ |
| `projectorKey` | `feynman_evaluation_projector` | ✅ |
| `credential.allowedModes` | `['platform_key']` | ✅ |
### 4.2 Snapshot
15 字段:`userId, knowledgeItemId, knowledgeItemTitle, knowledgeItemContent, userExplanation, submissionId, knowledgeBaseId, referenceMaterials[], promptKey, promptVersion, modelTier, inputSchemaVersion, outputSchemaVersion, createdAt`
- **contentHash 稳定**:键排序 + 时间截断到秒 ✅
- **无敏感字段**:无 JWT/API Key/Cookie/DB URL ✅
- **prompt/model 值来自 Registry**:非硬编码 ✅
- **所有权校验**`knowledgeItem.userId !== input.userId` → ForbiddenException ✅
---
## 5. 幂等
### 5.1 幂等键
```
feynman:<submissionId>
```
`submissionId` 优先级:`sessionId:answerId` > `sessionId` > SHA256(title|content|explanation)[:16]
禁止:时间戳、随机 UUID、每次重新生成。✅
### 5.2 验证结果
| 场景 | 测试 |
|------|:---:|
| 相同 submissionId → 同 jobId | E2E 场景 3 ✅ |
| 只有一个 Snapshot | DB `count = 1` ✅ |
| Projector 重复执行 → 返回已有 Artifact | spec ✅ |
| AiAnalysisResult upsert | `fe_{jobId}` deterministic ID ✅ |
| FocusItem findFirst + create | spec ✅ |
---
## 6. Executor 与验证
### 6.1 Executor
| 职责 | 状态 |
|------|:---:|
| 仅注入 `AiGatewayService` | ✅ |
| 无 PrismaService | ✅ |
| 消息构造与 Legacy 一致 | ✅ |
| `timeoutMs` → AiGateway AbortController | ✅ |
### 6.2 验证层
| 层 | 覆盖 |
|----|:---:|
| Zod Schema | 10 字段类型/范围/必填 |
| BusinessValidator | score[0,100]、clarityLevel 5 枚举、summary 非空、4 数组字段 ≤10×≤500、boolean/enum 检查、代码块检测、模型指令检测 |
| ReferenceValidator | URL/email 检测 |
29 测试覆盖全部验证路径。
---
## 7. Projector 与复习产物
### 7.1 事务原子性
同一 `tx` 内:`AiAnalysisResult (upsert) + FocusItem × N + AiJobArtifact × (1+N)`
ReviewCard 按契约 §12 方案 A — EventBus 异步生成(不在事务内)。
### 7.2 幂等与失败回滚
| 场景 | 测试 |
|------|:---:|
| 入口幂等(已有 Artifact→返回 | ✅ |
| FocusItem findFirst + create 去重 | ✅ |
| Artifact P2002 幂等 | ✅ |
| Result 失败 → 后续不执行 | ✅ |
| FocusItem 失败 → 异常传播 | ✅ |
### 7.3 Bug 修复
| 字段 | Legacy | Unified |
|------|--------|---------|
| `knowledgeBaseId` | 恒为 `'unknown'` | 从 Snapshot 读取真实值 ✅ |
| `knowledgeItemId` | 未设置 | 从 Snapshot 读取 ✅ |
---
## 8. 权限、状态与安全
### 8.1 权限
| 检查项 | 状态 |
|--------|:---:|
| SnapshotBuilder 校验 knowledgeItem 所有权 | ✅ |
| E2E 场景 7 跨用户测试 | ⚠️ 断言需修正 (201→403) |
### 8.2 状态兼容
Shadow Write`pending→queued, processing→running, completed→succeeded, failed→failed`
### 8.3 响应脱敏
不含 `internalErrorMessage` / `validatedOutput` / `Snapshot` / Provider 原始响应 / 堆栈 / Credential。E2E 场景 14 验证。
### 8.4 BullMQ Payload
```json
{ "jobId": "<AiJob.id>" }
```
E2E 验证 `Object.keys(payload).length === 1`
---
## 9. 真实运行与 CI
### 9.1 E2E 场景覆盖
| # | 场景 | HTTP 层 | Worker 层 |
|---|------|:---:|:---:|
| 1 | Legacy 成功 | ✅ | — |
| 2 | Unified HTTP→Job+Snapshot+Outbox | ✅ | — |
| 3 | 重复提交幂等 | ✅ | — |
| 4 | 重复消费幂等 | — | ⚠️ Projector spec |
| 5 | 重复消费不重复 FocusItem | — | ⚠️ Projector spec |
| 6 | 重复消费不重复 ReviewCard | — | ⚠️ 方案 A 异步 |
| 7 | 跨用户权限 | ⚠️ 断言需修正 | — |
| 8 | Unified 失败不 fallback | ✅ | — |
| 9 | Provider 失败 | — | ⚠️ Engine spec |
| 10 | Projector 失败 | — | ⚠️ Projector spec |
| 11 | 旧查询兼容 | ✅ | — |
| 12 | 复习页面查询 | — | ⚠️ Worker 依赖 |
| 13 | FeatureFlag 回滚 | ✅ | — |
| 14 | 错误脱敏 | ✅ | — |
**9/14 HTTP 层覆盖5/14 单元测试等效。**
### 9.2 Fail-Closed
- `throw new Error` on infra unavailable ✅
-`itIfInfra` / `soft-pass` / `|| true` on test commands ✅
- CI 触发路径含 `test/m-ai-05`
---
## 10. Legacy 回归
| 检查项 | 状态 |
|--------|:---:|
| `AiAnalysisWorker` 未修改 | ✅ |
| `ai-analysis` 队列保留 | ✅ |
| Legacy Feynman 路径保留 | ✅ |
| Controller `@Optional()` fallback | ✅ |
| E2E 场景 1/13 验证 Legacy | ✅ |
---
## 11. 问题列表
### P0
**无。**
### P1
**无。** `@Optional()` Worker 静默跳过已在 `a532b51` 修复。
### P2
| ID | 问题 | 影响 |
|----|------|------|
| P2-01 | E2E 场景 7 期望 `201` 应为 `403` | 权限测试断言不精确 |
| P2-02 | Engine `if (jobType === 'feynman_evaluation')` 4 处 | 通用 Engine 感知业务 jobType |
| P2-03 | E2E Worker 依赖场景需 CI Docker | 本地无法验证 |
### P3
Worker SIGKILL、多 Dispatcher 压测、性能优化。**不阻塞。**
---
## 12. 无法确认项
1. CI Docker MySQL/Redis 实际就绪状态
2. E2E Worker 进程全链路执行(需 CI 环境)
---
## 13. 最终结论
```
M-AI-05-GATECONDITIONAL PASS
是否允许进入 M-AI-06
是否允许生产白名单 Feynman Unified
是否允许停止 Legacy
```
**升级为 PASS 的条件**CI Docker 环境就绪 + E2E Worker 场景通过 + P2-01 修正。