@@ -139,8 +139,9 @@ export function getGroupInvalidationColumns(group: WorkflowGroup): string[] {
139139 * workflow group whose deps were unmet *before* the patch and are satisfied
140140 * *after*, OR whose dep column was touched by the patch (the server will
141141 * cancel+re-run via `deriveExecClearsForDataPatch` + the in-flight cancel
142- * orchestration), return a new `executions` map with that group flipped to
143- * `pending`. Enrichments are only flipped when a provider can build a request.
142+ * orchestration), return a new `executions` map. Eligible automatic groups are
143+ * flipped to `pending`; terminal manual groups and automatic enrichments that
144+ * cannot build provider params are cleared to mirror the server's re-arm.
144145 * The cell renderer treats `pending` as "Queued".
145146 *
146147 * Returns `null` when nothing changed, so callers can short-circuit.
@@ -160,10 +161,29 @@ export function optimisticallyScheduleNewlyEligibleGroups(
160161 const patchedColumns = new Set ( Object . keys ( patch ) )
161162
162163 let next : RowExecutions | null = null
163- let flipped = 0
164+ let changed = 0
164165 let skipped = 0
165166 for ( const group of groups ) {
167+ const exec = beforeRow . executions ?. [ group . id ]
168+ const depTouched = getGroupInvalidationColumns ( group ) . some ( ( columnId ) =>
169+ patchedColumns . has ( columnId )
170+ )
171+
172+ const clearInvalidatedTerminalExecution = ( ) : boolean => {
173+ if (
174+ ! depTouched ||
175+ ( exec ?. status !== 'completed' && exec ?. status !== 'error' && exec ?. status !== 'cancelled' )
176+ ) {
177+ return false
178+ }
179+ if ( next === null ) next = { ...( beforeRow . executions ?? { } ) }
180+ delete next [ group . id ]
181+ changed ++
182+ return true
183+ }
184+
166185 if ( group . autoRun === false ) {
186+ if ( areGroupDepsSatisfied ( group , afterRow ) ) clearInvalidatedTerminalExecution ( )
167187 skipped ++
168188 continue
169189 }
@@ -177,12 +197,12 @@ export function optimisticallyScheduleNewlyEligibleGroups(
177197 enrichment &&
178198 ! getEnrichmentRowReadiness ( enrichment , group , afterRow . data , columns ) . ready
179199 ) {
200+ clearInvalidatedTerminalExecution ( )
180201 skipped ++
181202 continue
182203 }
183204 }
184205
185- const exec = beforeRow . executions ?. [ group . id ]
186206 if ( exec ?. status === 'pending' && exec . jobId ) {
187207 skipped ++
188208 continue
@@ -196,10 +216,6 @@ export function optimisticallyScheduleNewlyEligibleGroups(
196216 // flight downstream groups, so optimistically flip to `pending`
197217 // regardless of current exec status (queued/running included — they're
198218 // about to be cancelled and re-run).
199- const depTouched = getGroupInvalidationColumns ( group ) . some ( ( columnId ) =>
200- patchedColumns . has ( columnId )
201- )
202-
203219 if ( ! depTouched && ( exec ?. status === 'queued' || exec ?. status === 'running' ) ) {
204220 skipped ++
205221 continue
@@ -209,7 +225,7 @@ export function optimisticallyScheduleNewlyEligibleGroups(
209225 continue
210226 }
211227
212- flipped ++
228+ changed ++
213229 if ( next === null ) next = { ...( beforeRow . executions ?? { } ) }
214230 const pending : RowExecutionMetadata = {
215231 status : 'pending' ,
@@ -221,8 +237,8 @@ export function optimisticallyScheduleNewlyEligibleGroups(
221237 }
222238 next [ group . id ] = pending
223239 }
224- if ( flipped > 0 ) {
225- logger . debug ( `[OptimisticCascade] row=${ beforeRow . id } flipped =${ flipped } skipped=${ skipped } ` )
240+ if ( changed > 0 ) {
241+ logger . debug ( `[OptimisticCascade] row=${ beforeRow . id } changed =${ changed } skipped=${ skipped } ` )
226242 }
227243 return next
228244}
0 commit comments