From d647f0c8d9af6fe4c7084417328e44174fdc5f44 Mon Sep 17 00:00:00 2001 From: lili Date: Wed, 24 Jun 2026 00:52:05 -0700 Subject: [PATCH] =?UTF-8?q?fix(ai):=20runtime=20=E6=8E=88=E6=9D=83?= =?UTF-8?q?=E5=91=BD=E4=B8=AD=E5=89=A5=E7=A6=BB=20sourceSnapshotId?= =?UTF-8?q?=EF=BC=8C=E4=BF=AE=20AI=20=E6=89=A7=E8=A1=8C=E6=81=92=20fail-cl?= =?UTF-8?q?osed=20=E6=AD=BB=E7=BB=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 问题: ProjectionSecurityRuntimePermissionFacade 的 runtime permission 门要求 grant.scope.sourceSnapshotId 精确匹配(SQL WHERE + Java scope 校验双重),但 sourceSnapshotId 每次 task 随机生成(ContentMuseWorkOwnerFacade:82 = "src-"+UUID)、且 grant 须在 task 提交前置存在 → admin 无法预审批含未来随机值的 grant → P1R-4 下任何 AI 执行(含 agent test)永远 fail-closed 被拒。 根因: 授权链两侧粒度语义错配——校验侧按 per-execution(含随机执行证据)匹配,产出侧仅有 per-registration(admin 预登记)能力;sourceSnapshotId 本是单次执行的事后审计证据,被误当成能力授权命中条件。 修正(最小): 从 grant 命中条件剥离 sourceSnapshotId——删 SQL WHERE 条件 + mapper 参数 + requireMatchingScope 校验;保留为 envelope summary 审计字段 + requireCompleteRequest 非空校验(执行证据仍必填)。授权回归能力粒度(operationId/targetType/targetId·targetKey/agentId/resourceRefs,均为可预登记的稳定标识)。 验证: ProjectionFacade 15(含新增 should_issueWhenScopeOmitsSourceSnapshotId)+ MuseAiTaskService 40 + ToolGrantService 6 + LocalApproval 7 全绿。 Co-Authored-By: Claude Opus 4.8 (1M context) --- ...ectionSecurityRuntimePermissionFacade.java | 6 +-- .../dal/mysql/muse/MuseToolGrantMapper.java | 16 ++++---- .../muse/MuseAiTaskServiceTest.java | 2 +- ...onSecurityRuntimePermissionFacadeTest.java | 40 ++++++++++++------- 4 files changed, 38 insertions(+), 26 deletions(-) diff --git a/muse-cloud/muse-module-ai/muse-module-ai-server/src/main/java/cn/iocoder/muse/module/ai/application/muse/facade/ProjectionSecurityRuntimePermissionFacade.java b/muse-cloud/muse-module-ai/muse-module-ai-server/src/main/java/cn/iocoder/muse/module/ai/application/muse/facade/ProjectionSecurityRuntimePermissionFacade.java index 6375a801..5d08867d 100644 --- a/muse-cloud/muse-module-ai/muse-module-ai-server/src/main/java/cn/iocoder/muse/module/ai/application/muse/facade/ProjectionSecurityRuntimePermissionFacade.java +++ b/muse-cloud/muse-module-ai/muse-module-ai-server/src/main/java/cn/iocoder/muse/module/ai/application/muse/facade/ProjectionSecurityRuntimePermissionFacade.java @@ -56,8 +56,7 @@ public class ProjectionSecurityRuntimePermissionFacade implements SecurityRuntim requireCompleteRequest(request); Long tenantId = TenantContextHolder.getRequiredTenantId(); MuseToolGrantDO grant = toolGrantMapper.selectRuntimeApprovedGrant(tenantId, request.operationId(), - request.targetType(), request.targetId(), request.targetKey(), request.sourceSnapshotId(), - request.agentId()); + request.targetType(), request.targetId(), request.targetKey(), request.agentId()); if (!isApprovedSecurityProjection(grant)) { // Security projection 不存在或不是 approved/active/security 时,AI runtime 只能拒绝执行。 throw new ServiceException(AI_RUNTIME_PERMISSION_DENIED); @@ -113,7 +112,8 @@ public class ProjectionSecurityRuntimePermissionFacade implements SecurityRuntim requireTextEquals(scope, "operationId", request.operationId()); requireTextEquals(scope, "targetType", request.targetType()); requireMatchingTargetFields(scope, request); - requireTextEquals(scope, "sourceSnapshotId", request.sourceSnapshotId()); + // sourceSnapshotId 是单次执行的运行时证据(随机生成),按设计不参与能力授权命中(详见 MuseToolGrantMapper 注释), + // 仅由 summary() 记入 envelope 作事后审计;requireCompleteRequest 仍要求其非空(执行证据必须存在)。 requireTextEquals(scope, "agentId", String.valueOf(request.agentId())); List> resourceRefs = resourceRefs(scope.get("resourceRefs")); diff --git a/muse-cloud/muse-module-ai/muse-module-ai-server/src/main/java/cn/iocoder/muse/module/ai/dal/mysql/muse/MuseToolGrantMapper.java b/muse-cloud/muse-module-ai/muse-module-ai-server/src/main/java/cn/iocoder/muse/module/ai/dal/mysql/muse/MuseToolGrantMapper.java index 34e481a0..b082c086 100644 --- a/muse-cloud/muse-module-ai/muse-module-ai-server/src/main/java/cn/iocoder/muse/module/ai/dal/mysql/muse/MuseToolGrantMapper.java +++ b/muse-cloud/muse-module-ai/muse-module-ai-server/src/main/java/cn/iocoder/muse/module/ai/dal/mysql/muse/MuseToolGrantMapper.java @@ -27,6 +27,11 @@ public interface MuseToolGrantMapper extends BaseMapperX { *

P1R-4 还没有接入 Security 主流程 RPC,只能消费已落库的 ToolGrant 投影。查询必须固定 * security owner、approved、active 三个条件;scope 必须显式携带本次 runtime 可消费的资源引用,避免 AI * 模块把普通审批号或缺字段投影当成 runtime envelope。

+ * + *

命中按「能力授权」粒度匹配 operationId/targetType/targetId·targetKey/agentId/resourceRefs——均为可预登记的稳定标识。 + * sourceSnapshotId 是单次执行的运行时证据(每次随机 "src-"+UUID 生成),属事后审计字段而非授权条件:若纳入命中, + * 等于要求预登记 grant 预知未来随机值,逻辑无解(任何 AI 执行都永远 fail-closed)。故剥离出命中条件, + * 仅由 runtime envelope summary 记录追溯(见 ProjectionSecurityRuntimePermissionFacade)。

*/ @Select("""