From 2973e8da6694e6350a9f9ebab9309f6b54d9a251 Mon Sep 17 00:00:00 2001 From: lili Date: Tue, 7 Jul 2026 23:16:05 -0700 Subject: [PATCH] =?UTF-8?q?docs(agent-specs):=20S7=20=E7=94=9F=E6=88=90?= =?UTF-8?q?=E4=B8=BB=E9=93=BE=E5=85=A8=E6=96=87=E9=80=9A=E8=B7=AF(?= =?UTF-8?q?=E5=88=86=E6=94=AF=E2=91=A0)=E8=AF=84=E5=AE=A1=E7=89=88=20v0.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 现状经三路测绘实测校正:完整正文只活在 real client success() 里、构造结果前即被截成 60/80 字摘要丢弃(唯一捕获点);SSE 严格只重放已持久化事件、当前只发无正文的 done; "三审"审的也是摘要;Dify 完成原因硬编码常量、截断护栏无效。 设计=一条"瞬态全文"通路:捕获处对完整正文做护栏→写瞬态加密缓存(复用 MuseAiRuntimePayloadStore 范式,TTL+决定即清)→事件表发 chunk 引用行、SSE 重放回缓存取全文填已声明的 chunk.data.content→ 前端 streamContent 已接线零改动→采纳恒回传完整正文(所见即所写)写 Canonical。候选/正文表永不含 完整正文。含 mermaid 数据流 + svg/html 速览 + 现状锚点附录。三个材料级点待所有者裁决。 Co-Authored-By: Claude Opus 4.8 (1M context) --- ...2026-07-07-S7-生成主链全文通路-review.html | 141 ++++++++++++ .../2026-07-07-S7-生成主链全文通路-review.md | 203 ++++++++++++++++++ 2 files changed, 344 insertions(+) create mode 100644 docs/agent-specs/2026-07-07-S7-生成主链全文通路-review.html create mode 100644 docs/agent-specs/2026-07-07-S7-生成主链全文通路-review.md diff --git a/docs/agent-specs/2026-07-07-S7-生成主链全文通路-review.html b/docs/agent-specs/2026-07-07-S7-生成主链全文通路-review.html new file mode 100644 index 00000000..8d0e8659 --- /dev/null +++ b/docs/agent-specs/2026-07-07-S7-生成主链全文通路-review.html @@ -0,0 +1,141 @@ + + + + + +S7 生成主链全文通路(分支①)· 评审版一览 + + + +
+
+

S7 生成主链全文通路(分支①)· 评审版一览

+
让用户看到、审定、采纳 AI 的完整正文,同时守住「原始产出不落长期库、先审后入」主权。
+
v0.2 · 2026-07-07 · 现状已实测校正 · 待两轮评审转执行版 · S7.0 已裁分支①
+
+ +
核心:把「事件顺序(持久)」与「正文载荷(瞬态)」分层——完整正文只走内存+瞬态加密缓存+SSE+客户端,永不进候选表/正文表。
+ +

① 问题:AI 的全文,从未到过用户面前

+
+
全文生成即丢

完整正文只在 real client 的 success() 里存在一瞬,构造结果前就被截成 60/80 字摘要,出栈丢弃;下游只见摘要。

+
SSE 不带正文

同步生成,只发一条 done(候选 id / 完成原因 / 质量分);chunk 契约与前端渲染早就位,但后端从不产生 chunk 行。

+
「审」审的是摘要

合规扫描 / 静态检查作用在 60/80 字摘要上;截断护栏无效(Dify 完成原因是常量、长度检查跑摘要)。

+
+ +

② 目标通路:一条「瞬态全文」链

