docs(agent-specs): S7 全文通路材料点裁决落定 + 执行版 v1.0

三个材料级点所有者 2026-07-07 裁决全部采纳推荐:①独立加密缓存表 ②采纳恒回传完整正文(所见即所写)
③护栏+合规审上移到完整正文。据此产出执行版 v1.0,拆 S7a(捕获+护栏,provider 无关)→S7b(瞬态载体
+SSE 送达,V35 迁移)→S7c(前端所见即所写)→S7d(切 Dify,依赖 workspace)。总账同步。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
lili 2026-07-07 23:25:41 -07:00
parent 2973e8da66
commit ca045e3b13
3 changed files with 139 additions and 2 deletions

View File

@ -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<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/workflowL227-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、采纳/放弃 ownerpurge |
**动作**
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` 优先非空 refL71-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
| | |
|---|---|
| 前置 | S7afullOutput 透出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-inDify 生成 → Shadow 候选(三审齐、审的是完整正文)→ 完整正文经 SSE 可见 → 回传采纳写 Canonicalfail-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 完成原因不可信(护栏为唯一信号)。

View File

@ -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 只重放持久化事件、无内存直推通道,且重连必丢正文,故纯内存现状下不可行。

View File

@ -41,6 +41,8 @@
**2.0.0 单人版改造 S7.0 输出契约裁决(2026-07-07)**:在 live 单体(真 48080 + 真 New-API上取全 plan 要求的三项证据后裁定 **S7 走分支①(建运行时不落库全文通路)**,项目所有者 2026-07-07 确认。证据①SSE 流——后端不发 chunkhistogram chunk=0dispatcher 同步生成完整文本后只写一条 `done``MuseAiTaskStreamServiceImpl.doneData` 只带 `{taskId, suggestionId, summary?}`,全文不经 SSE②UI——CandidatePanel 凭 suggestionId 走 `GET /suggestions/{id}``data.content`③DB——`muse_ai_suggestion.content_snapshot.content` 实测仅 5060 字脱敏摘要(键 `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 auditAI 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/0Ereal-PG `P1rContentCoreCompletedApprovalIT` 13/0F/0E/0S + `P1rContentMergeSuggestionIT` 5/0F/0E/0S + `P1rContentMergeGeneratedSuggestionIT` 1/0F/0E/0Sstudio `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/0Estudio `tsc -b --force` 通过、目标 ESLint 0 errors、Vitest 29/0F/0EMSW-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 archivedSlot unbind DB 核验 revision 2→3、status archived、响应 `sourceStatus=unbound`。边界:本轮未跑 studio 全量 e2e。