@@ -115,6 +115,7 @@ describe('POST /api/copilot/chat/abort', () => {
115115 allowPersonalApiKeys : false ,
116116 } )
117117 mockAuthorize . mockResolvedValue ( undefined )
118+ mockOrganizationAuthorize . mockResolvedValue ( undefined )
118119 mockBannedUsers . mockResolvedValue ( [ ] )
119120 mockWorkspaceContext . mockResolvedValue ( {
120121 workspaceId : 'workspace-1' ,
@@ -271,7 +272,81 @@ describe('POST /api/copilot/chat/abort', () => {
271272 'chat-1'
272273 )
273274 expect ( mockRequestExplicitStreamAbort ) . toHaveBeenCalledWith (
274- expect . objectContaining ( { chatId : 'chat-1' , userId : 'user-1' , workspaceId : 'workspace-1' } )
275+ expect . objectContaining ( { chatId : 'chat-1' , userId : 'user-1' , streamId : 'stream-1' } )
276+ )
277+ } )
278+
279+ it ( 'preserves organization scope through parsing for a chatless pre-admission Stop' , async ( ) => {
280+ mockGetLatestRunForStream . mockResolvedValue ( null )
281+ mockRequestRunStop . mockResolvedValue ( null )
282+ const response = await POST (
283+ createMockRequest ( 'POST' , { streamId : 'early-stream' , organizationId : 'org-1' } )
284+ )
285+ expect ( response . status ) . toBe ( 200 )
286+ expect ( await response . json ( ) ) . toEqual ( { aborted : true , settled : true } )
287+ expect ( mockOrganizationAuthorize ) . toHaveBeenCalledWith (
288+ expect . objectContaining ( { kind : 'session' , userId : 'user-1' } ) ,
289+ expect . objectContaining ( { id : 'mothership.runs.abort' , minimumRole : 'member' } ) ,
290+ { organizationId : 'org-1' }
291+ )
292+ expect ( mockRequestRunStop ) . toHaveBeenCalledWith ( {
293+ streamId : 'early-stream' ,
294+ userId : 'user-1' ,
295+ organizationId : 'org-1' ,
296+ workspaceId : undefined ,
297+ } )
298+ expect ( mockWorkspaceContext ) . not . toHaveBeenCalled ( )
299+ expect ( mockAuthorize ) . not . toHaveBeenCalled ( )
300+ expect ( mockRequestExplicitStreamAbort ) . not . toHaveBeenCalled ( )
301+ } )
302+
303+ it ( 'refuses mixed owner scopes in the HTTP contract before protected lookup' , async ( ) => {
304+ const response = await POST (
305+ createMockRequest ( 'POST' , {
306+ streamId : 'early-stream' ,
307+ organizationId : 'org-1' ,
308+ workspaceId : 'workspace-1' ,
309+ } )
310+ )
311+ expect ( response . status ) . toBe ( 400 )
312+ expect ( mockGetLatestRunForStream ) . not . toHaveBeenCalled ( )
313+ expect ( mockRequestRunStop ) . not . toHaveBeenCalled ( )
314+ } )
315+
316+ it ( 'rechecks organization membership before writing a pre-admission Stop' , async ( ) => {
317+ mockGetLatestRunForStream . mockResolvedValue ( null )
318+ mockOrganizationAuthorize . mockRejectedValueOnce (
319+ new OrchestrationError ( 'forbidden' , 'Membership revoked' )
320+ )
321+ const response = await POST (
322+ createMockRequest ( 'POST' , { streamId : 'early-stream' , organizationId : 'org-1' } )
323+ )
324+ expect ( response . status ) . toBe ( 403 )
325+ expect ( mockRequestRunStop ) . not . toHaveBeenCalled ( )
326+ } )
327+
328+ it ( 'binds an organization admission that wins the Stop race before signalling it' , async ( ) => {
329+ mockGetLatestRunForStream . mockResolvedValue ( null )
330+ mockChatContext . mockResolvedValue ( {
331+ chatId : 'new-chat' ,
332+ userId : 'user-1' ,
333+ organizationId : 'org-1' ,
334+ } )
335+ mockRequestRunStop . mockResolvedValue ( {
336+ chatId : 'new-chat' ,
337+ workspaceId : null ,
338+ organizationId : 'org-1' ,
339+ } )
340+ const response = await POST (
341+ createMockRequest ( 'POST' , { streamId : 'early-stream' , organizationId : 'org-1' } )
342+ )
343+ expect ( response . status ) . toBe ( 200 )
344+ expect ( await response . json ( ) ) . toMatchObject ( { settled : true } )
345+ expect ( mockRequestExplicitStreamAbort ) . toHaveBeenCalledWith (
346+ expect . objectContaining ( {
347+ streamId : 'early-stream' ,
348+ chatId : 'new-chat' ,
349+ } )
275350 )
276351 } )
277352
@@ -296,8 +371,6 @@ describe('POST /api/copilot/chat/abort', () => {
296371 expect ( mockAuthorize ) . not . toHaveBeenCalled ( )
297372 expect ( mockRequestExplicitStreamAbort ) . toHaveBeenCalledWith (
298373 expect . objectContaining ( {
299- organizationId : 'org-1' ,
300- workspaceId : undefined ,
301374 chatId : 'chat-1' ,
302375 userId : 'user-1' ,
303376 } )
@@ -399,7 +472,7 @@ describe('POST /api/copilot/chat/abort', () => {
399472 )
400473 expect ( response . status ) . toBe ( 200 )
401474 expect ( mockRequestExplicitStreamAbort ) . toHaveBeenCalledWith (
402- expect . objectContaining ( { chatId : 'chat-1' , userId : 'user-1' , workspaceId : 'workspace-1' } )
475+ expect . objectContaining ( { chatId : 'chat-1' , userId : 'user-1' } )
403476 )
404477 } )
405478 it ( 'rejects a run admitted in a different scope during the lookup race' , async ( ) => {
0 commit comments