+
+ 长期数据(主权红线内:只存摘要+三审 / 回传正文) + 瞬态(内存 / 加密缓存 / SSE / 客户端,决定即清) + 护栏(作用在完整正文) +
+
+
real client success()
完整正文唯一存在处 ★捕获
+ +
★完整性+合规护栏
作用在完整正文,不信 provider 完成原因
+ +
★瞬态加密缓存
复用 PayloadStore 范式·TTL·决定即清
+ +
SSE chunk(回缓存取全文)
填已声明 chunk.data.content·非破坏
+ +
前端 streamContent → 可编辑候选
显示/交接零改动
+ +
采纳=回传完整正文
★所见即所写 → Canonical
+
+
+
候选表 muse_ai_suggestion
恒=脱敏摘要+三审(无完整正文)
+ +
Canonical 正文
由用户回传正文写入+归因+revision
+ +
决定即清瞬态缓存
采纳/放弃后物理删除
+
+
主权不变式(红线)

完整正文只存在于:执行线程内存 · 瞬态加密缓存(TTL+决定即清)· SSE 传输 · 客户端。永不写入候选表 / 正文表 / 任何长期列。候选恒为摘要+三审;Canonical 恒由客户端回传正文写入。

+ +

③ 待所有者拍板的材料级点

+
+
瞬态全文载体口径

完整正文瞬态存在于独立加密缓存表(TTL+决定即清、永不进候选/正文表),是否算「不落长期库」?纯内存直推现状不可行(SSE 只重放持久化、重连必丢)。

推荐:可接受,同 PayloadStore 范式
+
采纳=所见即所写

采纳(含原样采纳)恒由客户端回传用户审定的完整正文写 Canonical,后端不再用摘要写正文。

推荐:采纳,兑现主权模型+修潜伏缺陷
+
护栏/审上移到全文

完整性校验与合规扫描从「作用在摘要」改为「作用在完整正文」(捕获处执行)。

推荐:采纳,摘要上做护栏无效
+
+ +

④ 现状 vs 目标

+
+ + + + + + + + + +
维度现状分支①目标
用户看到的正文60/80 字脱敏摘要provider 完整正文
完整正文去向success() 内出栈即丢瞬态加密缓存 → SSE 送达,决定即清
「审」的对象60/80 字摘要完整正文
截断护栏无效(信常量/跑摘要)对完整正文结构化/长度校验
采纳写 Canonical原样采纳写摘要(潜伏缺陷)恒回传完整正文(所见即所写)
候选表 / 正文表不含完整正文不变(仍不含)
生成 providerNew-API可切 Dify(fail-closed,New-API 留到 S8)
+
+ +

⑤ 步骤与依赖

+
    +
  1. S7a 捕获+护栏(provider 无关,不依赖 Dify):结果加不落库全文内存字段,两 adapter 接出完整正文,护栏移到全文。
  2. +
  3. S7b 瞬态载体+SSE 送达:全文写瞬态加密缓存,事件表发 chunk 引用行,SSE 重放回取填 content;候选表口径不变。
  4. +
  5. S7c 前端接成采纳基准+所见即所写:显示/交接零改动;采纳恒回传全文;区分「流不完整/流为空」防静默采纳截断。
  6. +
  7. S7d 生成 provider 切 Dify(依赖 Dify workspace):runtimeProvider=dify+fail-closed,New-API 保留到 S8。
  8. +
