diff --git a/mateclaw-server/src/main/java/vip/mate/workflow/api/WorkflowController.java b/mateclaw-server/src/main/java/vip/mate/workflow/api/WorkflowController.java index e376f172..75965eb8 100644 --- a/mateclaw-server/src/main/java/vip/mate/workflow/api/WorkflowController.java +++ b/mateclaw-server/src/main/java/vip/mate/workflow/api/WorkflowController.java @@ -104,8 +104,9 @@ public class WorkflowController { } WorkflowCompiler.Result result; try { + // PublishContext is (workspaceId, publisherId) — mind the order. result = compiler.compile(row.getDraftJson(), - new PublishContext(0L, row.getWorkspaceId()), aclPort); + new PublishContext(row.getWorkspaceId(), 0L), aclPort); } catch (vip.mate.workflow.compiler.WorkflowParseException e) { // Malformed JSON / structurally invalid graph → render as a // single-error compile failure so the UI's existing errors diff --git a/mateclaw-server/src/main/java/vip/mate/workflow/api/WorkflowResumeController.java b/mateclaw-server/src/main/java/vip/mate/workflow/api/WorkflowResumeController.java index 8289ad5e..55df9356 100644 --- a/mateclaw-server/src/main/java/vip/mate/workflow/api/WorkflowResumeController.java +++ b/mateclaw-server/src/main/java/vip/mate/workflow/api/WorkflowResumeController.java @@ -102,8 +102,10 @@ public class WorkflowResumeController { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body(R.fail("revision " + run.getRevisionId() + " missing for run " + runId)); } + // PublishContext is (workspaceId, publisherId) — mind the order; + // ACL resolution scopes by workspace. WorkflowCompiler.Result compiled = compiler.compile(revision.getGraphJson(), - new PublishContext(0L, run.getWorkspaceId()), aclPort); + new PublishContext(run.getWorkspaceId(), 0L), aclPort); if (!compiled.ok()) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body(R.fail("revision graph failed to recompile on resume")); diff --git a/mateclaw-server/src/main/java/vip/mate/workflow/runtime/DefaultAgentInvoker.java b/mateclaw-server/src/main/java/vip/mate/workflow/runtime/DefaultAgentInvoker.java index e89e166b..8de087a9 100644 --- a/mateclaw-server/src/main/java/vip/mate/workflow/runtime/DefaultAgentInvoker.java +++ b/mateclaw-server/src/main/java/vip/mate/workflow/runtime/DefaultAgentInvoker.java @@ -32,18 +32,17 @@ public class DefaultAgentInvoker implements AgentInvoker { @Override public Long resolveAgentId(long workspaceId, String agentName) { if (agentName == null || agentName.isBlank()) return null; + // Workspace-scoped only — no fallback to a global lookup. The + // earlier "fall back to workspace-agnostic" branch let an old + // revision (or any code path that bypassed publish-time ACL) + // pull a same-named agent from a different workspace at runtime, + // which is exactly what tenant isolation forbids. The + // publish-time ACL layer is also workspace-scoped, so a draft + // referencing a foreign agent is rejected before it ever runs. AgentEntity entity = agentMapper.selectOne(new LambdaQueryWrapper() .eq(AgentEntity::getWorkspaceId, workspaceId) .eq(AgentEntity::getName, agentName.trim()) .eq(AgentEntity::getEnabled, true)); - if (entity == null) { - // Fall back to a workspace-agnostic lookup so global agents still - // resolve. This mirrors how the workflow ACL phase counts an agent - // as "resolvable" if it exists anywhere the user can see it. - entity = agentMapper.selectOne(new LambdaQueryWrapper() - .eq(AgentEntity::getName, agentName.trim()) - .eq(AgentEntity::getEnabled, true)); - } return entity == null ? null : entity.getId(); } } diff --git a/mateclaw-server/src/main/java/vip/mate/workflow/service/WorkflowService.java b/mateclaw-server/src/main/java/vip/mate/workflow/service/WorkflowService.java index bbc51b10..f98be7f0 100644 --- a/mateclaw-server/src/main/java/vip/mate/workflow/service/WorkflowService.java +++ b/mateclaw-server/src/main/java/vip/mate/workflow/service/WorkflowService.java @@ -134,8 +134,12 @@ public class WorkflowService { throw new IllegalStateException("cannot publish workflow " + workflowId + " without a draft"); } - PublishContext ctx = new PublishContext(publisherId == null ? 0L : publisherId, - workflow.getWorkspaceId()); + // PublishContext is (workspaceId, publisherId) — mind the order. + // ACL validators read ctx.workspaceId() to scope agent / channel / + // employee resolution; passing the publisherId in that slot + // would silently let cross-workspace references through. + PublishContext ctx = new PublishContext(workflow.getWorkspaceId(), + publisherId == null ? 0L : publisherId); WorkflowCompiler.Result compileResult = compiler.compile(draft, ctx, aclPort); compileResult.requireOk();