@@ -124,42 +124,46 @@ export class DAGExecutor {
124124 throw new Error ( validation . error )
125125 }
126126
127- const { dirtySet, upstreamSet } = computeExecutionSets ( dag , startBlockId )
127+ const { dirtySet, upstreamSet, reachableUpstreamSet } = computeExecutionSets ( dag , startBlockId )
128128 const effectiveStartBlockId = resolveContainerToSentinelStart ( startBlockId , dag ) ?? startBlockId
129129
130- // Extract container IDs from sentinel IDs in upstream set
131- const upstreamContainerIds = new Set < string > ( )
132- for ( const nodeId of upstreamSet ) {
130+ // Extract container IDs from sentinel IDs in reachable upstream set
131+ // Use reachableUpstreamSet (not upstreamSet) to preserve sibling branch outputs
132+ // Example: A->C, B->C where C references A.result || B.result
133+ // When running from A, B's output should be preserved for C to reference
134+ const reachableContainerIds = new Set < string > ( )
135+ for ( const nodeId of reachableUpstreamSet ) {
133136 const loopId = extractLoopIdFromSentinel ( nodeId )
134- if ( loopId ) upstreamContainerIds . add ( loopId )
137+ if ( loopId ) reachableContainerIds . add ( loopId )
135138 const parallelId = extractParallelIdFromSentinel ( nodeId )
136- if ( parallelId ) upstreamContainerIds . add ( parallelId )
139+ if ( parallelId ) reachableContainerIds . add ( parallelId )
137140 }
138141
139- // Filter snapshot to only include upstream blocks - prevents references to non-upstream blocks
142+ // Filter snapshot to include all blocks reachable from dirty blocks
143+ // This preserves sibling branch outputs that dirty blocks may reference
140144 const filteredBlockStates : Record < string , any > = { }
141145 for ( const [ blockId , state ] of Object . entries ( sourceSnapshot . blockStates ) ) {
142- if ( upstreamSet . has ( blockId ) || upstreamContainerIds . has ( blockId ) ) {
146+ if ( reachableUpstreamSet . has ( blockId ) || reachableContainerIds . has ( blockId ) ) {
143147 filteredBlockStates [ blockId ] = state
144148 }
145149 }
146150 const filteredExecutedBlocks = sourceSnapshot . executedBlocks . filter (
147- ( id ) => upstreamSet . has ( id ) || upstreamContainerIds . has ( id )
151+ ( id ) => reachableUpstreamSet . has ( id ) || reachableContainerIds . has ( id )
148152 )
149153
150- // Filter loop/parallel executions to only include upstream containers
154+ // Filter loop/parallel executions to only include reachable containers
151155 const filteredLoopExecutions : Record < string , any > = { }
152156 if ( sourceSnapshot . loopExecutions ) {
153157 for ( const [ loopId , execution ] of Object . entries ( sourceSnapshot . loopExecutions ) ) {
154- if ( upstreamContainerIds . has ( loopId ) ) {
158+ if ( reachableContainerIds . has ( loopId ) ) {
155159 filteredLoopExecutions [ loopId ] = execution
156160 }
157161 }
158162 }
159163 const filteredParallelExecutions : Record < string , any > = { }
160164 if ( sourceSnapshot . parallelExecutions ) {
161165 for ( const [ parallelId , execution ] of Object . entries ( sourceSnapshot . parallelExecutions ) ) {
162- if ( upstreamContainerIds . has ( parallelId ) ) {
166+ if ( reachableContainerIds . has ( parallelId ) ) {
163167 filteredParallelExecutions [ parallelId ] = execution
164168 }
165169 }
@@ -179,6 +183,7 @@ export class DAGExecutor {
179183 effectiveStartBlockId,
180184 dirtySetSize : dirtySet . size ,
181185 upstreamSetSize : upstreamSet . size ,
186+ reachableUpstreamSetSize : reachableUpstreamSet . size ,
182187 } )
183188
184189 // Remove incoming edges from non-dirty sources so convergent blocks don't wait for cached upstream
0 commit comments