@@ -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' ,
@@ -263,6 +264,80 @@ describe('POST /api/copilot/chat/abort', () => {
263264 expect ( mockReleasePendingChatStream ) . toHaveBeenCalledWith ( 'chat-1' , 'stream-1' )
264265 } )
265266
267+ it ( 'preserves organization scope through parsing for a chatless pre-admission Stop' , async ( ) => {
268+ mockGetLatestRunForStream . mockResolvedValue ( null )
269+ mockRequestRunStop . mockResolvedValue ( null )
270+ const response = await POST (
271+ createMockRequest ( 'POST' , { streamId : 'early-stream' , organizationId : 'org-1' } )
272+ )
273+ expect ( response . status ) . toBe ( 200 )
274+ expect ( await response . json ( ) ) . toEqual ( { aborted : true , settled : true } )
275+ expect ( mockOrganizationAuthorize ) . toHaveBeenCalledWith (
276+ expect . objectContaining ( { kind : 'session' , userId : 'user-1' } ) ,
277+ expect . objectContaining ( { id : 'mothership.runs.abort' , minimumRole : 'member' } ) ,
278+ { organizationId : 'org-1' }
279+ )
280+ expect ( mockRequestRunStop ) . toHaveBeenCalledWith ( {
281+ streamId : 'early-stream' ,
282+ userId : 'user-1' ,
283+ organizationId : 'org-1' ,
284+ workspaceId : undefined ,
285+ } )
286+ expect ( mockWorkspaceContext ) . not . toHaveBeenCalled ( )
287+ expect ( mockAuthorize ) . not . toHaveBeenCalled ( )
288+ expect ( mockRequestExplicitStreamAbort ) . not . toHaveBeenCalled ( )
289+ } )
290+
291+ it ( 'refuses mixed owner scopes in the HTTP contract before protected lookup' , async ( ) => {
292+ const response = await POST (
293+ createMockRequest ( 'POST' , {
294+ streamId : 'early-stream' ,
295+ organizationId : 'org-1' ,
296+ workspaceId : 'workspace-1' ,
297+ } )
298+ )
299+ expect ( response . status ) . toBe ( 400 )
300+ expect ( mockGetLatestRunForStream ) . not . toHaveBeenCalled ( )
301+ expect ( mockRequestRunStop ) . not . toHaveBeenCalled ( )
302+ } )
303+
304+ it ( 'rechecks organization membership before writing a pre-admission Stop' , async ( ) => {
305+ mockGetLatestRunForStream . mockResolvedValue ( null )
306+ mockOrganizationAuthorize . mockRejectedValueOnce (
307+ new OrchestrationError ( 'forbidden' , 'Membership revoked' )
308+ )
309+ const response = await POST (
310+ createMockRequest ( 'POST' , { streamId : 'early-stream' , organizationId : 'org-1' } )
311+ )
312+ expect ( response . status ) . toBe ( 403 )
313+ expect ( mockRequestRunStop ) . not . toHaveBeenCalled ( )
314+ } )
315+
316+ it ( 'binds an organization admission that wins the Stop race before signalling it' , async ( ) => {
317+ mockGetLatestRunForStream . mockResolvedValue ( null )
318+ mockChatContext . mockResolvedValue ( {
319+ chatId : 'new-chat' ,
320+ userId : 'user-1' ,
321+ organizationId : 'org-1' ,
322+ } )
323+ mockRequestRunStop . mockResolvedValue ( {
324+ chatId : 'new-chat' ,
325+ workspaceId : null ,
326+ organizationId : 'org-1' ,
327+ } )
328+ const response = await POST (
329+ createMockRequest ( 'POST' , { streamId : 'early-stream' , organizationId : 'org-1' } )
330+ )
331+ expect ( response . status ) . toBe ( 200 )
332+ expect ( await response . json ( ) ) . toMatchObject ( { settled : true } )
333+ expect ( mockRequestExplicitStreamAbort ) . toHaveBeenCalledWith (
334+ expect . objectContaining ( {
335+ streamId : 'early-stream' ,
336+ chatId : 'new-chat' ,
337+ } )
338+ )
339+ } )
340+
266341 it ( 'stops an owned organization stream without borrowing a workspace grant' , async ( ) => {
267342 mockChatContext . mockResolvedValue ( {
268343 userId : 'user-1' ,
@@ -284,8 +359,6 @@ describe('POST /api/copilot/chat/abort', () => {
284359 expect ( mockAuthorize ) . not . toHaveBeenCalled ( )
285360 expect ( mockRequestExplicitStreamAbort ) . toHaveBeenCalledWith (
286361 expect . objectContaining ( {
287- organizationId : 'org-1' ,
288- workspaceId : undefined ,
289362 chatId : 'chat-1' ,
290363 userId : 'user-1' ,
291364 } )
@@ -387,7 +460,7 @@ describe('POST /api/copilot/chat/abort', () => {
387460 )
388461 expect ( response . status ) . toBe ( 200 )
389462 expect ( mockRequestExplicitStreamAbort ) . toHaveBeenCalledWith (
390- expect . objectContaining ( { chatId : 'chat-1' , userId : 'user-1' , workspaceId : 'workspace-1' } )
463+ expect . objectContaining ( { chatId : 'chat-1' , userId : 'user-1' } )
391464 )
392465 } )
393466 it ( 'rejects a run admitted in a different scope during the lookup race' , async ( ) => {
0 commit comments