@@ -17,6 +17,7 @@ const {
1717 mockFinalizePausedCancellationForTerminalRun,
1818 mockGetPausedCancellationStatus,
1919 mockGetActiveResumeCancellationTarget,
20+ mockGetActiveResumeCancellationTargets,
2021 mockRollbackActiveResumeCancellation,
2122 mockFinalizeExecutionStream,
2223 mockReadExecutionMetaState,
@@ -39,6 +40,7 @@ const {
3940 mockFinalizePausedCancellationForTerminalRun : vi . fn ( ) ,
4041 mockGetPausedCancellationStatus : vi . fn ( ) ,
4142 mockGetActiveResumeCancellationTarget : vi . fn ( ) ,
43+ mockGetActiveResumeCancellationTargets : vi . fn ( ) ,
4244 mockRollbackActiveResumeCancellation : vi . fn ( ) ,
4345 mockFinalizeExecutionStream : vi . fn ( ) ,
4446 mockReadExecutionMetaState : vi . fn ( ) ,
@@ -82,6 +84,8 @@ vi.mock('@/lib/workflows/executor/human-in-the-loop-manager', () => ({
8284 getPausedCancellationStatus : ( ...args : unknown [ ] ) => mockGetPausedCancellationStatus ( ...args ) ,
8385 getActiveResumeCancellationTarget : ( ...args : unknown [ ] ) =>
8486 mockGetActiveResumeCancellationTarget ( ...args ) ,
87+ getActiveResumeCancellationTargets : ( ...args : unknown [ ] ) =>
88+ mockGetActiveResumeCancellationTargets ( ...args ) ,
8589 rollbackActiveResumeCancellation : ( ...args : unknown [ ] ) =>
8690 mockRollbackActiveResumeCancellation ( ...args ) ,
8791 } ,
@@ -191,6 +195,7 @@ describe('cancelWorkflowExecution', () => {
191195 mockFinalizePausedCancellationForTerminalRun . mockReset ( ) . mockResolvedValue ( true )
192196 mockGetPausedCancellationStatus . mockReset ( ) . mockResolvedValue ( null )
193197 mockGetActiveResumeCancellationTarget . mockReset ( ) . mockResolvedValue ( null )
198+ mockGetActiveResumeCancellationTargets . mockReset ( ) . mockResolvedValue ( [ ] )
194199 mockRollbackActiveResumeCancellation . mockReset ( ) . mockResolvedValue ( true )
195200 mockFinalizeExecutionStream . mockReset ( ) . mockResolvedValue ( true )
196201 mockReadExecutionMetaState . mockReset ( ) . mockResolvedValue ( { status : 'missing' } )
@@ -1650,7 +1655,7 @@ describe('cancelWorkflowExecution', () => {
16501655 } )
16511656 expect ( mockWriteTerminalEvent ) . not . toHaveBeenCalled ( )
16521657 expect ( mockCompletePausedCancellation ) . not . toHaveBeenCalled ( )
1653- expect ( mockFinalizePausedCancellationForTerminalRun ) . toHaveBeenCalledWith ( 'ex-1' , 'wf-1' )
1658+ expect ( mockFinalizePausedCancellationForTerminalRun ) . toHaveBeenCalledWith ( 'ex-1' , 'wf-1' , [ ] )
16541659 } )
16551660
16561661 it ( 'retries paused cancellation finalization before returning a terminal-race conflict' , async ( ) => {
@@ -1682,6 +1687,7 @@ describe('cancelWorkflowExecution', () => {
16821687 kind : 'active_resume' ,
16831688 target : ACTIVE_RESUME_TARGET ,
16841689 } )
1690+ mockGetActiveResumeCancellationTargets . mockResolvedValue ( [ ACTIVE_RESUME_TARGET ] )
16851691 mockMarkExecutionCancelled . mockResolvedValue ( { durablyRecorded : true , reason : 'recorded' } )
16861692 const returning = vi . fn ( ) . mockResolvedValue ( [ ] )
16871693 const where = vi . fn ( ( ) => ( { returning } ) )
@@ -1698,12 +1704,40 @@ describe('cancelWorkflowExecution', () => {
16981704 await expect ( response . json ( ) ) . resolves . toEqual ( {
16991705 error : 'Execution cannot be cancelled while completed' ,
17001706 } )
1701- expect ( mockFinalizePausedCancellationForTerminalRun ) . toHaveBeenCalledWith ( 'ex-1' , 'wf-1' )
1707+ expect ( mockFinalizePausedCancellationForTerminalRun ) . toHaveBeenCalledWith ( 'ex-1' , 'wf-1' , [
1708+ 'resume-entry-1' ,
1709+ ] )
17021710 expect ( mockRollbackActiveResumeCancellation ) . not . toHaveBeenCalled ( )
17031711 expect ( mockClearPausedCancellationIntent ) . not . toHaveBeenCalled ( )
17041712 expect ( mockClearExecutionCancellation ) . not . toHaveBeenCalled ( )
17051713 } )
17061714
1715+ it ( 'does not finalize a claimed resume that cannot be stopped after its parent is terminal' , async ( ) => {
1716+ mockGetActiveResumeCancellationTargets . mockResolvedValue ( [ ACTIVE_RESUME_TARGET ] )
1717+ mockGetActiveResumeCancellationTarget . mockResolvedValue ( ACTIVE_RESUME_TARGET )
1718+ dbChainMockFns . limit . mockResolvedValueOnce ( [
1719+ {
1720+ executionDeadlineAt : null ,
1721+ executionOrigin : null ,
1722+ status : 'completed' ,
1723+ workspaceId : 'workspace-1' ,
1724+ } ,
1725+ ] )
1726+
1727+ const response = await POST ( makeRequest ( ) , makeParams ( ) )
1728+
1729+ expect ( response . status ) . toBe ( 500 )
1730+ await expect ( response . json ( ) ) . resolves . toEqual ( {
1731+ error : 'Failed to reconcile paused execution after cancellation was rejected' ,
1732+ } )
1733+ expect ( mockMarkExecutionCancelled ) . toHaveBeenCalledTimes ( 3 )
1734+ expect ( mockMarkExecutionCancelled ) . toHaveBeenCalledWith ( 'resume-ex-1' , {
1735+ executionDeadlineAt : null ,
1736+ } )
1737+ expect ( mockFinalizePausedCancellationForTerminalRun ) . not . toHaveBeenCalled ( )
1738+ expect ( mockReleaseExecutionSlot ) . not . toHaveBeenCalled ( )
1739+ } )
1740+
17071741 it ( 'treats a concurrent cancellation as an idempotent success' , async ( ) => {
17081742 mockMarkExecutionCancelled . mockResolvedValue ( { durablyRecorded : true , reason : 'recorded' } )
17091743 dbChainMockFns . limit
0 commit comments