@@ -301,6 +301,23 @@ test('track leaf dataclip when step has no downstream jobs', async (t) => {
301301 t . is ( state . leafDataclipIds . length , 1 ) ;
302302} ) ;
303303
304+ test ( 'track leaf dataclip when step has undefined next' , async ( t ) => {
305+ const plan = createPlan ( ) ;
306+
307+ const state = createRunState ( plan ) ;
308+ state . activeJob = 'job-1' ;
309+ state . activeStep = 'b' ;
310+
311+ const channel = mockChannel ( {
312+ [ STEP_COMPLETE ] : ( ) => true ,
313+ } ) ;
314+
315+ const event = { state : { x : 10 } } as any ;
316+ await handleStepComplete ( { channel, state } as any , event ) ;
317+
318+ t . is ( state . leafDataclipIds . length , 1 ) ;
319+ } ) ;
320+
304321test ( 'do not track leaf dataclip when step has downstream jobs' , async ( t ) => {
305322 const plan = createPlan ( ) ;
306323
@@ -317,3 +334,63 @@ test('do not track leaf dataclip when step has downstream jobs', async (t) => {
317334
318335 t . is ( state . leafDataclipIds . length , 0 ) ;
319336} ) ;
337+
338+ // Multiple leaf nodes: start → job-a (leaf), start → job-b (leaf)
339+ test ( 'accumulate multiple leaf dataclips for branching workflow' , async ( t ) => {
340+ const plan = createPlan ( ) ;
341+ const state = createRunState ( plan ) ;
342+
343+ const channel = mockChannel ( {
344+ [ STEP_COMPLETE ] : ( ) => true ,
345+ } ) ;
346+
347+ // First leaf completes
348+ state . activeJob = 'job-a' ;
349+ state . activeStep = 'step-a' ;
350+ await handleStepComplete (
351+ { channel, state } as any ,
352+ { state : { a : true } , next : [ ] } as any
353+ ) ;
354+
355+ // Second leaf completes
356+ state . activeJob = 'job-b' ;
357+ state . activeStep = 'step-b' ;
358+ await handleStepComplete (
359+ { channel, state } as any ,
360+ { state : { b : true } , next : [ ] } as any
361+ ) ;
362+
363+ t . is ( state . leafDataclipIds . length , 2 ) ;
364+ // Each leaf gets a distinct dataclip id
365+ t . not ( state . leafDataclipIds [ 0 ] , state . leafDataclipIds [ 1 ] ) ;
366+ } ) ;
367+
368+ // Single leaf reached by two paths: start → a → x, start → b → x
369+ // x executes twice, both times with no downstream
370+ test ( 'accumulate two leaf dataclips when same node reached by two paths' , async ( t ) => {
371+ const plan = createPlan ( ) ;
372+ const state = createRunState ( plan ) ;
373+
374+ const channel = mockChannel ( {
375+ [ STEP_COMPLETE ] : ( ) => true ,
376+ } ) ;
377+
378+ // x completes first time (via path a)
379+ state . activeJob = 'job-x' ;
380+ state . activeStep = 'step-x' ;
381+ await handleStepComplete (
382+ { channel, state } as any ,
383+ { state : { from : 'a' } , next : [ ] } as any
384+ ) ;
385+
386+ // x completes second time (via path b)
387+ state . activeJob = 'job-x' ;
388+ state . activeStep = 'step-x-1' ;
389+ await handleStepComplete (
390+ { channel, state } as any ,
391+ { state : { from : 'b' } , next : [ ] } as any
392+ ) ;
393+
394+ t . is ( state . leafDataclipIds . length , 2 ) ;
395+ t . not ( state . leafDataclipIds [ 0 ] , state . leafDataclipIds [ 1 ] ) ;
396+ } ) ;
0 commit comments