# 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/V36__ai_generated_fulltext_transient.sql` 建独立表(key by taskId、加密载荷列、`expires_at` TTL、租户列)。**DDL 版本协调(S6 核验确认)**:当前最大 V34;S6 的 `runtime_batch_id` 迁移占 V35,本瞬态表占 V36;若实际提交顺序不同,按提交时最大版本+1 顺延、勿撞号。,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 完成原因不可信(护栏为唯一信号)。