diff --git a/docs/agent-specs/2026-07-07-S7-生成主链全文通路-execution.md b/docs/agent-specs/2026-07-07-S7-生成主链全文通路-execution.md new file mode 100644 index 00000000..2ba3a792 --- /dev/null +++ b/docs/agent-specs/2026-07-07-S7-生成主链全文通路-execution.md @@ -0,0 +1,135 @@ +# S7 生成主链全文通路(分支①)执行计划(执行版) + +- 版本:v1.0 +- 日期:2026-07-07 +- 承接:[评审版 v0.2](2026-07-07-S7-生成主链全文通路-review.md)(现状已三路测绘校正;三个材料级点所有者 2026-07-07 已裁:①独立加密缓存表 ②采纳恒回传完整正文 ③护栏+合规审上移到完整正文)。**设计意图/边界/主权红线以 review v0.2 为准,本文只做任务级拆解**;两文冲突以 review v0.2 为准并回改本文。 +- 读者:执行 agent(每个 S7x 可独立派工)与项目所有者。 + +--- + +## 0. 执行纪律(每步适用) + +1. **主权红线(最高优先)**:完整正文只允许存在于「执行线程内存 / 瞬态加密缓存(TTL+决定即清) / SSE 传输 / 客户端」;**任何一步都不得把完整正文写入 `muse_ai_suggestion`、`muse_content_block` 或任何长期列**。每步验收含「候选表 content_snapshot.content 仍是摘要、完整正文不在长期列」的机械断言。 +2. **完成=机械验证**:每步「验收」命令真跑留证;改动后按依赖链回归(ai 模块单测 → local → real-PG;涉 Dify 再跑 live)。 +3. **契约先行**:`chunk.data.content`/`sequenceNo` 已声明(`docs/api-contracts/ai/openapi.yaml` SSEChunkEvent),只填充不改 schema;如需新增瞬态取全文端点则先改契约再实现。DDL 只走 `sql/muse/V__*.sql`(编号取当前最大+1,现状最大 V34)。 +4. **最小改动 + 中文注释**:只动本步清单文件;完整正文相关字段/表必须有「永不落长期库、TTL、决定即清」的中文注释与脱敏日志。 +5. **回写**:每步完成回写 `docs/mvp/进度总账.md` + `muse-module-ai/.agent`;不新增过程文档。 +6. **环境与坑**:real-PG/live 按 `.agents/knowledge/external-deps-and-gotchas.md` §四(清代理、`_test` 库、`-am` 防 stale jar、`-DreuseForks=false`)。 +7. **派工分档**:各步执行子代理 `opus`;纯机械(配置值、菜单)可 `haiku`;S7d 的 Dify fail-closed 语义与 live 验收如遇跨模块硬判可升 `fable` 或主会话终裁。 + +--- + +## 1. 步骤总图 + +```mermaid +flowchart LR + S7a["S7a 捕获+护栏
(provider 无关)"] --> S7b["S7b 瞬态载体+SSE 送达"] + S7b --> S7c["S7c 前端采纳基准+所见即所写"] + S7a --> S7d["S7d 生成 provider 切 Dify"] + S7c --> DONE["S7 验收"] + S7d --> DONE + DEP["依赖: Muse 专属 Dify workspace"] -. 仅 S7d .-> S7d +``` + +**S7a→S7b→S7c 不依赖 Dify workspace,可先行连跑**;S7d 依赖 Dify workspace 就绪,可与 S7c 并行准备、workspace 到位后收口。 + +--- + +## S7a 捕获完整正文 + 护栏上移(provider 无关,不依赖 Dify) + +| | | +|---|---| +| 前置 | 无 | +| 触及 | `muse-module-ai-server`:`MuseAiRuntimeClient`(RuntimeResult)、`RealNewApiMuseAiRuntimeClient`、`RealDifyMuseAiRuntimeClient`、`MuseAiCandidateReviewService`、`MuseAiRuntimeProjectionService` | + +**动作** +1. **运行时结果加不落库全文字段**:`facade/MuseAiRuntimeClient.java` 的 `RuntimeResult`(L70-78)增加**仅内存**字段(如 `fullOutput`),中文注释写明「provider 完整正文,仅进程内传递,永不持久化到任何列/表;落库只用 outputSummary 摘要」。 +2. **两个 real client 成功分支捕获完整正文**: + - `RealNewApiMuseAiRuntimeClient.success()`(L163-192):在 `shortOutputSummary`(L172)**之前**保留完整 `content`(L171),先跑护栏(见 3),通过后把完整正文塞进 `RuntimeResult.fullOutput`,仍派生 60 字摘要供落库。 + - `RealDifyMuseAiRuntimeClient` chat/workflow(L227-285):`answer`(L230)/`text`(L253)同理捕获;注意 Dify 完成原因硬编码为常量(L239),不可用作完整性信号。 +3. **护栏 + 合规审上移到完整正文(裁决③)**:`MuseAiCandidateReviewService.review`(L42-84)的输入从摘要改为**完整正文**——完整性校验(长度/结构合理性,判断截断,不信 provider finishReason)+ 凭据痕迹合规扫描均作用于完整正文;疑似截断/不合规 → 返回 `RuntimeFailure`(可重试),**不产出候选**。`MuseAiRuntimeProjectionService`(L250-283)侧:审通过后 `content_snapshot.content` 仍写摘要(L328 不变)、三审字段照旧(L361-377)。 +4. **落库口径不变**:候选表恒为摘要 + 三审;`fullOutput` 不进 `outputSummary` Map、不进任何列。 + +**验收** +- ai 模块单测:两 adapter 返回 `fullOutput`(日志脱敏,只打长度/hash);护栏对完整正文——截断样本→failure/可重试、正常→pass;合规扫描命中凭据痕迹→failure。 +- 静态/代码审查断言:`fullOutput` 无任何持久化路径(不被 mapper/DO 引用)。 +- `run-p1r-verification.sh local` 全绿。 + +**回滚**:`fullOutput` 字段与护栏输入改动 revert 即回;无 DDL、无契约变更。 + +--- + +## S7b 瞬态载体 + SSE 送达(裁决①:独立加密缓存表) + +| | | +|---|---| +| 前置 | S7a | +| 触及 | 新增瞬态全文表(DDL)、executor、`MuseAiRuntimeProjectionService`(事件写入)、`MuseAiTaskStreamServiceImpl`(读侧 chunk)、采纳/放弃 owner(purge) | + +**动作** +1. **瞬态全文存储(仿 `MuseAiRuntimePayloadStore`)**:新增 `sql/muse/V35__ai_generated_fulltext_transient.sql` 建独立表(key by taskId、加密载荷列、`expires_at` TTL、租户列),DAO/Store 对齐 `MuseAiRuntimePayloadStore`(L24-46)的加密 + 短 TTL 语义。**表注释写明「瞬态出向完整正文,TTL + 决定即清,非系统记录,永不作为正文来源落 Canonical」**。 +2. **executor 写入 + 终态 purge**:`MuseAiRuntimeJobExecutor.execute`(L75-95)在拿到 `RuntimeResult` 后、投影前,把 `fullOutput` 写入瞬态存储;沿用现有终态 `finally`(L62-64 `runtimePayloadStore.remove` 同处)追加瞬态全文的兜底清理(异常/超时也不残留)。 +3. **事件表发 chunk 引用行**:放宽 `MuseAiRuntimeProjectionService.appendTaskEvent` 的 `isValidTerminalEvent`(L319-322)以允许写**一个非终态 chunk 事件**——`sequence_no` 排在 done 之前,`payload_summary` 只放**指向瞬态存储的引用**(如 `{contentRef, sequenceNo}`),**不含正文**。保持每任务至多一条终态的部分唯一索引不变(chunk 非终态,不受该约束)。 +4. **SSE 读侧回取**:`MuseAiTaskStreamServiceImpl.chunkData`(L279-285)遇到带 `contentRef` 的 chunk 行时回瞬态存储取完整正文填 `content`;缓存缺失(已 purge/过期)→ `content=""`(用户已决定,无害)。回放/轮询/重连语义不变(重放 chunk 引用行→再回取,TTL 内可续)。 +5. **决定即清**:采纳(Content `mergeBlockSuggestion` owner)与放弃(reject owner)成功路径 purge 该 taskId 的瞬态全文。 + +**验收** +- real-PG IT(新增,仿 `P1rContentMergeGeneratedSuggestionIT` 模式):真生成后 SSE 重放的 chunk 事件 `content` = 完整正文(来自瞬态存储,长度 > 摘要阈值);**候选表 `content_snapshot.content` 仍是摘要、完整正文不在任何长期列**(主权不变式断言);采纳/放弃后瞬态表该行已删;断线重连(seq 0 全量重放)仍能取到完整正文(TTL 内)。 +- 契约结构门绿(`chunk.data.content` 为已声明字段,非破坏)。 +- `local` + `real-pg` 全绿。 + +**回滚**:以开关门控 chunk 引用行发射 + 瞬态写入;关闭即回退「仅 done」旧行为;V35 迁移向前兼容(表未用即空)。 + +--- + +## S7c 前端接成采纳基准 + 所见即所写(裁决②) + +| | | +|---|---| +| 前置 | S7b | +| 触及 | `muse-studio`:`AIPanel.tsx`、`useAcceptSuggestion.ts`、`sse.ts`(健壮性)、`CandidatePanel.tsx`(如需) | + +**动作** +1. **显示/交接零改动确认**:`AIPanel.tsx` 的 `streamContent`(L48/56)已接线 `chunk.data.content`(L130-132)并实时渲染(L189),`handleStreamDone`(L63-89)优先流正文(L71-75)→ 完整正文经既有值拷贝链落到 `CandidatePanel` 可编辑 `reviewedText`(L97-103)。本步只需回归验证该链在真 chunk 下贯通。 +2. **采纳恒回传完整正文(裁决②)**:`useAcceptSuggestion.ts` 的 `shouldMergeAfterEdit`(L39-51)改为**恒带 `finalContent`** = 用户审定的完整正文(原样采纳也带);即采纳恒以「用户回传最终正文」语义写 Canonical。**须先核对后端 `suggestion-merges` 合同**:确认 `accept_as_is + finalContent` 或统一走 `modify_then_merge` 的后端语义,避免前端恒传但后端忽略。契约文档 `docs/api-contracts/content/*` 的 `MergeBlockSuggestionRequest` 同步(如语义调整)。 +3. **健壮性:区分「流不完整」与「流为空」**:因完整正文不落长期库,中途断连可能只收到截断的流正文,而 `handleStreamDone` 优先非空 ref(L71-75)会静默采信截断。改为:收到 done 但流正文疑似不完整(如与 done 的完整性标记/长度不符)→ 提示重生成,**不静默采纳截断**;确实为空→回源(此时只剩摘要,明确提示而非当正文采纳)。 +4. `quality_check` 事件当前被 AIPanel 丢弃(次要),本步不强制接入。 + +**验收** +- `tsc -b`、`vitest run`、`vite build` 绿;`AIPanel.contract.test.tsx` 扩展「真 chunk→完整正文→恒回传 finalContent」断言。 +- MSW-off Playwright `ai-generation.spec` 扩展:渲染正文长度 > 阈值且与 provider 输出一致(非 60/80 字摘要);采纳后 DB 反查 Canonical 落**完整正文**(长度一致)、归因 ai_suggestion、revision 递增。 +- 隔离/回归:创作主线其余 spec 无回归。 + +**回滚**:revert 前端提交;后端不受影响。 + +--- + +## S7d 生成 provider 切 Dify(依赖 Muse 专属 Dify workspace) + +| | | +|---|---| +| 前置 | S7a(fullOutput 透出);S7b/c 不阻塞;**依赖 Dify workspace 就绪** | +| 触及 | 在用系统 Agent 版本 config、单人配置、`RoutingMuseAiRuntimeClient` fail-closed | + +**动作** +1. **provider 切换**:在用系统 Agent 版本 config 设 `runtimeProvider=dify` + `providerRef.dify`(app=「muse-写作透传」,credentialRef→S1 凭据,须落 Muse 专属 workspace);`muse.ai.dify.enabled=true` 进单人配置;`non-stream-read-timeout-seconds` 对齐 ≥180s 总预算(现 Dify 90/180)。 +2. **完整性信号**:Dify 完成原因硬编码常量(`RealDifyMuseAiRuntimeClient` L239)不可信 → S7a 的完整正文护栏是唯一截断信号,补 Dify 形态断言。 +3. **fail-closed 反向**:`RoutingMuseAiRuntimeClient`(L18-53,现有 requiresSourceRefs fail-closed L37-40)补「dify 未配/凭据错 → 拒绝、不回退 New-API」路径与断言。 +4. **New-API 保留**:本步不摘 New-API 配置(S8 验收后才摘)。 + +**验收** +- live IT(新增/扩展 `P1rDify*`,opt-in):Dify 生成 → Shadow 候选(三审齐、审的是完整正文)→ 完整正文经 SSE 可见 → 回传采纳写 Canonical;fail-closed 反向(dify 凭据错→拒绝,无 New-API 回退)。 +- `local` + `real-pg` 全绿;MSW-off `ai-generation.spec` 真 Dify 复跑绿。 + +**回滚**:在用 Agent `runtimeProvider` 拨回 `new-api` 即回(兼容逻辑未动);配置 revert 独立。 + +--- + +## 2. 全局完成判据 + +- review v0.2 §9 验收全部逐条留证:**主权不变式**(候选/正文表无完整正文、瞬态表决定即清)、**可见**(前端渲染完整正文)、**所见即所写**(Canonical=回传完整正文)、**护栏**(作用于完整正文)、**Dify 形态 + fail-closed**、**local+real-PG 回归**。 +- 里程碑:M1=S7a(捕获+护栏,无 Dify 依赖)/ M2=S7b+S7c(全文经 SSE 可见、所见即所写采纳,仍 New-API)/ M3=S7d(切 Dify)。每 M 点回写总账可暂停评估。 + +## 3. 关键风险(承 review §11) + +瞬态正文暴露(TTL+决定即清+加密+脱敏日志)、断连丢正文致静默采纳截断(S7c 区分不完整/为空)、单 chunk 体积(必要时分片)、Dify 完成原因不可信(护栏为唯一信号)。 diff --git a/docs/agent-specs/2026-07-07-S7-生成主链全文通路-review.md b/docs/agent-specs/2026-07-07-S7-生成主链全文通路-review.md index 9cc689d5..a1e2d56e 100644 --- a/docs/agent-specs/2026-07-07-S7-生成主链全文通路-review.md +++ b/docs/agent-specs/2026-07-07-S7-生成主链全文通路-review.md @@ -99,9 +99,9 @@ flowchart TD **主权不变式(红线,自洽)**:完整正文只存在于 ①执行线程内存 ②瞬态加密缓存(TTL+决定即清) ③SSE 传输 ④客户端。它**永不**写入 `muse_ai_suggestion` / `muse_content_block` 或任何长期列。候选表恒为摘要+三审;Canonical 恒由客户端回传正文写入。「不落库」=不进长期数据模型,瞬态加密缓存是代码库既有的、对称使用的瞬态层;「先审后入」现在有真实完整正文可审。 -## 5. 三个待所有者拍板的材料级点 +## 5. 三个材料级点(所有者 2026-07-07 已裁:三条均采纳推荐) -分支①触及三处主权/契约边界,均非最小改动能自决,请拍板(每条附我方推荐与理由): +分支①触及三处主权/契约边界,均非最小改动能自决。**所有者 2026-07-07 裁决:三条全部采纳下方推荐**——①瞬态全文用独立加密缓存表;②采纳恒回传完整正文(所见即所写);③护栏与合规审上移到完整正文。执行版据此落地。 1. **瞬态全文载体口径**:完整正文瞬态存在于「独立加密缓存表(TTL+决定即清,永不进候选/正文表)」是否满足「原始产出不落长期库」? *推荐:可接受。* 它与代码库既有 `MuseAiRuntimePayloadStore`(入向用户指令)同范式、同保护(加密+短 TTL+终态即删),只是方向相反;durable 数据模型始终不含完整正文。替代是「纯内存直推」,但现有 SSE 只重放持久化事件、无内存直推通道,且重连必丢正文,故纯内存现状下不可行。 diff --git a/docs/mvp/进度总账.md b/docs/mvp/进度总账.md index 053c8a2e..c69cd869 100644 --- a/docs/mvp/进度总账.md +++ b/docs/mvp/进度总账.md @@ -41,6 +41,8 @@ **2.0.0 单人版改造 S7.0 输出契约裁决(2026-07-07)**:在 live 单体(真 48080 + 真 New-API)上取全 plan 要求的三项证据后裁定 **S7 走分支①(建运行时不落库全文通路)**,项目所有者 2026-07-07 确认。证据:①SSE 流——后端不发 chunk(histogram chunk=0),dispatcher 同步生成完整文本后只写一条 `done`,`MuseAiTaskStreamServiceImpl.doneData` 只带 `{taskId, suggestionId, summary?}`,全文不经 SSE;②UI——CandidatePanel 凭 suggestionId 走 `GET /suggestions/{id}` 读 `data.content`;③DB——`muse_ai_suggestion.content_snapshot.content` 实测仅 50–60 字脱敏摘要(键 `content`+`outputSummary`),而原始 LLM 输出 >120 字。结论:现状用户在候选面板只看到 ~50 字脱敏摘要、看不到 AI 全文,分支②“已有全文通路”的前提不成立,AI 生成对用户长期半残。分支① 目标:runtime 内部结果加不落库的内存全文字段,经 SSE 填充 OpenAPI 已声明但当前未发的 `chunk.data.content` 推给前端(不新增破坏性字段),候选表仍只存脱敏摘要+三审,accept 走 merge_after_edit——全文只在传输/内存存在、永不落候选表,保住“AI 原始产出不落库、先审后入”主权。该通路 provider 无关(New-API/Dify 均透出全文到该字段),与切 Dify 正交。落地前置:按工程约定,S7 属跨模块+用户可见行为变更,须先出评审版设计再实现;provider 切 Dify 半段依赖 Muse 专属 Dify workspace 就绪。 +**2.0.0 单人版改造 S7 全文通路评审版+执行版设计(2026-07-07)**:经三路 opus 子代理对后端生成路径/SSE 持久化重放/前端消费的精确测绘,产出 S7 分支①设计(`docs/agent-specs/2026-07-07-S7-生成主链全文通路-review.md` v0.2 + 同名 .html 速览 + `-execution.md` v1.0)。测绘校正了三个关键现状:完整正文只活在两个 real client 的 `success()` 里、构造结果前即被截成 60/80 字摘要出栈丢弃(唯一捕获点);SSE 严格只重放已持久化事件(凡送出必先落 `muse_ai_task_event`)、当前只发无正文的 done、chunk 契约与前端 `streamContent` 渲染早就位但后端从不产 chunk;且「三审」审的也是摘要、Dify 完成原因硬编码常量致截断护栏无效。设计=一条「瞬态全文」通路:捕获处对完整正文跑护栏→写瞬态加密缓存(对称复用既有 `MuseAiRuntimePayloadStore` 范式:独立表+短 TTL+加密+决定即清)→事件表发 chunk 引用行、SSE 重放回缓存取全文填已声明的 `chunk.data.content`(非破坏)→前端 `streamContent` 零改动渲染→采纳恒回传完整正文写 Canonical。主权红线:完整正文永不进候选表/正文表任何列。三个材料级点**所有者 2026-07-07 裁决全部采纳推荐**:①瞬态全文用独立加密缓存表(非 Redis、非落主表);②采纳恒回传完整正文(所见即所写,修掉「原样采纳把摘要写进 Canonical」的潜伏缺陷);③完整性护栏与合规审上移到完整正文(现状审的是摘要)。执行版拆 S7a(捕获+护栏,provider 无关不依赖 Dify)→S7b(瞬态载体+SSE 送达,新增 V35 迁移)→S7c(前端所见即所写+区分流不完整/为空)→S7d(切 Dify,依赖 Muse 专属 workspace)。下一步开工 S7a。 + **E1 content 创作闭环切片(2026-06-27)**:已补 AI suggestion 采纳归档、前端“改后合并”入口、IndexedDB 草稿键对账、旧知识草稿失效、工作台知识/导入/导出/记录入口、Block 版本历史最小 API/UI。Content merge 写 Canonical 与来源归因后,必须由 AI owner 写 `accepted` 状态、accepted decision archive、AI command、business audit;AI owner 不可用时整笔 merge 回滚,避免 Canonical 已写但候选仍 pending。Content 正文变更后通过 Knowledge owner API 将关联 pending draft 标为 `conflicted/needs_recheck` 并写 Knowledge draft decision archive;通知失败不回滚 Canonical 主写,Knowledge confirm 端仍按来源状态 fail-closed。Studio `CandidatePanel` 支持编辑最终正文并按 `accept_as_is`/`modify_then_merge` 提交,IndexedDB 草稿键改为 `workId+blockId+revision` 防跨作品/版本污染。`saveBlock`/`mergeBlockSuggestion` 现在写 `muse_content_block_revision_snapshot`,工作台“历史”Tab 只读展示 Canonical revision 快照;导入可创建真实任务,导出支持范围/格式选择、任务查询与下载凭证消费,知识/记录入口跳转对应工作台。fresh 证据:后端局部单测 `ContentSourceServiceTest` 25/0F/0E、`ContentAppServiceTest` 19/0F/0E、`MuseKnowledgeDraftInvalidationServiceTest` 3/0F/0E、`AiSuggestionMergeProjectionFacadeTest` 7/0F/0E;契约/覆盖门 `ContractFirstGateTest` 4/0F/0E + `P1rApiCoverageReportTest` 8/0F/0E;real-PG `P1rContentCoreCompletedApprovalIT` 13/0F/0E/0S + `P1rContentMergeSuggestionIT` 5/0F/0E/0S + `P1rContentMergeGeneratedSuggestionIT` 1/0F/0E/0S;studio `tsc -b --force` 通过、lint 0 errors、Vitest 12/12,追加 `AIPanel.contract.test.tsx` + `sse.test.ts` 22/0F/0E;导出 UI 追加 `useWorks.test.tsx`+`ExportWorkModal.test.tsx` 11/0F/0E、目标 ESLint 0 errors。共享 PG 写入闸门批准后已补跑 MSW-off Playwright 真后端 `accept-suggestion.spec.ts` 2/0F/0E:正路真 New-API 生成 suggestionId=79、authz `rpe-local-*`、Block revision 160→161、AI owner accepted decision archive 非空;负路 stale revision 业务冲突。边界:`muse-studio/src/types/content.ts` 生成类型因 openapi-typescript 版本漂移未同步,hook 内暂维护 `BlockRevision` 最小类型;完整导入向导已在 RC 后补,见下方记录;真后端导出下载 e2e 已在 2026-06-28 补跑通过,FileApi 异常仍按后端合同 fail-closed。 **E2 ai 智能体生命周期与候选处置闭环(2026-06-27)**:已补用户自建 Agent update/archive、初始版本自动创建、版本列表/激活/归档非当前版本、作品槽位 unbind、Studio reject 调后端 AI owner 决策归档。Agent update 会创建下一 active version 并更新 `current_version_id`,archive Agent 同步归档仍 active 的槽位绑定;slot unbind 将绑定行 revision+1 且 `status=archived`,运行时回到默认能力;WorkspacePage “放弃修改”不再只清本地候选,而是真打 `POST /suggestions/{id}/reject` 写 `rejected` 与 decision archive。启动修红:最新单体启动时暴露 `muse.codegen.importEnable` 缺省,已在 `muse-server/src/main/resources/application.yaml` 补 `import-enable:false`,随后 48080 成功启动并 `GET /app-api/muse/agents` smoke 返回 `code=0`。fresh 证据:后端 AI targeted `MuseAgentServiceTest`+`MuseAgentSlotServiceTest`+Controller annotation 63/0F/0E;契约门 `ContractFirstGateTest` 4/0F/0E;studio `tsc -b --force` 通过、目标 ESLint 0 errors、Vitest 29/0F/0E;MSW-off Playwright 真后端 `agent-create.spec.ts`+`agent-slot-bind.spec.ts`+`accept-suggestion.spec.ts` 5/0F/0E:采纳真生成 suggestionId=80、Block revision 161→162、accepted decision archive 非空;拒绝真生成 suggestionId=81、`status=rejected`、decision archive 非空;Agent 生命周期 DB 核验 v1/v2 current 切换与 v2 archived;Slot unbind DB 核验 revision 2→3、status archived、响应 `sourceStatus=unbound`。边界:本轮未跑 studio 全量 e2e。