fix(workflow): PublishContext arg order + drop workspace-agnostic agent fallback

This commit is contained in:
matevip 2026-05-08 15:07:32 +08:00
parent 6bcf6b5ee4
commit ed64cda043
4 changed files with 18 additions and 12 deletions

View File

@ -104,8 +104,9 @@ public class WorkflowController {
} }
WorkflowCompiler.Result result; WorkflowCompiler.Result result;
try { try {
// PublishContext is (workspaceId, publisherId) mind the order.
result = compiler.compile(row.getDraftJson(), result = compiler.compile(row.getDraftJson(),
new PublishContext(0L, row.getWorkspaceId()), aclPort); new PublishContext(row.getWorkspaceId(), 0L), aclPort);
} catch (vip.mate.workflow.compiler.WorkflowParseException e) { } catch (vip.mate.workflow.compiler.WorkflowParseException e) {
// Malformed JSON / structurally invalid graph render as a // Malformed JSON / structurally invalid graph render as a
// single-error compile failure so the UI's existing errors // single-error compile failure so the UI's existing errors

View File

@ -102,8 +102,10 @@ public class WorkflowResumeController {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(R.fail("revision " + run.getRevisionId() + " missing for run " + runId)); .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(), WorkflowCompiler.Result compiled = compiler.compile(revision.getGraphJson(),
new PublishContext(0L, run.getWorkspaceId()), aclPort); new PublishContext(run.getWorkspaceId(), 0L), aclPort);
if (!compiled.ok()) { if (!compiled.ok()) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(R.fail("revision graph failed to recompile on resume")); .body(R.fail("revision graph failed to recompile on resume"));

View File

@ -32,18 +32,17 @@ public class DefaultAgentInvoker implements AgentInvoker {
@Override @Override
public Long resolveAgentId(long workspaceId, String agentName) { public Long resolveAgentId(long workspaceId, String agentName) {
if (agentName == null || agentName.isBlank()) return null; 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<AgentEntity>() AgentEntity entity = agentMapper.selectOne(new LambdaQueryWrapper<AgentEntity>()
.eq(AgentEntity::getWorkspaceId, workspaceId) .eq(AgentEntity::getWorkspaceId, workspaceId)
.eq(AgentEntity::getName, agentName.trim()) .eq(AgentEntity::getName, agentName.trim())
.eq(AgentEntity::getEnabled, true)); .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<AgentEntity>()
.eq(AgentEntity::getName, agentName.trim())
.eq(AgentEntity::getEnabled, true));
}
return entity == null ? null : entity.getId(); return entity == null ? null : entity.getId();
} }
} }

View File

@ -134,8 +134,12 @@ public class WorkflowService {
throw new IllegalStateException("cannot publish workflow " + workflowId throw new IllegalStateException("cannot publish workflow " + workflowId
+ " without a draft"); + " without a draft");
} }
PublishContext ctx = new PublishContext(publisherId == null ? 0L : publisherId, // PublishContext is (workspaceId, publisherId) mind the order.
workflow.getWorkspaceId()); // 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); WorkflowCompiler.Result compileResult = compiler.compile(draft, ctx, aclPort);
compileResult.requireOk(); compileResult.requireOk();