分支①第一步(provider 无关):RuntimeResult 加仅内存 fullOutput 字段承载 provider 完整正文 (供 S7b 瞬态通路),两个 real client(New-API/Dify chat+workflow)在派生 60/80 字摘要前捕获 完整正文并跑护栏,通过才透出。护栏 guardFullOutput 纯静态 provider 无关:空/超长/凭据痕迹/ 疑似截断(≥60 字未以句末标点结束)→失败可重试,不信 provider finishReason(Dify 硬编码常量、 New-API 只记录)。「审」输入从脱敏摘要上移到 result.fullOutput(缺省回退摘要,兼容 shadow/历史)。 主权红线(三层亲验):content_snapshot.content 仍写摘要(resolveContentText,不变);完整正文仅 作 review() 局部变量,findings 落库只含布尔/marker 名/长度、审 ID 指纹用 hashCode+length 不可逆, 无正文明文入库;fullOutput 无任何 set/put/序列化/持久化出口,toString 只打 length。 验证:ai 模块 79 用例全绿(MuseAiCandidateReviewServiceTest 11 / MuseAiRuntimeClientTest 22 / MuseAiRuntimeProjectionServiceTest 6 / MuseAiTaskServiceTest 40),BUILD SUCCESS。改动严格 7 文件。 真实生成经护栏的活体验证并入 M1(S7b real-PG)。附:DDL 协调 S7b 瞬态表改 V36(S6 batch 占 V35)。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
136 lines
12 KiB
Markdown
136 lines
12 KiB
Markdown
# 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<next>__*.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 捕获+护栏<br/>(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 完成原因不可信(护栏为唯一信号)。
|