Skip to content

Commit 016683a

Browse files
committed
fix(executor): preserve background execution principals
1 parent 36bb427 commit 016683a

58 files changed

Lines changed: 1742 additions & 455 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/sim/app/api/knowledge/[id]/documents/[documentId]/chunks/[chunkId]/route.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ function resolveContentProvenance(
3838
headers: request.headers,
3939
payload,
4040
authType: internalKnowledgeAuthType(principal),
41-
userId: internalKnowledgeProvenanceUserId(request, principal, workspaceId),
41+
userId: internalKnowledgeProvenanceUserId(request.headers, principal, workspaceId),
4242
...(workspaceId ? { workspaceId } : {}),
4343
selectionKeys: includeContent ? ['chunk-content'] : [],
4444
})
@@ -65,7 +65,7 @@ export const GET = defineInternalJsonRoute({
6565
finalizeKnowledgePersistedResponse({
6666
headers: request.headers,
6767
authType: internalKnowledgeAuthType(principal),
68-
userId: internalKnowledgeProvenanceUserId(request, principal, result.workspaceId),
68+
userId: internalKnowledgeProvenanceUserId(request.headers, principal, result.workspaceId),
6969
workspaceId: result.workspaceId,
7070
body,
7171
chunks: [
@@ -102,7 +102,7 @@ export const PUT = defineInternalJsonRoute({
102102
finalizeKnowledgePersistedResponse({
103103
headers: request.headers,
104104
authType: internalKnowledgeAuthType(principal),
105-
userId: internalKnowledgeProvenanceUserId(request, principal, result.workspaceId),
105+
userId: internalKnowledgeProvenanceUserId(request.headers, principal, result.workspaceId),
106106
workspaceId: result.workspaceId,
107107
body,
108108
chunks: [

apps/sim/app/api/knowledge/[id]/documents/[documentId]/chunks/route.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ function resolveContentProvenance(
3939
headers: request.headers,
4040
payload,
4141
authType: internalKnowledgeAuthType(principal),
42-
userId: internalKnowledgeProvenanceUserId(request, principal, workspaceId),
42+
userId: internalKnowledgeProvenanceUserId(request.headers, principal, workspaceId),
4343
...(workspaceId ? { workspaceId } : {}),
4444
selectionKeys: includeContent ? ['chunk-content'] : [],
4545
})
@@ -70,7 +70,7 @@ export const GET = defineInternalJsonRoute({
7070
finalizeKnowledgePersistedResponse({
7171
headers: request.headers,
7272
authType: internalKnowledgeAuthType(principal),
73-
userId: internalKnowledgeProvenanceUserId(request, principal, result.workspaceId),
73+
userId: internalKnowledgeProvenanceUserId(request.headers, principal, result.workspaceId),
7474
workspaceId: result.workspaceId,
7575
body,
7676
chunks: result.chunks.map((chunk) => ({

apps/sim/app/api/knowledge/[id]/documents/[documentId]/route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ export const GET = defineInternalJsonRoute({
4545
finalizeKnowledgePersistedResponse({
4646
headers: request.headers,
4747
authType: internalKnowledgeAuthType(principal),
48-
userId: internalKnowledgeProvenanceUserId(request, principal, result.workspaceId),
48+
userId: internalKnowledgeProvenanceUserId(request.headers, principal, result.workspaceId),
4949
workspaceId: result.workspaceId,
5050
body,
5151
documents: [

apps/sim/app/api/knowledge/[id]/documents/route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ export const GET = defineInternalJsonRoute({
6060
finalizeKnowledgePersistedResponse({
6161
headers: request.headers,
6262
authType: internalKnowledgeAuthType(principal),
63-
userId: internalKnowledgeProvenanceUserId(request, principal, result.workspaceId),
63+
userId: internalKnowledgeProvenanceUserId(request.headers, principal, result.workspaceId),
6464
workspaceId: result.workspaceId,
6565
body,
6666
documents: result.documents.map((document) => ({

apps/sim/background/async-preprocessing-correlation.test.ts

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,74 @@ describe('async preprocessing correlation threading', () => {
227227
)
228228
})
229229

230+
it.each([
231+
{
232+
name: 'workspace API key',
233+
serializedPrincipal: {
234+
version: 1 as const,
235+
principal: {
236+
kind: 'workspace_api_key' as const,
237+
workspaceId: 'workspace-1',
238+
keyId: 'workspace-key-1',
239+
},
240+
},
241+
isPublicApiAccess: false,
242+
},
243+
{
244+
name: 'public API system',
245+
serializedPrincipal: {
246+
version: 1 as const,
247+
principal: {
248+
kind: 'system' as const,
249+
serviceId: 'public_api' as const,
250+
workspaceId: 'workspace-1',
251+
workflowId: 'workflow-1',
252+
},
253+
},
254+
isPublicApiAccess: true,
255+
},
256+
])(
257+
'restores the exact serialized $name principal before Trigger worker execution',
258+
async ({ serializedPrincipal, isPublicApiAccess }) => {
259+
mockPreprocessExecution.mockResolvedValueOnce({
260+
success: true,
261+
actorUserId: 'actor-1',
262+
workflowRecord: {
263+
id: 'workflow-1',
264+
userId: 'owner-1',
265+
workspaceId: 'workspace-1',
266+
variables: {},
267+
},
268+
billingAttribution,
269+
executionTimeout: {},
270+
})
271+
mockExecuteWorkflowCore.mockResolvedValueOnce({
272+
success: true,
273+
status: 'success',
274+
output: { ok: true },
275+
metadata: { duration: 10, userId: 'actor-1' },
276+
})
277+
278+
await executeWorkflowJob({
279+
principal: serializedPrincipal,
280+
workflowId: 'workflow-1',
281+
userId: 'actor-1',
282+
workspaceId: 'workspace-1',
283+
billingAttribution,
284+
triggerType: 'api',
285+
executionId: `execution-${serializedPrincipal.principal.kind}`,
286+
requestId: `request-${serializedPrincipal.principal.kind}`,
287+
isPublicApiAccess,
288+
})
289+
290+
const executionMetadata = mockExecutionSnapshot.mock.calls[0]?.[0]
291+
expect(executionMetadata.userId).toBe('actor-1')
292+
expect(executionMetadata.principal).toEqual(serializedPrincipal.principal)
293+
expect(executionMetadata.isPublicApiAccess).toBe(isPublicApiAccess)
294+
expect(executionMetadata.principal).not.toHaveProperty('userId')
295+
}
296+
)
297+
230298
it('restores a legacy authenticated workflow job as its recorded user actor', async () => {
231299
mockPreprocessExecution.mockResolvedValueOnce({
232300
success: true,
@@ -545,6 +613,14 @@ describe('async preprocessing correlation threading', () => {
545613
loggingSession,
546614
})
547615
)
616+
const executionMetadata = mockExecutionSnapshot.mock.calls[0]?.[0]
617+
expect(executionMetadata.userId).toBe('actor-2')
618+
expect(executionMetadata.principal).toEqual({
619+
kind: 'system',
620+
serviceId: 'schedule',
621+
workspaceId: 'workspace-1',
622+
workflowId: 'workflow-1',
623+
})
548624
})
549625

550626
it('passes workflow correlation into preprocessing', async () => {

apps/sim/background/webhook-execution.test.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,51 @@ describe('executeWebhookJob fault vs error handling', () => {
346346
)
347347
})
348348

349+
it('restores the exact serialized webhook principal without substituting the billing actor', async () => {
350+
const serializedPrincipal = {
351+
version: 1 as const,
352+
principal: {
353+
kind: 'system' as const,
354+
serviceId: 'webhook' as const,
355+
webhookId: 'webhook-1',
356+
workflowId: 'workflow-1',
357+
workspaceId: 'workspace-1',
358+
provider: 'slack',
359+
subject: {
360+
kind: 'external_user' as const,
361+
provider: 'slack',
362+
tenantId: 'team-1',
363+
subjectId: 'slack-user-1',
364+
},
365+
},
366+
}
367+
mockExecuteWorkflowCore.mockResolvedValueOnce({
368+
success: true,
369+
status: 'completed',
370+
output: {},
371+
logs: [],
372+
executionState: {
373+
blockStates: {},
374+
executedBlocks: [],
375+
blockLogs: [],
376+
decisions: {},
377+
completedLoops: [],
378+
activeExecutionPath: [],
379+
},
380+
})
381+
382+
await executeWebhookJob({
383+
...payload,
384+
provider: 'slack',
385+
principal: serializedPrincipal,
386+
})
387+
388+
const executionMetadata = mockExecutionSnapshot.mock.calls[0]?.[0]
389+
expect(executionMetadata.userId).toBe('user-1')
390+
expect(executionMetadata.principal).toEqual(serializedPrincipal.principal)
391+
expect(executionMetadata.principal).not.toHaveProperty('userId')
392+
})
393+
349394
it('persists the reconstructed legacy principal on setup retries', async () => {
350395
executionPreprocessingMockFns.mockPreprocessExecution.mockResolvedValueOnce({
351396
success: false,

apps/sim/executor/handlers/agent/agent-handler.test.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3688,9 +3688,11 @@ describe('AgentBlockHandler', () => {
36883688

36893689
expect(mockDiscoverMcpServerToolsAsExecutor).toHaveBeenCalledWith(
36903690
expect.objectContaining({
3691-
userId: contextWithWorkspace.userId,
36923691
workspaceId: 'test-workspace-123',
3693-
workflowId: 'test-workflow-456',
3692+
context: expect.objectContaining({
3693+
userId: contextWithWorkspace.userId,
3694+
workflowId: 'test-workflow-456',
3695+
}),
36943696
serverId: 'mcp-legacy-server',
36953697
})
36963698
)

apps/sim/executor/handlers/agent/agent-handler.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1283,10 +1283,14 @@ export class AgentBlockHandler implements BlockHandler {
12831283
}
12841284

12851285
return discoverMcpServerToolsAsExecutor({
1286-
userId: ctx.userId,
12871286
workspaceId: ctx.workspaceId,
1288-
workflowId: ctx.workflowId,
1289-
...(ctx.executionId ? { executionId: ctx.executionId } : {}),
1287+
context: {
1288+
workflowId: ctx.workflowId,
1289+
workspaceId: ctx.workspaceId,
1290+
executionId: ctx.executionId,
1291+
userId: ctx.userId,
1292+
executorDelegationOrigin: ctx.executorDelegationOrigin,
1293+
},
12901294
serverId,
12911295
signal: ctx.abortSignal,
12921296
})
@@ -1349,6 +1353,7 @@ export class AgentBlockHandler implements BlockHandler {
13491353
workspaceId: ctx.workspaceId,
13501354
executionId: ctx.executionId,
13511355
userId: ctx.userId,
1356+
executorDelegationOrigin: ctx.executorDelegationOrigin,
13521357
},
13531358
toolIndex,
13541359
resolveCustomBlockBinding: (blockType: string) =>

apps/sim/executor/handlers/pi/local/sim-tools.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,7 @@ export async function buildSimToolSpecs(
220220
workspaceId: ctx.workspaceId,
221221
executionId: ctx.executionId,
222222
userId: ctx.userId,
223+
executorDelegationOrigin: ctx.executorDelegationOrigin,
223224
},
224225
resolveCustomBlockBinding: (blockType: string) =>
225226
resolveCustomBlockToolBinding(blockType, ctx.workspaceId),

apps/sim/executor/handlers/workflow/workflow-handler.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,8 +229,16 @@ describe('WorkflowBlockHandler', () => {
229229

230230
mockContext = {
231231
workflowId: 'parent-workflow-id',
232+
executionId: 'parent-execution-id',
232233
userId: 'user-1',
233234
principal: { kind: 'session', userId: 'user-1', sessionId: 'session-1' },
235+
executorDelegationOrigin: {
236+
subjectUserId: 'user-1',
237+
workflowId: 'parent-workflow-id',
238+
executionId: 'parent-execution-id',
239+
principal: { kind: 'session', userId: 'user-1', sessionId: 'session-1' },
240+
currentWorkflow: { workflowId: 'parent-workflow-id', mode: 'draft' },
241+
},
234242
blockStates: new Map(),
235243
blockLogs: [],
236244
metadata: {
@@ -438,6 +446,7 @@ describe('WorkflowBlockHandler', () => {
438446
workflowId: 'parent-workflow-id',
439447
executionId: 'parent-execution-id',
440448
principal: { kind: 'session', userId: 'user-1', sessionId: 'session-1' },
449+
currentWorkflow: { workflowId: 'parent-workflow-id', mode: 'draft' },
441450
},
442451
})
443452
)
@@ -2096,6 +2105,7 @@ describe('WorkflowBlockHandler', () => {
20962105
workflowId: 'parent-workflow-id',
20972106
executionId: 'parent-execution-id',
20982107
principal: { kind: 'session', userId: 'user-1', sessionId: 'session-1' },
2108+
currentWorkflow: { workflowId: 'parent-workflow-id', mode: 'draft' },
20992109
},
21002110
})
21012111
)

0 commit comments

Comments
 (0)