@db-lyon/flowkit 0.5.3 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/config/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,iBAAiB,wCAAwB,CAAC;AAEvD,eAAO,MAAM,oBAAoB;;;;;IAK/B,sEAAsE;;;;;;;;;;;;;;;;;EAGtE,CAAC;AAEH,eAAO,MAAM,cAAc;;;;IAKvB,0DAA0D;;IAE1D,8CAA8C;;IAE9C,iEAAiE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EASlE,CAAC;AAEJ,eAAO,MAAM,oBAAoB;;;;;;QAf7B,0DAA0D;;QAE1D,8CAA8C;;QAE9C,iEAAiE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAcnE,+EAA+E;;;;;QAlB7E,0DAA0D;;QAE1D,8CAA8C;;QAE9C,iEAAiE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAgBnE,oCAAoC;;;;;QApBlC,0DAA0D;;QAE1D,8CAA8C;;QAE9C,iEAAiE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAkBnE,gCAAgC;;;;;QAtB9B,0DAA0D;;QAE1D,8CAA8C;;QAE9C,iEAAiE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAoBnE,kEAAkE;;;;;QAxBhE,0DAA0D;;QAE1D,8CAA8C;;QAE9C,iEAAiE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAsBnE,yFAAyF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAEzF,CAAC;AAEH,0FAA0F;AAC1F,eAAO,MAAM,kBAAkB;;;;;;QAzC7B,sEAAsE;;;;;;;;;;;;;;;;;;;;;;;;YAUpE,0DAA0D;;YAE1D,8CAA8C;;YAE9C,iEAAiE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAcnE,+EAA+E;;;;;YAlB7E,0DAA0D;;YAE1D,8CAA8C;;YAE9C,iEAAiE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAgBnE,oCAAoC;;;;;YApBlC,0DAA0D;;YAE1D,8CAA8C;;YAE9C,iEAAiE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAkBnE,gCAAgC;;;;;YAtB9B,0DAA0D;;YAE1D,8CAA8C;;YAE9C,iEAAiE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAoBnE,kEAAkE;;;;;YAxBhE,0DAA0D;;YAE1D,8CAA8C;;YAE9C,iEAAiE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAsBnE,yFAAyF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAQzF,CAAC;AAEH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAC5D,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAClE,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AACtD,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAClE,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC"}
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/config/schema.ts"],"names":[],"mappings":"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"}
@@ -20,6 +20,15 @@ export const FlowStepSchema = z
20
20
  retryDelay: z.number().int().nonnegative().optional(),
21
21
  /** Only retry when the error message contains this substring. */
22
22
  retryOn: z.string().optional(),
23
+ /**
24
+ * Conditional execution. A boolean runs/skips the step directly; a string is
25
+ * an expression evaluated at runtime by the runner's `conditionEvaluator`
26
+ * (or the built-in `${...}`-reference truthiness check if none is set). A
27
+ * falsy result skips the step without failing the flow.
28
+ */
29
+ when: z.union([z.string(), z.boolean()]).optional(),
30
+ /** If true, a failure of this step is recorded but does not abort the flow. */
31
+ ignore_failure: z.boolean().optional(),
23
32
  })
