Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const POST = defineV2JsonRoute({
operation: workflowOperations.cancelRun,
rateLimit: v2RateLimits.publicApi,
errorPolicy: v2WorkflowErrorPolicies.cancelRun,
mapInput: ({ params }) => ({ runId: params.runId }),
mapInput: ({ params }) => ({ workflowId: params.workflowId, runId: params.runId }),
useCase: cancelWorkflowRun,
present: (result) => ({
data: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,7 @@ describe('v2 run detail and cancel adapters', () => {
})
expect(mocks.cancel).toHaveBeenCalledWith({
principal,
input: { runId: 'run-1' },
input: { workflowId: 'workflow-1', runId: 'run-1' },
request: expect.anything(),
})
expect(v2RouteMocks.operationRate).toHaveBeenCalledTimes(2)
Expand Down Expand Up @@ -417,7 +417,7 @@ describe('v2 run detail and cancel adapters', () => {
expect(response.status).toBe(200)
expect(mocks.cancel).toHaveBeenCalledWith({
principal: personalPrincipal,
input: { runId: 'run-1' },
input: { workflowId: 'workflow-1', runId: 'run-1' },
request: expect.anything(),
})
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ describe('POST /api/workflows/[id]/executions/[executionId]/cancel', () => {
expect(mocks.cancel).toHaveBeenCalledWith({
principal,
input: {
workflowId: 'workflow-1',
runId: 'execution-1',
abortSignal: expect.any(AbortSignal),
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export const POST = defineInternalJsonRoute({
}),
errorPolicy: internalWorkflowErrorPolicies.concealRunAuthorization,
mapInput: ({ params }, { request }) => ({
workflowId: params.id,
runId: params.executionId,
abortSignal: request.signal,
}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ describe('workflow mutation Copilot adapters', () => {
}),
{
runId: 'execution-1',
assertedWorkspaceId: 'workspace-1',
}
)
})
Expand Down
1 change: 1 addition & 0 deletions apps/sim/lib/copilot/tools/handlers/workflow/mutations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,7 @@ export async function executeCancelWorkflowRun(
)
const result = await executeCopilotWorkflowUseCase(context, cancelWorkflowRun, {
runId: executionId,
assertedWorkspaceId: context.workspaceId,
...(context.abortSignal ? { abortSignal: context.abortSignal } : {}),
})

Expand Down
4 changes: 4 additions & 0 deletions apps/sim/lib/workflows/application/cancel-run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ import { resolveActiveWorkflowRunApplicationContext } from '@/lib/workflows/appl
import { workflowOperations } from '@/lib/workflows/application/operations'

export interface CancelWorkflowRunInput {
workflowId?: string
runId: string
assertedWorkspaceId?: string
abortSignal?: AbortSignal
}

Expand All @@ -19,6 +21,8 @@ export const cancelWorkflowRun = defineAuthorizedWorkflowUseCase({
resolveContext: ({ input }: { input: CancelWorkflowRunInput }) =>
resolveActiveWorkflowRunApplicationContext({
runId: input.runId,
assertedWorkflowId: input.workflowId,
assertedWorkspaceId: input.assertedWorkspaceId,
}),
async execute({ principal, context, input }) {
const attribution = resolvePrincipalAttribution(principal, {
Expand Down
41 changes: 41 additions & 0 deletions apps/sim/lib/workflows/application/context.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,47 @@ describe('workflow application contexts', () => {
})
})

it('resolves the canonical workflow from an execution ID without a workflow assertion', async () => {
queueCanonicalBindings({ log: 'workflow-1' })
queueTableRows(schemaMock.workflow, [
{
workflowId: 'workflow-1',
workflow: { id: 'workflow-1', name: 'Canonical workflow' },
workspaceId: 'workspace-1',
},
])

await expect(
resolveActiveWorkflowRunApplicationContext({
runId: 'run-1',
assertedWorkspaceId: 'workspace-1',
})
).resolves.toMatchObject({
runId: 'run-1',
workflowId: 'workflow-1',
workspaceId: 'workspace-1',
})
})

it('conceals a workspace mismatch for an execution-only lookup', async () => {
queueCanonicalBindings({ log: 'workflow-1' })
queueTableRows(schemaMock.workflow, [
{
workflowId: 'workflow-1',
workflow: { id: 'workflow-1', name: 'Canonical workflow' },
workspaceId: 'workspace-1',
},
])

await expect(
resolveActiveWorkflowRunApplicationContext({
runId: 'run-1',
assertedWorkspaceId: 'workspace-2',
})
).rejects.toMatchObject({ code: 'not_found', message: 'Workflow not found' })
expect(mocks.loadWorkspace).not.toHaveBeenCalled()
})

it('binds live execution authority to the deployment version stored on its durable log', async () => {
queueTableRows(schemaMock.workflowExecutionLogs, [
{
Expand Down
21 changes: 21 additions & 0 deletions apps/sim/lib/workflows/application/workflow-run-control.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,27 @@ describe('workflow run-control application use cases', () => {
}
)

it('derives cancellation workflow scope from the execution ID when no workflow is asserted', async () => {
const result = await cancelWorkflowRun.execute({
principal: principals[0].principal,
input: { runId: 'parent-run-1', assertedWorkspaceId: 'workspace-1' },
})

expect(mocks.resolveRunContext).toHaveBeenCalledWith({
runId: 'parent-run-1',
assertedWorkflowId: undefined,
assertedWorkspaceId: 'workspace-1',
})
expect(mocks.cancel).toHaveBeenCalledWith(
expect.objectContaining({
executionId: 'parent-run-1',
workflowId: 'workflow-1',
workspaceId: 'workspace-1',
})
)
expect(result).toMatchObject({ workflowId: 'workflow-1', workspaceId: 'workspace-1' })
})

it.each(principals)(
'authorizes $principal.kind resume and preserves the parent/new run distinction',
async ({ principal, actorUserId }) => {
Expand Down
Loading