oh-my-muse/docs/agent-specs/2026-06-22-handoff跨空间统一接入-review.md
lili 9f0d581f38 docs(agent-specs): handoff 评审版记录拍板(三 owner 全补 + Market consume 端点)
用户走查拍板:① 范围=一次补全 knowledge+agent+content 三 owner 兑现侧(非 MVP 单条);
② token 核销=Market 新增显式 consume 端点(核销 authority 留签发方)。据此将出执行版。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 08:32:13 -07:00

19 KiB
Raw Blame History

跨空间 Handoff 统一接入 —— 评审版

版本:v0.1(评审版) · 日期:2026-06-22 · 目标读者:架构 review + 实现 agent · 类型:评审版(结论先行,无代码细节) 上游:admin+studio 全功能域缺口盘点横切共性缺口 ①(handoff 跨空间安全交接=架构级缺失,五域全卡)。A 类(接已就绪后端)三切片收官后,转 B 类头号架构缺口。 触发:三方并行调研(后端契约 / design-docs SSOT / studio 现状)证实——handoff 红线在五域零落地,且后端兑现侧本身断链,纯前端接线无法让红线生效。

【评审结论 · 2026-06-22 已拍板】 ① 实现范围 = 一次补全 knowledge+agent+content 三 owner 兑现侧(§十.1 选最彻底方案,非 MVP 单条); ② token 核销 = Market 新增显式 consume 端点,核销 authority 留签发方 Market(§十.2); ③ 落地仍以 knowledge 为首条打通验证(P1),再并入 agent(P2)/content(P3)——三 owner 全覆盖但按 owner 分阶段独立验证。 据此出执行版:统一前端层(基座)+ 后端 Market consume/核验端口 + 三 owner 兑现回验 + 三套目标落地。下文 §三范围以本结论放大到三 owner 为准(原 MVP 单条措辞仅作首阶段)。


一、结论先行

  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 硬要求,防刷新重复消费;属实现要求不是决策

五、推荐方案数据流

flowchart TD
    subgraph SRC[来源空间 02F 市场资产详情]
      A1[用户:获取授权后发起绑定] --> A2["POST /assets/id/bind-precheck<br/>✅前端已有(止于此)"]
      A2 --> A3["POST /handoffs<br/>❌前端缺:拿 handoffToken + targetPage"]
    end
    A3 -->|"跳转 targetPage?token=...(query)"| B1
    subgraph TGT[目标空间 02E 知识库落地页 ❌前端整段缺]
      B1["落地路由解析 token<br/>❌缺:replaceState 立即清 URL"] --> B2["消费 token 换 session<br/>POST .../knowledge-bindings/prechecks(带 handoffToken)"]
      B2 --> B3["owner 预检 → kbBindPrecheckId<br/>⚠️后端缺:回验 token 属主/过期/一次性"]
      B3 --> B4[用户确认]
      B4 --> B5["原子写:POST .../knowledge-bindings(凭 precheckId)<br/>+ ⚠️后端缺:核销 token→consumed"]
    end
    B5 -->|Return Context 只刷状态| C1["返回来源刷新<br/>GET /handoffs/token 反映 completed"]
    A3 -. 失败/取消 .-> X1["POST /handoffs/token/cancel<br/>CAS expectedStatus ❌前端缺"]

    HS["统一 HandoffStore (Zustand)<br/>前端-05 §25.3:session/precheckId/expiry/returnUrl<br/>+ consumeToken/clearSession/isSessionValid<br/>❌前端缺,本轮新建,五域复用"]
    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 纵切。