1+ import type { Principal } from '@sim/auth/principal'
2+ import { createPersonalApiKeyPrincipal } from '@sim/testing/factories/principal.factory'
13import {
24 executorPrincipalMock ,
35 executorPrincipalMockFns ,
@@ -12,15 +14,18 @@ const mocks = vi.hoisted(() => ({
1214vi . mock ( '@/lib/internal/principals/executor' , ( ) => executorPrincipalMock )
1315
1416vi . mock ( '@/lib/function-execution/application/execute-function' , ( ) => ( {
15- executeFunction : { execute : mocks . execute } ,
17+ executeFunction : { execute : mocks . execute , delegationAudience : 'sim:function-executions' } ,
1618} ) )
1719
1820vi . mock ( '@/lib/function-execution/application/execute-chat-function' , ( ) => ( {
1921 executeChatFunction : { execute : mocks . executeChat } ,
2022} ) )
2123
24+ import { markCopilotWorkspaceInvocation } from '@/lib/core/application/copilot-workspace-invocation'
2225import { FUNCTION_EXECUTION_DELEGATION_AUDIENCE } from '@/lib/function-execution/application/authorization'
2326import { executeFunctionTool } from '@/lib/internal/function/execute'
27+ import { createCopilotChatPrincipal } from '@/lib/mothership/auth/application-delegation'
28+ import { TOOL_EXECUTION_DELEGATION_AUDIENCE } from '@/lib/tool-execution/application/operations'
2429import { ResolvedSecretTraceRegistry } from '@/executor/utils/resolved-secret-trace-registry'
2530
2631const { mockCreateExecutorPrincipalFromExecutionContext } = executorPrincipalMockFns
@@ -169,4 +174,57 @@ describe('executeFunctionTool', () => {
169174 expect ( mocks . executeChat ) . not . toHaveBeenCalled ( )
170175 expect ( mocks . execute ) . not . toHaveBeenCalled ( )
171176 } )
177+
178+ describe ( 'direct tool calls (POST /api/v2/tools/{id}/execute)' , ( ) => {
179+ function directContext ( callerPrincipal : Principal ) {
180+ return {
181+ workflowId : '' ,
182+ workspaceId : 'workspace-1' ,
183+ userId : 'user-1' ,
184+ callerPrincipal,
185+ }
186+ }
187+
188+ it ( 'rebinds an admitted Mothership caller to the function-execution audience' , async ( ) => {
189+ const caller = createCopilotChatPrincipal (
190+ { userId : 'user-1' , workspaceId : 'workspace-1' , chatId : 'chat-1' } ,
191+ TOOL_EXECUTION_DELEGATION_AUDIENCE
192+ )
193+ markCopilotWorkspaceInvocation ( caller )
194+
195+ await executeFunctionTool ( {
196+ body : { code : 'return 1' } ,
197+ headers : new Headers ( ) ,
198+ requestId : 'request-1' ,
199+ context : directContext ( caller ) ,
200+ } )
201+
202+ expect ( mockCreateExecutorPrincipalFromExecutionContext ) . not . toHaveBeenCalled ( )
203+ expect ( mocks . execute ) . toHaveBeenCalledWith (
204+ expect . objectContaining ( {
205+ principal : expect . objectContaining ( {
206+ kind : 'delegated' ,
207+ serviceId : 'copilot' ,
208+ subjectUserId : 'user-1' ,
209+ workspaceId : 'workspace-1' ,
210+ audience : FUNCTION_EXECUTION_DELEGATION_AUDIENCE ,
211+ } ) ,
212+ } )
213+ )
214+ } )
215+
216+ it ( 'hands any other caller to the function-execution policy unchanged' , async ( ) => {
217+ const caller = createPersonalApiKeyPrincipal ( { keyId : 'personal-key-1' } )
218+
219+ await executeFunctionTool ( {
220+ body : { code : 'return 1' } ,
221+ headers : new Headers ( ) ,
222+ requestId : 'request-1' ,
223+ context : directContext ( caller ) ,
224+ } )
225+
226+ expect ( mockCreateExecutorPrincipalFromExecutionContext ) . not . toHaveBeenCalled ( )
227+ expect ( mocks . execute ) . toHaveBeenCalledWith ( expect . objectContaining ( { principal : caller } ) )
228+ } )
229+ } )
172230} )
0 commit comments