test(ai): guard runtime from tool grant writes

This commit is contained in:
lili 2026-06-19 07:56:49 -07:00
parent be8b4e835e
commit d5f62a16e4
5 changed files with 101 additions and 15 deletions

View File

@ -11,7 +11,7 @@
- 认证、授权、租户隔离、数据归属校验必须在后端可信边界执行;前端隐藏按钮或本地状态不算安全控制。
- `/admin-api/**` 与 `/app-api/**` 必须保持调用身份隔离;管理员能力不得经 app API 暴露,普通用户能力不得依赖 admin 端兜底。
- 查询和写入必须带可信的 `tenant_id` / `user_id` / owner 条件;不得信任前端传来的 owner、tenant 或权限声明。
- AI runtime 不得自授权。授权事实必须来自授权/权限端口或已验证的快照,运行时只能消费。
- AI runtime 不得自授权。授权事实必须来自授权/权限端口或已验证的快照,运行时只能消费;当前由 `AiGrantRuntimeBoundaryArchTest` 守住 runtime/任务执行侧不得直接依赖 `ToolGrant` 写模型,完整 grant/runtime 物理包拆分另行推进。
---
@ -59,6 +59,7 @@
| 约束 | 机械落点 |
|---|---|
| 跨 BC 不直连他域实现 / DAL | `BcBoundaryArchTest` |
| AI runtime 不得自授权 / 不得直连 Tool Grant 写模型 | `AiGrantRuntimeBoundaryArchTest` |
| API/DB 契约与迁移卫生 | `ContractFirstGateTest` + `openapi-diff` |
| completed 必须有测试证据 | `P1rApiCoverageReportTest` |
| 真实 PG / 外部依赖 fail-closed 证据 | `P1r*IT` 与总账证据 |

View File

