- 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>
400 lines
56 KiB
Markdown
400 lines
56 KiB
Markdown
# 临时-05 · Market Agent 类资产物化(拷配置 · installed 型 agent · 改授权门)执行 Plan
|
||
|
||
- 版本:v1(执行版,待人类 review/拍板后执行;本稿只读出代码、不改任何代码、不 commit)
|
||
- 更新日期:2026-06-26
|
||
- 目标读者:后端 / 架构 / PR reviewer
|
||
- 阅读时间:30–40 分钟
|
||
- 文档性质:**临时件**(执行依据,落定后归并入正式分册或删除,不作长期 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-020;Source 传播事件驱动归 ADR-017;BC 边界规则归 [`.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` 带 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 实现单元依赖图
|
||
|
||
```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 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
|
||
|
||
```mermaid
|
||
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 物化后)
|
||
|
||
```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 版本 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)
|
||
|
||
```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_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 假红(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 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**:
|
||
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)`**,事实 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`(用户确认绑定的写事实点)。
|
||
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 实体若需携带授权快照,沿用 VARCHAR(D4 决策)。
|
||
6. **事务原子性**:物化(建 agent + version)与"消费 handoff token + 写槽位绑定"在同一 `@Transactional`(现状 `bindAgentSlot:157`/`consumeMarketHandoff:348` 已沿用调用方事务),失败整体回滚。
|
||
7. **多安装者各建各的**:不同安装者各建自己的 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'`(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 物化时已写 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**:
|
||
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)**|输入:发布者建 agent(active version,config 完整)→ 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 G2,B 试图用 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 agent(config 各一份),各自跑 AI 任务互不影响、互不可见对方实体。
|
||
6. **召回/下架回滚(开放项,照搬 KB 的诚实标注)**|输入:发布者 agent 资产被 admin 召回/下架|预期:已物化 installed agent 被阻断新使用(授权失效→A-runtime 授权支拒)。**开放项**:market 召回当前对目标 owner 传播是 fail-closed blocked 记录式(与 KB 同源缺口,临时-04 事实 7),ai 侧消费"召回→停用 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`),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 正文;不 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 授权快照承载 | 数值主键 / 字符串 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,`writeSourceStatusBlocked` errorCode=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`](临时-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 断点"已点名留作后续)
|