- 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>
56 KiB
临时-05 · Market Agent 类资产物化(拷配置 · installed 型 agent · 改授权门)执行 Plan
- 版本:v1(执行版,待人类 review/拍板后执行;本稿只读出代码、不改任何代码、不 commit)
- 更新日期:2026-06-26
- 目标读者:后端 / 架构 / PR reviewer
- 阅读时间:30–40 分钟
- 文档性质:临时件(执行依据,落定后归并入正式分册或删除,不作长期 SSOT)。本稿只给"怎么做",不重定义概念:方案演进与 C/B'/暂缓权衡见
临时-02-market-install下游物化方案.md(§2.2 B' 智能体侧、§3.2 智能体 schema 增量为本稿 WHAT 上游);KB 类资产物化的姊妹方向(已闭环、本稿照搬其物化范式与开放项结构)见临时-04-market-KB物化-D0fork执行plan.md;术语与 owner 边界以架构-02-核心数据结构与双轨模型.md为准(§5 智能体运行权限、§6 市场资产、§11.2 绑定安装授权、授权快照"引用对象必须保存快照 id");表结构归后端-04§7 AI/Agent;授权快照字符串承载归 ADR-020;Source 传播事件驱动归 ADR-017;BC 边界规则归.agents/rules/bc-boundaries.md。 - 配套人读图:
临时-05-market-agent物化执行plan.html
一句话:市场安装一个智能体后,槽位能绑进去(闸 A 已放宽),但 AI 运行时
requireVisibleAgent仍把 market 来源的 agent 一律拒掉——这是 agent 物化的唯一真断点。本 plan 拍板已定的方向是 ①不 fork、拷配置(agent 不像 KB 有"上架后还能往库里加私有文档"的泄露向量:版本一经 active 即不可变、运行时不回读发布者私有 prompt、config本身就是被授权出售的商品本体),②把市场 agent 物化成安装者租户内一个独立的 installed 型muse_agent+ activemuse_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 不再重开权衡):
-
不 fork、拷配置。KB 之所以要 fork,是因为 KB 上架后生命周期不冻结、发布者还能继续往自己库里加私有文档,安装者若共享发布者活 dataset 就会读到上架时根本不存在的私有内容。agent 没有这条链:一个 agent 版本一旦 active 就不可变(改动只能开新版本行,见 §1 事实 4),运行时也不回读发布者的私有 prompt 正文、只用版本里冻结的
config,而config本身就是发布者亲自写好、上架出售的商品本体(name/description/promptTemplate/slotBindings/toolGrantIds)。安装者拿到这份 config = 拿到他买的东西,这不是泄露、正是交付。因此 agent 物化不需要 fork 任何东西,直接把发布者那一份 active 版本的config拷进安装者租户内一份新建的 agent 版本即可。 -
物化成 installed 型独立 agent + 改授权门。对齐临时-04 的 installed_ref 范式:在安装者租户里建一行独立的
muse_agent(一个区别于system/user的 installed 型,source_market_asset_id回填溯源)+ 一行 activemuse_agent_version(config 拷自发布者)。光建实体还不够——运行时授权门当前对非 system/非本人 user 一律拒(§1 事实 1),必须把它放行"本人所有的 installed 型 + 授权有效"。这道改动是 trust boundary,是本 plan 最需要严防的一点(§4 R1 + A-verify 负路测)。 -
物化落点在绑定路径、不在 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 带 config(A-source)+ 安装侧建 installed agent 拷 config(A-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)+ 改运行时授权门(事实 1,trust boundary)+ 命名收口(事实 8);KB 那套 fork 公开副本 / 发布侧异步管线 / 上架事件 / 副本资源回收(临时-04 的核心增量),在 agent 下全部不需要(事实 6/7)。
2. 总览:实现单元、数据流与授权门改造对比
2.1 实现单元依赖图
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 IT(install→物化→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
flowchart LR
subgraph PUB["发布者域(owner = 发布者)"]
PAGENT["发布者 muse_agent + active muse_agent_version<br/>config(promptTemplate/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/>🟢 命中物化 agent,AI 任务可跑"]
NOTE["对照 KB:KB 必须 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 物化后)
flowchart LR
A["用户跳转→ai 槽位绑定路径<br/>precheckAgentSlot / bindAgentSlot"]
A --> B["消费 handoff token(原有,闸 A 已放宽 market 型,不动)"]
B --> C["A-source: MarketAssetSourceApi.getAssetSource(assetId)<br/>assetType=agent 放行 + 带回发布者 active 版本 config(D1)"]
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)
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_id;agent config 的来源见 D1) - D1 选 A(market 反调 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 选 B(ai 自有读)时:不扩 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-closed:agent 绑定路径只接纳 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)。
- 优点:①安装侧只跟一个端口打交道(
- 选项 B:ai 绑定路径自己读发布者 agent。
MarketAssetSourceApi仍只回 source_id(发布者 agentId),ai 绑定路径凭它读发布者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=X(asset_type=agent,source_id=发布者 agentId=G,G 有 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-closed(exists=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 假红(memorymuse-market-install-isacquired-enrich教训)。 - ArchUnit:
mvn -pl ... test -Dtest=BcBoundaryArchTest(确认 market→ai 不成环)。
A-materialize · 安装侧建 installed agent 拷 config(照搬 KB materializeInstalledRefKb 形态)
Goal:在 ai 目标域绑定路径里,把"槽位写裸引用"升级为"建出可用的本地 installed 型 agent + active version(config 拷自发布者)+ 槽位指向本地 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:
- DO 补映射:
MuseAgentDO加sourceMarketAssetId(@TableField 普通列,V27 已建source_market_asset_id BIGINT)。这是物化溯源 + 幂等键的载体,唯一的 schema 触动且不需新迁移(事实 3)。 - 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 行,与 KBmaterializeInstalledRefKb注释同理)。 - 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),事实 3:installed 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(用户确认绑定的写事实点)。
- insert
- 幂等(照搬 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→回读模式兜(仿 KBpersistDatasetBinding的回读幂等)。这保证同一安装者对同一资产多次绑定只有一行 installed agent。 - 授权快照承载:installed agent 的授权溯源沿用 ADR-020 字符串 envelope;槽位 binding 的
authorization_snapshot_id已 V31 VARCHAR 直透传(事实见MuseAgentSlotServiceImpl:281-282),不回退 BIGINT/parseLong。物化 agent 实体若需携带授权快照,沿用 VARCHAR(D4 决策)。 - 事务原子性:物化(建 agent + version)与"消费 handoff token + 写槽位绑定"在同一
@Transactional(现状bindAgentSlot:157/consumeMarketHandoff:348已沿用调用方事务),失败整体回滚。 - 多安装者各建各的:不同安装者各建自己的 installed agent(各自 owner + 各自 agent_key),config 各拷一份(agent config 是轻量 JSONB,不像 KB dataset 那样有共享必要;agent 无"副本仅 1 份"概念——这是与 KB 的又一差异:KB 副本 dataset 重、N 安装者共享只读一份;agent config 轻、各拷各的最简单且无泄露顾虑)。
Test scenarios(输入+预期):
- 首次安装绑定|输入:T_A 安装 assetId=X(asset_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 agent(G2、G3,各自 owner + 各自唯一 agent_key),各有自己的 active version(config 各一份),不违反
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'(mockMarketAssetSourceApi,覆盖建 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:
- 放行条件扩展(精确改
:329-331):原判定"非 system 且 非(user 且本人)→拒"扩为"非 system 且 非(user 且本人) 且 非(installed 且本人 且授权有效)→拒"。即新增一条放行支:agent_type=<installed 取值> 且 ownerUserId==agent.ownerUserId 且 授权快照有效。三个收紧点缺一不可:①必须是 installed 型(不放开 market 裸引用——物化后槽位指的是本地 installed agent,不存在裸 market 引用进运行时);②必须本人所有(ownerUserId比对,防越权用他人 installed agent);③授权快照有效(防授权已撤销/过期仍可跑)。 - 授权有效性判定:installed agent 行的
source_market_asset_id+ 授权快照(ADR-020 VARCHAR envelope)钉死"凭哪份授权可用"。运行时校验授权快照有效(非blocked:前缀等,参照闸 ArejectBlockedSource:326-330的 envelope 语义),授权失效则拒(与召回/下架回滚开放项联动,§5)。 - 三入口自动覆盖:
:220/:308/:315都调requireVisibleAgent,改门体即三入口同时生效,无需逐入口改(与 KB 检索"改数据正确即第四门自然通"异曲同工——这里是"改门一处即三入口通")。 - status/version 两道维持:
:326-327(agent.status=active)+:333-336(active version 存在)不放松,installed agent 物化时已写 active(A-materialize),自然满足。
Test scenarios(输入+预期):
- 本人 installed agent 放行(正路)|输入:A 物化的 installed agent G2(active + 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:
- 取值候选与倾向:
installed(简洁、与 KBinstalled_ref风格呼应但更短)/market_installed(与后端-04现有文档一致、语义最明确)/installed_ref(完全对齐 KB)。倾向market_installed(与后端-04既有文档零漂移、语义自明"市场安装来的",且 V27 注释已写"对齐 knowledgesource_market_asset_id"暗示与 knowledge 风格协调即可、不强求同名);最终由人类拍(D3)。 - 单一来源:拍定后 A-materialize/A-runtime 共用一个常量,
后端-04同步订正,避免再次漂移。 - 与
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§7agent_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*ITlive 范式) - 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(输入+预期):
- install→物化→bind→runtime 端到端(真 PG)|输入:发布者建 agent(active version,config 完整)→ admin 审核上架成 market agent 资产 → 用户安装该资产 → 槽位绑定(物化 installed agent + version + 槽位指本地 agentId)→ 发起 AI 任务命中该槽位|预期:运行时
requireVisibleAgent放行物化 installed agent → 真 New-API 出候选(AI 外部依赖在 mini-infra 100.64.0.8 在线,memorymuse-ai-external-deps-online);对照现状(必AI_AGENT_SCOPE_FORBIDDEN)。 - 授权门负路(trust boundary 核心验证)|输入:A 物化 installed agent G2,B 试图用 G2 跑 AI 任务(伪造 agentOverrideRef)|预期:抛
AI_AGENT_SCOPE_FORBIDDEN,B 拿不到 G2——这是 agent plan 安全验收的命门,缺此不算通过。 - config 完整性真验|输入:发布者 config 含特定 promptTemplate/modelKey/toolGrantIds|预期:物化 agent 真跑时用的是拷来的 config(候选反映发布者 prompt 行为、modelKey 生效),不落缺省、不丢 toolGrantIds。
- 幂等|输入:重复 install + 重复 bind|预期:DB 中该安装者该资产仅一行 installed agent + 一行 active version。
- 多安装者各跑各的|输入:A、B 各装同一 agent 资产|预期:各自 installed agent(config 各一份),各自跑 AI 任务互不影响、互不可见对方实体。
- 召回/下架回滚(开放项,照搬 KB 的诚实标注)|输入:发布者 agent 资产被 admin 召回/下架|预期:已物化 installed agent 被阻断新使用(授权失效→A-runtime 授权支拒)。开放项:market 召回当前对目标 owner 传播是 fail-closed blocked 记录式(与 KB 同源缺口,临时-04 事实 7),ai 侧消费"召回→停用 installed agent"的入口本期缺——A-verify 须如实验证现状传播是否触达 installed agent;若不触达,标开放项(与 KB 召回回滚同一未触达面,§5),不假装闭环。
- 机械门禁|输入:ArchUnit
BcBoundaryArchTest+ 覆盖 JSON 门|预期:物化经 ai facade +MarketAssetSourceApi,market 不直写 ai.dal,market→ai(若 D1 选 A)不成环,test 绿。
Verification:
- 真 PG IT:从
muse-cloud/跑,按 memorymuse-p1r-it-run-recipe配p1r.flyway.*argLine + 密码经 env(~/.config/muse-repo/infra.env,set -a && . it && set +a),online(New-API 在 mini-infra 在线)。_test库铁律:IT 的 flyway 目标库绝不指 muse_slice_live。 - studio e2e:起全栈 app(PG 宿主 100.64.0.8 在线时),
npx playwright test market-install-agent-runtime.spec.ts。 - 凭据红线:所有脚本/日志过滤
password|secret|token|sk-;不打印 config promptTemplate 正文;不 rmdump.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 授权快照承载 | 数值主键 / 字符串 envelope(VARCHAR) | 字符串 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,
writeSourceStatusBlockederrorCode=TARGET_OWNER_UNAVAILABLE),ai 侧没有"召回→停用 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 选 B(ai 自读)并显式处理跨 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)(事实 3):installed 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 + forkStatus,market 读自有 tags 即可) |
扩 MarketAssetSourceApi 带 config(market 域够不着 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_ref;e2e fixture 待补 | 召回回滚未触达 installed agent(同面);e2e fixture 待对齐 |
10. 关联阅读
- 方案演进与权衡(本 plan 的 WHAT 上游,agent 侧 B'):
临时-02-market-install下游物化方案.md(§1.3.2 断点② 机理、§2.2 B' 智能体侧、§3.2 智能体 schema 增量) - 姊妹方向 KB 物化(已闭环,本 plan 照搬其物化范式与开放项结构):
临时-04-market-KB物化-D0fork执行plan.md - 概念与 owner 边界:
架构-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/knowledge/market-install-downstream-materialization.md(KB 物化 + D0-fork 隔离单一归属,agent 断点在"二、agent 断点"已点名留作后续)