@ -20,7 +20,7 @@
3. **三类最危险缺口(详见第六节):**
- **前端用户端(muse-studio)严重滞后**:AI 候选闭环断链(后端齐全、studio 未接 `suggestion-merges`)、知识库工作台核心交互(草稿确认/绑定/发布/图谱)完全缺失、市场生产侧(发布/上架/申诉)无 UI、个人中心仅约 21% 端面且跑在 MSW mock 上。**这是 objective"AI 先审后入"在用户可见层的落点,当前不可用。**
- **跨 BC facade 多为 `Unavailable` 默认实现**:meta 的 impact-preview、account 的 New-API/FileService/Market 投影等 facade 仅有 fail-closed 占位,导致大量 operation 运行期返回 `*_UNAVAILABLE`(meta 写链路、account 21/33 端面运行期阻断)。代码诚实留白,但端到端不可用。
- **设计承诺与机械门禁缺口**:ADR 级"AI 授权包级隔离 + ArchUnit"全仓 0 依赖 0 规则(被显式后置第二期);gateway 配置层已补 Muse API 路由并清理死路由,但运行态转发仍未首跑。
- **设计承诺与机械门禁缺口**:AI 授权包级隔离已有最小 ArchUnit 守卫(runtime/任务执行侧禁直连 ToolGrant 写模型),但完整 grant/runtime 物理包拆分仍未完成;gateway 配置层已补 Muse API 路由并清理死路由,但运行态转发仍未首跑。
4. **真实整体完成度评估:约 76%**(后端实现高、内部验证门禁中、前端用户端低、跨 BC 集成与机械门禁低)。
---
@ -116,7 +116,7 @@ fork yudao-cloud:保留 `system/infra/framework/gateway/member/pay/bpm/report/mp
| 阶段7 fork yudao-cloud,逻辑 owner 优先(ADR-014/015) | yudao-cloud 作工程基座与过渡物理落点;逻辑 owner/facade/状态机/写入边界以 BC 为准;跨 BC 写入走 owner facade |
| Governance 按消费者拆散,MetaSchema 独立(ADR-016) | MetaSchema→独立 BC;Protection Node + Quality Policy→AI grant 包;市场治理→Marketplace admin 包 |
| Source 传播=事件驱动各模块自治;去 needs_recheck(ADR-017/018) | 无独立 source 模块与 Candidate Decision Envelope;候选接受时实时对比来源版本 + 同步质量/合规/静态检查;source_snapshot 独立表 DDL 归 infra |
| AI 授权包级隔离(grant vs runtime)+ ArchUnit | runtime 包禁调 grant 写入;工具调用/上下文读取/外发须经服务端 tool broker 校验不可变 envelope。**⚠ 现状:全仓 0 ArchUnit,被显式后置第二期,当前仅软约束** |
| AI 授权包级隔离(grant vs runtime)+ ArchUnit | runtime 包禁调 grant 写入;工具调用/上下文读取/外发须经服务端 tool broker 校验不可变 envelope。**⚠ 现状:已有最小 ArchUnit 守卫 `AiGrantRuntimeBoundaryArchTest`;完整 grant/runtime 物理包拆分仍未完成** |
| Account 物理落 member,不新增 account 模块 | member = 端侧用户 = Account BC 物理承载,扩 Entitlement/Quota/Usage/Security/Binding;唯一写入 authority |
| New-API 职责边界(ADR-009) | New-API 保供应商路由/限流/成本/原始日志 authority;Muse 只保绑定引用/任务摘要/归属/correlationId/幂等;凭据禁入日志/Prompt/候选/导出/用户错误 |
| 作品资产能力 Feature Gate 默认全关 | `template/reference/ai_context_enabled=false`;开启前须补 ADR + owner + Schema/API + 预检 + lineage + 授权快照 + 导出限制 + 状态机 |
@ -146,7 +146,7 @@ fork yudao-cloud:保留 `system/infra/framework/gateway/member/pay/bpm/report/mp
| 模块(BC) | server | api | contract | 前端 | DB迁移 | 测试 | 真实完成度 | 构建 | 契约一致性 | 置信度 | 最关键风险 |
|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|---|:---:|---|
| **AI 编排** (muse-module-ai) | ✅ | ✅ | ✅ | ⚠ | ✅ | ✅ | **72%** | 未实跑/可编译 | 较好有偏差 | high | ADR 级包隔离+ArchUnit 缺失(软约束);前端全 MSW,SSE 疑漂移;真实 New-API 未端到端验收 |
| **AI 编排** (muse-module-ai) | ✅ | ✅ | ✅ | ⚠ | ✅ | ✅ | **72%** | 未实跑/可编译 | 较好有偏差 | high | ADR 级完整包隔离仍未完成(已有最小 ArchUnit 守卫);前端全 MSW,SSE 疑漂移;真实 New-API 未端到端验收 |
| **作品/编辑器** (muse-module-content) | ✅ | ✅ | ✅ | ⚠ | ✅ | ✅ | **82%** | 未实跑/可编译 | 高度一致 | high | **前端 Accept Suggestion 断链**:后端齐全但 studio 未接 suggestion-merges,AI 候选进正文用户端不可用 |
| **知识库** (muse-module-knowledge) | ✅ | ⚠ | ✅ | ⚠ | ✅ | ✅ | **72%** | 未实跑/可编译 | 高度一致 | high | 前端 2/3 面缺失(草稿确认/绑定/发布/图谱无 hook);Task4 跨 BC owner 未实现;recheck/worker 默认未生效 |
| **市场** (muse-module-market) | ✅ | ⚠ | ✅ | ⚠ | ✅ | ✅ | **82%** | 未实跑/可编译 | 高度一致 | high | studio 仅消费侧(浏览/授权/安装);**生产侧发布/上架/申诉无 UI,飞轮 UI 层断裂** |
@ -178,12 +178,12 @@ fork yudao-cloud:保留 `system/infra/framework/gateway/member/pay/bpm/report/mp
**桩与缺口:**
- `muse-module-ai-contract-server` **整模块空壳**(仅 pom,0 java),且 `muse-server` 已改依赖 `-server`,成为**孤儿 submodule**。
- **ArchUnit + grant/runtime 包级隔离:全仓 0 依赖 0 规则 0 包目录**,实为类命名约定 + 运行时 Guard 软约束。
- **完整 grant/runtime 物理包级隔离仍未完成**;现已有 `AiGrantRuntimeBoundaryArchTest` 最小门禁,守住 runtime/任务执行侧不得直连 ToolGrant 写模型,但它不是完整 ADR 级物理拆分。
- 真实 New-API 端到端**未验收**(P1R4 smoke qwen3.5/doubao 超时);本地无配置即 fail-closed 全失败。
- Evaluation run 起 job 但**同步评分/LLM-judge 闭环深度未见**(疑 job 异步延迟)。
- 前端 agent 特性 **DEV 全程 MSW**(8 端点全 mock),真实连通未验证;`AgentPage` 有 `WORK_ID_FOR_SLOT_PREVIEW` 硬编码;**AI-stream SSE 与契约疑 drift**(有独立待办 chip)。
**设计 vs 现实差异:** ADR/CLAUDE.md 反复声称"AI 授权包级隔离 + ArchUnit",现实为软约束(被 2026-06-13 评审文档明确"列入第二期不实施")——**有意后置而非偷工,但设计承诺的硬隔离未达成**。
**设计 vs 现实差异:** ADR/CLAUDE.md 反复声称"AI 授权包级隔离 + ArchUnit",现实为已补最小执行侧 ArchUnit 守卫、但完整 grant/runtime 物理隔离仍后置——**有意后置而非偷工,但设计承诺的完整硬隔离未达成**。
**"完成"声称 vs 现实:** **AI 域是文档低报而非高报。** `2026-05-30-P1R4AI真实API规格计划.md` 全程坚持 completedOperations=0、明确 streamAiTask 曾是 sse_placeholder、记录 New-API smoke 超时拒写"已完成"。代码现实强于其保守声称。唯一名实不符是 ArchUnit/包隔离承诺(被显式后置)。
@ -196,7 +196,7 @@ fork yudao-cloud:保留 `system/infra/framework/gateway/member/pay/bpm/report/mp
- `.../application/muse/MuseSuggestionServiceImpl.java:66-127`(reject,无 accept,符合 content owner)
- `.../application/muse/MuseAiTaskStreamServiceImpl.java:57-95,144`(SSE 真实)
- `muse-server/pom.xml:114`(依赖 -server 非 contract-server,证孤儿)
- 全仓 grep archunit/ArchRule = **0 命中**
- `muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/arch/AiGrantRuntimeBoundaryArchTest.java:1`(最小 ArchUnit 门禁:runtime/任务执行侧禁直连 ToolGrant 写模型)
- `docs/memorys/2026-05-30-P1R4AI真实API规格计划.md:49,115,448`(拒写 completed;smoke 超时)
- `muse-studio/src/main.tsx:9-11` + `src/api/mocks/handlers/ai.ts:130-305`(DEV MSW 全拦截)
@ -415,7 +415,7 @@ fork yudao-cloud:保留 `system/infra/framework/gateway/member/pay/bpm/report/mp
| 内部验证门禁(coverage) | **中(147/233≈63%)** | completed 是严格门禁口径(HTTP+真实 PG+Flyway+证据);其余 86 needs_verification 多为"已实现未走完验证"非"未做" |
| 跨 BC 集成 | **低** | meta/account/部分链路的对端 facade 多为 Unavailable 占位,运行期端到端被阻断 |
| 前端用户端(studio) | **低(~30%)** | AI 候选断链、知识库工作台 2/3 面缺、市场生产侧无 UI、个人中心约 21% 且跑 MSW;管理端(admin)反而较真实 |
| 机械门禁/底座接线 | **低** | ArchUnit 0 落地(后置);gateway 配置层已收口但运行态转发未首跑;底座 schema 未 Flyway 化 |
| 机械门禁/底座接线 | **低** | AI grant/runtime 已有最小 ArchUnit 守卫,但完整物理包拆分后置;gateway 配置层已收口但运行态转发未首跑;底座 schema 未 Flyway 化 |
### 6.2 churn 声称 vs 现实差距
@ -423,7 +423,7 @@ fork yudao-cloud:保留 `system/infra/framework/gateway/member/pay/bpm/report/mp
|---|---|
| 过程文档 churn 是否真实 | **真实存在**(34 memorys + 33 agent-specs,被 `2026-06-13-agent开发基建迁移-review.md` 自我诊断为"接口未锁 + 无单一进度源导致的过程文档 churn,不是真实交付量") |
| "完成"声称是否注水 | **否,系统性低报。** 所有核查域的 memos 一致区分"covered/completed(门禁口径)"与"端到端可用",反复拒绝宣称 33/33、32/32;coverage json 盘中复核未被篡改 |
| 名实不符点 | **仅一处:** ADR 级"AI 包级隔离 + ArchUnit"承诺未兑现(被显式后置第二期)。其余皆为代码强于声称,或诚实标注的 scoped 缺口 |
| 名实不符点 | **仅一处:** ADR 级"AI 包级隔离 + ArchUnit"完整物理隔离承诺未兑现;当前只补了最小执行侧 ArchUnit 守卫。其余皆为代码强于声称,或诚实标注的 scoped 缺口 |
| 治理风险 | churn 反映"接口未锁、无单一进度源",**本基线文档即是收敛此风险的单一进度源起点** |
### 6.3 最危险缺口 Top 6
@ -434,7 +434,7 @@ fork yudao-cloud:保留 `system/infra/framework/gateway/member/pay/bpm/report/mp
| 2 | **跨 BC facade 多为 Unavailable**(meta impact-preview / account New-API·File·Market 投影):运行期抛 `*_UNAVAILABLE` | **高** | MetaSchema 治理写链路端到端阻断(发布跑不通);account 21/33 端面运行期不可用;"completed"被误读为"可用"风险高 |
| 3 | **前端用户端整体严重滞后**:知识库工作台 2/3 面缺、市场生产侧(发布/上架/申诉)无 UI、个人中心约 21% 且跑 MSW | **高** | 产品-02 多数用户旅程无前端实现;市场飞轮在 UI 层断裂,创作者无法自助上架 |
| 4 | **gateway 运行态转发未首跑**:配置层已有 `/admin-api/muse/**` 与 `/app-api/muse/** → muse-server`,但本地默认端口/注册发现方案未验证 | **中** | 若启用网关部署而未完成注册发现/端口方案,可能出现转发不可达;不影响当前 `muse-server` 单体直服验收 |
| 5 | **ADR 级 ArchUnit + grant/runtime 包隔离缺失**:全仓 0 依赖 0 规则 0 包目录,仅类命名 + 运行时 Guard 软约束 | **中-高** | 高价值安全边界(grant 写入 vs runtime 只读强隔离)无机械保证;被有意后置但仍是安全缺口 |
| 5 | **ADR 级完整 grant/runtime 物理包隔离未完成**:已有 `AiGrantRuntimeBoundaryArchTest` 守住 runtime/任务执行侧禁直连 ToolGrant 写模型,但包目录与更细粒度依赖隔离仍后置 | **中-高** | 高价值安全边界(grant 写入 vs runtime 只读强隔离)已有最小防线,但完整物理隔离仍无机械保证;被有意后置但仍是安全缺口 |
| 6 | **前端 connectAIStream 契约漂移**(events):data.type 派发 + done 字段名错误,被 MSW mock 掩盖致测试假绿 | **中** | 真实前后端联调时 done/quality_check 不触发回调,AI 流联调阻断;统一事件客户端 connectEventStream 为孤儿无 UI 消费 |
**横切观察:** 后端"completed"(门禁口径)与"端到端可用"是双关语,**对外汇报必须明确区分"代码实现完成度(高)"与"内部验证门禁通过度(中)"与"端到端运行可用(受跨 BC facade + 前端拖累偏低)"**,否则极易高估。多数运行期阻断点已被后端正确 fail-safe(不伪造成功),属"功能不可用"而非"产生脏数据"。

View File

@ -11,6 +11,7 @@
|---|---|---|---|
| P0 | CI 真跑测试(JDK21、去 `-Dmaven.test.skip`)+ 覆盖台账去硬编码 + P0 冻结令 | ✅ | `muse-cloud/.github/workflows/maven.yml`、`P1rApiCoverageReportTest`、[脊柱规则](../../.agents/rules/verification-and-anti-false-green.md) |
| ① | BC 边界 ArchUnit 门——**通用覆盖全业务 BC 间方向** | ✅ **全绿**(AI/knowledge/market 三处直连他域 DAL 违例均已整改,豁免清单清空) | `BcBoundaryArchTest`(1/0F,0 Architecture Violation,`KNOWN_VIOLATION_EXEMPTIONS`=空);[bc-boundaries](../../.agents/rules/bc-boundaries.md) §三 |
| ①b | AI grant/runtime 最小 ArchUnit 门 | ✅ 绿;完整物理包拆分仍后置 | `AiGrantRuntimeBoundaryArchTest`(1/0F):runtime/任务执行侧不得直连 `ToolGrant` 写模型 |
| ② | 契约先行门(Flyway 迁移卫生 + OpenAPI **存在性/结构**) | ✅ 绿;语义破坏由 openapi-diff 补强——**检测逻辑已本地实证**(2026-06-17,oasdiff v1.19.1,7 域真实契约 5/5),root workflow 已接线,远端 Gitea Actions 首跑待确认 | `ContractFirstGateTest`(2/0F);[openapi-diff.yml](../../.github/workflows/openapi-diff.yml)+[verify 脚本](../../muse-cloud/scripts/verify-openapi-diff.sh);[contract-first](../../.agents/rules/contract-first.md) §四 |
| ⑦ | loop 机械牙 + CI 接电(round-2) | ✅ 绿 | `AgentsInfraIntegrityTest`(3/0F);`maven.yml` 触发分支修为 `main` |
| ③ | knowledge 蒸馏(定位架构 / 现状基线指针 / 决策) | ✅ | [`.agents/knowledge/`](../../.agents/knowledge/) |
@ -166,8 +167,9 @@
- ✅ **配置层收口(2026-06-19)**:`muse-gateway` 已新增 `/admin-api/muse/**`、`/app-api/muse/** → grayLb://muse-server`,删除当前聚合入口未启用的 bpm/pay/report/mp 死路由,Knife4j 聚合入口改为 `muse-server`。新增 `GatewayRouteConfigurationTest` 机械门禁,断言 Muse 路由存在、停用模块路由不回潮、Knife4j 不展示停用服务;本轮 `mvn -pl muse-gateway test -Dtest=GatewayRouteConfigurationTest` **3/3 绿**。
- ⏸ **运行态仍后置**:单体 P1R-7 仍以 `muse-server` 直服 48080 `/app-api` 为验收关键路径;`muse-server` 默认禁用 Nacos 注册,且 gateway 与 server 本地默认端口同为 48080,所以本轮不声称网关真实转发已完成。后续若启用网关部署,需补注册发现/端口方案并做 curl/e2e 首跑。
### E. ADR 级 ArchUnit grant/runtime 包隔离(B5,**判定:项目显式后置二期**)
- ⏸ BC 边界门 `BcBoundaryArchTest` 已存在且强制(豁免空)。grant/runtime 包级隔离被项目显式后置二期,严格说不在 P1 范围;当前类命名 + 运行时 Guard 软约束。收口:补 `ArchRule` 强约束 runtime 不得 import grant,或文档确认后置。
### E. ADR 级 ArchUnit grant/runtime 包隔离(B5,**最小机械门已落;完整物理包拆分后置**)
- ✅ **最小门禁(2026-06-19)**:新增 `AiGrantRuntimeBoundaryArchTest`,强制 AI runtime/任务执行侧不得直接依赖 `ToolGrant` 写模型/DO/Mapper/管理 VO,只能消费 `SecurityRuntimePermissionFacade` 产出的 runtime permission envelope。首轮误把 `ProjectionSecurityRuntimePermissionFacade` 纳入执行侧并红,确认它是 Security 投影适配器、合法读取 ToolGrant 后收窄规则;最终 `mvn -pl muse-server -am test -Dtest=AiGrantRuntimeBoundaryArchTest` **1/1 绿**。
- ⏸ **仍未宣称完整达成**:grant/runtime 物理包拆分、完整 ADR 级隔离仍后置;当前只是防运行时自授权的最小机械护栏。
### 收口顺序(价值/可验证性优先)
1. ✅ 红测试修复(本轮) 2. ⬜ MetaImpact 真实化(解 meta 发布链,多模块) 3. ⬜ account 投影/归因(解 21 端) 4. ⬜ 验收债按 BC 补真实 PG IT 5. ⬜ 前端逐切片 + playwright 活体 e2e 6. ✅ gateway 配置层收口 / ⏸ ArchUnit grant-runtime 判定收尾。
1. ✅ 红测试修复(本轮) 2. ⬜ MetaImpact 真实化(解 meta 发布链,多模块) 3. ⬜ account 投影/归因(解 21 端) 4. ⬜ 验收债按 BC 补真实 PG IT 5. ⬜ 前端逐切片 + playwright 活体 e2e 6. ✅ gateway 配置层收口 / ✅ grant-runtime 最小门禁 / ⏸ 完整物理包拆分后置。

View File

@ -3,7 +3,7 @@
> 模块跨会话记忆。真实现状权威见 [现状基线 §5.1](../../docs/agent-specs/2026-06-13-项目目标与模块现状基线.md);进度见 [总账](../../docs/mvp/进度总账.md)。本文件只记目标/边界/out-of-scope/现状指针/TODO,过时即更。
- **目标(owner 职责)**:链路编排 / 上下文组装 / 生成 / 分析 / 全书解析 / 质量门控 / AI 候选(Suggestion)/ 规划候选 / 风险标记 / Quality Result / 运行记录;承载 Protection Node(grant)与 Quality Policy。
- **边界(不可违反)**:**只产 Shadow,绝不直写 Canonical**;只消费服务端签发的 Runtime Permission Envelope,不能自授权;grant 与 runtime **包级隔离**;守 BC 边界不碰他域 `.dal`([bc-boundaries](../../.agents/rules/bc-boundaries.md);历史违例 `ContentMuseWorkOwnerFacade` **已整改消除**——改消费 content `MuseContentWorkOwnerApi`,门禁已收紧、无豁免)。
- **边界(不可违反)**:**只产 Shadow,绝不直写 Canonical**;只消费服务端签发的 Runtime Permission Envelope,不能自授权;runtime/任务执行侧不得直连 `ToolGrant` 写模型(`AiGrantRuntimeBoundaryArchTest` 最小门禁已守住);grant 与 runtime 完整物理包隔离仍后置;守 BC 边界不碰他域 `.dal`([bc-boundaries](../../.agents/rules/bc-boundaries.md);历史违例 `ContentMuseWorkOwnerFacade` **已整改消除**——改消费 content `MuseContentWorkOwnerApi`,门禁已收紧、无豁免)。
- **out-of-scope**:Accept Suggestion(归 content BC)、正文写入。
- **现状**:只读评估 72%;后端真实较高质量(无 stub),`RealNewApiMuseAiRuntimeClient` 真打 New-API,SSE 已脱占位;studio `/agents` 已补自建智能体写路活体证(2026-06-19:`agent-create.spec.ts` MSW-off 真单体 1/1,DB 证 `muse_agent` user/owner=1/active + command/audit)。
- **关键风险 / TODO**:真实 New-API 未端到端验收;前端 agent 仍缺“运行试用/槽位绑定”等外部或多状态深链活体覆盖。SSE `event:` 解析漂移已修复并有 targeted Vitest 证据(2026-06-19:`sse.test.ts` + `AIPanel.contract.test.tsx` 17/17);`muse-module-ai-contract-server` 空壳孤儿 submodule 已清理(2026-06-19:从 AI reactor 移除并删除唯一 pom,`P1rAiRouteOwnershipTest` 加回潮门禁)。
- **关键风险 / TODO**:真实 New-API 未端到端验收;前端 agent 仍缺“运行试用/槽位绑定”等外部或多状态深链活体覆盖;grant/runtime 完整物理包拆分仍未做。SSE `event:` 解析漂移已修复并有 targeted Vitest 证据(2026-06-19:`sse.test.ts` + `AIPanel.contract.test.tsx` 17/17);`muse-module-ai-contract-server` 空壳孤儿 submodule 已清理(2026-06-19:从 AI reactor 移除并删除唯一 pom,`P1rAiRouteOwnershipTest` 加回潮门禁)。

View File

@ -0,0 +1,83 @@
package cn.iocoder.muse.server.framework.arch;
import com.tngtech.archunit.core.domain.Dependency;
import com.tngtech.archunit.core.domain.JavaClass;
import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.core.importer.ImportOption;
import org.junit.jupiter.api.Test;
import java.util.List;
import java.util.Set;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* AI grant/runtime 边界机械门禁。
*
* <p>当前代码尚未完成物理包拆分,所以本测试先做最小硬约束:runtime/任务执行侧只能消费
* Security 下发的 runtime permission envelope,不得直接依赖 Tool Grant 写链路类、Mapper、DO 或管理端 VO。
* 这避免运行时通过本模块授权写模型自授权,为后续真正的 grant/runtime 包拆分兜底。</p>
*/
class AiGrantRuntimeBoundaryArchTest {
private static final String AI_MODULE_PREFIX = "cn.iocoder.muse.module.ai.";
private static final String APPLICATION_MUSE_PREFIX = AI_MODULE_PREFIX + "application.muse.";
private static final String APPLICATION_MUSE_FACADE_PREFIX = APPLICATION_MUSE_PREFIX + "facade.";
private static final String DOMAIN_MUSE_PREFIX = AI_MODULE_PREFIX + "domain.muse.";
/**
* 名称里没有 Runtime、但位于 runtime 调度链路上的任务服务也纳入保护。
*/
private static final Set<String> RUNTIME_ENTRYPOINTS = Set.of(
APPLICATION_MUSE_PREFIX + "MuseAiTaskServiceImpl",
APPLICATION_MUSE_PREFIX + "MuseAiTaskStreamServiceImpl",
APPLICATION_MUSE_PREFIX + "MuseAiRuntimeJobDispatcher",
APPLICATION_MUSE_PREFIX + "MuseAiRuntimeJobExecutor",
APPLICATION_MUSE_PREFIX + "MuseAiRuntimePolicyProvider",
APPLICATION_MUSE_PREFIX + "MuseAiRuntimePayloadStore",
APPLICATION_MUSE_PREFIX + "MuseAiRuntimeCallRecorder",
APPLICATION_MUSE_PREFIX + "MuseAiRuntimeProjectionService",
APPLICATION_MUSE_PREFIX + "MuseAiRuntimeService",
APPLICATION_MUSE_PREFIX + "MuseAiRuntimeServiceImpl",
APPLICATION_MUSE_FACADE_PREFIX + "MuseAiRuntimeClient",
APPLICATION_MUSE_FACADE_PREFIX + "RealNewApiMuseAiRuntimeClient",
APPLICATION_MUSE_FACADE_PREFIX + "UnavailableMuseAiRuntimeClient",
APPLICATION_MUSE_FACADE_PREFIX + "SecurityRuntimePermissionFacade",
APPLICATION_MUSE_FACADE_PREFIX + "UnavailableSecurityRuntimePermissionFacade",
DOMAIN_MUSE_PREFIX + "MuseAiRuntimePermissionGuard");
private final JavaClasses aiClasses = new ClassFileImporter()
.withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
.importPackages("cn.iocoder.muse.module.ai");
@Test
void runtime_side_must_not_depend_on_tool_grant_write_model() {
List<String> violations = aiClasses.stream()
.filter(AiGrantRuntimeBoundaryArchTest::isRuntimeSide)
.flatMap(javaClass -> javaClass.getDirectDependenciesFromSelf().stream())
.filter(dependency -> isToolGrantWriteModel(dependency.getTargetClass()))
.map(Dependency::getDescription)
.sorted()
.toList();
assertTrue(violations.isEmpty(), () -> "AI runtime/任务执行侧不得直连 Tool Grant 写链路;违规依赖:\n"
+ String.join("\n", violations));
}
private static boolean isRuntimeSide(JavaClass javaClass) {
String fullName = javaClass.getFullName();
String simpleName = javaClass.getSimpleName();
return RUNTIME_ENTRYPOINTS.contains(fullName)
|| fullName.startsWith(APPLICATION_MUSE_PREFIX)
&& !fullName.startsWith(APPLICATION_MUSE_FACADE_PREFIX)
&& simpleName.contains("Runtime")
|| fullName.startsWith(DOMAIN_MUSE_PREFIX) && simpleName.contains("RuntimePermission");
}
private static boolean isToolGrantWriteModel(JavaClass targetClass) {
String fullName = targetClass.getFullName();
return fullName.startsWith(AI_MODULE_PREFIX) && targetClass.getSimpleName().contains("ToolGrant");
}
}