24
33
  .refine((data) => {
25
34
  if (data.task === 'None')
@@ -1 +1 @@
1
- {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/config/schema.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/config/schema.ts"],"names":[],"mappings":"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"}
@@ -4,6 +4,12 @@ import type { TaskResult } from '../task/base-task.js';
4
4
  import type { TaskContext } from '../task/base-task.js';
5
5
  import type { TaskRegistry } from '../task/registry.js';
6
6
  export type HookPhase = 'on_start' | 'on_success' | 'on_failure' | 'finally';
7
+ /**
8
+ * Per-task option overrides injected by an enclosing flow step, keyed by the
9
+ * inner task (or flow) name. When a flow step carries `options`, those options
10
+ * are interpreted as this map and threaded down into the nested flow.
11
+ */
12
+ export type ParentOptions = Record<string, Record<string, unknown>>;
7
13
  export interface FlowRunOptions {
8
14
  flowName: string;
9
15
  skip?: string[];
@@ -12,7 +18,31 @@ export interface FlowRunOptions {
12
18
  params?: Record<string, unknown>;
13
19
  /** If true, invoke rollback records from completed steps in reverse order on failure. */
14
20
  rollback_on_failure?: boolean;
21
+ /**
22
+ * Plan mode only: recursively expand nested-flow steps into their child steps
23
+ * (each annotated with a hierarchical `path`). Default false preserves the
24
+ * flat, one-line-per-flow-step plan.
25
+ */
26
+ expandNestedFlows?: boolean;
15
27
  }
28
+ /** Context handed to a `conditionEvaluator` when resolving a string `when:`. */
29
+ export interface ConditionContext {
30
+ steps: FlowStepResult[];
31
+ params?: Record<string, unknown>;
32
+ context: TaskContext;
33
+ error?: {
34
+ message: string;
35
+ name: string;
36
+ stack?: string;
37
+ step?: string;
38
+ };
39
+ }
40
+ /**
41
+ * Evaluates a string `when:` expression to a boolean. Supply one to use a real
42
+ * expression language (e.g. jinja-style with project/org context). When absent,
43
+ * the runner falls back to resolving `${...}` references and testing truthiness.
44
+ */
45
+ export type ConditionEvaluator = (expression: string, ctx: ConditionContext) => boolean | Promise<boolean>;
16
46
  export interface FlowStepResult {
17
47
  stepNumber: number;
18
48
  type: 'task' | 'flow';
@@ -22,6 +52,10 @@ export interface FlowStepResult {
22
52
  duration: number;
23
53
  /** Number of attempts including the first try (≥1 when executed). */
24
54
  attempts?: number;
55
+ /** Why the step was skipped: 'static' (skip list / task: None) or 'when' (condition false). */
56
+ skipReason?: 'static' | 'when';
57
+ /** True when the step failed but `ignore_failure` let the flow continue. */
58
+ ignoredFailure?: boolean;
25
59
  }
26
60
  export interface HookError {
27
61
  phase: HookPhase;
@@ -55,8 +89,16 @@ export interface PlanStep {
55
89
  retries?: number;
56
90
  retryDelay?: number;
57
91
  retryOn?: string;
92
+ /** Conditional execution — evaluated at run time, so plan reports it unresolved. */
93
+ when?: string | boolean;
94
+ /** Whether a failure of this step is tolerated. */
95
+ ignore_failure?: boolean;
58
96
  /** For hook steps: the phase they belong to. Undefined for main steps. */
59
97
  phase?: HookPhase;
98
+ /** Hierarchical id (e.g. "2/1") — only set when a plan expands nested flows. */
99
+ path?: string;
100
+ /** Nesting depth — 0 for top-level, increments per expanded nested flow. */
101
+ depth?: number;
60
102
  }
61
103
  export interface FlowRunnerHooks {
62
104
  beforeRun?(flowName: string, plan: PlanStep[]): Promise<void>;
@@ -72,6 +114,8 @@ export interface FlowRunnerConfig {
72
114
  context: TaskContext;
73
115
  hooks?: FlowRunnerHooks;
74
116
  logger?: Logger;
117
+ /** Optional evaluator for string `when:` expressions. */
118
+ conditionEvaluator?: ConditionEvaluator;
75
119
  }
76
120
  export declare class FlowRunner {
77
121
  private logger;
@@ -80,12 +124,32 @@ export declare class FlowRunner {
80
124
  private registry;
81
125
  private ctx;
82
126
  private hooks;
127
+ private conditionEvaluator?;
83
128
  private runDepth;
84
129
  constructor(config: FlowRunnerConfig);
85
130
  run(options: FlowRunOptions): Promise<FlowRunResult>;
131
+ /**
132
+ * Run a single task by name, directly — the leaf unit of work, without a flow.
133
+ * A flow is a composition of these; this is the same primitive each flow step
134
+ * executes (see `executeTask`), so a task behaves identically whether it's run
135
+ * on its own or as a step. `options` merge over the task's configured defaults.
136
+ */
137
+ runTask(taskName: string, options?: Record<string, unknown>): Promise<TaskResult>;
138
+ /** Instantiate and run a task with fully-resolved options (the shared leaf). */
139
+ private executeTask;
140
+ private runWith;
86
141
  resolveExecutionPlan(flow: FlowDefinition, skipSet: Set<string>): PlanStep[];
87
142
  private planStepFromDef;
88
143
  private planHookSteps;
144
+ /**
145
+ * Merge an enclosing flow step's per-task override map onto inherited parent
146
+ * options. Inner (closer) overrides win over outer for the same task+key.
147
+ */
148
+ private mergeParentOptions;
149
+ /** Recursively expand a plan step's nested flow into its child steps. */
150
+ private expandPlanStep;
151
+ /** Evaluate a step's `when:` to a boolean. Undefined `when` always runs. */
152
+ private evaluateWhen;
89
153
  private executeFlow;
90
154
  private runHookStep;
91
155
  private executeTaskStepWithRetry;
@@ -1 +1 @@
1
- {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/flow/runner.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/flow/runner.ts"],"names":[],"mappings":"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"}
@@ -10,6 +10,7 @@ export class FlowRunner {
10
10
  registry;
11
11
  ctx;
12
12
  hooks;
13
+ conditionEvaluator;
13
14
  runDepth = 0;
14
15
  constructor(config) {
15
16
  this.logger = (config.logger ?? noopLogger).child({ component: 'flow-runner' });
@@ -17,13 +18,33 @@ export class FlowRunner {
17
18
  this.flows = config.flows;
18
19
  this.registry = config.registry;
19
20
  this.hooks = config.hooks ?? {};
21
+ this.conditionEvaluator = config.conditionEvaluator;
20
22
  this.ctx = { ...config.context, registry: config.registry };
21
23
  }
22
24
  async run(options) {
25
+ return this.runWith(options, {});
26
+ }
27
+ /**
28
+ * Run a single task by name, directly — the leaf unit of work, without a flow.
29
+ * A flow is a composition of these; this is the same primitive each flow step
30
+ * executes (see `executeTask`), so a task behaves identically whether it's run
31
+ * on its own or as a step. `options` merge over the task's configured defaults.
32
+ */
33
+ async runTask(taskName, options = {}) {
34
+ const taskDef = this.resolveTaskDefinition(taskName);
35
+ this.logger.info({ task: taskName }, `Running task ${taskName}`);
36
+ return this.executeTask(taskDef.class_path, { ...taskDef.options, ...options });
37
+ }
38
+ /** Instantiate and run a task with fully-resolved options (the shared leaf). */
39
+ async executeTask(classPath, options) {
40
+ const task = await this.registry.create(classPath, this.ctx, options);
41
+ return task.run();
42
+ }
43
+ async runWith(options, parentOptions) {
23
44
  this.runDepth++;
24
45
  const isTopLevel = this.runDepth === 1;
25
46
  try {
26
- return await this.executeFlow(options, isTopLevel);
47
+ return await this.executeFlow(options, isTopLevel, parentOptions);
27
48
  }
28
49
  finally {
29
50
  this.runDepth--;
@@ -50,6 +71,8 @@ export class FlowRunner {
50
71
  retries: step.retries,
51
72
  retryDelay: step.retryDelay,
52
73
  retryOn: step.retryOn,
74
+ when: step.when,
75
+ ignore_failure: step.ignore_failure,
53
76
  };
54
77
  }
55
78
  planHookSteps(hookSteps, phase, skipSet, baseStepNumber) {
@@ -60,10 +83,66 @@ export class FlowRunner {
60
83
  phase,
61
84
  }));
62
85
  }
86
+ /**
87
+ * Merge an enclosing flow step's per-task override map onto inherited parent
88
+ * options. Inner (closer) overrides win over outer for the same task+key.
89
+ */
90
+ mergeParentOptions(base, overrideMap) {
91
+ if (!overrideMap)
92
+ return base;
93
+ const out = { ...base };
94
+ for (const [name, opts] of Object.entries(overrideMap)) {
95
+ if (opts && typeof opts === 'object' && !Array.isArray(opts)) {
96
+ out[name] = { ...(base[name] ?? {}), ...opts };
97
+ }
98
+ }
99
+ return out;
100
+ }
101
+ /** Recursively expand a plan step's nested flow into its child steps. */
102
+ expandPlanStep(planStep, parentOptions, pathPrefix, depth, ancestors, skipSet) {
103
+ const self = { ...planStep, path: pathPrefix, depth };
104
+ if (planStep.type !== 'flow' || planStep.skipped || ancestors.has(planStep.name)) {
105
+ return [self];
106
+ }
107
+ const childFlow = this.flows[planStep.name];
108
+ if (!childFlow)
109
+ return [self];
110
+ const childParentOptions = this.mergeParentOptions(parentOptions, planStep.options);
111
+ const nextAncestors = new Set(ancestors).add(planStep.name);
112
+ const childPlan = this.resolveExecutionPlan(childFlow, skipSet);
113
+ const children = childPlan.flatMap((cs) => this.expandPlanStep(cs, childParentOptions, `${pathPrefix}/${cs.stepNumber}`, depth + 1, nextAncestors, skipSet));
114
+ return [self, ...children];
115
+ }
116
+ /** Evaluate a step's `when:` to a boolean. Undefined `when` always runs. */
117
+ async evaluateWhen(when, completedSteps, params, errorCtx) {
118
+ if (when === undefined)
119
+ return true;
120
+ if (typeof when === 'boolean')
121
+ return when;
122
+ const error = errorCtx
123
+ ? {
124
+ message: errorCtx.error.message,
125
+ name: errorCtx.error.name,
126
+ stack: errorCtx.error.stack,
127
+ step: errorCtx.step,
128
+ }
129
+ : undefined;
130
+ if (this.conditionEvaluator) {
131
+ return await this.conditionEvaluator(when, {
132
+ steps: completedSteps,
133
+ params,
134
+ context: this.ctx,
135
+ error,
136
+ });
137
+ }
138
+ // Built-in fallback: resolve ${...} references, then test truthiness.
139
+ const resolved = resolveReferences(when, { steps: completedSteps, error });
140
+ return truthy(resolved);
141
+ }
63
142
  // ---------------------------------------------------------------------------
64
143
  // Internal
65
144
  // ---------------------------------------------------------------------------
66
- async executeFlow(options, isTopLevel) {
145
+ async executeFlow(options, isTopLevel, parentOptions) {
67
146
  const startTime = Date.now();
68
147
  const skipSet = new Set(options.skip ?? []);
69
148
  const completedSteps = [];
@@ -77,9 +156,12 @@ export class FlowRunner {
77
156
  const executionPlan = this.resolveExecutionPlan(flow, skipSet);
78
157
  // Plan mode — dump all phases for visibility, nothing runs.
79
158
  if (options.plan) {
159
+ const mainPlan = options.expandNestedFlows
160
+ ? executionPlan.flatMap((s) => this.expandPlanStep(s, parentOptions, String(s.stepNumber), 0, new Set([options.flowName]), skipSet))
161
+ : executionPlan;
80
162
  const fullPlan = [
81
163
  ...this.planHookSteps(flow.on_start, 'on_start', skipSet, -3000),
82
- ...executionPlan,
164
+ ...mainPlan,
83
165
  ...this.planHookSteps(flow.on_success, 'on_success', skipSet, 10_000),
84
166
  ...this.planHookSteps(flow.on_failure, 'on_failure', skipSet, 20_000),
85
167
  ...this.planHookSteps(flow.finally, 'finally', skipSet, 30_000),
@@ -92,6 +174,7 @@ export class FlowRunner {
92
174
  name: s.name,
93
175
  skipped: s.skipped,
94
176
  duration: 0,
177
+ ...(s.path !== undefined ? { path: s.path, depth: s.depth } : {}),
95
178
  })),
96
179
  duration: 0,
97
180
  };
@@ -105,7 +188,7 @@ export class FlowRunner {
105
188
  {
106
189
  const startPlan = this.planHookSteps(flow.on_start, 'on_start', skipSet, -3000);
107
190
  for (const hookStep of startPlan) {
108
- const ok = await this.runHookStep(hookStep, options, completedSteps, undefined, hookErrors);
191
+ const ok = await this.runHookStep(hookStep, options, completedSteps, parentOptions, undefined, hookErrors);
109
192
  if (!ok) {
110
193
  flowError = hookErrors[hookErrors.length - 1]?.error;
111
194
  flowErrorStepName = hookStep.name;
@@ -116,13 +199,46 @@ export class FlowRunner {
116
199
  // ---- main steps ----
117
200
  if (!flowError) {
118
201
  for (const planStep of executionPlan) {
119
- if (planStep.skipped) {
202
+ // Resolve conditional execution (`when:`) at run time.
203
+ let conditionMet = true;
204
+ let conditionError;
205
+ if (!planStep.skipped && planStep.when !== undefined) {
206
+ try {
207
+ conditionMet = await this.evaluateWhen(planStep.when, completedSteps, options.params);
208
+ }
209
+ catch (err) {
210
+ conditionError = err instanceof Error ? err : new Error(String(err));
211
+ }
212
+ }
213
+ if (conditionError) {
214
+ // A condition that throws is treated like a step failure.
215
+ const sr = {
216
+ stepNumber: planStep.stepNumber,
217
+ type: planStep.type,
218
+ name: planStep.name,
219
+ skipped: false,
220
+ duration: 0,
221
+ result: { success: false, error: conditionError },
222
+ };
223
+ completedSteps.push(sr);
224
+ await this.hooks.afterStep?.(planStep, sr);
225
+ if (planStep.ignore_failure) {
226
+ sr.ignoredFailure = true;
227
+ continue;
228
+ }
229
+ flowError = conditionError;
230
+ flowErrorStepName = planStep.name;
231
+ await this.hooks.onStepError?.(planStep, conditionError, completedSteps);
232
+ break;
233
+ }
234
+ if (planStep.skipped || !conditionMet) {
120
235
  const sr = {
121
236
  stepNumber: planStep.stepNumber,
122
237
  type: planStep.type,
123
238
  name: planStep.name,
124
239
  skipped: true,
125
240
  duration: 0,
241
+ skipReason: planStep.skipped ? 'static' : 'when',
126
242
  };
127
243
  completedSteps.push(sr);
128
244
  await this.hooks.afterStep?.(planStep, sr);
@@ -133,7 +249,7 @@ export class FlowRunner {
133
249
  try {
134
250
  let stepResult;
135
251
  if (planStep.type === 'task') {
136
- const { result: taskResult, attempts } = await this.executeTaskStepWithRetry(planStep, options.params, completedSteps);
252
+ const { result: taskResult, attempts } = await this.executeTaskStepWithRetry(planStep, options.params, completedSteps, parentOptions);
137
253
  stepResult = {
138
254
  stepNumber: planStep.stepNumber,
139
255
  type: 'task',
@@ -151,11 +267,8 @@ export class FlowRunner {
151
267
  }
152
268
  }
153
269
  else {
154
- const nestedResult = await this.run({
155
- ...options,
156
- flowName: planStep.name,
157
- plan: false,
158
- });
270
+ const childParentOptions = this.mergeParentOptions(parentOptions, planStep.options);
271
+ const nestedResult = await this.runWith({ ...options, flowName: planStep.name, plan: false }, childParentOptions);
159
272
  stepResult = {
160
273
  stepNumber: planStep.stepNumber,
161
274
  type: 'flow',
@@ -163,6 +276,7 @@ export class FlowRunner {
163
276
  result: {
164
277
  success: nestedResult.success,
165
278
  data: { stepCount: nestedResult.steps.length },
279
+ error: nestedResult.success ? undefined : nestedResult.error,
166
280
  },
167
281
  skipped: false,
168
282
  duration: Date.now() - stepStart,
@@ -176,31 +290,39 @@ export class FlowRunner {
176
290
  });
177
291
  }
178
292
  }
179
- if (!nestedResult.success) {
180
- flowError = nestedResult.error ?? new Error(`Nested flow ${planStep.name} failed`);
181
- flowErrorStepName = planStep.name;
182
- }
183
293
  }
184
294
  completedSteps.push(stepResult);
185
295
  await this.hooks.afterStep?.(planStep, stepResult);
186
296
  if (!stepResult.result?.success) {
297
+ if (planStep.ignore_failure) {
298
+ stepResult.ignoredFailure = true;
299
+ this.logger.info({ step: planStep.stepNumber, task: planStep.name }, `Step ${planStep.name} failed but ignore_failure is set; continuing`);
300
+ continue;
301
+ }
187
302
  flowError =
188
- flowError ?? stepResult.result?.error ?? new Error(`Step ${planStep.name} failed`);
189
- flowErrorStepName = flowErrorStepName ?? planStep.name;
303
+ stepResult.result?.error ?? new Error(`Step ${planStep.name} failed`);
304
+ flowErrorStepName = planStep.name;
190
305
  await this.hooks.onStepError?.(planStep, flowError, completedSteps);
191
306
  break;
192
307
  }
193
308
  }
194
309
  catch (error) {
195
310
  const err = error instanceof Error ? error : new Error(String(error));
196
- completedSteps.push({
311
+ const sr = {
197
312
  stepNumber: planStep.stepNumber,
198
313
  type: planStep.type,
199
314
  name: planStep.name,
200
315
  skipped: false,
201
316
  duration: Date.now() - stepStart,
202
317
  result: { success: false, error: err },
203
- });
318
+ };
319
+ completedSteps.push(sr);
320
+ if (planStep.ignore_failure) {
321
+ sr.ignoredFailure = true;
322
+ await this.hooks.afterStep?.(planStep, sr);
323
+ this.logger.info({ step: planStep.stepNumber, task: planStep.name }, `Step ${planStep.name} threw but ignore_failure is set; continuing`);
324
+ continue;
325
+ }
204
326
  flowError = err;
205
327
  flowErrorStepName = planStep.name;
206
328
  await this.hooks.onStepError?.(planStep, err, completedSteps);
@@ -212,13 +334,13 @@ export class FlowRunner {
212
334
  if (flowError) {
213
335
  const failPlan = this.planHookSteps(flow.on_failure, 'on_failure', skipSet, 20_000);
214
336
  for (const hookStep of failPlan) {
215
- await this.runHookStep(hookStep, options, completedSteps, { error: flowError, step: flowErrorStepName }, hookErrors);
337
+ await this.runHookStep(hookStep, options, completedSteps, parentOptions, { error: flowError, step: flowErrorStepName }, hookErrors);
216
338
  }
217
339
  }
218
340
  else {
219
341
  const successPlan = this.planHookSteps(flow.on_success, 'on_success', skipSet, 10_000);
220
342
  for (const hookStep of successPlan) {
221
- await this.runHookStep(hookStep, options, completedSteps, undefined, hookErrors);
343
+ await this.runHookStep(hookStep, options, completedSteps, parentOptions, undefined, hookErrors);
222
344
  }
223
345
  }
224
346
  // ---- rollback ----
@@ -230,7 +352,7 @@ export class FlowRunner {
230
352
  {
231
353
  const finallyPlan = this.planHookSteps(flow.finally, 'finally', skipSet, 30_000);
232
354
  for (const hookStep of finallyPlan) {
233
- await this.runHookStep(hookStep, options, completedSteps, flowError ? { error: flowError, step: flowErrorStepName } : undefined, hookErrors);
355
+ await this.runHookStep(hookStep, options, completedSteps, parentOptions, flowError ? { error: flowError, step: flowErrorStepName } : undefined, hookErrors);
234
356
  }
235
357
  }
236
358
  const result = {
@@ -246,16 +368,29 @@ export class FlowRunner {
246
368
  }
247
369
  return result;
248
370
  }
249
- async runHookStep(hookStep, options, completedSteps, errorCtx, hookErrors) {
371
+ async runHookStep(hookStep, options, completedSteps, parentOptions, errorCtx, hookErrors) {
250
372
  if (hookStep.skipped)
251
373
  return true;
374
+ // Hook steps honor `when:` too — a falsy condition skips them silently.
375
+ if (hookStep.when !== undefined) {
376
+ try {
377
+ const ok = await this.evaluateWhen(hookStep.when, completedSteps, options.params, errorCtx);
378
+ if (!ok)
379
+ return true;
380
+ }
381
+ catch (err) {
382
+ hookErrors.push({
383
+ phase: hookStep.phase,
384
+ name: hookStep.name,
385
+ error: err instanceof Error ? err : new Error(String(err)),
386
+ });
387
+ return false;
388
+ }
389
+ }
252
390
  try {
253
391
  if (hookStep.type === 'flow') {
254
- const nested = await this.run({
255
- ...options,
256
- flowName: hookStep.name,
257
- plan: false,
258
- });
392
+ const childParentOptions = this.mergeParentOptions(parentOptions, hookStep.options);
393
+ const nested = await this.runWith({ ...options, flowName: hookStep.name, plan: false }, childParentOptions);
259
394
  if (!nested.success) {
260
395
  hookErrors.push({
261
396
  phase: hookStep.phase,
@@ -266,7 +401,7 @@ export class FlowRunner {
266
401
  }
267
402
  return true;
268
403
  }
269
- const { result } = await this.executeTaskStepWithRetry(hookStep, options.params, completedSteps, errorCtx);
404
+ const { result } = await this.executeTaskStepWithRetry(hookStep, options.params, completedSteps, parentOptions, errorCtx);
270
405
  if (!result.success) {
271
406
  hookErrors.push({
272
407
  phase: hookStep.phase,
@@ -286,13 +421,13 @@ export class FlowRunner {
286
421
  return false;
287
422
  }
288
423
  }
289
- async executeTaskStepWithRetry(step, flowParams, completedSteps, errorCtx) {
424
+ async executeTaskStepWithRetry(step, flowParams, completedSteps, parentOptions, errorCtx) {
290
425
  const maxAttempts = Math.max(1, 1 + (step.retries ?? 0));
291
426
  const delayMs = step.retryDelay ?? 0;
292
427
  const retryOn = step.retryOn;
293
428
  let lastResult = { success: false, error: new Error('no attempts executed') };
294
429
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
295
- lastResult = await this.executeTaskStep(step, flowParams, completedSteps, errorCtx);
430
+ lastResult = await this.executeTaskStep(step, flowParams, completedSteps, parentOptions, errorCtx);
296
431
  if (lastResult.success)
297
432
  return { result: lastResult, attempts: attempt };
298
433
  const errMsg = lastResult.error?.message ?? '';
@@ -307,9 +442,15 @@ export class FlowRunner {
307
442
  }
308
443
  return { result: lastResult, attempts: maxAttempts };
309
444
  }
310
- async executeTaskStep(step, flowParams, completedSteps, errorCtx) {
445
+ async executeTaskStep(step, flowParams, completedSteps, parentOptions, errorCtx) {
311
446
  const taskDef = this.resolveTaskDefinition(step.name);
312
- const rawOptions = { ...taskDef.options, ...step.options, ...flowParams };
447
+ // Precedence (low → high): task default → enclosing-flow override → step inline → runtime params.
448
+ const rawOptions = {
449
+ ...taskDef.options,
450
+ ...(parentOptions[step.name] ?? {}),
451
+ ...step.options,
452
+ ...flowParams,
453
+ };
313
454
  const refCtx = {
314
455
  steps: completedSteps,
315
456
  error: errorCtx
@@ -332,8 +473,7 @@ export class FlowRunner {
332
473
  };
333
474
  }
334
475
  this.logger.info({ step: step.stepNumber, task: step.name, type: step.type }, `Executing step ${step.stepNumber}: ${step.name}`);
335
- const task = await this.registry.create(taskDef.class_path, this.ctx, mergedOptions);
336
- return task.run();
476
+ return this.executeTask(taskDef.class_path, mergedOptions);
337
477
  }
338
478
  async performRollback(records) {
339
479
  const result = { attempted: 0, succeeded: 0, errors: [] };
@@ -342,11 +482,7 @@ export class FlowRunner {
342
482
  result.attempted++;
343
483
  try {
344
484
  const taskDef = this.resolveTaskDefinition(rec.taskName);
345
- const task = await this.registry.create(taskDef.class_path, this.ctx, {
346
- ...taskDef.options,
347
- ...rec.payload,
348
- });
349
- const r = await task.run();
485
+ const r = await this.executeTask(taskDef.class_path, { ...taskDef.options, ...rec.payload });
350
486
  if (r.success) {
351
487
  result.succeeded++;
352
488
  }
@@ -374,6 +510,17 @@ export class FlowRunner {
374
510
  return { class_path: taskName, options: {} };
375
511
  }
376
512
  }
513
+ /** Truthiness of a resolved `when:` value, with string special-cases. */
514
+ function truthy(value) {
515
+ if (typeof value === 'boolean')
516
+ return value;
517
+ if (value == null)
518
+ return false;
519
+ if (typeof value === 'number')
520
+ return value !== 0;
521
+ const s = String(value).trim().toLowerCase();
522
+ return !(s === '' || s === 'false' || s === '0' || s === 'null' || s === 'undefined');
523
+ }
377
524
  function sleep(ms) {
378
525
  return new Promise((resolve) => setTimeout(resolve, ms));
379
526
  }