@@ -140,45 +140,42 @@ maybe_fail_step AS (
140140 WHERE pgflow .step_states .run_id = fail_task .run_id
141141 AND pgflow .step_states .step_slug = fail_task .step_slug
142142 RETURNING pgflow .step_states .*
143+ ),
144+ run_update AS (
145+ -- Update run status: only fail when when_exhausted='fail' and step was failed
146+ UPDATE pgflow .runs
147+ SET status = CASE
148+ WHEN (select status from maybe_fail_step) = ' failed' THEN ' failed'
149+ ELSE status
150+ END,
151+ failed_at = CASE
152+ WHEN (select status from maybe_fail_step) = ' failed' THEN now()
153+ ELSE NULL
154+ END,
155+ -- Decrement remaining_steps when step was skipped (not failed, run continues)
156+ remaining_steps = CASE
157+ WHEN (select status from maybe_fail_step) = ' skipped' THEN pgflow .runs .remaining_steps - 1
158+ ELSE pgflow .runs .remaining_steps
159+ END
160+ WHERE pgflow .runs .run_id = fail_task .run_id
161+ RETURNING pgflow .runs .status
143162)
144- -- Update run status: only fail when when_exhausted='fail' and step was failed
145- UPDATE pgflow .runs
146- SET status = CASE
147- WHEN (select status from maybe_fail_step) = ' failed' THEN ' failed'
148- ELSE status
149- END,
150- failed_at = CASE
151- WHEN (select status from maybe_fail_step) = ' failed' THEN now()
152- ELSE NULL
153- END,
154- -- Decrement remaining_steps when step was skipped (not failed, run continues)
155- remaining_steps = CASE
156- WHEN (select status from maybe_fail_step) = ' skipped' THEN pgflow .runs .remaining_steps - 1
157- ELSE pgflow .runs .remaining_steps
158- END
159- WHERE pgflow .runs .run_id = fail_task .run_id
160- RETURNING (status = ' failed' ) INTO v_run_failed;
163+ SELECT
164+ COALESCE((SELECT status = ' failed' FROM run_update), false),
165+ COALESCE((SELECT status = ' failed' FROM maybe_fail_step), false),
166+ COALESCE((SELECT status = ' skipped' FROM maybe_fail_step), false),
167+ COALESCE((SELECT is_exhausted FROM task_status), false)
168+ INTO v_run_failed, v_step_failed, v_step_skipped, v_task_exhausted;
161169
162- -- Capture when_exhausted mode and check if step was skipped for later processing
170+ -- Capture when_exhausted mode for later skip handling
163171 SELECT s .when_exhausted INTO v_when_exhausted
164172 FROM pgflow .steps s
165173JOIN pgflow .runs r ON r .flow_slug = s .flow_slug
166- WHERE r .run_id = fail_task .run_id
167- AND s .step_slug = fail_task .step_slug ;
168-
169- SELECT (status = ' skipped' ) INTO v_step_skipped
170- FROM pgflow .step_states
171- WHERE pgflow .step_states .run_id = fail_task .run_id
172- AND pgflow .step_states .step_slug = fail_task .step_slug ;
173-
174- -- Check if step failed by querying the step_states table
175- SELECT (status = ' failed' ) INTO v_step_failed
176- FROM pgflow .step_states
177- WHERE pgflow .step_states .run_id = fail_task .run_id
178- AND pgflow .step_states .step_slug = fail_task .step_slug ;
174+ WHERE r .run_id = fail_task .run_id
175+ AND s .step_slug = fail_task .step_slug ;
179176
180177-- Send broadcast event for step failure if the step was failed
181- IF v_step_failed THEN
178+ IF v_task_exhausted AND v_step_failed THEN
182179 PERFORM realtime .send (
183180 jsonb_build_object(
184181 ' event_type' , ' step:failed' ,
@@ -194,8 +191,8 @@ IF v_step_failed THEN
194191 );
195192END IF;
196193
197- -- Handle step skipping (when_exhausted = 'skip' or 'skip-cascade')
198- IF v_step_skipped THEN
194+ -- Handle step skipping (when_exhausted = 'skip' or 'skip-cascade')
195+ IF v_task_exhausted AND v_step_skipped THEN
199196 -- Send broadcast event for step skipped
200197 PERFORM realtime .send (
201198 jsonb_build_object(
@@ -237,11 +234,26 @@ END IF;
237234 AND dep .dep_slug = fail_task .step_slug
238235 AND child_state .step_slug = dep .step_slug ;
239236
240- -- Start any steps that became ready after decrementing remaining_deps
241- PERFORM pgflow .start_ready_steps (fail_task .run_id );
237+ -- Evaluate conditions on newly-ready dependent steps
238+ -- This must happen before cascade_complete_taskless_steps so that
239+ -- skipped steps can set initial_tasks=0 for their map dependents
240+ IF NOT pgflow .cascade_resolve_conditions (fail_task .run_id ) THEN
241+ -- Run was failed due to a condition with when_unmet='fail'
242+ -- Archive the failed task's message before returning
243+ PERFORM pgflow ._archive_task_message (fail_task .run_id , fail_task .step_slug , fail_task .task_index );
244+ -- Return the task row (API contract)
245+ RETURN QUERY SELECT * FROM pgflow .step_tasks
246+ WHERE pgflow .step_tasks .run_id = fail_task .run_id
247+ AND pgflow .step_tasks .step_slug = fail_task .step_slug
248+ AND pgflow .step_tasks .task_index = fail_task .task_index ;
249+ RETURN;
250+ END IF;
242251
243252 -- Auto-complete taskless steps (e.g., map steps with initial_tasks=0 from skipped dep)
244253 PERFORM pgflow .cascade_complete_taskless_steps (fail_task .run_id );
254+
255+ -- Start steps that became ready after condition resolution and taskless completion
256+ PERFORM pgflow .start_ready_steps (fail_task .run_id );
245257 END IF;
246258
247259 -- Try to complete the run (remaining_steps may now be 0)
0 commit comments