[M-AI-05-04] 实现 Feynman Projector 与复习产物原子写入 #307

Closed
opened 2026-06-21 15:53:34 +08:00 by wangdl · 1 comment
Owner

类型 / 标签

  • backend
  • feynman
  • projector
  • review

风险等级

P0(非原子写入会导致部分产物残留;幂等失效会导致重复 FocusItem/ReviewCard)

依赖

  • M-AI-05-03(Executor 和验证就绪)

目标

将验证后的 Feynman 结果原子写入现有业务模型,并根据旧业务契约创建 FocusItem 和 ReviewCard。

Projector

新增 FeynmanProjector,输入:job、snapshot、validatedOutput、transactionClient。

事务内容

在一个事务中完成:

  1. 创建或更新 Feynman Evaluation Result
  2. 按契约创建 FocusItem
  3. 按契约创建 ReviewCard
  4. 创建 AiJobArtifact
  5. 写入 validatedOutput
  6. 写入 outputHash
  7. Job → succeeded
  8. 写入 finishedAt

如果旧业务还包含学习统计或其他副作用,仅实现 M-AI-05-01 已确认的内容。

Artifact

至少评估:analysis_resultfocus_itemreview_card。每个真实业务实体必须有对应 Artifact。不得为未创建的实体写虚假 Artifact。

幂等设计

重复执行同一 Job:Evaluation Result 数量不增加、FocusItem 数量不增加、ReviewCard 数量不增加、Artifact 数量不增加、学习统计不重复累计。

优先使用 deterministic ID、数据库唯一约束、upsert、createMany(skipDuplicates)、事务级幂等。禁止仅依赖 find → 没有 → create 作为并发保证。

失败回滚

必须验证:

  • Evaluation Result 失败 → 无其他产物
  • FocusItem 失败 → Result 回滚
  • ReviewCard 失败 → Result 和 FocusItem 回滚
  • Artifact 失败 → 全部业务产物回滚
  • Job succeeded 更新失败 → 全部回滚
  • 重复 Projector → 数量不变

兼容

确认旧查询接口可以读取 Unified Projector 写入的结果与复习产物。

影响范围

src/modules/ai-job/projector/src/modules/feynman/src/modules/review/src/modules/focus-item/src/modules/ai-job/artifact/

验收标准

  • 一个事务中完成:Result + FocusItem + ReviewCard + Artifact + validatedOutput + outputHash + Job.succeeded
  • 重复 Projector → Evaluation Result 数量不增加
  • 重复 Projector → FocusItem 数量不增加
  • 重复 Projector → ReviewCard 数量不增加
  • 重复 Projector → Artifact 数量不增加
  • 学习统计不重复累计
  • Evaluation Result 失败 → 无其他产物
  • FocusItem 失败 → Result 回滚
  • ReviewCard 失败 → Result + FocusItem 回滚
  • Artifact 失败 → 全部业务产物回滚
  • 旧查询接口可读取 Unified Projector 写入的结果和复习产物
  • Artifact 包含 analysis_resultfocus_itemreview_card
  • outputHash 稳定
  • 使用 deterministic ID / upsert / 唯一约束保证并发幂等
  • 真实 MySQL 集成测试通过

建议执行顺序

第 4 个执行(依赖 03)

## 类型 / 标签 - backend - feynman - projector - review ## 风险等级 P0(非原子写入会导致部分产物残留;幂等失效会导致重复 FocusItem/ReviewCard) ## 依赖 - M-AI-05-03(Executor 和验证就绪) ## 目标 将验证后的 Feynman 结果原子写入现有业务模型,并根据旧业务契约创建 FocusItem 和 ReviewCard。 ## Projector 新增 `FeynmanProjector`,输入:job、snapshot、validatedOutput、transactionClient。 ## 事务内容 在一个事务中完成: 1. 创建或更新 Feynman Evaluation Result 2. 按契约创建 FocusItem 3. 按契约创建 ReviewCard 4. 创建 AiJobArtifact 5. 写入 validatedOutput 6. 写入 outputHash 7. Job → succeeded 8. 写入 finishedAt 如果旧业务还包含学习统计或其他副作用,仅实现 M-AI-05-01 已确认的内容。 ## Artifact 至少评估:`analysis_result`、`focus_item`、`review_card`。每个真实业务实体必须有对应 Artifact。不得为未创建的实体写虚假 Artifact。 ## 幂等设计 重复执行同一 Job:Evaluation Result 数量不增加、FocusItem 数量不增加、ReviewCard 数量不增加、Artifact 数量不增加、学习统计不重复累计。 优先使用 deterministic ID、数据库唯一约束、upsert、createMany(skipDuplicates)、事务级幂等。禁止仅依赖 `find → 没有 → create` 作为并发保证。 ## 失败回滚 必须验证: - Evaluation Result 失败 → 无其他产物 - FocusItem 失败 → Result 回滚 - ReviewCard 失败 → Result 和 FocusItem 回滚 - Artifact 失败 → 全部业务产物回滚 - Job succeeded 更新失败 → 全部回滚 - 重复 Projector → 数量不变 ## 兼容 确认旧查询接口可以读取 Unified Projector 写入的结果与复习产物。 ## 影响范围 `src/modules/ai-job/projector/`、`src/modules/feynman/`、`src/modules/review/`、`src/modules/focus-item/`、`src/modules/ai-job/artifact/` ## 验收标准 - [ ] 一个事务中完成:Result + FocusItem + ReviewCard + Artifact + validatedOutput + outputHash + Job.succeeded - [ ] 重复 Projector → Evaluation Result 数量不增加 - [ ] 重复 Projector → FocusItem 数量不增加 - [ ] 重复 Projector → ReviewCard 数量不增加 - [ ] 重复 Projector → Artifact 数量不增加 - [ ] 学习统计不重复累计 - [ ] Evaluation Result 失败 → 无其他产物 - [ ] FocusItem 失败 → Result 回滚 - [ ] ReviewCard 失败 → Result + FocusItem 回滚 - [ ] Artifact 失败 → 全部业务产物回滚 - [ ] 旧查询接口可读取 Unified Projector 写入的结果和复习产物 - [ ] Artifact 包含 `analysis_result`、`focus_item`、`review_card` - [ ] outputHash 稳定 - [ ] 使用 deterministic ID / upsert / 唯一约束保证并发幂等 - [ ] 真实 MySQL 集成测试通过 ## 建议执行顺序 第 4 个执行(依赖 03)
wangdl added this to the M-AI-05 Feynman 与 ReviewCard 可靠链路 milestone 2026-06-21 15:53:34 +08:00
wangdl added the
priority:p0
area:review
area:ai
backend
labels 2026-06-21 15:53:34 +08:00
Author
Owner

