@@ -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,31 @@ 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+ -- condition_met = (if IS NULL OR input @> if) AND (ifNot IS NULL OR NOT(input @> ifNot))
192+ (swc .condition_pattern IS NULL OR
193+ CASE WHEN swc .deps_count = 0 THEN v_run_input ELSE COALESCE(sdo .deps_output , ' {}' ::jsonb) END @> swc .condition_pattern )
194+ AND
195+ (swc .condition_not_pattern IS NULL OR
196+ NOT (CASE WHEN swc .deps_count = 0 THEN v_run_input ELSE COALESCE(sdo .deps_output , ' {}' ::jsonb) END @> swc .condition_not_pattern ))
197+ AS condition_met
191198 FROM steps_with_conditions swc
192199 LEFT JOIN step_deps_output sdo ON sdo .step_slug = swc .step_slug
193200 )
194- SELECT flow_slug, step_slug, condition_pattern
201+ SELECT flow_slug, step_slug, condition_pattern, condition_not_pattern
195202 INTO v_first_fail
196203 FROM condition_evaluations
197204 WHERE NOT condition_met AND when_unmet = ' fail'
198205 ORDER BY step_index
199206 LIMIT 1 ;
200207
201208 -- Handle fail mode: fail step and run, return false
202- IF v_first_fail IS NOT NULL THEN
209+ -- Note: Cannot use "v_first_fail IS NOT NULL" because records with NULL fields
210+ -- evaluate to NULL in IS NOT NULL checks. Use FOUND instead.
211+ IF FOUND THEN
203212 UPDATE pgflow .step_states
204213 SET status = ' failed' ,
205214 failed_at = now(),
206- error_message = ' Condition not met: ' || v_first_fail . condition_pattern :: text
215+ error_message = ' Condition not met'
207216 WHERE pgflow .step_states .run_id = cascade_resolve_conditions .run_id
208217 AND pgflow .step_states .step_slug = v_first_fail .step_slug ;
209218
@@ -219,12 +228,13 @@ BEGIN
219228 -- PHASE 1b: HANDLE SKIP CONDITIONS (with propagation)
220229 -- ==========================================
221230 -- Skip steps with unmet conditions and whenUnmet='skip'.
222- -- NEW: Also decrement remaining_deps on dependents and set initial_tasks=0 for map dependents.
231+ -- Also decrement remaining_deps on dependents and set initial_tasks=0 for map dependents.
223232 WITH steps_with_conditions AS (
224233 SELECT
225234 step_state .flow_slug ,
226235 step_state .step_slug ,
227236 step .condition_pattern ,
237+ step .condition_not_pattern ,
228238 step .when_unmet ,
229239 step .deps_count ,
230240 step .step_index
@@ -235,7 +245,7 @@ BEGIN
235245 WHERE step_state .run_id = cascade_resolve_conditions .run_id
236246 AND step_state .status = ' created'
237247 AND step_state .remaining_deps = 0
238- AND step .condition_pattern IS NOT NULL
248+ AND ( step .condition_pattern IS NOT NULL OR step . condition_not_pattern IS NOT NULL )
239249 ),
240250 step_deps_output AS (
241251 SELECT
@@ -253,10 +263,13 @@ BEGIN
253263 condition_evaluations AS (
254264 SELECT
255265 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
266+ -- condition_met = (if IS NULL OR input @> if) AND (ifNot IS NULL OR NOT(input @> ifNot))
267+ (swc .condition_pattern IS NULL OR
268+ CASE WHEN swc .deps_count = 0 THEN v_run_input ELSE COALESCE(sdo .deps_output , ' {}' ::jsonb) END @> swc .condition_pattern )
269+ AND
270+ (swc .condition_not_pattern IS NULL OR
271+ NOT (CASE WHEN swc .deps_count = 0 THEN v_run_input ELSE COALESCE(sdo .deps_output , ' {}' ::jsonb) END @> swc .condition_not_pattern ))
272+ AS condition_met
260273 FROM steps_with_conditions swc
261274 LEFT JOIN step_deps_output sdo ON sdo .step_slug = swc .step_slug
262275 ),
@@ -336,13 +349,15 @@ BEGIN
336349 WHERE ready_step .run_id = cascade_resolve_conditions .run_id
337350 AND ready_step .status = ' created'
338351 AND ready_step .remaining_deps = 0
339- AND step .condition_pattern IS NOT NULL
352+ AND ( step .condition_pattern IS NOT NULL OR step . condition_not_pattern IS NOT NULL )
340353 AND step .when_unmet = ' skip-cascade'
354+ -- Condition is NOT met when: (if fails) OR (ifNot fails)
341355 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
356+ (step .condition_pattern IS NULL OR
357+ CASE WHEN step .deps_count = 0 THEN v_run_input ELSE COALESCE(agg_deps .deps_output , ' {}' ::jsonb) END @> step .condition_pattern )
358+ AND
359+ (step .condition_not_pattern IS NULL OR
360+ NOT (CASE WHEN step .deps_count = 0 THEN v_run_input ELSE COALESCE(agg_deps .deps_output , ' {}' ::jsonb) END @> step .condition_not_pattern ))
346361 )
347362 ORDER BY step .step_index ;
348363
@@ -1440,7 +1455,7 @@ with tasks as (
14401455 dep_out .step_slug = st .step_slug
14411456$$;
14421457-- 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 $$
1458+ 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 $$
14441459DECLARE
14451460 result_step pgflow .steps ;
14461461 next_idx int ;
@@ -1465,7 +1480,7 @@ BEGIN
14651480 INSERT INTO pgflow .steps (
14661481 flow_slug, step_slug, step_type, step_index, deps_count,
14671482 opt_max_attempts, opt_base_delay, opt_timeout, opt_start_delay,
1468- condition_pattern, when_unmet, when_failed
1483+ condition_pattern, condition_not_pattern, when_unmet, when_failed
14691484 )
14701485 VALUES (
14711486 add_step .flow_slug ,
@@ -1478,6 +1493,7 @@ BEGIN
14781493 add_step .timeout ,
14791494 add_step .start_delay ,
14801495 add_step .condition_pattern ,
1496+ add_step .condition_not_pattern ,
14811497 add_step .when_unmet ,
14821498 add_step .when_failed
14831499 )
0 commit comments