@@ -51,48 +51,89 @@ export interface RunFromBlockContext {
5151}
5252
5353/**
54- * Computes all blocks that need re-execution when running from a specific block.
55- * Uses BFS to find all downstream blocks reachable via outgoing edges.
54+ * Result of computing execution sets for run-from-block mode.
55+ */
56+ export interface ExecutionSets {
57+ /** Blocks that need re-execution (start block + all downstream) */
58+ dirtySet : Set < string >
59+ /** Blocks that are upstream (ancestors) of the start block */
60+ upstreamSet : Set < string >
61+ }
62+
63+ /**
64+ * Computes both the dirty set (downstream) and upstream set in a single traversal pass.
65+ * - Dirty set: start block + all blocks reachable via outgoing edges (need re-execution)
66+ * - Upstream set: all blocks reachable via incoming edges (can be referenced)
5667 *
5768 * For loop/parallel containers, starts from the sentinel-start node and includes
5869 * the container ID itself in the dirty set.
5970 *
6071 * @param dag - The workflow DAG
6172 * @param startBlockId - The block to start execution from
62- * @returns Set of block IDs that are "dirty" and need re-execution
73+ * @returns Object containing both dirtySet and upstreamSet
6374 */
64- export function computeDirtySet ( dag : DAG , startBlockId : string ) : Set < string > {
75+ export function computeExecutionSets ( dag : DAG , startBlockId : string ) : ExecutionSets {
6576 const dirty = new Set < string > ( [ startBlockId ] )
77+ const upstream = new Set < string > ( )
6678 const sentinelStartId = resolveContainerToSentinelStart ( startBlockId , dag )
6779 const traversalStartId = sentinelStartId ?? startBlockId
6880
6981 if ( sentinelStartId ) {
7082 dirty . add ( sentinelStartId )
7183 }
7284
73- const queue = [ traversalStartId ]
74-
75- while ( queue . length > 0 ) {
76- const nodeId = queue . shift ( ) !
85+ // BFS downstream for dirty set
86+ const downstreamQueue = [ traversalStartId ]
87+ while ( downstreamQueue . length > 0 ) {
88+ const nodeId = downstreamQueue . shift ( ) !
7789 const node = dag . nodes . get ( nodeId )
7890 if ( ! node ) continue
7991
8092 for ( const [ , edge ] of node . outgoingEdges ) {
8193 if ( ! dirty . has ( edge . target ) ) {
8294 dirty . add ( edge . target )
83- queue . push ( edge . target )
95+ downstreamQueue . push ( edge . target )
8496 }
8597 }
8698 }
8799
88- logger . debug ( 'Computed dirty set' , {
100+ // BFS upstream for upstream set
101+ const upstreamQueue = [ traversalStartId ]
102+ while ( upstreamQueue . length > 0 ) {
103+ const nodeId = upstreamQueue . shift ( ) !
104+ const node = dag . nodes . get ( nodeId )
105+ if ( ! node ) continue
106+
107+ for ( const sourceId of node . incomingEdges ) {
108+ if ( ! upstream . has ( sourceId ) ) {
109+ upstream . add ( sourceId )
110+ upstreamQueue . push ( sourceId )
111+ }
112+ }
113+ }
114+
115+ logger . debug ( 'Computed execution sets' , {
89116 startBlockId,
90117 traversalStartId,
91118 dirtySetSize : dirty . size ,
92- dirtyBlocks : Array . from ( dirty ) ,
119+ upstreamSetSize : upstream . size ,
93120 } )
94121
95- return dirty
122+ return { dirtySet : dirty , upstreamSet : upstream }
123+ }
124+
125+ /**
126+ * @deprecated Use computeExecutionSets instead for combined computation
127+ */
128+ export function computeDirtySet ( dag : DAG , startBlockId : string ) : Set < string > {
129+ return computeExecutionSets ( dag , startBlockId ) . dirtySet
130+ }
131+
132+ /**
133+ * @deprecated Use computeExecutionSets instead for combined computation
134+ */
135+ export function computeUpstreamSet ( dag : DAG , blockId : string ) : Set < string > {
136+ return computeExecutionSets ( dag , blockId ) . upstreamSet
96137}
97138
98139/**
0 commit comments