oh-my-muse/design-docs/临时-05-market-agent物化执行plan.md
lili 2dde0a45ef docs(mvp): 八域交付盘点+1.0.0交付计划+收尾对齐
- 8域交付盘点(codex多实例+claude深盘、均亲验以代码+真测试为准)→收敛5类系统性病灶
- 新增 docs/mvp/1.0.0-交付计划.md:基准线A最小可用子集、8域对账矩阵、E0-E7 Epic分解、反假绿验证标准
- 进度总账加指针对齐(接口门completed≠端到端可闭环;可闭环严判完成度~55-65%)
- 临时-01~04归档删除(SSE/KB物化过程文档、结论已并入memory与各.agent)、大纲清理悬空引用、临时-05并入E4

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

400 lines
56 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 临时-05 · Market Agent 类资产物化(拷配置 · installed 型 agent · 改授权门)执行 Plan
- 版本v1执行版待人类 review/拍板后执行;本稿只读出代码、不改任何代码、不 commit
- 更新日期2026-06-26
- 目标读者:后端 / 架构 / PR reviewer
- 阅读时间3040 分钟
- 文档性质:**临时件**(执行依据,落定后归并入正式分册或删除,不作长期 SSOT。本稿只给"怎么做",不重定义概念:方案演进与 C/B'/暂缓权衡见 [`临时-02-market-install下游物化方案.md`](临时-02-market-install下游物化方案.md)§2.2 B' 智能体侧、§3.2 智能体 schema 增量为本稿 WHAT 上游KB 类资产物化的姊妹方向(已闭环、本稿照搬其物化范式与开放项结构)见 [`临时-04-market-KB物化-D0fork执行plan.md`](临时-04-market-KB物化-D0fork执行plan.md);术语与 owner 边界以 [`架构-02-核心数据结构与双轨模型.md`](架构-02-核心数据结构与双轨模型.md) 为准§5 智能体运行权限、§6 市场资产、§11.2 绑定安装授权、授权快照"引用对象必须保存快照 id");表结构归 `后端-04` §7 AI/Agent授权快照字符串承载归 ADR-020Source 传播事件驱动归 ADR-017BC 边界规则归 [`.agents/rules/bc-boundaries.md`](../.agents/rules/bc-boundaries.md)。
- 配套人读图:[`临时-05-market-agent物化执行plan.html`](临时-05-market-agent物化执行plan.html)
> **一句话**:市场安装一个智能体后,槽位能绑进去(闸 A 已放宽),但 AI 运行时 `requireVisibleAgent` 仍把 market 来源的 agent 一律拒掉——这是 agent 物化的**唯一真断点**。本 plan 拍板已定的方向是 **①不 fork、拷配置**agent 不像 KB 有"上架后还能往库里加私有文档"的泄露向量:版本一经 active 即不可变、运行时不回读发布者私有 prompt、`config` 本身就是被授权出售的商品本体),**②把市场 agent 物化成安装者租户内一个独立的 installed 型 `muse_agent` + active `muse_agent_version`config 拷自发布者)+ 改运行时授权门放行**。物化落点严格在**绑定路径**(与 KB 同范式)、不在 install 侧。本 plan 把它拆成 **A-source / A-materialize / A-runtime / A-naming / A-verify 五个可独立验证的实现单元**。
>
> **与 KB D0-fork 的根本差异(一句话)**KB 的难点是"发布者私有内容不能泄露",逼出了发布侧 fork 一份只含公开快照的独立 dataset重活agent **没有这层泄露向量**,所以**不 fork、不碰任何外部资源、无发布侧异步工程**——物化退化为"安装侧同步把发布者的 agent 配置拷成一份本地独立 agent",唯一新增的硬约束是**改运行时授权门**trust boundary必须做负路测
---
## 0. 背景与拍板(读过临时-02/临时-04 的人只需看这一节就能对齐)
临时-02 把市场安装的两个物化断点摆开诊断:知识库检索的"数据集门"永远命不中(断点①),智能体在运行期被授权门直接拒(断点②)。临时-04 已经把断点①用 D0-fork 完整闭环(上架 fork 公开副本 + 安装侧物化 installed_ref KB + 检索打通 + 真验私有不泄露commit c12eb01→8b3258e 已真验收)。本 plan 处理**剩下的另一半**——断点②,即智能体类资产的物化。
**拍板已定(三条,本 plan 不再重开权衡)**
1. **不 fork、拷配置**。KB 之所以要 fork是因为 KB 上架后生命周期不冻结、发布者还能继续往自己库里加私有文档,安装者若共享发布者活 dataset 就会读到上架时根本不存在的私有内容。**agent 没有这条链**:一个 agent 版本一旦 active 就不可变(改动只能开新版本行,见 §1 事实 4运行时也不回读发布者的私有 prompt 正文、只用版本里冻结的 `config`,而 `config` 本身就是发布者**亲自写好、上架出售的商品本体**name/description/promptTemplate/slotBindings/toolGrantIds。安装者拿到这份 config = 拿到他买的东西,**这不是泄露、正是交付**。因此 agent 物化不需要 fork 任何东西,直接把发布者那一份 active 版本的 `config` **拷**进安装者租户内一份新建的 agent 版本即可。
2. **物化成 installed 型独立 agent + 改授权门**。对齐临时-04 的 installed_ref 范式:在安装者租户里建一行独立的 `muse_agent`(一个区别于 `system`/`user` 的 installed 型,`source_market_asset_id` 回填溯源)+ 一行 active `muse_agent_version`config 拷自发布者)。光建实体还不够——运行时授权门当前对非 system/非本人 user 一律拒§1 事实 1必须把它**放行"本人所有的 installed 型 + 授权有效"**。这道改动是 trust boundary是本 plan 最需要严防的一点§4 R1 + A-verify 负路测)。
3. **物化落点在绑定路径、不在 install 侧**。与临时-02 §1.2 / 临时-04 完全一致install 维持"只记账、`targetFactsWritten=false`"§1 事实印证物化是目标域ai绑定路径在消费 handoff token、写槽位绑定的**同一事务**里多做的一步。
---
## 1. 执行前必读:把工作量钉死的硬事实(基于真实代码,行号为只读核验所得)
**事实 1 — 运行时授权门 `requireVisibleAgent` 对 market 来源 agent 一律拒,这是 agent 物化的唯一真断点。**
`MuseAiTaskServiceImpl.requireVisibleAgent:321-342` 的可见性判定在 `:329-331``agentType` 不是 `system`、且不是(`user``ownerUserId==agent.ownerUserId`)就抛 `AI_AGENT_SCOPE_FORBIDDEN`。market 出身的 agent 既非 system、也非"本人所有的 user"**必拒**。三个入口全部经它:`:220`agentTest 试运行)、`:308`agentOverrideRef 临时覆盖)、`:315`agentSlotKey→查 slot binding→拿 agentId。门内还有两道`:326-327``agent.status=active``:333-336` 要存在 active 的 `muse_agent_version`(按 versionNo 或最新)。**结论**:物化只要在安装者租户造出"本人所有的 installed 型 active agent + active version",再把 `:329-331` 放行 installed 型,三入口同时通——这是本 plan 的命门A-materialize 造实体、A-runtime 放门。
**事实 2 — 槽位闸 A `requireVisibleActiveSourceAgent` 已对 market 放宽,槽位已能绑 market agent 进去。**
`MuseAgentSlotServiceImpl.requireVisibleActiveSourceAgent:292-314``:304-308`:当 `marketHandoff=true`token 已被 Market verify 通过)时**放宽 agentType 可见性、接纳 market 型 agent**;非 handoff 路径维持原校验。其 precheck`:93`/bindAgentSlot`:158`/consumeMarketHandoff`:350`)链路已闭环,绑定持久化是稀疏 override 的 upsert`:250`02D 首次创建死锁已修commit 9115d4f + V29 唯一约束)。**结论**:闸 A 不是断点、**本 plan 不动它**;断点纯在运行时闸 B事实 1。槽位里存的目前是一个指向 market 出身 agentId 的**裸引用**,物化后要让槽位指向**安装者本地新建的 installed agentId**A-materialize 的去裸引用,类比 KB 的 kb_id 去污染)。
**事实 3 — `MuseAgentDO` 未映射 `source_market_asset_id`,但该列 V27 已就位、休眠。**
`muse_agent` 表的列由 `V4__init_ai_schema.sql:71-91` 定义(`agent_key`/`name`/`description`/`agent_type` DEFAULT 'system'/`owner_user_id`/`prompt_key`/`status` DEFAULT 'active'/`current_version_id`/`slot_bindings` JSONB/`category`/`tags` JSONB唯一键 `uk_muse_agent_key = (tenant_id, agent_key)``V27__extend_ai_agent_handoff_source.sql:11``ALTER TABLE muse_agent ADD COLUMN source_market_asset_id BIGINT`,注释自证"install 自动物化为独立后续主线,本列先就位、对齐 knowledge `muse_knowledge_base.source_market_asset_id`"——**列在、但休眠**`MuseAgentDO.java` 当前只映射到 `tags` 为止,**没有 `sourceMarketAssetId` 字段**。**结论**A-materialize 要给 `MuseAgentDO` 补映射这一列(@TableField),物化时回填,承载溯源 + 幂等键之一。**这是唯一的 schema 触动,且只补 DO 映射、不需新 Flyway 迁移**(列已存在)。
**事实 4 — agent 实体 = `muse_agent` 元数据 + `muse_agent_version.config` JSONB 商品本体version active 即不可变runtime 从 config 取 modelKey/promptTemplateVersion。**
`muse_agent_version``V4:96-110`)的 `config` 是 JSONB承载发布者亲写的 agent 配置;唯一键 `uk_muse_agent_version_agent_ver = (tenant_id, agent_id, version)``status` DEFAULT 'draft'。运行时 `requireVisibleAgent:338-341``config``modelKey`(缺省 `DEFAULT_MODEL_KEY`+ `promptTemplateVersion`(缺省 `DEFAULT_PROMPT_TEMPLATE_VERSION`)组装 `AgentRuntimeRef`**运行时只读这份冻结的 config、不回读发布者任何活数据**。版本一经 active 即不可变(改动开新版本行,由 `(agent_id, version)` 唯一键保证),故"拷一份 active 版本的 config"= 拷一个不会再变的快照。**结论**:这正是"不 fork、拷配置"成立的根据——拷 config = 完整拷下商品本体,无外部资源(无 RAGFlow dataset 那类需要重建索引的东西无后续漂移。A-materialize 拷 config 时须**整体拷贝、保完整性**§4 R3不能只拷部分键导致运行时取不到 modelKey/promptTemplate。
**事实 5 — config 拷贝的来源读取是本 plan 的核心决策点market 当前不回 config且 market 对 ai 零依赖、不能回读发布者 `muse_agent_version`。**
`MuseMarketAssetDO``source_id` BIGINT 指发布者 `muse_agent.id``asset_type``publisher_id``tags` JSONB**只存来源指针 + 元数据,不承载 config 本体**。临时-04 已落的跨 BC 读端口 `MarketAssetSourceApi.getAssetSource(assetId)` 当前回的是 **KB-fork 形状**的 `MarketAssetSourceRespDTO{exists, assetType, sourceId, publisherUserId, assetName, publicForkDatasetId, forkStatus}``MarketAssetSourceApiImpl:37-54` 读 `muse_market_asset` + tags JSONB 里 U-fork 写回的副本状态)——**没有 agent config 这一项**`publicForkDatasetId/forkStatus` 是 KB 专属、对 agent 无意义。而 market 对 ai **零依赖**(不能直读 ai 的 `.dal`、ArchUnit `BcBoundaryArchTest` 拦着),所以 market 自己也回读不到发布者的 `muse_agent_version.config`。**结论**agent 物化要拿到"发布者 active 版本的 config",必须新增一条读 config 的途径——这是本 plan 最大的决策点§3 D1**扩 `MarketAssetSourceApi` 让 market 反向调 ai 读端口带回 config** vs **ai 侧自有读端口 ai 自己读发布者 agent**)。这是 agent 相对 KB 物化最不一样的地方KB 的副本 dataset id 是 market 自己 fork 时写回 tags 的、market 读自有数据即可带回agent 的 config 在 ai 域、market 域天生够不着。
**事实 6 — agent 无私有泄露向量(已实证),故不需要 KB 那套 fork。**
三条实证:①版本不可变(事实 4`(agent_id, version)` 唯一键 + active 不可变);②运行时不回读发布者私有 prompt 正文,只用版本冻结 config事实 4`requireVisibleAgent:338-341`);③`config` 是被授权出售的商品本体(发布者亲写的 promptTemplate/slotBindings/toolGrantIds上架即为供人安装使用。对照 KB 的泄露链(市场对 knowledge 零依赖 → 上架不 fork → 安装者共享发布者活 dataset → 发布者上架后还能往库里加私有文档 → 安装者整库检索读到私有 chunk见临时-04 §0/事实 1**agent 这条链的每一环都不成立**:没有"上架后还能改"的活资源,没有"整库返回"的运行时读放大config 本就是公开交付物。**结论**agent 物化**不需要** fork 公开副本、不需要发布侧异步重活、不需要上架侧任何改动——物化全部落在**安装侧同步**完成(拷 config 是一次本地 DB 写 + 一次跨 BC 读,无外部调用、无长任务)。这是 agent 比 KB **轻得多**的根本原因。
**事实 7 — 可复用与不需要的 KB 已落资产,钉死本 plan 的真实增量。**
**可复用(照搬/扩展)**:①跨 BC 读端口 `MarketAssetSourceApi`(临时-04 已落,本 plan **扩**它支持 `assetType=agent` 放行 + 带 config而非新建第二个端口②handoff token 链路 `MarketHandoffTokenApi`P2 已闭环agent 槽位 handoff 的 `expectedTargetOwner=agent` 已支持,`HandoffVerifyRespDTO.targetOwner/targetAction` 已具备③KB 的 `materializeInstalledRefKb` 物化形态(`MuseKnowledgeBindingService:377-417`:写本地实体 + 幂等键 `(source_market_asset_id, owner_user_id, deleted=false)` + 与绑定/投影同 `@Transactional`——agent 照搬这个形态,把"建 installed_ref KB + dataset binding"换成"建 installed agent + active version + 槽位指本地 agentId"。**不需要agent 没有对应物)**:①`MarketKbListedEvent` 孪生事件agent 无 fork、无上架侧异步物化全在安装侧同步**不引入任何上架事件/消费者**);②`MarketAssetForkApi` 写回端口(那是 fork 状态投影回 market 用的agent 不 fork`KnowledgeMarketForkService`/消费者/兜底 worker 那一整套发布侧异步管线agent 全免)。**结论**agent 的真实增量 = **扩 `MarketAssetSourceApi` 带 configA-source+ 安装侧建 installed agent 拷 configA-materialize+ 改运行时授权门A-runtime+ 命名收口A-naming+ 验证A-verify**,没有任何 fork/异步/外部资源工程。
**事实 8 — 命名漂移须收口O3 开放项)。**
`后端-04`/临时-02 §3.2 文档里写的是 `market_installed`P2 落地的槽位绑定来源标记是 `market``MuseAgentSlotServiceImpl``BINDING_SOURCE_MARKET` / `SOURCE_TYPE_MARKET_AGENT`KB 侧用的是 `installed_ref`;本 plan 拟为 agent 新增的 `agent_type` 值待定(候选 `installed` / `market_installed` / `installed_ref`)。**结论**A-naming 必须在落地前把"安装物化 agent 的 `agent_type` 取值"统一拍定一个值,并同步 `后端-04` 文档、消除文档与实现漂移(事实 3 的 V27 注释已埋"对齐 knowledge `source_market_asset_id`"的伏笔,命名也应与 knowledge 风格一致或显式说明差异)。
> 这八条把 agent 物化的真实成本钉死:**核心增量 = config 跨 BC 读取途径(事实 5决策点 D1+ 安装侧建 installed agent 拷 config事实 4/7+ 改运行时授权门(事实 1trust boundary+ 命名收口(事实 8**KB 那套 fork 公开副本 / 发布侧异步管线 / 上架事件 / 副本资源回收(临时-04 的核心增量),在 agent 下**全部不需要**(事实 6/7
---
## 2. 总览:实现单元、数据流与授权门改造对比
### 2.1 实现单元依赖图
```mermaid
flowchart TD
ASRC["A-source扩跨 BC 读端口带 config核心新读路径<br/>MarketAssetSourceApi 放行 assetType=agent<br/>+ 带回发布者 active 版本 config决策点 D1经 market 反调 ai vs ai 自有读)"]
AMAT["A-materialize安装侧建 installed agent 拷 config<br/>建本地 muse_agent(installed 型)+ active muse_agent_version(config 拷自发布者)<br/>+ DO 映射 source_market_asset_id + 幂等 + 槽位指本地 agentId去裸引用"]
ART["A-runtime改运行时授权门trust boundary<br/>requireVisibleAgent 放行 installed 型 + 本人所有 + 授权快照<br/>三入口(220/308/315)同时通"]
ANAME["A-naming命名收口<br/>installed agent 的 agent_type 取值统一<br/>+ 同步 后端-04 文档"]
AVER["A-verify验证<br/>真 PG ITinstall→物化→bind→runtime 跑 AI 任务命中物化 agent<br/>+ studio e2e + 授权门负路(他人 installed agent 必拒)"]
ASRC --> AMAT
AMAT --> ART
ANAME -.贯穿命名.-> AMAT
ANAME -.贯穿命名.-> ART
ART --> AVER
DEC{"前置决策门人类拍板§3<br/>D1 config 读取途径market 反调 ai vs ai 自有读)<br/>D2 拷 vs 引用 prompt 库<br/>D3 agent_type 命名最终值<br/>D4 installed agent 是否计配额/列表可见"}
DEC --> ASRC
style ASRC fill:#fde2e2,stroke:#c0392b,stroke-width:3px
style ART fill:#fde2e2,stroke:#c0392b,stroke-width:3px
style AMAT fill:#e2f7e8,stroke:#27ae60
style ANAME fill:#e2f7e8,stroke:#27ae60
style AVER fill:#fff6d6,stroke:#b8860b
style DEC fill:#eef3fb,stroke:#3b6fb6,stroke-width:2px
```
### 2.2 拷配置 vs KB fork为什么 agent 不需要 fork
```mermaid
flowchart LR
subgraph PUB["发布者域owner = 发布者)"]
PAGENT["发布者 muse_agent + active muse_agent_version<br/>configpromptTemplate/slotBindings/toolGrantIds<br/>🔒 active 即不可变、不会再变"]
end
subgraph INS["安装者域owner = 安装者)"]
IAGENT["installed 型 muse_agent本人所有<br/>source_market_asset_id=assetId"]
IVER["active muse_agent_version<br/>config = 整体拷自发布者快照"]
end
PAGENT ==>|安装侧同步拷 config<br/>无 fork·无外部资源·无异步| IVER
IAGENT --> IVER
IVER --> RT["运行时 requireVisibleAgent<br/>放行 installed 型 + 本人所有 + 授权有效<br/>→ 从本地 config 取 modelKey/promptTemplate<br/>🟢 命中物化 agentAI 任务可跑"]
NOTE["对照 KBKB 必须 fork 一份只含公开快照的独立 dataset<br/>(因发布者活 dataset 上架后还能加私有文档→整库检索泄露)<br/>agent 无此向量→config 即商品本体、拷下即交付"]
style PAGENT fill:#fde2e2,stroke:#c0392b
style IVER fill:#e2f7e8,stroke:#27ae60,stroke-width:2px
style RT fill:#e2f7e8,stroke:#27ae60
style NOTE fill:#eef3fb,stroke:#3b6fb6
```
### 2.3 安装侧物化 + 运行时数据流agent 物化后)
```mermaid
flowchart LR
A["用户跳转→ai 槽位绑定路径<br/>precheckAgentSlot / bindAgentSlot"]
A --> B["消费 handoff token原有闸 A 已放宽 market 型,不动)"]
B --> C["A-source: MarketAssetSourceApi.getAssetSource(assetId)<br/>assetType=agent 放行 + 带回发布者 active 版本 configD1"]
C --> D{资产存在且=agent?}
D -->|否| E["fail-closed: 拒绝绑定0 写"]
D -->|是| F["A-materialize: 建本地 muse_agent(installed 型)<br/>source_market_asset_id=assetId, owner=安装者, status=active"]
F --> G["建 active muse_agent_version<br/>config=整体拷自发布者快照(保完整性)"]
F --> H["槽位 binding.agentId=本地 installed agentId去裸引用不再指发布者 agentId"]
G --> I["A-runtime: 运行时 requireVisibleAgent(本地 agentId)<br/>放行 installed 型 + 本人所有 + 授权快照<br/>→ 取本地 config → 🟢 命中物化 agent"]
H --> I
style C fill:#fff6d6,stroke:#b8860b
style F fill:#e2f7e8,stroke:#27ae60
style G fill:#e2f7e8,stroke:#27ae60
style I fill:#e2f7e8,stroke:#27ae60
style E fill:#fde2e2,stroke:#c0392b
```
### 2.4 授权门改造前后对比A-runtime · trust boundary
```mermaid
flowchart TD
subgraph BEFORE["改造前 requireVisibleAgent:329-331断点②"]
B1["agentType=system"] -->|否| B2["agentType=user 且 owner==本人?"]
B2 -->|否| BX["🔴 AI_AGENT_SCOPE_FORBIDDEN<br/>market/installed 一律拒"]
B1 -->|是| BOK["放行"]
B2 -->|是| BOK
end
subgraph AFTER["改造后A-runtime 放行 installed"]
A1["agentType=system"] -->|否| A2["agentType=user 且 owner==本人?"]
A2 -->|否| A3["agentType=installed 且 owner==本人<br/>且授权快照有效?"]
A3 -->|否| AX["🔴 SCOPE_FORBIDDEN<br/>(他人 installed / 无授权 仍拒——负路测命门)"]
A3 -->|是| AOK["🟢 放行 installed"]
A1 -->|是| AOK
A2 -->|是| AOK
end
style BX fill:#fde2e2,stroke:#c0392b
style AX fill:#fde2e2,stroke:#c0392b
style AOK fill:#e2f7e8,stroke:#27ae60,stroke-width:2px
```
---
## 3. 实现单元
> 文件路径均为 repo 相对路径。每个单元含 Goal / Files / Approach / Test scenarios输入+预期)/ Verification。测试场景"输入"指具体调用入参或数据状态,"预期"指可断言的可观测结果。
### A-source · 扩跨 BC 读端口带 config + 放行 assetType=agent核心新读路径
**Goal**:让安装侧能跨 BC 拿到"市场 agent 资产的来源 + 发布者 active 版本的 config 本体",作为拷配置的输入。复用临时-04 已落的 `MarketAssetSourceApi`**扩**它支持 `assetType=agent`(不再只服务 KB并解决"market 域够不着 ai 域 config"这一核心矛盾(决策点 D1
**Files**
- 扩跨 BC 读端口(被 ai 依赖):`muse-cloud/muse-module-market/muse-module-market-api/src/main/java/cn/iocoder/muse/module/market/api/asset/MarketAssetSourceApi.java`(已存在,扩语义 + 可能新增带 config 的方法或扩 DTO+ `.../api/asset/dto/MarketAssetSourceRespDTO.java`已存在KB-fork 形状,需为 agent 增 config 字段或拆 agent 专用 DTO见 Approach
- market 侧实现:`muse-cloud/muse-module-market/muse-module-market-server/src/main/java/cn/iocoder/muse/module/market/api/asset/MarketAssetSourceApiImpl.java`(已存在,读 `MuseMarketAssetDO` 的 source_id/asset_type/publisher_idagent config 的来源见 D1
- **D1 选 Amarket 反调 ai 读端口)时新增**ai 侧对外读端口 `muse-cloud/muse-module-ai/muse-module-ai-api/src/main/java/cn/iocoder/muse/module/ai/api/agent/AgentConfigSourceApi.java`(新)+ DTO`{exists, agentType, status, activeVersion, config, ownerUserId}`market 注入它读发布者 active 版本 config范式同 `MarketHandoffTokenApi`(本地 Bean、不挂 Feign
- **D1 选 Bai 自有读)时**:不扩 market改在 ai 绑定路径内由 ai 自己读发布者 `muse_agent_version`(同域读自有 `.dal` 合规),但需 ai 凭 assetId→发布者 agentId 的映射(仍要 market 回 source_id故仍需 `MarketAssetSourceApi` 回 sourceId只是 config 由 ai 自取)。
- 复用范本:`MarketAssetSourceApiImpl`KB 已落的跨 BC 读 + tags 解析写法)、`MarketHandoffTokenApi`(本地进程内 Bean 端口范式)。
**Approach**
**① DTO 形状(与 KB 共存的取舍)**:现有 `MarketAssetSourceRespDTO` 是 KB-fork 形状(带 `publicForkDatasetId/forkStatus`,对 agent 无意义)。两种处置:(a) **给现有 DTO 增 agent 专属字段**`agentConfig`/`agentActiveVersion`KB 字段对 agent 留 null、反之亦然——一个 DTO 承两类资产,简单但字段半数恒 null(b) **按 assetType 拆 agent 专用 DTO/方法**`getAgentAssetSource(assetId)→MarketAgentSourceRespDTO{exists, sourceId, publisherUserId, agentName, agentConfig, activeVersion}`),干净但端口多一个方法。**倾向 (b)**接口隔离KB/agent 各取所需,与临时-04 注释里"读写分离、避免互相依赖用不到的方法"的设计取向一致);最终由实现时定夺并回填。
**② assetType=agent 放行**:现有 `getAssetSource` 对所有 assetType 一视同仁返回 KB 形状;扩为按 `asset.getAssetType()` 分流——`agent` 类资产走 agent 路径(带 config`knowledge_base` 维持现状(带副本)。安装侧据 assetType fail-closedagent 绑定路径只接纳 `assetType=agent`KB 绑定路径只接纳 `knowledge_base`(防止把 KB 当 agent 物化或反之)。
**③ config 读取途径(决策点 D1核心给推荐 + 理由)**——**倾向"market 反调 ai 读端口带回 config选 A"**
- **选项 A推荐market 反调 ai 读端口**。新增 ai 侧 `AgentConfigSourceApi`ai 域读自有 `muse_agent_version` 合规market 的 `MarketAssetSourceApiImpl` 注入它,凭 `source_id`(发布者 agentId读发布者 active 版本 config组装进返回 DTO 一并带给安装侧。
- 优点:①**安装侧只跟一个端口打交道**`MarketAssetSourceApi`),与 KB 物化的调用形态完全对称(安装侧"一次跨 BC 读拿全物化所需信息"②config 读取的 BC 合规性由"ai 读自有 + market→ai 经 -api 端口"双重保证③market 作为资产来源的权威出口,"按 assetId 给来源全貌"语义自洽。
- 代价:引入 market→ai 的新依赖方向(当前 ai→market 已有 `MarketHandoffTokenApi`/`MarketAssetSourceApi` 依赖market→ai 是新方向,需 ArchUnit 确认不成环——market 与 ai 同进程聚合、经 -api 端口不成循环依赖,但需 review
- **选项 Bai 绑定路径自己读发布者 agent**。`MarketAssetSourceApi` 仍只回 source_id发布者 agentIdai 绑定路径凭它读发布者 `muse_agent_version`(同域读自有)。
- 优点:不引入 market→ai 新依赖。
- 代价:①**跨 owner 读**——ai 绑定路径以安装者身份读发布者租户的 agent 版本,要绕过/显式处理租户拦截器(类似临时-03 被 D0-fork 消解掉的"跨 owner 读发布者 dataset"难题,在 agent 这里若选 B 会重新出现②config 读取逻辑散在绑定路径、不如端口聚合清晰。
- **权衡结论**:选 A 让安装侧调用对称、避免跨 owner 读难题B 的真痛点代价market→ai 新方向)经 -api 端口 + ArchUnit 可控。**推荐 A**;若 review 认为 market→ai 依赖方向不可接受,退 B 并显式处理跨 owner 读A-materialize ⑤ 标注)。
**④ 凭据/审计**:跨 BC 读 config 不涉及外部服务、无 key但 config 可能含 promptTemplate 正文,端口日志**不打印 config 内容**(只记 assetId/agentType/版本号),避免商品本体进日志。
**Test scenarios输入+预期)**
- agent 资产来源 + config 带回输入assetId=Xasset_type=agentsource_id=发布者 agentId=GG 有 active version v1 config={name,promptTemplate,modelKey,...}),调 `getAgentAssetSource(X)`|预期:返回 `{exists=true, assetType=agent, sourceId=G, publisherUserId, agentConfig=v1 的完整 config, activeVersion=v1}`config 各键齐全(含 modelKey/promptTemplate
- 非 agent 资产输入assetId 指向 knowledge_base 类型预期agent 读路径 fail-closedexists=false 或显式 assetType≠agent安装侧据此拒绝按 agent 物化)。
- 资产不存在|输入:错 assetId/跨租户|预期:`notFound()`exists=false安装侧 fail-closed。
- 发布者无 active 版本输入G 存在但无 active muse_agent_version预期带回 config=null / activeVersion=null安装侧 fail-closed不物化出一个无 config 的空 agent
- BC 边界输入ArchUnit `BcBoundaryArchTest`|预期:选 A 时 market→ai 经 -api 端口、不直读 ai `.dal`且不成循环依赖test 绿。
**Verification**
- 模块单测:`cd muse-cloud && mvn -pl muse-module-market/muse-module-market-server -am test -Dtest='MarketAssetSourceApiImplTest'`mock ai 读端口,覆盖 agent 分流 + config 带回 + 非 agent/不存在 fail-closed
- **改了 market-api / ai-api 模块务必先 `mvn -pl muse-module-market/muse-module-market-api -am install`(及 ai-api** 再跑依赖模块测试,否则 stale jar 假红memory `muse-market-install-isacquired-enrich` 教训)。
- ArchUnit`mvn -pl ... test -Dtest=BcBoundaryArchTest`(确认 market→ai 不成环)。
---
### A-materialize · 安装侧建 installed agent 拷 config照搬 KB materializeInstalledRefKb 形态)
**Goal**:在 ai 目标域绑定路径里,把"槽位写裸引用"升级为"建出可用的本地 installed 型 agent + active versionconfig 拷自发布者)+ 槽位指向本地 agentId"。做到 `MuseAgentDO` 补映射 `source_market_asset_id` + 幂等 + config 整体拷贝 + 去裸引用。**与 KB U-materialize 同范式、更轻**(无 dataset、无 fork、无外部调用
**Files**
- DO 补映射(事实 3`muse-cloud/muse-module-ai/muse-module-ai-server/src/main/java/cn/iocoder/muse/module/ai/dal/dataobject/muse/MuseAgentDO.java`(加 `private Long sourceMarketAssetId;` + @TableField,列 V27 已在、不需新迁移)
- 主改:`muse-cloud/muse-module-ai/muse-module-ai-server/src/main/java/cn/iocoder/muse/module/ai/application/muse/MuseAgentSlotServiceImpl.java``precheckAgentSlot:93``bindAgentSlot:158``consumeMarketHandoff:350`、绑定持久化 upsert `:250`
- installed agent 写入 DAL`.../ai/dal/mysql/muse/MuseAgentMapper.java`(复用 insert + 按 source_market_asset_id 幂等查)、`MuseAgentVersionMapper.java`(复用 insert active version
- 参照范本(照搬物化形态):`muse-cloud/muse-module-knowledge/muse-module-knowledge-server/src/main/java/cn/iocoder/muse/module/knowledge/application/muse/MuseKnowledgeBindingService.java``materializeInstalledRefKb:377-417`:幂等键 `(source_market_asset_id, owner_user_id, deleted=false)` + 建本地实体 + 与绑定同 `@Transactional`§128-139 precheck 固化物化所需事实、bind 阶段读回建实体的两段式)
**Approach**
1. **DO 补映射**`MuseAgentDO``sourceMarketAssetId`@TableField 普通列V27 已建 `source_market_asset_id BIGINT`)。这是物化溯源 + 幂等键的载体,**唯一的 schema 触动且不需新迁移**(事实 3
2. **precheck 固化 + assetType 校验(照搬 KB 两段式)**`precheckAgentSlot` 对 market 来源(`source_owner=market`,事实 2调 A-source 的 `getAgentAssetSource(assetId)` 校验"资产存在且 `assetType=agent` 且发布者有 active 版本 config"fail-closed不存在/非 agent/无 config 拒),并把"发布者 agentId + 待拷 config + 授权快照"固化进 precheck 快照供 bind 读回(避免 bind 时再跨 BC 读)。**precheck 不建任何 agent 实体**(避免 precheck 失败留孤儿 agent 行,与 KB `materializeInstalledRefKb` 注释同理)。
3. **bind 阶段建 installed agent落点`bindAgentSlot` 同一 `@Transactional`**
- insert `muse_agent``agent_type=<installed 取值A-naming 定>``source_market_asset_id=assetId``owner_user_id=安装者``status=active``name/description/prompt_key/category` 取自拷来的 config、`agent_key=<安装者维度唯一>`**注意 `uk_muse_agent_key=(tenant_id, agent_key)`**,事实 3installed agent 的 agent_key 须保证安装者租户内唯一,建议 `installed-{assetId}-{ownerUserId}` 或带 uuid避免与发布者/其他 installed agent 撞键);拿到**本地新建 agentId**。
- insert `muse_agent_version``agent_id=本地agentId``version=1`(或拷发布者版本号)、`config=整体拷自发布者 active 版本 config``status=active`**config 整体拷贝**保完整性R3拿到 versionId 回写 `muse_agent.current_version_id`
- **去裸引用**:槽位 binding 的 `agentId` 落**本地新建 agentId**(不再是发布者 agentId`agentVersion` 落本地版本号;建实体放 `bindAgentSlot`(用户确认绑定的写事实点)。
4. **幂等(照搬 KB**:建 installed agent 前先 `SELECT ... WHERE source_market_asset_id=assetId AND owner_user_id=安装者 AND agent_type=<installed> AND deleted=false`,命中则复用既有 installed agentId连同其 active version不重复建并发由 `uk_muse_agent_key` 唯一键 + `DataIntegrityViolationException`→回读模式兜(仿 KB `persistDatasetBinding` 的回读幂等)。**这保证同一安装者对同一资产多次绑定只有一行 installed agent。**
5. **授权快照承载**installed agent 的授权溯源沿用 ADR-020 字符串 envelope槽位 binding 的 `authorization_snapshot_id` 已 V31 VARCHAR 直透传(事实见 `MuseAgentSlotServiceImpl:281-282`),不回退 BIGINT/parseLong。物化 agent 实体若需携带授权快照,沿用 VARCHARD4 决策)。
6. **事务原子性**:物化(建 agent + version与"消费 handoff token + 写槽位绑定"在同一 `@Transactional`(现状 `bindAgentSlot:157`/`consumeMarketHandoff:348` 已沿用调用方事务),失败整体回滚。
7. **多安装者各建各的**:不同安装者各建自己的 installed agent各自 owner + 各自 agent_keyconfig 各拷一份agent config 是轻量 JSONB不像 KB dataset 那样有共享必要;**agent 无"副本仅 1 份"概念**——这是与 KB 的又一差异KB 副本 dataset 重、N 安装者共享只读一份agent config 轻、各拷各的最简单且无泄露顾虑)。
**Test scenarios输入+预期)**
- 首次安装绑定输入T_A 安装 assetId=Xasset_type=agent发布者 active config 完整),`bindAgentSlot`(合法 handoff token + precheckId + slotKey预期新建 `muse_agent(agent_type=installed, source_market_asset_id=X, owner_user_id=A, status=active)` 记 id=G2新建 `muse_agent_version(agent_id=G2, status=active, config=X 的完整 config)``muse_agent.current_version_id` 指向该 version槽位 binding.agentId=**G2**(非发布者 agentId
- config 完整性|输入:发布者 config 含 {name, description, promptKey, promptTemplate, slotBindings, toolGrantIds, modelKey, promptTemplateVersion}|预期:拷出的本地 version config **逐键齐全**(运行时取 modelKey/promptTemplate 不落缺省、不丢 toolGrantIds
- 多安装者各建各的输入A、B 各装 assetId=X预期建两行 installed agentG2、G3各自 owner + 各自唯一 agent_key各有自己的 active versionconfig 各一份),不违反 `uk_muse_agent_key`
- 幂等-重复绑定|输入:同一 (A, X) 再次 bind预期不新建第二行 installed agent复用 G2不抛 500。
- 资产不存在/非 agent输入assetId 指向不存在 / knowledge_base 类型预期fail-closed 拒绝0 写 agent/version/binding。
- agent_key 唯一键不撞输入A 装 X、又装另一 agent 资产 Y预期两行 installed agent 的 agent_key 互不相同(按 assetId 维度),不触发 `uk_muse_agent_key` 冲突。
- BC 边界输入ArchUnit `BcBoundaryArchTest`预期ai 经 `MarketAssetSourceApi` 读、不直读 market `.dal`test 绿。
**Verification**
- 模块单测:`cd muse-cloud && mvn -pl muse-module-ai/muse-module-ai-server -am test -Dtest='MuseAgentSlotServiceTest'`mock `MarketAssetSourceApi`,覆盖建 installed agent + config 完整拷贝 + 幂等 + 多安装者 + fail-closed
- **改了 market-api/ai-api 先 `mvn -pl ...-api -am install`** 再跑 server 模块测试stale jar 假红教训)。
- ArchUnit`mvn -pl ... test -Dtest=BcBoundaryArchTest`
---
### A-runtime · 改运行时授权门放行 installed 型trust boundary本 plan 最需严防处)
**Goal**:把运行时授权门 `requireVisibleAgent` 从"只放行 system / 本人 user"扩展为"**+ 本人所有的 installed 型 + 授权快照有效**"使三入口agentTest/agentOverrideRef/agentSlotKey对物化出的 installed agent 同时放行;**同时严守边界——他人的 installed agent、无授权的 installed agent 仍必须拒**(这是 trust boundary负路测是验收命门
**Files**
- 主改(唯一断点):`muse-cloud/muse-module-ai/muse-module-ai-server/src/main/java/cn/iocoder/muse/module/ai/application/muse/MuseAiTaskServiceImpl.java``requireVisibleAgent:321-342`,可见性判定 `:329-331`;三入口 `:220`/`:308`/`:315` 不改、自动随门放行)
- 不改:闸 A `MuseAgentSlotServiceImpl.requireVisibleActiveSourceAgent`(事实 2已放宽、与运行时是两套门运行时是本单元唯一战场
**Approach**
1. **放行条件扩展(精确改 `:329-331`**:原判定"非 system 且 非(user 且本人)→拒"扩为"非 system 且 非(user 且本人) 且 非(installed 且本人 且授权有效)→拒"。即新增一条放行支:`agent_type=<installed 取值> 且 ownerUserId==agent.ownerUserId 且 授权快照有效`。**三个收紧点缺一不可**:①必须是 installed 型(不放开 market 裸引用——物化后槽位指的是本地 installed agent不存在裸 market 引用进运行时);②必须本人所有(`ownerUserId` 比对,防越权用他人 installed agent③授权快照有效防授权已撤销/过期仍可跑)。
2. **授权有效性判定**installed agent 行的 `source_market_asset_id` + 授权快照ADR-020 VARCHAR envelope钉死"凭哪份授权可用"。运行时校验授权快照有效(非 `blocked:` 前缀等,参照闸 A `rejectBlockedSource:326-330` 的 envelope 语义),授权失效则拒(与召回/下架回滚开放项联动§5
3. **三入口自动覆盖**`:220`/`:308`/`:315` 都调 `requireVisibleAgent`,改门体即三入口同时生效,无需逐入口改(与 KB 检索"改数据正确即第四门自然通"异曲同工——这里是"改门一处即三入口通")。
4. **status/version 两道维持**`:326-327`agent.status=active+ `:333-336`active version 存在不放松installed agent 物化时已写 activeA-materialize自然满足。
**Test scenarios输入+预期)**
- 本人 installed agent 放行正路输入A 物化的 installed agent G2active + active version + 授权有效A 发起 AI 任务命中 G2经 slotKey/override/agentTest 任一入口)|预期:`requireVisibleAgent` 放行,返回 `AgentRuntimeRef`(从 G2 active version config 取 modelKey/promptTemplate不抛 SCOPE_FORBIDDEN。
- **他人 installed agent 必拒负路trust boundary 命门)**输入A 物化的 installed agent G2**B** 试图用 G2伪造 agentOverrideRef 或越权 slotKey预期`AI_AGENT_SCOPE_FORBIDDEN`ownerUserId 不符0 放行——**这是本 plan 安全验收的核心断言**。
- 授权失效的 installed agent 必拒负路输入A 的 installed agent G2 但授权快照已 blocked/撤销|预期:拒(授权无效支),不放行。
- market 裸引用仍拒(负路,防回归)|输入:直接用一个未物化的 market 出身 agentId非 installed 型、非本人 user预期仍抛 SCOPE_FORBIDDEN只放行 installed 型本地实体,不放开 market 型本身)。
- system/本人 user 不回归输入system agent / 本人 user agent预期照旧放行原有行为不变
- 三入口一致|输入:同一 installed agent 分别经 agentTest(:220)/override(:308)/slotKey(:315)|预期:三入口行为一致(都放行本人 installed、都拒他人 installed
**Verification**
- 单测:`mvn -pl muse-module-ai/muse-module-ai-server -am test -Dtest='MuseAiTaskServiceTest'`(覆盖正路放行 + **他人 installed 必拒** + 授权失效必拒 + market 裸引用必拒 + system/user 不回归 + 三入口一致)。**负路测是本单元验收门,缺负路不算通过**。
- 活体并入 A-verify 端到端(真跑 AI 任务命中物化 agent + 真验他人不可用)。
---
### A-naming · 命名收口(贯穿 A-materialize / A-runtime
**Goal**:把"安装物化 agent 的 `agent_type` 取值"统一拍定一个值,消除文档(`market_installed`/ P2 落地(`market`/ KB 风格(`installed_ref`/ 本 plan 拟新增(`installed`)的四处漂移(事实 8并同步 `后端-04`
**Files**
- 文档同步:`design-docs/后端-04-统一数据库Schema-v1.md`§7 AI/Agent`muse_agent.agent_type` 取值定义;当前写 `system/user/market_installed`,按拍定值订正)
- 落地一致性A-materialize 写入的 `agent_type` 值 + A-runtime 放行判定的 `agent_type` 值必须用同一常量(建议 ai 模块内定义常量,避免散字符串漂移)
**Approach**
1. **取值候选与倾向**`installed`(简洁、与 KB `installed_ref` 风格呼应但更短)/ `market_installed`(与 `后端-04` 现有文档一致、语义最明确)/ `installed_ref`(完全对齐 KB。**倾向 `market_installed`**(与 `后端-04` 既有文档零漂移、语义自明"市场安装来的",且 V27 注释已写"对齐 knowledge `source_market_asset_id`"暗示与 knowledge 风格协调即可、不强求同名最终由人类拍D3
2. **单一来源**:拍定后 A-materialize/A-runtime 共用一个常量,`后端-04` 同步订正,避免再次漂移。
3. **与 `BINDING_SOURCE_MARKET` 区分**:注意 `agent_type`agent 实体类型)与槽位 binding 的 `binding_source=market`(绑定来源标记,事实 2是两个维度**不要混淆**——物化后 binding_source 仍可标 market来源是市场但 binding 指向的 agent 其 agent_type 是 installed实体是本地物化的。A-naming 须在文档里点明这一区分,防后续 agent 误把两者当一回事。
**Test scenarios输入+预期)**
- 取值一致性输入grep `agent_type` 写入点与放行判定点|预期:用同一常量、无散字符串。
- 文档零漂移|输入:`后端-04` §7 `agent_type` 取值 vs 代码常量|预期:一致。
**Verification**:人工核对 `后端-04` 与代码常量一致A-materialize/A-runtime 单测断言用同一常量值。
---
### A-verify · 验证(真 PG 端到端 + studio e2e + 授权门负路)
**Goal**:用真实证据证明"安装 agent→物化 installed agent→槽位绑定→运行时跑 AI 任务命中物化 agent"端到端可真用;且**授权门负路成立**(他人 installed agent / 无授权必拒——trust boundary 的真验收)。
**Files**
- 真 PG IT`muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rMarketAgentMaterializationIT.java`(参照 KB 的 `P1rMarketKbForkMaterializationIT` + 既有 `P1r*IT` live 范式)
- studio e2e`muse-studio/e2e/market-install-agent-runtime.spec.ts`(参照 `agent-slot.spec.ts` + `market-install.spec.ts`,及 KB 的 `market-install-kb-retrieval.spec.ts` 范式)
- 单测(已在各单元):`MuseAiTaskServiceTest`(授权门负路)/ `MuseAgentSlotServiceTest`(物化)/ `MarketAssetSourceApiImplTest`(带 config
**Approach / Test scenarios输入+预期)**
1. **install→物化→bind→runtime 端到端(真 PG**|输入:发布者建 agentactive versionconfig 完整)→ admin 审核上架成 market agent 资产 → 用户安装该资产 → 槽位绑定(物化 installed agent + version + 槽位指本地 agentId→ 发起 AI 任务命中该槽位|预期:运行时 `requireVisibleAgent` 放行物化 installed agent → 真 New-API 出候选AI 外部依赖在 mini-infra 100.64.0.8 在线memory `muse-ai-external-deps-online`);对照现状(必 `AI_AGENT_SCOPE_FORBIDDEN`)。
2. **授权门负路trust boundary 核心验证)**输入A 物化 installed agent G2B 试图用 G2 跑 AI 任务(伪造 agentOverrideRef预期`AI_AGENT_SCOPE_FORBIDDEN`B 拿不到 G2——**这是 agent plan 安全验收的命门,缺此不算通过**。
3. **config 完整性真验**|输入:发布者 config 含特定 promptTemplate/modelKey/toolGrantIds预期物化 agent 真跑时用的是拷来的 config候选反映发布者 prompt 行为、modelKey 生效),不落缺省、不丢 toolGrantIds。
4. **幂等**|输入:重复 install + 重复 bind预期DB 中该安装者该资产仅一行 installed agent + 一行 active version。
5. **多安装者各跑各的**输入A、B 各装同一 agent 资产|预期:各自 installed agentconfig 各一份),各自跑 AI 任务互不影响、互不可见对方实体。
6. **召回/下架回滚(开放项,照搬 KB 的诚实标注)**|输入:发布者 agent 资产被 admin 召回/下架|预期:已物化 installed agent 被阻断新使用授权失效→A-runtime 授权支拒)。**开放项**market 召回当前对目标 owner 传播是 fail-closed blocked 记录式(与 KB 同源缺口,临时-04 事实 7ai 侧消费"召回→停用 installed agent"的入口**本期缺**——A-verify 须如实验证现状传播是否触达 installed agent若不触达标开放项与 KB 召回回滚同一未触达面§5不假装闭环。
7. **机械门禁**输入ArchUnit `BcBoundaryArchTest` + 覆盖 JSON 门|预期:物化经 ai facade + `MarketAssetSourceApi`market 不直写 ai `.dal`market→ai若 D1 选 A不成环test 绿。
**Verification**
- 真 PG IT`muse-cloud/` 跑,按 memory `muse-p1r-it-run-recipe``p1r.flyway.*` argLine + 密码经 env`~/.config/muse-repo/infra.env``set -a && . it && set +a`onlineNew-API 在 mini-infra 在线)。**`_test` 库铁律IT 的 flyway 目标库绝不指 muse_slice_live。**
- studio e2e起全栈 appPG 宿主 100.64.0.8 在线时),`npx playwright test market-install-agent-runtime.spec.ts`
- 凭据红线:所有脚本/日志过滤 `password|secret|token|sk-`;不打印 config promptTemplate 正文;不 rm `dump.rdb`/`lefthook`
---
## 4. 决策点(待人类拍板,按优先级)
| # | 决策点 | 选项 | 倾向 | 阻塞谁 |
|---|---|---|---|---|
| **D1** | **config 跨 BC 读取途径**(核心) | 扩 `MarketAssetSourceApi` 让 market 反调 ai 读端口带回 config / ai 绑定路径自己读发布者 agent | **market 反调 ai选 A**:安装侧调用对称 + 避免跨 owner 读难题代价market→ai 新方向)经 -api 端口+ArchUnit 可控。若 review 否决 market→ai 方向则退 B 并显式处理跨 owner 读 | A-source 全部 |
| **D2** | **拷 vs 引用 prompt 库** | config 整体拷贝(含 promptTemplate 正文快照)/ 引用发布者 prompt 库promptKey 指回) | **整体拷贝**agent 无泄露向量、config 即商品本体、版本不可变→拷下即冻结快照,与"不 fork、拷配置"拍板一致;引用会重新引入"发布者改 prompt 库影响安装者"的漂移) | A-materialize config 写法 |
| **D3** | **installed agent 的 `agent_type` 命名最终值** | `market_installed` / `installed` / `installed_ref` | **`market_installed`**(与 `后端-04` 既有文档零漂移、语义自明;与 knowledge 风格协调即可不强求同名) | A-naming / A-materialize / A-runtime |
| D4 | installed agent 授权快照承载 | 数值主键 / 字符串 envelopeVARCHAR | **字符串 envelope**(沿用 ADR-020槽位 binding 已 V31 VARCHAR物化实体一致 | A-materialize / A-runtime |
| D5 | installed agent 是否计用户配额 / 列表可见性 | 计入用户 agent 配额并在"我的智能体"列表可见 / 不计配额、列表单独分区或隐藏 | 倾向**列表可见但标 installed 来源、是否计配额随产品定**(不阻断本 plan 核心链路,可后置;但需 A-materialize 明确 installed agent 的列表查询是否纳入,避免"装了看不到"或"污染自建列表" | 列表/配额读模型(非核心链路) |
| D6 | DTO 形状 | 现有 `MarketAssetSourceRespDTO` 增 agent 字段 / 拆 agent 专用 DTO+方法 | **拆 agent 专用**接口隔离KB/agent 各取所需;与临时-04 读写分离取向一致) | A-source DTO |
| D7 | 召回/下架回滚自动化 | 复用现有 market→目标 owner 传播 / 本期标开放项 | 与 KB 同一未触达面(临时-04 事实 7/D9**倾向本期标开放项**,不假装闭环 | A-verify |
---
## 5. 横切开放项(与 KB 同未触达,如实标,不假装闭环)
- **召回回滚未触达 installed agent与 KB installed_ref 同一开放面)**。market 召回/下架资产时对目标 owner 的传播当前是 fail-closed blocked 记录式(临时-04 事实 7`writeSourceStatusBlocked` errorCode=TARGET_OWNER_UNAVAILABLEai 侧没有"召回→停用 installed agent"的消费入口。本期 agent 物化落地后,这条回滚链与 KB 的 installed_ref 回滚是**同一个未补的传播消费面**——倾向两者一起补一个 market→knowledge+ai的召回传播消费者统一处置 installed 实体停用),但**本期标开放项**A-runtime 的"授权快照失效即拒"是兜底(授权撤销路径若与召回联动则部分覆盖,否则纯开放)。
- **e2e fixture vs 真物化**。A-verify 的 IT 覆盖后端链路studio e2e 的 global-setup 物化 fixture 是否走真物化(真建 installed agent还是种子数据需与 KB e2e 同步对齐KB 侧 e2e fixture 也是开放项,临时-04 §诚实遗留)——避免 fixture 巧合假绿(临时-02 §1.3.1 注脚那种"种子已存在实体绕过断点"的假绿教训)。
- **installed agent 列表/配额读模型D5**。非核心链路,但物化出的 installed agent 在"我的智能体"列表的可见性 + 是否计配额是产品决策,本期标横切待定,不阻断运行时打通。
---
## 6. 风险
- **R1最高改授权门 = trust boundary必负路测**。A-runtime 放行 installed 型是对运行时权限边界的修改,最大风险是**放过头**——把"本人 installed"误放成"任意 installed / market 裸引用 / 他人 installed"。**缓解**放行三收紧点缺一不可installed 型 + 本人所有 + 授权有效§A-runtime ①A-verify 场景 2 + A-runtime 负路测把"他人 installed 必拒 / 授权失效必拒 / market 裸引用必拒"作为**验收命门**,缺负路不算通过。这是 CLAUDE.md"trust boundary 服务端强制 + 改授权必负路"红线的直接落点。
- **R2 config 跨 BC 读取的依赖方向D1**。选 A 引入 market→ai 新依赖方向,需 ArchUnit 确认不成环market 与 ai 同进程聚合、经 -api 端口不构成循环依赖,但要 review 确认)。**缓解**A-source ArchUnit 门;若 review 否决则退 D1 选 Bai 自读)并显式处理跨 owner 读。
- **R3 config 拷贝完整性**。拷 config 若只拷部分键(漏 modelKey/promptTemplate/toolGrantIds运行时取值落缺省或丢工具授权 → 物化 agent 行为与发布者不一致(用户买到"残品")。**缓解**A-materialize ③ 整体拷贝D2 整体拷)+ A-verify 场景 3 真验 config 完整性(候选反映发布者 prompt 行为 + modelKey 生效 + toolGrantIds 不丢)。
- **R4 命名漂移收口(事实 8**。`agent_type` 四处漂移若不收口A-materialize 写入值与 A-runtime 放行值不一致 → 物化了但运行时仍拒(自造断点)。**缓解**A-naming 拍定单一常量 + 同步 `后端-04`A-materialize/A-runtime 共用常量。
- **R5 agent_key 唯一键冲突**。`uk_muse_agent_key=(tenant_id, agent_key)`(事实 3installed agent 的 agent_key 若按发布者 agent_key 直拷,多安装者/与发布者会撞键 500。**缓解**A-materialize ③ agent_key 按安装者维度生成(`installed-{assetId}-{ownerUserId}` 或带 uuid保安装者租户内唯一幂等查走 `source_market_asset_id`(非 agent_key
- **R6 召回回滚未自动化(开放项,与 KB 同面)**。临时-04 事实 7本期标开放§5 / D7不假装闭环A-runtime 授权失效兜底部分覆盖。
---
## 7. 回滚(本 plan 执行后如何撤回)
- **代码回滚**A-source 扩端口 + A-materialize`MuseAgentSlotServiceImpl` 物化 + `MuseAgentDO` 补映射)+ A-runtime`requireVisibleAgent` 放行支)+ A-naming文档。revert 这些 commit 即回到"槽位写裸引用 + 运行时拒 market agent"现状(断点②,功能不可用但与现状一致、不报新错)。
- **数据回滚**:已物化的 installed agent 行 + active version 经软删停用(`deleted=true`**无外部资源需清理**agent 不像 KB 有 fork 出的 dataset这是 agent 回滚比 KB 简单处——无 RAGFlow 删 dataset 步骤)。
- **迁移回滚**:本 plan **不引入新 Flyway 迁移**`source_market_asset_id` 列 V27 已在、仅补 DO 映射;授权快照 D4 沿用 V31 VARCHAR无迁移需回滚。
---
## 8. Scope 边界(明确不做什么)
- **agent 限定**:本 plan **只做智能体agent类资产的物化**KB 物化已由临时-04 D0-fork 闭环,不重做。
- **不 fork、不碰外部资源**agent 无泄露向量(事实 6物化 = 安装侧同步拷 config**不引入任何 fork / 发布侧异步管线 / 上架事件 / 外部服务调用**(与 KB D0-fork 的核心区别)。
- **install 解耦不动**install 维持"只记账、`targetFactsWritten=false`"现状,**不在 install 侧物化**——物化落点严格在 ai 槽位绑定路径。
- **闸 A 不动**:槽位授权门 `requireVisibleActiveSourceAgent` 已放宽 market事实 2本 plan 不碰;断点纯在运行时闸 B。
- **handoff 非物化桥**handoff token 只负责跳转 + 被目标域消费(`MarketHandoffTokenApi.verify/consume` 不动)。
- **副本资源回收不适用**agent 无副本 dataset无 KB 那条"下架/无安装清理副本"开放项。
- **召回/下架回滚自动化不做**本期标开放项R6/D7/§5与 KB 同面,不假装闭环。
- **installed agent 列表/配额读模型**D5 标横切待定,非核心链路,本期不强制实现。
- **market 两列 BIGINT 议题不纳入**`muse_market_installation.authorization_snapshot_id`/`source_snapshot_id` 仍 BIGINT 是独立契约议题,本 plan 不处理。
---
## 9. 与 KB D0-fork 的异同摘要(一表看清)
| 维度 | KB 物化(临时-04 D0-fork已闭环 | Agent 物化(本 plan 临时-05 |
|---|---|---|
| 泄露向量 | **有**:发布者活 dataset 上架后还能加私有文档→整库检索泄露 | **无**version 不可变 + 运行时不回读私有 prompt + config 即商品本体 |
| 物化手段 | **fork 公开副本**(建副本 dataset + 逐文档复制 + 重索引,异步重活) | **拷配置**(安装侧同步拷 active version config无外部资源、无异步 |
| 发布侧工程 | 有:上架事件 + knowledge fork 消费者 + 兜底 worker + `MarketAssetForkApi` 写回 | **无**:物化全在安装侧同步、上架侧零改动 |
| 跨 BC 读端口 | `MarketAssetSourceApi`(带回副本 dataset id + forkStatusmarket 读自有 tags 即可) | **扩** `MarketAssetSourceApi` 带 configmarket 域够不着 ai config→需反调 ai 或 ai 自读,决策点 D1 |
| 安装侧物化形态 | `materializeInstalledRefKb`:建 installed_ref KB + dataset binding 指副本(**副本仅 1 份共享** | 照搬形态:建 installed 型 agent + active version**config 各拷各的、无共享** |
| 去污染/去裸引用 | kb_id 去污染assetId→本地 kbId | 槽位 agentId 去裸引用(发布者 agentId→本地 installed agentId |
| 唯一断点 | 检索第四门 `selectActiveDatasetByKbId`(数据正确即通,代码几乎不改) | 运行时授权门 `requireVisibleAgent:329-331`**必须改门放行 installed**trust boundary |
| 改授权门 | 不改(检索门靠数据命中) | **改**A-runtime本 plan 最需严防、负路测命门) |
| schema 触动 | 无(落 V5/V14 已有列) | 仅补 `MuseAgentDO` 映射 V27 已有列(不需新迁移) |
| 外部资源回滚 | 有:删 fork 出的副本 dataset | **无**:软删本地 agent 行即可 |
| 共同开放项 | 召回回滚未触达 installed_refe2e fixture 待补 | 召回回滚未触达 installed agent同面e2e fixture 待对齐 |
---
## 10. 关联阅读
- 方案演进与权衡(本 plan 的 WHAT 上游agent 侧 B'[`临时-02-market-install下游物化方案.md`](临时-02-market-install下游物化方案.md)§1.3.2 断点② 机理、§2.2 B' 智能体侧、§3.2 智能体 schema 增量)
- 姊妹方向 KB 物化(已闭环,本 plan 照搬其物化范式与开放项结构):[`临时-04-market-KB物化-D0fork执行plan.md`](临时-04-market-KB物化-D0fork执行plan.md)
- 概念与 owner 边界:[`架构-02-核心数据结构与双轨模型.md`](架构-02-核心数据结构与双轨模型.md)§5 智能体运行权限、§6 市场资产、§11.2 绑定安装授权、授权快照语义)
- 关键决策:`架构-03-关键决策与原则(ADR).md`ADR-017 Source 传播事件驱动、ADR-020 授权快照字符串承载)
- 表结构 SSOT`后端-04-统一数据库Schema-v1.md`§7 AI/Agent`muse_agent.agent_type` 取值待 A-naming 收口)
- BC 边界规则:[`.agents/rules/bc-boundaries.md`](../.agents/rules/bc-boundaries.md)
- 已沉淀知识:[`.agents/knowledge/market-install-downstream-materialization.md`](../.agents/knowledge/market-install-downstream-materialization.md)KB 物化 + D0-fork 隔离单一归属agent 断点在"二、agent 断点"已点名留作后续)