分支①第一步(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>
12 KiB
12 KiB
S7 生成主链全文通路(分支①)执行计划(执行版)
- 版本:v1.0
- 日期:2026-07-07
- 承接:评审版 v0.2(现状已三路测绘校正;三个材料级点所有者 2026-07-07 已裁:①独立加密缓存表 ②采纳恒回传完整正文 ③护栏+合规审上移到完整正文)。设计意图/边界/主权红线以 review v0.2 为准,本文只做任务级拆解;两文冲突以 review v0.2 为准并回改本文。
- 读者:执行 agent(每个 S7x 可独立派工)与项目所有者。
0. 执行纪律(每步适用)
- 主权红线(最高优先):完整正文只允许存在于「执行线程内存 / 瞬态加密缓存(TTL+决定即清) / SSE 传输 / 客户端」;任何一步都不得把完整正文写入
muse_ai_suggestion、muse_content_block或任何长期列。每步验收含「候选表 content_snapshot.content 仍是摘要、完整正文不在长期列」的机械断言。 - 完成=机械验证:每步「验收」命令真跑留证;改动后按依赖链回归(ai 模块单测 → local → real-PG;涉 Dify 再跑 live)。
- 契约先行:
chunk.data.content/sequenceNo已声明(docs/api-contracts/ai/openapi.yamlSSEChunkEvent),只填充不改 schema;如需新增瞬态取全文端点则先改契约再实现。DDL 只走sql/muse/V<next>__*.sql(编号取当前最大+1,现状最大 V34)。 - 最小改动 + 中文注释:只动本步清单文件;完整正文相关字段/表必须有「永不落长期库、TTL、决定即清」的中文注释与脱敏日志。
- 回写:每步完成回写
docs/mvp/进度总账.md+muse-module-ai/.agent;不新增过程文档。 - 环境与坑:real-PG/live 按
.agents/knowledge/external-deps-and-gotchas.md§四(清代理、_test库、-am防 stale jar、-DreuseForks=false)。 - 派工分档:各步执行子代理
opus;纯机械(配置值、菜单)可haiku;S7d 的 Dify fail-closed 语义与 live 验收如遇跨模块硬判可升fable或主会话终裁。
1. 步骤总图
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 |
动作
- 运行时结果加不落库全文字段:
facade/MuseAiRuntimeClient.java的RuntimeResult(L70-78)增加仅内存字段(如fullOutput),中文注释写明「provider 完整正文,仅进程内传递,永不持久化到任何列/表;落库只用 outputSummary 摘要」。 - 两个 real client 成功分支捕获完整正文:
RealNewApiMuseAiRuntimeClient.success()(L163-192):在shortOutputSummary(L172)之前保留完整content(L171),先跑护栏(见 3),通过后把完整正文塞进RuntimeResult.fullOutput,仍派生 60 字摘要供落库。RealDifyMuseAiRuntimeClientchat/workflow(L227-285):answer(L230)/text(L253)同理捕获;注意 Dify 完成原因硬编码为常量(L239),不可用作完整性信号。
- 护栏 + 合规审上移到完整正文(裁决③):
MuseAiCandidateReviewService.review(L42-84)的输入从摘要改为完整正文——完整性校验(长度/结构合理性,判断截断,不信 provider finishReason)+ 凭据痕迹合规扫描均作用于完整正文;疑似截断/不合规 → 返回RuntimeFailure(可重试),不产出候选。MuseAiRuntimeProjectionService(L250-283)侧:审通过后content_snapshot.content仍写摘要(L328 不变)、三审字段照旧(L361-377)。 - 落库口径不变:候选表恒为摘要 + 三审;
fullOutput不进outputSummaryMap、不进任何列。
验收
- 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) |
动作
- 瞬态全文存储(仿
MuseAiRuntimePayloadStore):新增sql/muse/V36__ai_generated_fulltext_transient.sql建独立表(key by taskId、加密载荷列、expires_atTTL、租户列)。DDL 版本协调(S6 核验确认):当前最大 V34;S6 的runtime_batch_id迁移占 V35,本瞬态表占 V36;若实际提交顺序不同,按提交时最大版本+1 顺延、勿撞号。,DAO/Store 对齐MuseAiRuntimePayloadStore(L24-46)的加密 + 短 TTL 语义。表注释写明「瞬态出向完整正文,TTL + 决定即清,非系统记录,永不作为正文来源落 Canonical」。 - executor 写入 + 终态 purge:
MuseAiRuntimeJobExecutor.execute(L75-95)在拿到RuntimeResult后、投影前,把fullOutput写入瞬态存储;沿用现有终态finally(L62-64runtimePayloadStore.remove同处)追加瞬态全文的兜底清理(异常/超时也不残留)。 - 事件表发 chunk 引用行:放宽
MuseAiRuntimeProjectionService.appendTaskEvent的isValidTerminalEvent(L319-322)以允许写一个非终态 chunk 事件——sequence_no排在 done 之前,payload_summary只放指向瞬态存储的引用(如{contentRef, sequenceNo}),不含正文。保持每任务至多一条终态的部分唯一索引不变(chunk 非终态,不受该约束)。 - SSE 读侧回取:
MuseAiTaskStreamServiceImpl.chunkData(L279-285)遇到带contentRef的 chunk 行时回瞬态存储取完整正文填content;缓存缺失(已 purge/过期)→content=""(用户已决定,无害)。回放/轮询/重连语义不变(重放 chunk 引用行→再回取,TTL 内可续)。 - 决定即清:采纳(Content
mergeBlockSuggestionowner)与放弃(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(如需) |
动作
- 显示/交接零改动确认:
AIPanel.tsx的streamContent(L48/56)已接线chunk.data.content(L130-132)并实时渲染(L189),handleStreamDone(L63-89)优先流正文(L71-75)→ 完整正文经既有值拷贝链落到CandidatePanel可编辑reviewedText(L97-103)。本步只需回归验证该链在真 chunk 下贯通。 - 采纳恒回传完整正文(裁决②):
useAcceptSuggestion.ts的shouldMergeAfterEdit(L39-51)改为恒带finalContent= 用户审定的完整正文(原样采纳也带);即采纳恒以「用户回传最终正文」语义写 Canonical。须先核对后端suggestion-merges合同:确认accept_as_is + finalContent或统一走modify_then_merge的后端语义,避免前端恒传但后端忽略。契约文档docs/api-contracts/content/*的MergeBlockSuggestionRequest同步(如语义调整)。 - 健壮性:区分「流不完整」与「流为空」:因完整正文不落长期库,中途断连可能只收到截断的流正文,而
handleStreamDone优先非空 ref(L71-75)会静默采信截断。改为:收到 done 但流正文疑似不完整(如与 done 的完整性标记/长度不符)→ 提示重生成,不静默采纳截断;确实为空→回源(此时只剩摘要,明确提示而非当正文采纳)。 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 |
动作
- 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)。 - 完整性信号:Dify 完成原因硬编码常量(
RealDifyMuseAiRuntimeClientL239)不可信 → S7a 的完整正文护栏是唯一截断信号,补 Dify 形态断言。 - fail-closed 反向:
RoutingMuseAiRuntimeClient(L18-53,现有 requiresSourceRefs fail-closed L37-40)补「dify 未配/凭据错 → 拒绝、不回退 New-API」路径与断言。 - New-API 保留:本步不摘 New-API 配置(S8 验收后才摘)。
验收
- live IT(新增/扩展
P1rDify*,opt-in):Dify 生成 → Shadow 候选(三审齐、审的是完整正文)→ 完整正文经 SSE 可见 → 回传采纳写 Canonical;fail-closed 反向(dify 凭据错→拒绝,无 New-API 回退)。 local+real-pg全绿;MSW-offai-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 完成原因不可信(护栏为唯一信号)。