+
⚠ 依赖:S7d 需 Muse 专属、与游戏项目隔离的 Dify workspace 就绪(所有者侧基础设施)。S7a–c 不依赖,可先行。
+ +
配套正文见 2026-07-07-S7-生成主链全文通路-review.md(含 mermaid 数据流与现状锚点附录)。本页为「一眼看懂边界与核心」的图文速览,非实现细节。
+
+ + diff --git a/docs/agent-specs/2026-07-07-S7-生成主链全文通路-review.md b/docs/agent-specs/2026-07-07-S7-生成主链全文通路-review.md new file mode 100644 index 00000000..9cc689d5 --- /dev/null +++ b/docs/agent-specs/2026-07-07-S7-生成主链全文通路-review.md @@ -0,0 +1,203 @@ +# S7 生成主链全文通路(分支①)评审版设计 + +- 版本:v0.2(现状已实测校正,待两轮评审转执行版) +- 日期:2026-07-07 +- 承接:[2.0.0 执行计划 S7/S7.0](2026-07-07-2.0.0单人版改造-execution.md) + 总账 S7.0 裁决(分支①,所有者 2026-07-07 确认) +- 读者:执行 agent 与项目所有者 +- 定位:回答「S7 要做什么、为什么这样做、边界在哪、怎么验收」。主文无代码细节;现状锚点集中在文末附录,供执行版落地对照。 + +--- + +## 1. 背景:AI 生成的全文,从未到过用户面前 + +Muse 的 AI 续写走「影子候选 → 先审后入」主权:AI 产出先落 Shadow 候选,用户审阅、编辑后才合并进 Canonical 正文。这套模型隐含一个前提——**用户能看到 AI 写的完整正文**,据此决定采不采、怎么改。 + +实测显示这个前提当前根本不成立,而且比「少发了一个字段」严重得多: + +- **完整正文在生成的那一刻就被丢弃。** provider 返回的完整正文,只在两个真实运行时客户端的 `success()` 方法里以局部变量存在过一瞬,随即被截成 60/80 字的脱敏摘要塞进结果对象,方法返回后完整正文再无任何引用。运行时结果对象本身没有承载完整正文的字段,因此其下游——执行器、投影、数据库、SSE——**从头到尾只见得到那 60/80 字摘要**。 +- **SSE 只发一条终态、且不带正文。** 后端生成是同步非流式的:执行器一次拿到完整结果,只写一条 `done` 事件,载荷是候选 id、完成原因、质量分,没有正文;`chunk` 事件的契约与前端渲染都早已就位,但后端从不产生 `chunk` 行。 +- **连「审」审的也是摘要。** 输出合规扫描与静态检查作用在那条 60/80 字摘要上,而非 provider 完整正文——「先审后入」审的其实是个片段。 +- **截断护栏形同虚设。** Dify 的完成原因被硬编码成常量,根本不反映真实完成度;New-API 的完成原因只记录、不设门;长度检查跑在天然就被截断的摘要上,永不触发。 + +结论:这不是外部依赖问题,也不是 Dify 切换问题,而是**从运行时到用户之间缺一条完整正文通路**,且这条缺失让「可见 / 可审 / 可采纳」三件事同时落空。分支①要补的就是它,且它与切不切 Dify 相互独立。 + +## 2. 目标 + +一句话:**让用户看到、并据以编辑、采纳 AI 生成的完整正文;同时不把 AI 原始产出写进任何长期数据(候选表 / 正文表),守住「原始产出不落长期库、先审后入」主权。** + +可验收的四条: + +1. **可见**:一次生成后,用户在前端看到的是本次生成的完整正文(长度与 provider 实际输出一致),不再是 60/80 字摘要。 +2. **可审**:完整性与合规护栏作用在**完整正文**上(当前只作用在摘要上)——疑似截断或含凭据痕迹→判失败可重试,不静默进候选。 +3. **主权不破**:候选表仍只存脱敏摘要 + 三审结果;Canonical 正文由用户在前端审定、回传的最终正文写入;完整正文只在「内存 / 瞬态加密缓存 / SSE 传输 / 客户端」中存在,用户做出决定后即清除,**永不进入候选表或正文表任何列**。 +4. **provider 可切 Dify**:完整正文透出对 provider 透明,New-API 与 Dify 两个 adapter 都把完整正文送到同一处;在此之上把在用系统 Agent 的生成 provider 切到 Dify,未配置/凭据错时 fail-closed 拒绝,不静默回退 New-API。 + +## 3. 边界(MECE) + +**属于 S7 分支①:** +- 在唯一可捕获处(两个 real client 的成功分支)把完整正文接出到一个**不落长期库**的传递位,并将完整性/合规护栏移到此处对**完整正文**执行; +- 用一套瞬态加密缓存承载完整正文(供 SSE 送达与断线重连),用户决定后即清除; +- 让前端把流到的完整正文接成「采纳基准」,采纳时**恒把该完整正文作为最终正文回传**(所见即所写),Canonical 由回传正文写入; +- 生成 provider 切 Dify(配置 + fail-closed);New-API 配置本步**保留**,到 S8 验收后才摘。 + +**明确不做(边界外):** +- 不做 provider 逐 token 真流式(本步完整正文一次性送达即满足「可见」;真流式是后续独立增强); +- 不改「三审 + 用户回传最终正文采纳」这套主权模型本身,只给它补上「可审的完整正文」与「所见即所写的采纳」; +- 不把完整正文写进候选表 / 正文表 / 任何长期列(红线); +- 不做 S8 导入解析切 Dify(New-API 配置的最终摘除是 S8 的门); +- 不承担 Dify 专属 workspace 搭建(基础设施事项,见依赖)。 + +## 4. 核心机制:一条「瞬态全文」通路 + +设计的技术核心是一个真实存在的张力:**SSE 严格「只重放已持久化事件」——凡经 SSE 送出的每个字节,都必须先落进任务事件表;而主权要求完整正文不落长期库。** 二者直接对冲。破解办法是把「事件的顺序与可靠性」和「正文载荷本身」分层,并复用代码库**已经存在**的瞬态敏感数据范式。 + +一次生成的完整数据流(★=本设计新增/改动的环节): + +```mermaid +flowchart TD + U["用户 · AIPanel 发送指令"] -->|"POST /ai/tasks"| Q["入队 generation+job(queued)"] + Q --> D["Dispatcher 领取 job(@Scheduled 1s)"] + D --> X["Executor 同步执行"] + X -->|"execute(command)"| RC["real client success()
【完整正文唯一存在处】"] + + RC --> G["★ 完整性+合规护栏
作用在完整正文(非摘要)"] + G -->|"疑似截断/含凭据"| FAIL["判失败·可重试
不进候选"] + G -->|"通过"| CAP["★ 接出完整正文到
结果的不落库内存字段"] + + CAP --> SUM["派生 60/80 字脱敏摘要"] + SUM --> SUG["候选落库 muse_ai_suggestion
content_snapshot=摘要 + 三审
(主权:无完整正文)"] + CAP --> TS["★ 完整正文写瞬态加密缓存
(独立表·短 TTL·决定即清)"] + X --> EV["★ 任务事件表写 chunk-ref 行 + done 行
(chunk 载荷只存引用,非正文)"] + + EV --> SSE["SSE streamTask 重放/轮询事件"] + TS -. "★ 重放 chunk-ref 时回缓存取全文" .-> SSE + SSE -->|"chunk.data.content=完整正文"| AP["AIPanel streamContent 实时渲染"] + SSE -->|"done"| AP + AP --> CP["CandidatePanel 可编辑最终正文"] + CP -->|"★ 采纳恒回传完整正文 finalContent"| MG["Content merge_after_edit"] + MG --> CAN["Canonical 正文 = 用户回传正文
+ 归因 + revision"] + MG --> PUR["★ 决定即清瞬态缓存"] + CP -->|"放弃"| PUR + + classDef durable fill:#e8f0ff,stroke:#3358cc,color:#0b2a6b; + classDef transient fill:#fff4e0,stroke:#cc7a00,color:#6b3d00; + classDef guard fill:#e9f9ec,stroke:#2f9e44,color:#12521f; + class SUG,CAN durable; + class CAP,TS,EV,SSE,AP,CP transient; + class G guard; +``` + +四个要点: + +1. **唯一捕获处 + 就地护栏**:完整正文只在两个 real client 的成功分支存在,故完整性/合规护栏必须在这里、对完整正文执行(当前它们跑在下游摘要上、无效)。护栏通过后,把完整正文接出到运行时结果的一个**仅内存、明确不落库**的字段(不破坏「结果只带摘要」的持久化契约——该字段永不进任何列)。 + +2. **正文与事件分层**:完整正文写进一套**瞬态加密缓存**——对称复用代码库既有的 `MuseAiRuntimePayloadStore` 范式(独立表、短 TTL、字段加密、终态即物理删除),它本就是为「入向用户指令」这类瞬态敏感数据设计的,分支①用同一范式装「出向完整正文」。任务事件表里的 `chunk` 行只放一个**指向缓存的引用**,不放正文本身。 + +3. **SSE 重放时回取**:SSE 在重放该 `chunk` 引用行时回缓存取完整正文,填进契约早已声明、当前从不发的 `chunk.data.content`(填充既有字段=契约兼容,非破坏)。断线重连=全量重放事件→再次回缓存取(决定窗口内缓存仍在)→完整正文可续;缓存清除后的迟到重连拿到空正文即可(用户已决定)。前端 `streamContent` 已接线到 `chunk.data.content` 并实时渲染,**显示层零改动**。 + +4. **采纳=所见即所写**:因完整正文自始不落长期库,能写进 Canonical 的完整正文只能来自**客户端回传**。故采纳时前端**恒**把用户审定的完整正文作为最终正文上送(无论改没改过),Canonical 由回传正文写入。这既坐实主权模型「采纳=用户回传所审最终正文」,也修掉当前「原样采纳会把 60/80 字摘要写进 Canonical」的潜伏缺陷。 + +**主权不变式(红线,自洽)**:完整正文只存在于 ①执行线程内存 ②瞬态加密缓存(TTL+决定即清) ③SSE 传输 ④客户端。它**永不**写入 `muse_ai_suggestion` / `muse_content_block` 或任何长期列。候选表恒为摘要+三审;Canonical 恒由客户端回传正文写入。「不落库」=不进长期数据模型,瞬态加密缓存是代码库既有的、对称使用的瞬态层;「先审后入」现在有真实完整正文可审。 + +## 5. 三个待所有者拍板的材料级点 + +分支①触及三处主权/契约边界,均非最小改动能自决,请拍板(每条附我方推荐与理由): + +1. **瞬态全文载体口径**:完整正文瞬态存在于「独立加密缓存表(TTL+决定即清,永不进候选/正文表)」是否满足「原始产出不落长期库」? + *推荐:可接受。* 它与代码库既有 `MuseAiRuntimePayloadStore`(入向用户指令)同范式、同保护(加密+短 TTL+终态即删),只是方向相反;durable 数据模型始终不含完整正文。替代是「纯内存直推」,但现有 SSE 只重放持久化事件、无内存直推通道,且重连必丢正文,故纯内存现状下不可行。 + +2. **采纳语义=所见即所写**:采纳时恒由客户端回传用户审定的完整正文写 Canonical(含原样采纳),后端不再用自己存的摘要写正文。 + *推荐:采纳。* 这正是主权模型「采纳=用户回传最终正文」的字面兑现,并修掉「原样采纳写摘要进 Canonical」的潜伏缺陷。 + +3. **护栏与「审」上移到完整正文**:完整性校验与合规扫描从当前的「作用在 60/80 字摘要」改为「作用在完整正文」(在捕获处执行)。 + *推荐:采纳。* 在天然截断的摘要上做完整性/合规检查是无效护栏;「先审后入」应审真实全文。 + +## 6. 完整性护栏(第 2 目标的落地要点) + +- **护栏对象=完整正文**,在捕获处执行:长度/结构合理性 + 凭据痕迹扫描;疑似截断或不合规→判失败可重试,不静默进候选。 +- **不信任 provider 的完成原因**:Dify 的完成原因是常量、New-API 的只记录不设门,均不可作完整性信号;截断判定只能靠对完整正文的结构化/长度校验。 +- 该护栏 provider 无关,同护 New-API 与 Dify。 + +## 7. 与切 Dify 的关系(正交) + +完整正文通路(§4–§6)provider 无关:两个 adapter 都把 provider 完整正文送到同一捕获/透出位。故通路可**先于 Dify workspace 就绪开工**。provider 切 Dify 只是通路建好后,把在用系统 Agent 的生成 provider 由 New-API 换 Dify(runtimeProvider=dify + providerRef 指向「写作透传」app + 凭据 + 读超时对齐 ≥180s 总预算 + fail-closed 反向),并补 Dify 形态护栏断言。New-API 配置保留到 S8。 + +## 8. 步骤分解(WHAT 已定,HOW 入执行版) + +- **S7a 捕获+护栏(provider 无关,不依赖 Dify)**:运行时结果加不落库的完整正文内存字段,两个 real client 成功分支接出完整正文;完整性/合规护栏移到此处对完整正文执行。 +- **S7b 瞬态载体+SSE 送达**:完整正文写瞬态加密缓存(复用 `MuseAiRuntimePayloadStore` 范式,TTL+决定即清);事件表发 `chunk` 引用行,SSE 重放时回缓存取正文填 `chunk.data.content`;候选表存储口径不变(摘要+三审)。 +- **S7c 前端接成采纳基准+所见即所写**:完整正文经既有 `streamContent`→候选链渲染(显示/交接零改动);采纳改为恒回传完整正文作 finalContent;处理「流不完整」与「流为空」的区分,避免静默采纳截断正文。 +- **S7d 生成 provider 切 Dify(依赖 Dify workspace)**:在用 Agent runtimeProvider=dify+providerRef.dify,`muse.ai.dify.enabled=true`,读超时对齐;fail-closed 反向;New-API 配置保留。 + +S7a–S7c 不依赖 Dify workspace,可先做;S7d 依赖 Muse 专属 Dify workspace 就绪。 + +## 9. 验收(每条须机械证据) + +- **主权不变式(最关键)**:真实 PG 下一次真生成后,`muse_ai_suggestion.content_snapshot.content` 仍是脱敏摘要(长度显著小于 provider 完整输出),完整正文**不在**候选表/正文表任何列;瞬态缓存在采纳/放弃后被清除(查表证明已删)。 +- **可见**:MSW-off 创作主线 e2e 断言前端渲染的正文长度 > 阈值且与 provider 实际输出一致,不再是 60/80 字摘要。 +- **所见即所写**:采纳(含原样采纳)后 Canonical 落的是用户在前端看到/编辑的完整正文(长度一致),非摘要;归因=ai_suggestion、revision 递增。 +- **护栏**:截断样本→失败可重试、不进候选;正常样本→通过;护栏作用于完整正文的证据。 +- **Dify 形态**:live IT——Dify 生成→Shadow 候选(三审齐)→完整正文可见→回传采纳写 Canonical;fail-closed 反向(dify 未配/凭据错→拒绝,不回退 New-API)。 +- **回归**:local + real-PG 全绿;契约结构门绿(`chunk.data.content` 为已声明字段、非破坏)。 + +## 10. 回滚 + +- provider 切换:在用 Agent runtimeProvider 拨回 new-api 即回(兼容逻辑未动)。 +- 全文通路:以开关门控 chunk 引用行发射+瞬态缓存写入+前端恒回传;关闭即回退「仅摘要」旧行为;代码 revert 独立。 + +## 11. 风险与缓解 + +- **瞬态正文暴露**:缓存含完整正文——TTL+决定即清+日志脱敏(不打正文)+字段加密+独立表(对齐既有范式)缓解;候选/正文表永不含正文。 +- **断线丢正文+静默采纳截断**:中途断连可能丢 chunk 尾部,而前端优先采信非空流正文→静默采纳截断——须在前端区分「流不完整(有终态但正文疑似截断)」与「流为空(回源)」,宁可提示重生成,不静默采纳截断(S7c 要点)。 +- **单 chunk 体积**:整章完整正文进单个 SSE 事件——校验无长度上限,必要时分片(仍非逐 token)。 +- **决定后重连**:缓存已清→空正文→可接受(已决定)。 +- **契约兼容**:`chunk.data.content` 已声明→非破坏;须验证前端能吞一个大 chunk。 + +## 12. 依赖 + +- **Dify 专属 workspace(所有者侧)**:S7d 依赖 Muse 专属、与游戏项目隔离的 Dify workspace 就绪(约束已入 `.agents/knowledge/external-deps-and-gotchas.md`)。S7a–c 不依赖,可先行。 + +--- + +## 附录 A · 现状锚点(实测,供执行版对照;均为现状事实,非待写代码) + +模块根 `muse-cloud/muse-module-ai/muse-module-ai-server/src/main/java/cn/iocoder/muse/module/ai/`。 + +**完整正文的唯一存在处 / 捕获点** +- New-API:`facade/RealNewApiMuseAiRuntimeClient.java` `success()` L163-192——完整正文 `content` L171,L172 `shortOutputSummary` 派生摘要(`OUTPUT_SNIPPET_LIMIT=60` L32),构造 `RuntimeResult` L186-187 后 `content` 出栈丢弃。 +- Dify:`facade/RealDifyMuseAiRuntimeClient.java` chat `answer` L230 / workflow `text` L253,L272-273 截 ≤80 字,`RuntimeResult` L238/262。 +- 结果对象无全文字段:`facade/MuseAiRuntimeClient.java` `RuntimeResult` L70-78(仅 `outputSummary` Map L72)。 + +**provider 选路(切 Dify 挂钩点)** +- `facade/RoutingMuseAiRuntimeClient.java` L18-53(new-api/dify/fail-closed);`facade/MuseAiRuntimeProviderSupport.java` L31-42(读 agent 版本 config 的 `runtimeProvider`/`providerRef`,缺省 new-api)。 + +**候选落库与三审** +- `application/muse/MuseAiRuntimeProjectionService.java`:`createRuntimeSuggestion` L250-283;`contentSnapshot` 映射 L324-333(`content`=摘要 L328);三审经 `diffSummary` L361-377 仅 `review.passed()` 时写;`source_status` 恒 active L274。 +- `application/muse/MuseAiCandidateReviewService.java` L42-84——审查作用在 `resolveContentText` 取出的摘要(L336-348),非完整正文(须上移的点)。 + +**SSE:只重放持久化事件** +- `application/muse/MuseAiTaskStreamServiceImpl.java` `streamTask` L66-106、回放 `buildReplaySnapshot` L114-124、轮询 `pollPersistedEvents` L195-232、读侧 `chunkData` L279-285(已就绪)。 +- 写侧终态门禁 `MuseAiRuntimeProjectionService.appendTaskEvent` L294-317、`isValidTerminalEvent` L319-322;`donePayload` L379-386(无正文)。 +- 事件表 `muse_ai_task_event` DDL `sql/muse/V12__extend_ai_real_api_schema.sql` L105-138。 +- 契约 `docs/api-contracts/ai/openapi.yaml`:`SSEChunkEvent` L3519-3537(`content`+`sequenceNo` 已 required)、`SSEDoneEvent` L3564-3586。 + +**可复用的瞬态敏感数据范式(分支①对称复用)** +- `application/muse/MuseAiRuntimePayloadStore.java` L24-46——独立表+30min TTL(`DEFAULT_TTL` L26)+字段加密+终态物理删除(executor `finally` 触发 `remove` L67-73)。 + +**前端** +- `muse-studio/src/features/editor/components/AIPanel.tsx`:`streamContent`/`streamContentRef` L48/56,`onChunk` 接线 L130-132,实时渲染 L189;`handleStreamDone` L63-89 优先采信流正文 L71-75。 +- `muse-studio/src/features/editor/hooks/useAcceptSuggestion.ts`:`shouldMergeAfterEdit` L39-51——仅编辑过才带 `finalContent`(所见即所写要改的写路径)。 +- `muse-studio/src/features/editor/components/CandidatePanel.tsx`:`reviewedText` 可编辑 L97-103。 +- `muse-studio/src/lib/sse.ts`:`connectAIStream` L255-406、重连 dedup L288-312(后端不读 lastEventId,客户端按 SSE id 去重)。 + +## 附录 B · 现状 vs 目标(一图速览的文字版) + +| 维度 | 现状 | 分支①目标 | +|---|---|---| +| 用户看到的正文 | 60/80 字脱敏摘要 | provider 完整正文 | +| 完整正文去向 | success() 内出栈即丢 | 瞬态加密缓存(TTL+决定即清),SSE 送达 | +| 「审」的对象 | 60/80 字摘要 | 完整正文 | +| 截断护栏 | 无效(信 provider 常量/跑摘要) | 对完整正文结构化/长度校验 | +| 采纳写 Canonical | 改过才回传,原样采纳写摘要 | 恒回传完整正文(所见即所写) | +| 候选表/正文表 | 摘要(正文本不落库) | 不变(仍不含完整正文) | +| 生成 provider | New-API | 可切 Dify(fail-closed,New-API 留到 S8) |