@@ -15,7 +15,7 @@ END) <= 1), ADD CONSTRAINT "skip_reason_matches_status" CHECK (((status = 'skipp
1515-- Create index "idx_step_states_skipped" to table: "step_states"
1616CREATE INDEX "idx_step_states_skipped " ON " pgflow" ." step_states" (" run_id" , " step_slug" ) WHERE (status = ' skipped' ::text );
1717-- Modify "steps" table
18- ALTER TABLE " pgflow" ." steps" ADD CONSTRAINT " when_failed_is_valid" CHECK (when_failed = ANY (ARRAY[' fail' ::text , ' skip' ::text , ' skip-cascade' ::text ])), ADD CONSTRAINT " when_unmet_is_valid" CHECK (when_unmet = ANY (ARRAY[' fail' ::text , ' skip' ::text , ' skip-cascade' ::text ])), ADD COLUMN " condition_pattern" jsonb NULL , ADD COLUMN " when_unmet" text NOT NULL DEFAULT ' skip' , ADD COLUMN " when_failed" text NOT NULL DEFAULT ' fail' ;
18+ ALTER TABLE " pgflow" ." steps" ADD CONSTRAINT " when_failed_is_valid" CHECK (when_failed = ANY (ARRAY[' fail' ::text , ' skip' ::text , ' skip-cascade' ::text ])), ADD CONSTRAINT " when_unmet_is_valid" CHECK (when_unmet = ANY (ARRAY[' fail' ::text , ' skip' ::text , ' skip-cascade' ::text ])), ADD COLUMN " condition_pattern" jsonb NULL , ADD COLUMN " condition_not_pattern " jsonb NULL , ADD COLUMN " when_unmet" text NOT NULL DEFAULT ' skip' , ADD COLUMN " when_failed" text NOT NULL DEFAULT ' fail' ;
1919-- Create "_cascade_force_skip_steps" function
2020CREATE FUNCTION "pgflow "." _cascade_force_skip_steps" (" run_id" uuid, " step_slug" text , " skip_reason" text ) RETURNS integer LANGUAGE plpgsql AS $$
2121DECLARE
@@ -151,11 +151,15 @@ BEGIN
151151 -- PHASE 1a: CHECK FOR FAIL CONDITIONS
152152 -- ==========================================
153153 -- Find first step (by topological order) with unmet condition and 'fail' mode.
154+ -- Condition is unmet when:
155+ -- (condition_pattern is set AND input does NOT contain it) OR
156+ -- (condition_not_pattern is set AND input DOES contain it)
154157 WITH steps_with_conditions AS (
155158 SELECT
156159 step_state .flow_slug ,
157160 step_state .step_slug ,
158161 step .condition_pattern ,
162+ step .condition_not_pattern ,
159163 step .when_unmet ,
160164 step .deps_count ,
161165 step .step_index
@@ -166,7 +170,7 @@ BEGIN
166170 WHERE step_state .run_id = cascade_resolve_conditions .run_id
167171 AND step_state .status = ' created'
168172 AND step_state .remaining_deps = 0
169- AND step .condition_pattern IS NOT NULL
173+ AND ( step .condition_pattern IS NOT NULL OR step . condition_not_pattern IS NOT NULL )
170174 ),
171175 step_deps_output AS (
172176 SELECT
@@ -184,26 +188,32 @@ BEGIN
184188 condition_evaluations AS (
185189 SELECT
186190 swc.* ,
187- CASE
188- WHEN swc .deps_count = 0 THEN v_run_input @> swc .condition_pattern
189- ELSE COALESCE(sdo .deps_output , ' {}' ::jsonb) @> swc .condition_pattern
190- END AS condition_met
191+ CASE WHEN swc .deps_count = 0 THEN v_run_input ELSE COALESCE(sdo .deps_output , ' {}' ::jsonb) END AS eval_input,
192+ -- condition_met = (if IS NULL OR input @> if) AND (ifNot IS NULL OR NOT(input @> ifNot))
193+ (swc .condition_pattern IS NULL OR
194+ CASE WHEN swc .deps_count = 0 THEN v_run_input ELSE COALESCE(sdo .deps_output , ' {}' ::jsonb) END @> swc .condition_pattern )
195+ AND
196+ (swc .condition_not_pattern IS NULL OR
197+ NOT (CASE WHEN swc .deps_count = 0 THEN v_run_input ELSE COALESCE(sdo .deps_output , ' {}' ::jsonb) END @> swc .condition_not_pattern ))
198+ AS condition_met
191199 FROM steps_with_conditions swc
192200 LEFT JOIN step_deps_output sdo ON sdo .step_slug = swc .step_slug
193201 )
194- SELECT flow_slug, step_slug, condition_pattern
202+ SELECT flow_slug, step_slug, condition_pattern, condition_not_pattern
195203 INTO v_first_fail
196204 FROM condition_evaluations
197205 WHERE NOT condition_met AND when_unmet = ' fail'
198206 ORDER BY step_index
199207 LIMIT 1 ;
200208
201209 -- Handle fail mode: fail step and run, return false
202- IF v_first_fail IS NOT NULL THEN
210+ -- Note: Cannot use "v_first_fail IS NOT NULL" because records with NULL fields
211+ -- evaluate to NULL in IS NOT NULL checks. Use FOUND instead.
212+ IF FOUND THEN
203213 UPDATE pgflow .step_states
204214 SET status = ' failed' ,
205215 failed_at = now(),
206- error_message = ' Condition not met: ' || v_first_fail . condition_pattern :: text
216+ error_message = ' Condition not met'
207217 WHERE pgflow .step_states .run_id = cascade_resolve_conditions .run_id
208218 AND pgflow .step_states .step_slug = v_first_fail .step_slug ;
209219
@@ -219,12 +229,13 @@ BEGIN
219229 -- PHASE 1b: HANDLE SKIP CONDITIONS (with propagation)
220230 -- ==========================================
221231 -- Skip steps with unmet conditions and whenUnmet='skip'.
222- -- NEW: Also decrement remaining_deps on dependents and set initial_tasks=0 for map dependents.
232+ -- Also decrement remaining_deps on dependents and set initial_tasks=0 for map dependents.
223233 WITH steps_with_conditions AS (
224234 SELECT
225235 step_state .flow_slug ,
226236 step_state .step_slug ,
227237 step .condition_pattern ,
238+ step .condition_not_pattern ,
228239 step .when_unmet ,
229240 step .deps_count ,
230241 step .step_index
@@ -235,7 +246,7 @@ BEGIN
235246 WHERE step_state .run_id = cascade_resolve_conditions .run_id
236247 AND step_state .status = ' created'
237248 AND step_state .remaining_deps = 0
238- AND step .condition_pattern IS NOT NULL
249+ AND ( step .condition_pattern IS NOT NULL OR step . condition_not_pattern IS NOT NULL )
239250 ),
240251 step_deps_output AS (
241252 SELECT
@@ -253,10 +264,13 @@ BEGIN
253264 condition_evaluations AS (
254265 SELECT
255266 swc.* ,
256- CASE
257- WHEN swc .deps_count = 0 THEN v_run_input @> swc .condition_pattern
258- ELSE COALESCE(sdo .deps_output , ' {}' ::jsonb) @> swc .condition_pattern
259- END AS condition_met
267+ -- condition_met = (if IS NULL OR input @> if) AND (ifNot IS NULL OR NOT(input @> ifNot))
268+ (swc .condition_pattern IS NULL OR
269+ CASE WHEN swc .deps_count = 0 THEN v_run_input ELSE COALESCE(sdo .deps_output , ' {}' ::jsonb) END @> swc .condition_pattern )
270+ AND
271+ (swc .condition_not_pattern IS NULL OR
272+ NOT (CASE WHEN swc .deps_count = 0 THEN v_run_input ELSE COALESCE(sdo .deps_output , ' {}' ::jsonb) END @> swc .condition_not_pattern ))
273+ AS condition_met
260274 FROM steps_with_conditions swc
261275 LEFT JOIN step_deps_output sdo ON sdo .step_slug = swc .step_slug
262276 ),
@@ -336,13 +350,15 @@ BEGIN
336350 WHERE ready_step .run_id = cascade_resolve_conditions .run_id
337351 AND ready_step .status = ' created'
338352 AND ready_step .remaining_deps = 0
339- AND step .condition_pattern IS NOT NULL
353+ AND ( step .condition_pattern IS NOT NULL OR step . condition_not_pattern IS NOT NULL )
340354 AND step .when_unmet = ' skip-cascade'
355+ -- Condition is NOT met when: (if fails) OR (ifNot fails)
341356 AND NOT (
342- CASE
343- WHEN step .deps_count = 0 THEN v_run_input @> step .condition_pattern
344- ELSE COALESCE(agg_deps .deps_output , ' {}' ::jsonb) @> step .condition_pattern
345- END
357+ (step .condition_pattern IS NULL OR
358+ CASE WHEN step .deps_count = 0 THEN v_run_input ELSE COALESCE(agg_deps .deps_output , ' {}' ::jsonb) END @> step .condition_pattern )
359+ AND
360+ (step .condition_not_pattern IS NULL OR
361+ NOT (CASE WHEN step .deps_count = 0 THEN v_run_input ELSE COALESCE(agg_deps .deps_output , ' {}' ::jsonb) END @> step .condition_not_pattern ))
346362 )
347363 ORDER BY step .step_index ;
348364
@@ -1440,7 +1456,7 @@ with tasks as (
14401456 dep_out .step_slug = st .step_slug
14411457$$;
14421458-- Create "add_step" function
1443- CREATE FUNCTION "pgflow "." add_step" (" flow_slug" text , " step_slug" text , " deps_slugs" text [] DEFAULT ' {}' , " max_attempts" integer DEFAULT NULL ::integer , " base_delay" integer DEFAULT NULL ::integer , " timeout" integer DEFAULT NULL ::integer , " start_delay" integer DEFAULT NULL ::integer , " step_type" text DEFAULT ' single' , " condition_pattern" jsonb DEFAULT NULL ::jsonb, " when_unmet" text DEFAULT ' skip' , " when_failed" text DEFAULT ' fail' ) RETURNS " pgflow" ." steps" LANGUAGE plpgsql SET " search_path" = ' ' AS $$
1459+ CREATE FUNCTION "pgflow "." add_step" (" flow_slug" text , " step_slug" text , " deps_slugs" text [] DEFAULT ' {}' , " max_attempts" integer DEFAULT NULL ::integer , " base_delay" integer DEFAULT NULL ::integer , " timeout" integer DEFAULT NULL ::integer , " start_delay" integer DEFAULT NULL ::integer , " step_type" text DEFAULT ' single' , " condition_pattern" jsonb DEFAULT NULL ::jsonb, " condition_not_pattern " jsonb DEFAULT NULL ::jsonb, " when_unmet" text DEFAULT ' skip' , " when_failed" text DEFAULT ' fail' ) RETURNS " pgflow" ." steps" LANGUAGE plpgsql SET " search_path" = ' ' AS $$
14441460DECLARE
14451461 result_step pgflow .steps ;
14461462 next_idx int ;
@@ -1465,7 +1481,7 @@ BEGIN
14651481 INSERT INTO pgflow .steps (
14661482 flow_slug, step_slug, step_type, step_index, deps_count,
14671483 opt_max_attempts, opt_base_delay, opt_timeout, opt_start_delay,
1468- condition_pattern, when_unmet, when_failed
1484+ condition_pattern, condition_not_pattern, when_unmet, when_failed
14691485 )
14701486 VALUES (
14711487 add_step .flow_slug ,
@@ -1478,6 +1494,7 @@ BEGIN
14781494 add_step .timeout ,
14791495 add_step .start_delay ,
14801496 add_step .condition_pattern ,
1497+ add_step .condition_not_pattern ,
14811498 add_step .when_unmet ,
14821499 add_step .when_failed
14831500 )
0 commit comments