@@ -2478,6 +2478,9 @@ describe('EdgeManager', () => {
24782478 expect ( readyNodes ) . toContain ( otherBranchId )
24792479 expect ( readyNodes ) . not . toContain ( sentinelStartId )
24802480
2481+ // sentinel_end should NOT be ready - it's on a fully deactivated path
2482+ expect ( readyNodes ) . not . toContain ( sentinelEndId )
2483+
24812484 // afterLoop should NOT be ready - its incoming edge from sentinel_end should be deactivated
24822485 expect ( readyNodes ) . not . toContain ( afterLoopId )
24832486
@@ -2545,6 +2548,84 @@ describe('EdgeManager', () => {
25452548 expect ( edgeManager . isNodeReady ( afterParallelNode ) ) . toBe ( true )
25462549 } )
25472550
2551+ it ( 'should not queue loop sentinel-end when upstream condition deactivates entire loop branch' , ( ) => {
2552+ // Regression test for: upstream condition → (if) → ... many blocks ... → sentinel_start → body → sentinel_end
2553+ // → (else) → exit_block
2554+ // When condition takes "else", the deep cascade deactivation should NOT queue sentinel_end.
2555+ // Previously, sentinel_end was flagged as a cascadeTarget (terminal control node) and
2556+ // spuriously queued, causing it to attempt loop scope initialization and fail.
2557+
2558+ const conditionId = 'condition'
2559+ const intermediateId = 'intermediate'
2560+ const sentinelStartId = 'sentinel-start'
2561+ const loopBodyId = 'loop-body'
2562+ const sentinelEndId = 'sentinel-end'
2563+ const afterLoopId = 'after-loop'
2564+ const exitBlockId = 'exit-block'
2565+
2566+ const conditionNode = createMockNode ( conditionId , [
2567+ { target : intermediateId , sourceHandle : 'condition-if' } ,
2568+ { target : exitBlockId , sourceHandle : 'condition-else' } ,
2569+ ] )
2570+
2571+ const intermediateNode = createMockNode (
2572+ intermediateId ,
2573+ [ { target : sentinelStartId } ] ,
2574+ [ conditionId ]
2575+ )
2576+
2577+ const sentinelStartNode = createMockNode (
2578+ sentinelStartId ,
2579+ [ { target : loopBodyId } ] ,
2580+ [ intermediateId ]
2581+ )
2582+
2583+ const loopBodyNode = createMockNode (
2584+ loopBodyId ,
2585+ [ { target : sentinelEndId } ] ,
2586+ [ sentinelStartId ]
2587+ )
2588+
2589+ const sentinelEndNode = createMockNode (
2590+ sentinelEndId ,
2591+ [
2592+ { target : sentinelStartId , sourceHandle : 'loop_continue' } ,
2593+ { target : afterLoopId , sourceHandle : 'loop_exit' } ,
2594+ ] ,
2595+ [ loopBodyId ]
2596+ )
2597+
2598+ const afterLoopNode = createMockNode ( afterLoopId , [ ] , [ sentinelEndId ] )
2599+ const exitBlockNode = createMockNode ( exitBlockId , [ ] , [ conditionId ] )
2600+
2601+ const nodes = new Map < string , DAGNode > ( [
2602+ [ conditionId , conditionNode ] ,
2603+ [ intermediateId , intermediateNode ] ,
2604+ [ sentinelStartId , sentinelStartNode ] ,
2605+ [ loopBodyId , loopBodyNode ] ,
2606+ [ sentinelEndId , sentinelEndNode ] ,
2607+ [ afterLoopId , afterLoopNode ] ,
2608+ [ exitBlockId , exitBlockNode ] ,
2609+ ] )
2610+
2611+ const dag = createMockDAG ( nodes )
2612+ const edgeManager = new EdgeManager ( dag )
2613+
2614+ const readyNodes = edgeManager . processOutgoingEdges ( conditionNode , {
2615+ selectedOption : 'else' ,
2616+ } )
2617+
2618+ // Only exitBlock should be ready
2619+ expect ( readyNodes ) . toContain ( exitBlockId )
2620+
2621+ // Nothing on the deactivated path should be queued
2622+ expect ( readyNodes ) . not . toContain ( intermediateId )
2623+ expect ( readyNodes ) . not . toContain ( sentinelStartId )
2624+ expect ( readyNodes ) . not . toContain ( loopBodyId )
2625+ expect ( readyNodes ) . not . toContain ( sentinelEndId )
2626+ expect ( readyNodes ) . not . toContain ( afterLoopId )
2627+ } )
2628+
25482629 it ( 'should still correctly handle normal loop exit (not deactivate when loop runs)' , ( ) => {
25492630 // When a loop actually executes and exits normally, after_loop should become ready
25502631 const sentinelStartId = 'sentinel-start'
0 commit comments