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>
319 lines
8.6 KiB
Markdown
319 lines
8.6 KiB
Markdown
# 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 HEAD:a532b51
|
||
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-GATE:CONDITIONAL PASS
|
||
是否允许进入 M-AI-06:是
|
||
是否允许生产白名单 Feynman Unified:否
|
||
是否允许停止 Legacy:否
|
||
```
|
||
|
||
**升级为 PASS 的条件**:CI Docker 环境就绪 + E2E Worker 场景通过 + P2-01 修正。
|