From cb14b40223fc95406210d32b8d200979329e4d9f Mon Sep 17 00:00:00 2001 From: lili Date: Mon, 22 Jun 2026 08:25:28 -0700 Subject: [PATCH] =?UTF-8?q?docs(agent-specs):=20handoff=20=E8=B7=A8?= =?UTF-8?q?=E7=A9=BA=E9=97=B4=E7=BB=9F=E4=B8=80=E6=8E=A5=E5=85=A5=E8=AF=84?= =?UTF-8?q?=E5=AE=A1=E7=89=88(B=20=E7=B1=BB=E5=A4=B4=E5=8F=B7=E6=9E=B6?= =?UTF-8?q?=E6=9E=84=E7=BC=BA=E5=8F=A3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 三方并行调研(后端契约/design-docs SSOT/studio 现状)证实 handoff 红线五域零落地、 后端兑现侧断链(token 不验真、Market 无 consume、agent/content 无兑现入口)。评审版定性 B+C 混合(前端四环统一层 + 后端兑现侧 token 核验),MVP 纵切市场→知识库,分 P1-P4 演进。 含拍板点表/Mermaid 数据流(标注前端缺/后端断链)/blast radius/验收/Open Items。 Co-Authored-By: Claude Opus 4.8 (1M context) --- ...2026-06-22-handoff跨空间统一接入-review.md | 187 ++++++++++++++++++ 1 file changed, 187 insertions(+) create mode 100644 docs/agent-specs/2026-06-22-handoff跨空间统一接入-review.md diff --git a/docs/agent-specs/2026-06-22-handoff跨空间统一接入-review.md b/docs/agent-specs/2026-06-22-handoff跨空间统一接入-review.md new file mode 100644 index 00000000..d4ddca40 --- /dev/null +++ b/docs/agent-specs/2026-06-22-handoff跨空间统一接入-review.md @@ -0,0 +1,187 @@ +# 跨空间 Handoff 统一接入 —— 评审版 + +> 版本:v0.1(评审版) · 日期:2026-06-22 · 目标读者:架构 review + 实现 agent · 类型:评审版(结论先行,无代码细节) +> 上游:[admin+studio 全功能域缺口盘点](../mvp/进度总账.md)横切共性缺口 ①(handoff 跨空间安全交接=架构级缺失,五域全卡)。A 类(接已就绪后端)三切片收官后,转 B 类头号架构缺口。 +> 触发:三方并行调研(后端契约 / design-docs SSOT / studio 现状)证实——handoff 红线在五域**零落地**,且后端**兑现侧本身断链**,纯前端接线无法让红线生效。 + +--- + +## 一、结论先行 + +1. **目标**:把 design-docs 已定义、后端发起侧已就绪、但端到端从未闭合的"跨空间 Handoff 安全交接",落地为**一套统一前端消费层 + 一条真实闭合的兑现链**,使"不信任客户端 URL 参数"红线首次真正生效。先打通**一条端到端纵切**(市场→知识库绑定),再按统一层向其余域铺开。 + +2. **推荐方案(一句话)**:按 SSOT 已给的 `HandoffStore`(前端-05 §25.3)+ token 生命周期(前端-01 §9)实现**统一前端 handoff 层**(创建 token→跳 targetPage→落地 replaceState 清 URL→目标 owner 消费换 session/precheck→确认原子写→返回刷新);**同时补强后端兑现侧 token 核验**(MVP 先 knowledge:兑现时回验 token 属主/未过期/一次性 + Market 加 token 核销使状态推进 completed),否则红线只是"形式落地、实质虚设"。 + +3. **关键依据(design-docs SSOT 已定义,实现须遵守,不得重新发明)**: + - **四模型**:Handoff Token(来源签发一次性凭据)/ Handoff Session(目标消费 token 后的更窄会话)/ Precheck Result(目标 owner 原子消费)/ Return Context(返回来源,只刷状态不补写)——架构-02 §7。 + - **红线**:"不能信任 URL 或客户端参数直接执行高影响动作"——产品-03 §9.2、流程-02B §13、各域 02C §10 / 02D §8 / 02E §3.7 / 02F §9 / 前端-05 反复声明。 + - **固定生命周期**(措辞跨产品-03 §9.3 / 流程-02B §13 一致):发起→落地→消费 token 生成 session→owner 预检→用户确认→原子消费→写 owner 事实→返回刷新。 + - **状态机**(架构-04 §10):Token `issued→consumed/expired/canceled`;Session `active→prechecked→confirmed→consumed`(旁路 rejected/failed/canceled/expired)。 + - **统一性**(强制):各域复用同一机制,由固定生命周期 + 统一不变式集 + 统一 API 契约(后端-05)三重锁定;ADR-014/015 把 Handoff 定为不可被实现层(物理模块/页面入口)改变的逻辑 owner。前端统一抽象 SSOT = 前端-05 §25.3 `HandoffStore` + 前端-01 §9。 + +4. **核心矛盾(本评审的 load-bearing 前提,§二详述)**:红线要求"目标 owner 服务端消费 token 换 session + 重新校验",但**后端兑现侧断链**——Market 无 consume 端点(token 永停 pending)、knowledge 兑现只存 token hash 从不回验、agent/content 兑现入口根本不存在。**因此这不是纯 B 类前端接线,而是 B(前端四环)+ C(后端兑现侧核验补强)混合,且 C 是 B 真实生效的前置依赖。** + +5. **拍板点(§四/§十)**:① 后端兑现侧补强范围(MVP knowledge 一条 vs 一次补全三 owner);② token 核销机制(目标兑现时回调 Market 标 consumed,还是 Market 加显式 consume 端点);③ `targetOwner='content'` 与后端 `/works/` 命名对齐;④ MVP 纵切确认(knowledge 优先)。 + +--- + +## 二、背景:三方现状与核心矛盾 + +### 2.1 后端(发起侧完整、兑现侧断链) + +- **发起侧 4 端点就绪、P1r 验证**(`AppMuseMarketHandoffController`):`POST /assets/{id}/bind-precheck`(拿 authorizationSummaryId,不发 token)→ `POST /handoffs`(拿明文 `handoffToken` + `targetPage`)→ `GET /handoffs/{token}`(只读状态)→ `POST /handoffs/{token}/cancel`(CAS 需 expectedStatus)。token 安全扎实:确定性派生幂等、明文仅创建响应返回、DB 只存 sha256 hash、属主隔离(`MARKET_RESOURCE_FORBIDDEN`)、`targetPage` URL **不带 token**(符合红线)。 +- **兑现侧断链(关键)**: + - Market **无 consume/核销端点**——token 状态永停 `pending`,`completed` 常量定义了但无任何写入路径,`getHandoffStatus` 不反映"目标已绑定"。 + - token 实际预期在**目标空间**兑现:knowledge `POST /works/{id}/knowledge-bindings/prechecks` 的 `PrecheckReqVO` 含 `handoffToken`(market_kb 来源时必填),但 knowledge **只对 token 取 hash 存档、从不回 Market 校验**(属主/一次性/过期);真正落绑定靠 knowledge 自己的 `kbBindPrecheckId`,不是 token。 + - **agent / content owner 兑现入口根本不存在**(白名单含 agent/content,但只有 knowledge 的 market_kb 分支有兑现代码)。 + - `targetPage` 白名单:owner `{agent,knowledge,content}` × action `{slot_bind,bind,asset_use,governance_handle}`,组合不受后端约束。 + +### 2.2 design-docs(SSOT 已完整定义统一机制) + +- handoff **无单一 owner**,分层归属:模型=架构-02 §7、边界=架构-01 §7.3、状态机=架构-04 §10、产品语义/旅程=产品-03 §9、API=后端-05、**前端统一消费=前端-01 §9 + 前端-04 §2.4 + 前端-05 §25.3**。 +- 五域来源→目标映射(产品-03 §9.4 + 前端-05 §25 跳转矩阵): + + | 域 | 角色 | 交接载荷 | 目标侧预检凭据 | + |---|---|---|---| + | 02C 作品台 | **目标**为主 + 返回来源 | agent 槽位绑定 / knowledge 绑定 | 由目标 owner 生成 | + | 02D 智能体 | 目标(接 02C) + 来源(发布到市场) | agent 槽位绑定 | `agentSlotPrecheckId` | + | 02E 知识库 | 目标(接 02C) + 来源(发布到市场) | knowledge 绑定 | `kbBindPrecheckId` | + | 02F 市场 | **纯来源侧**(只发起、不写目标事实) | listing/license/install/source status/return point | 市场不生成预检,只携摘要 | + | 02G 个人中心 | **记录跳转(中转)**,不写业务事实 | 按记录类型携带对象 + 返回点 | 目标空间自建 | + +- 命名提醒:前端-04 用 `targetOwner='content'` 指代作品台,后端绑定走 `/works/{workId}/...`——文档未显式统一,实现侧须对齐(content = 作品/Work 空间)。 + +### 2.3 studio(前端断在 bind-precheck 响应之后) + +- 唯一真实运行代码 = 02F `useBindPrecheck`(MarketAssetDetailPage),打 `bind-precheck`(targetOwner/targetAction **硬编码** 'knowledge'/'bind'),拿 authorizationSummaryId 后**只渲染只读 dl 就停了**。 +- token 消费四环(创建 `POST /handoffs`→跳 targetPage→目标消费→owner 确认)**全缺**;路由层 `useSearchParams`/`replaceState`/`?token` **全仓零命中**(无"凭 token 进目标空间"入口)。 +- 02D slot 绑定是**同空间流**(已安装 agent→槽位,请求体不含 handoffToken),**非**跨空间 handoff;02C/02E/02G 连来源侧入口都没有。 +- 技术债:`openapi.ts` 不导出任何 Handoff 类型;useMarket 手写 `MarketBindPrecheckResult`(字段比 market.ts `BindPrecheckResult` 少、id 用 number 而非 uuid string)——统一时收口。 + +### 2.4 核心矛盾一句话 + +> design-docs 红线("目标 owner 服务端消费 token 换 session + 重新校验")在后端兑现侧**根本没实现**(token 不验真、永停 pending、agent/content 无入口),前端则连发起后四环都缺。**纯前端接线会做出"看似有 handoff、实则 token 形同虚设"的假安全**——这正是 anti-false-green 要拦截的。故必须 B+C 同步。 + +--- + +## 三、范围与非目标 + +**范围(MVP 纵切:市场→知识库一条端到端真闭合)** +- 前端统一 handoff 层:`HandoffStore`(Zustand,对齐前端-05 §25.3)+ 通用创建/落地/消费/确认/取消 hooks + 目标落地路由(凭 token query 进入、`replaceState` 立即清 URL)。 +- `openapi.ts` 导出 Handoff 类型簇(BindPrecheckResult/HandoffCreateResult/HandoffStatusResult),useMarket 收口手写类型漂移。 +- 02F 来源侧:bind-precheck 后续接 `POST /handoffs` → 跳 targetPage。 +- 02E 目标侧:落地页消费 token → `kbBindPrecheck`(回验 token)→ 确认 `createKnowledgeBinding` → 返回来源刷新。 +- **后端兑现侧补强(C)**:knowledge 兑现时回验 token(属主/未过期/一次性)+ token 核销(状态推进 consumed/completed)。 +- 真后端 playwright e2e:市场资产 → 发起 → 跳转 → 知识库落地消费 → 绑定 → 返回,全链 MSW-off 活体。 + +**非目标(本轮)** +- 不一次铺开五域(02C/02D/02G 与 agent/content owner 留演进,见 §六)。 +- 不改后端 token 派生/属主/CAS 既有安全机制(已 P1r 验证,只**补**兑现侧)。 +- 不改 02D 现有同空间 slot bind(保留;新增的是"市场 agent→槽位"跨空间路径,本轮不做)。 +- 不做 handoff 的离线/多端同步(token 短期有效,单端落地)。 + +--- + +## 四、关键决策(拍板点) + +| # | 决策 | 选项 | 推荐 | 理由 | +|---|---|---|---|---| +| D-A | 后端兑现侧补强范围 | (a) MVP 仅 knowledge 一条 + 回验/核销;(b) 一次补全 agent+knowledge+content 三 owner 兑现;(c) 前端先做、后端暂不验真 | **(a)** | (c) 是假安全(违 anti-false-green)直接排除;(b) 工程量大且 agent/content 目标侧 UI 本就缺;(a) 先证红线可端到端落地,knowledge 后端骨架最全(已有 kbBindPrecheck/createKnowledgeBinding) | +| D-B | token 核销机制 | (a) 目标兑现成功后回调 Market 标 token consumed;(b) Market 新增显式 consume 端点供目标调用;(c) 不核销、靠 knowledge precheck 一次性兜底 | **(a) 或 (b),本轮拍一个** | 红线要求 token 一次性 + 状态可反映完成;(c) 留下 token 可重放窗口(虽 knowledge precheck 一次性,但 token 本身未失效)。(a)/(b) 都需后端改动,(b) 契约更清晰、(a) 耦合更松——属人类拍板 | +| D-C | targetOwner 命名 | (a) 前端枚举对齐后端白名单 {agent,knowledge,content},content→作品空间;(b) 前端用 work、转换层映射 | **(a)** | 后端白名单是事实源(`LocalMarketTargetOwnerFacade`),前端对齐避免双向映射;content→作品路由在落地层解析 | +| D-D | 前端统一层抽象边界 | (a) 通用 HandoffStore + 各域提供"目标消费适配器"(precheck 接口 + 确认接口 + 返回路由);(b) 每域独立实现 handoff | **(a)** | SSOT §5 强制统一;各域差异仅"目标 owner 的 precheck/确认端点 + 落地路由",抽象为适配器即可全域复用,一次修受益五域 | +| D-E | MVP 纵切方向 | (a) 市场→knowledge bind;(b) 市场→agent slot;(c) 市场→work asset_use | **(a)** | knowledge 兑现侧后端最接近完整(只差 token 回验),最快证闭环;agent/content 兑现入口为零,做纵切要先补目标侧整套 | +| D-F | URL token 卫生 | (固定,非选项)落地后立即 `replaceState` 移除 token query | **遵守 SSOT** | 前端-01 §9 / 前端-05 §11.3 硬要求,防刷新重复消费;属实现要求不是决策 | + +--- + +## 五、推荐方案数据流 + +```mermaid +flowchart TD + subgraph SRC[来源空间 02F 市场资产详情] + A1[用户:获取授权后发起绑定] --> A2["POST /assets/id/bind-precheck
✅前端已有(止于此)"] + A2 --> A3["POST /handoffs
❌前端缺:拿 handoffToken + targetPage"] + end + A3 -->|"跳转 targetPage?token=...(query)"| B1 + subgraph TGT[目标空间 02E 知识库落地页 ❌前端整段缺] + B1["落地路由解析 token
❌缺:replaceState 立即清 URL"] --> B2["消费 token 换 session
POST .../knowledge-bindings/prechecks(带 handoffToken)"] + B2 --> B3["owner 预检 → kbBindPrecheckId
⚠️后端缺:回验 token 属主/过期/一次性"] + B3 --> B4[用户确认] + B4 --> B5["原子写:POST .../knowledge-bindings(凭 precheckId)
+ ⚠️后端缺:核销 token→consumed"] + end + B5 -->|Return Context 只刷状态| C1["返回来源刷新
GET /handoffs/token 反映 completed"] + A3 -. 失败/取消 .-> X1["POST /handoffs/token/cancel
CAS expectedStatus ❌前端缺"] + + HS["统一 HandoffStore (Zustand)
前端-05 §25.3:session/precheckId/expiry/returnUrl
+ consumeToken/clearSession/isSessionValid
❌前端缺,本轮新建,五域复用"] + HS -.贯穿.-> B1 + HS -.贯穿.-> B4 + + classDef have fill:#d4f8d4,stroke:#2a2; + classDef femiss fill:#ffe0e0,stroke:#c33; + classDef bemiss fill:#fff0c0,stroke:#e90; + class A2 have; + class A3,B1,X1,HS femiss; + class B3,B5 bemiss; +``` + +**图例**:🟢=前端已有;🔴=前端缺(B 类四环 + 统一 Store);🟡=后端兑现侧断链需补强(C 类)。**职责切分(合 SSOT 边界)**:来源(02F)只签发 token,不写目标事实;目标 owner(02E)消费 token 换 session + 自建 precheck + 原子写;统一 HandoffStore 只承载客户端会话态,不承载授权事实。 + +--- + +## 六、演进路径(分阶段,各阶段独立可验证) + +| 阶段 | 内容 | 验证 | 受益域 | +|---|---|---|---| +| **P1(本轮 MVP)** | 统一 HandoffStore + 类型收口 + 02F 发起 `POST /handoffs` + 02E 落地消费 + **knowledge 后端回验/核销** | 真后端 e2e:市场→知识库绑定端到端闭合;红线首次真生效 | 02E + 02F | +| **P2** | agent owner 兑现侧补全(后端 agent precheck 回验 token + 前端落地页)+ 市场→agent 槽位跨空间 handoff | e2e:市场 agent→作品槽位 | 02D + 02F | +| **P3** | 02C 作为目标的统一落地(作品台内 agent 关联页 / 知识来源页发起)+ content owner asset_use | e2e:作品台发起→目标绑定 | 02C | +| **P4** | 02G 记录跳转(中转空间,生成跳转授权→目标重算 gate)+ governance_handle 治理处理 | e2e:个人中心记录→目标空间 | 02G | + +> 统一层(P1 的 HandoffStore + 适配器抽象)一次建成,P2-P4 各域只新增"目标消费适配器"(precheck/确认/返回路由)+ 后端对应 owner 的 token 回验,不重写主链。 + +--- + +## 七、Blast Radius / 兼容 / 回滚 + +- **前端 muse-studio**:新增 `HandoffStore` + 通用 hooks + 目标落地路由(`/...?token=` 解析 + replaceState);02F MarketAssetDetailPage 扩"发起绑定"动作;02E 新增落地页 + 消费 hooks;`openapi.ts` 导出 Handoff 类型 + useMarket 类型收口。**纯增量,不改既有 bind-precheck 只读展示路径**(可并存)。 +- **后端 muse-cloud**:knowledge 兑现侧补 token 回验(调 Market 校验或 Market 暴露核验)+ token 核销(Market `completed`/`consumed` 写入,D-B 拍板后定具体形态)。**触及 module-knowledge + module-market 跨 BC 协作**,需过 `BcBoundaryArchTest`(0 违例硬条件)。 +- **兼容**:现有 02F bind-precheck 行为不变(仅在其后追加发起);knowledge 现有同空间绑定不受影响。token 安全机制(派生/属主/CAS)不动,只**补**兑现侧。 +- **回滚**:前端统一层是新增模块,02F"发起绑定"按钮可 feature-gate;后端 token 回验若出问题,回退到"仅存 hash"(即现状)——但那等于红线不生效,故回退=已知降级而非安全态,需在发布决策里标注。 + +--- + +## 八、风险与缓解 + +| 风险 | 说明 | 缓解 | +|---|---|---| +| **假安全(头号)** | 只做前端、后端兑现侧不验真 → token 形同虚设,红线"形式落地实质虚设" | D-A 排除纯前端选项;P1 验收硬条件 = 后端回验 token(伪造/过期/跨属主 token 必须被拒,e2e 负路覆盖) | +| 跨 BC 边界违规 | knowledge 回验 token 需读 market 态 | 走 market-api 端口(非直连 market.dal);`BcBoundaryArchTest` 0 违例 | +| token 核销机制未定 | D-B 两方案都需后端改 | 本评审拍板 D-B 后再出执行版;P1 不动既有 CAS | +| content/work 命名漂移 | 前端 targetOwner 枚举 vs 后端 /works/ | D-C 对齐 + 落地路由集中解析,单点映射 | +| 02D 同空间/跨空间混淆 | 现有 slot bind 易被误改 | 范围明确隔离:本轮不碰 02D 同空间流,跨空间留 P2 | +| 真后端 fixture 缺资产/授权 | e2e 需已购 active 授权 + 可绑定知识库 | globalSetup 种子补齐(市场资产 + active license + 目标作品/知识库) | +| targetPage 白名单不匹配 | 前端 targetOwner 组合不在后端白名单 → MARKET_TARGET_OWNER_UNAVAILABLE | 前端枚举严格取自后端白名单 {agent,knowledge,content}×{slot_bind,bind,asset_use,governance_handle} | + +--- + +## 九、验收标准 + +- **统一层**:`HandoffStore` 单测(session 存取 / isSessionValid 过期判定 / consumeToken 一次性 / clearSession);类型收口后 `openapi.ts` 导出 Handoff 簇、useMarket 不再手写漂移类型(tsc 绿)。 +- **前端四环**:02F 发起 `POST /handoffs` 拿 token+targetPage;落地页解析 token 后 URL 立即无 token(replaceState 验证);消费换 precheckId;确认写绑定;取消走 CAS。 +- **后端兑现(C,反假绿核心)**:knowledge 兑现**回验 token**——合法 token 通过、过期/跨属主/已消费 token **必须被拒**(对应错误码);token 核销后 `GET /handoffs/{token}` 反映终态。 +- **端到端(真后端活体 playwright,MSW-off)**:市场资产详情 → 发起绑定 → 跳知识库落地 → 消费 token → 预检 → 确认 → `muse_knowledge_binding` 落库 + 返回来源 → 状态刷新为完成。**负路**:伪造/过期 token 落地 → 被拒、不写任何绑定事实。 +- **边界**:`BcBoundaryArchTest` 0 违例;knowledge↔market 仅经端口协作。 +- **回归**:现有全量 e2e(当前 47/0)+ vitest(当前 82)不退;02F 既有 bind-precheck 只读展示与 market-* 测试不破。 + +--- + +## 十、Open Items(须人类拍板) + +1. **(拍板·决定工程量)后端兑现侧补强范围**:D-A——MVP 仅 knowledge 一条 + 回验/核销(推荐),还是本轮即补全 agent+knowledge+content 三 owner?后两者目标侧 UI 本就缺,纵切成本高。 +2. **(拍板·安全机制)token 核销形态**:D-B——目标兑现成功后回调 Market 标 consumed,还是 Market 新增显式 consume 端点供目标 owner 调用?涉及后端跨 BC 协作形态与谁持有"核销 authority"(倾向 Market 持有,因 token 是 Market 签发)。 +3. **(拍板·命名)** `targetOwner='content'` ↔ 后端 `/works/` 对齐确认(content=作品/Work 空间),落地路由单点映射。 +4. **(确认)MVP 纵切 = 市场→知识库绑定**(D-E 推荐),是否同意作为 P1? +5. (实现期验证)knowledge 回验 token 时,Market 提供的核验端口形态(已有 `getHandoffStatus` 是否够用,还是需新增"按 tokenHash 核验属主+有效性"的内部端口)——依赖 D-B 结论。 + +> 下一步:本评审走查确认 §十.1/§十.2/§十.4 三个拍板点后,出**执行版 spec**(统一层模块结构 + 前端四环 hook/路由契约 + 后端兑现侧端口契约 + 边界验收 + 分步落地 + 回滚),再实现 P1 纵切。