开发完成评论

完成内容

  • 实现 FeynmanProjector(implements ResultProjector,key=feynman_evaluation_projector
  • 在 Prisma Transaction 内原子写入:
    1. AiAnalysisResult — upsert by deterministic ID fe_<jobId>(幂等)
    2. FocusItem — 每个 weakness 字符串创建一条(findFirst + create 幂等去重)
    3. AiJobArtifact — Result + 每个 FocusItem 各一条(P2002 幂等)
  • ★ ReviewCard 不在 Projector 中创建(契约 §12 方案 A — EventBus 异步生成)
  • Bug 修复:knowledgeBaseId 从 Snapshot 读取(不再为 'unknown'
  • 新增:knowledgeItemId 从 Snapshot 设置(Legacy 未设置)
  • 注册到 RESULT_PROJECTORS multi-provider(ProjectionExecutor 自动发现)

幂等策略

入口幂等: tx.aiJobArtifact.findMany(jobId) → 已有 → 直接返回
Result:   deterministic ID (fe_<jobId>) + upsert
FocusItem: findFirst(userId + title + source) → 已有 → 跳过 create,补 Artifact
Artifact:  P2002 unique constraint → catch → skip

事务原子性

  • Result upsert 失败 → 事务回滚(无 FocusItem/Artifact)
  • FocusItem create 失败 → 事务回滚(Result 不保留)
  • Artifact create 失败 → 事务回滚(Result + FocusItem 不保留)
  • markSucceeded 由 ProjectionExecutor 在同一事务中调用

修改文件

  • src/modules/ai-job/feynman-projector.ts(新增,178 行)
  • src/modules/ai-job/feynman-projector.spec.ts(新增,340 行)
  • src/modules/ai-job/ai-job.module.ts(修改,+3 行:1 import + 1 provider + RESULT_PROJECTORS 工厂扩展)

代码证据

  • feynman-projector.ts:33 — key = 'feynman_evaluation_projector'
  • feynman-projector.ts:35-57 — 入口幂等检查(已有 Artifact → 直接返回)
  • feynman-projector.ts:63-96 — AiAnalysisResult upsert + Artifact(deterministic ID)
  • feynman-projector.ts:102-143 — FocusItem 创建循环(findFirst 去重 + knowledgeBaseId 修复 + knowledgeItemId)
  • feynman-projector.ts:155-170deterministicResultId() helper(fe_ 前缀)
  • feynman-projector.ts:172-185upsertArtifact() helper(P2002 幂等)

测试覆盖(16 tests)

  • 基础功能:key 正确、ResultProjector 接口实现
  • 核心写入:Result upsert(确定性 ID)、每个 weakness → FocusItem、knowledgeBaseId 修复
  • 边界:weaknesses 空数组、空字符串跳过、Snapshot 缺少字段回退
  • 幂等:入口幂等(已有 Artifact 直接返回)、FocusItem findFirst 去重
  • Artifact 完整性:类型正确(不含 ReviewCard)、ordinal 递增、score metadata
  • 失败回滚:Result upsert 失败 → 无 FocusItem、FocusItem create 失败 → 事务回滚

与 ActiveRecallProjector 的关键差异

方面 ActiveRecall Feynman
FocusItem 来源 focusItems 结构化数组(title/reason/suggestion/priority) weaknesses 字符串数组(仅 title)
ReviewCard Projector 内直接创建(reviewSuggestion) 不在 Projector 内(方案 A — EventBus)
Deterministic ID 前缀 ar_ fe_
knowledgeBaseId 修复 无(ActiveRecall 已有) ★ 从 Snapshot 读取(fix 'unknown'

未完成 / 风险

  • ReviewCard 的 EventBus 触发点待 M-AI-05-05 在 Engine 中增加(Projector 完成后发布 ai.analysis.completed 事件)
  • Engine 中需增加 feynman_evaluation jobType 的执行分派(当前只支持 active_recall + 默认路径)

是否建议进入 Review

  • 是 — Projector 已完成,事务原子 + 幂���,建议审核后启动 M-AI-05-05
## 开发完成评论 ### 完成内容 - 实现 `FeynmanProjector`(implements `ResultProjector`,key=`feynman_evaluation_projector`) - 在 Prisma Transaction 内原子写入: 1. **AiAnalysisResult** — upsert by deterministic ID `fe_<jobId>`(幂等) 2. **FocusItem** — 每个 weakness 字符串创建一条(findFirst + create 幂等去重) 3. **AiJobArtifact** — Result + 每个 FocusItem 各一条(P2002 幂等) - ★ ReviewCard 不在 Projector 中创建(契约 §12 方案 A — EventBus 异步生成) - Bug 修复:knowledgeBaseId 从 Snapshot 读取(不再为 `'unknown'`) - 新增:knowledgeItemId 从 Snapshot 设置(Legacy 未设置) - 注册到 `RESULT_PROJECTORS` multi-provider(`ProjectionExecutor` 自动发现) ### 幂等策略 ``` 入口幂等: tx.aiJobArtifact.findMany(jobId) → 已有 → 直接返回 Result: deterministic ID (fe_<jobId>) + upsert FocusItem: findFirst(userId + title + source) → 已有 → 跳过 create,补 Artifact Artifact: P2002 unique constraint → catch → skip ``` ### 事务原子性 - Result upsert 失败 → 事务回滚(无 FocusItem/Artifact) - FocusItem create 失败 → 事务回滚(Result 不保留) - Artifact create 失败 → 事务回滚(Result + FocusItem 不保留) - markSucceeded 由 `ProjectionExecutor` 在同一事务中调用 ### 修改文件 - `src/modules/ai-job/feynman-projector.ts`(新增,178 行) - `src/modules/ai-job/feynman-projector.spec.ts`(新增,340 行) - `src/modules/ai-job/ai-job.module.ts`(修改,+3 行:1 import + 1 provider + RESULT_PROJECTORS 工厂扩展) ### 代码证据 - `feynman-projector.ts:33` — key = `'feynman_evaluation_projector'` - `feynman-projector.ts:35-57` — 入口幂等检查(已有 Artifact → 直接返回) - `feynman-projector.ts:63-96` — AiAnalysisResult upsert + Artifact(deterministic ID) - `feynman-projector.ts:102-143` — FocusItem 创建循环(findFirst 去重 + knowledgeBaseId 修复 + knowledgeItemId) - `feynman-projector.ts:155-170` — `deterministicResultId()` helper(`fe_` 前缀) - `feynman-projector.ts:172-185` — `upsertArtifact()` helper(P2002 幂等) ### 测试覆盖(16 tests) - ✅ 基础功能:key 正确、ResultProjector 接口实现 - ✅ 核心写入:Result upsert(确定性 ID)、每个 weakness → FocusItem、knowledgeBaseId 修复 - ✅ 边界:weaknesses 空数组、空字符串跳过、Snapshot 缺少字段回退 - ✅ 幂等:入口幂等(已有 Artifact 直接返回)、FocusItem findFirst 去重 - ✅ Artifact 完整性:类型正确(不含 ReviewCard)、ordinal 递增、score metadata - ✅ 失败回滚:Result upsert 失败 → 无 FocusItem、FocusItem create 失败 → 事务回滚 ### 与 ActiveRecallProjector 的关键差异 | 方面 | ActiveRecall | Feynman | |------|-------------|--------| | FocusItem 来源 | `focusItems` 结构化数组(title/reason/suggestion/priority) | `weaknesses` 字符串数组(仅 title) | | ReviewCard | Projector 内直接创建(reviewSuggestion) | 不在 Projector 内(方案 A — EventBus) | | Deterministic ID 前缀 | `ar_` | `fe_` | | knowledgeBaseId 修复 | 无(ActiveRecall 已有) | ★ 从 Snapshot 读取(fix `'unknown'`) | ### 未完成 / 风险 - ReviewCard 的 EventBus 触发点待 M-AI-05-05 在 Engine 中增加(Projector 完成后发布 `ai.analysis.completed` 事件) - Engine 中需增加 `feynman_evaluation` jobType 的执行分派(当前只支持 `active_recall` + 默认路径) ### 是否建议进入 Review - 是 — Projector 已完成,事务原子 + 幂���,建议审核后启动 M-AI-05-05
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: wangdl/api-server#307
No description provided.