@onlineapps/cookbook-core 2.1.15 → 2.1.17
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.
- package/README.md +27 -5
- package/package.json +1 -1
- package/schemas/cookbook.relaxed.schema.json +62 -279
- package/schemas/cookbook.strict.schema.json +87 -365
- package/src/parser/cookbookValidator.js +38 -36
- package/src/referenceValidator.js +22 -12
package/README.md
CHANGED
|
@@ -17,8 +17,9 @@ npm install @onlineapps/cookbook-core
|
|
|
17
17
|
- **JSON Schema validation** - Strict schema enforcement
|
|
18
18
|
- **Recursive step validation** - All step types supported
|
|
19
19
|
- **Unique ID validation** - Ensures no duplicate step IDs
|
|
20
|
-
- **Lightweight** -
|
|
21
|
-
- **
|
|
20
|
+
- **Lightweight** - minimal dependency surface
|
|
21
|
+
- **Two runtime dependencies** - `ajv` for schema validation and `ajv-formats`
|
|
22
|
+
for the format keywords the schema uses (`date-time`, `uri`, …)
|
|
22
23
|
|
|
23
24
|
## Usage
|
|
24
25
|
|
|
@@ -74,14 +75,35 @@ console.log(CookbookSchema.properties);
|
|
|
74
75
|
|
|
75
76
|
### Validator Functions
|
|
76
77
|
|
|
77
|
-
- `validateCookbook(cookbook)` - Full cookbook validation
|
|
78
|
+
- `validateCookbook(cookbook, options)` - Full cookbook validation. `options`:
|
|
79
|
+
`version` (skip detection), `strict` (default `true`), `warnOnly`
|
|
78
80
|
- `validateStep(step)` - Single step validation
|
|
81
|
+
- `detectVersion(cookbook)` - Detect cookbook format version
|
|
82
|
+
|
|
83
|
+
### Reference Validation
|
|
84
|
+
|
|
85
|
+
- `validateAllReferences(cookbook)` - Run every reference check below
|
|
86
|
+
- `validateDependsOnReferences(cookbook)` - `depends_on` targets exist
|
|
87
|
+
- `validateVariableReferences(cookbook)` - Variable references resolve
|
|
88
|
+
- `validateErrorHandlerReferences(cookbook)` - Error-handler targets exist
|
|
79
89
|
|
|
80
90
|
### Schema Functions
|
|
81
91
|
|
|
82
92
|
- `loadSchema()` - Load cookbook JSON schema
|
|
93
|
+
- `loadValidatorSchema()` - Load the schema used by the validator
|
|
83
94
|
- `CookbookSchema` - Pre-loaded schema object
|
|
84
95
|
|
|
96
|
+
### Helpers (V2.1)
|
|
97
|
+
|
|
98
|
+
- `CookbookHelpers` - Helper class bundling the functions below
|
|
99
|
+
- `findStep`, `hasStep`, `filterSteps`, `forEachStep`, `mapSteps`
|
|
100
|
+
- `getStepIds`, `getStepCount`, `getFirstStep`, `getNextStep`
|
|
101
|
+
- `isV21Format`, `isV20Format`, `convertV20ToV21`
|
|
102
|
+
|
|
103
|
+
### Constants
|
|
104
|
+
|
|
105
|
+
- `VERSION` / `SCHEMA_VERSION` - both `'2.1.0'`
|
|
106
|
+
|
|
85
107
|
### Error Classes
|
|
86
108
|
|
|
87
109
|
- `CookbookValidationError` - Validation error class
|
|
@@ -99,12 +121,12 @@ console.log(CookbookSchema.properties);
|
|
|
99
121
|
## Documentation
|
|
100
122
|
|
|
101
123
|
- [**Schema Audit Report**](docs/SCHEMA_AUDIT.md) - Critical analysis of validation rules
|
|
102
|
-
- [Test Coverage](
|
|
124
|
+
- [Test Coverage](tests/) - `tests/unit`, `tests/component`, `tests/integration`
|
|
103
125
|
|
|
104
126
|
## Related Packages
|
|
105
127
|
|
|
106
128
|
- `@onlineapps/cookbook-executor` - Workflow execution engine
|
|
107
|
-
|
|
129
|
+
<!-- cookbook-transformer removed in operations.json-first migration; see /api/docs/biz/30-operations/registration-wire.md -->
|
|
108
130
|
- `@onlineapps/cookbook-router` - Message routing
|
|
109
131
|
- `@onlineapps/connector-cookbook` - Full-featured wrapper for microservices
|
|
110
132
|
|
package/package.json
CHANGED
|
@@ -1,351 +1,134 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
-
"$id": "https://onlineapps.cz/schemas/cookbook-relaxed/v2.
|
|
4
|
-
"title": "Cookbook Schema - Relaxed Mode",
|
|
5
|
-
"description": "Relaxed validation schema for
|
|
3
|
+
"$id": "https://onlineapps.cz/schemas/cookbook-relaxed/v2.1.0",
|
|
4
|
+
"title": "Cookbook Schema - Relaxed Mode (V2.1)",
|
|
5
|
+
"description": "Relaxed validation schema for V2.1 cookbooks (allows additional properties on root).",
|
|
6
6
|
"type": "object",
|
|
7
|
-
"required": ["steps"],
|
|
7
|
+
"required": ["steps", "version"],
|
|
8
8
|
"additionalProperties": true,
|
|
9
9
|
"properties": {
|
|
10
|
-
"name": {
|
|
11
|
-
|
|
12
|
-
},
|
|
13
|
-
"
|
|
14
|
-
|
|
15
|
-
"description": "Recommended: Use semantic versioning (e.g., 1.0.0)"
|
|
16
|
-
},
|
|
17
|
-
"description": {
|
|
18
|
-
"type": "string"
|
|
19
|
-
},
|
|
20
|
-
"api_input": {
|
|
21
|
-
"type": "object"
|
|
22
|
-
},
|
|
10
|
+
"name": { "type": "string" },
|
|
11
|
+
"version": { "type": "string", "pattern": "^2\\.\\d+\\.\\d+$" },
|
|
12
|
+
"description": { "type": "string" },
|
|
13
|
+
"api_input": { "type": "object" },
|
|
14
|
+
"metadata": { "type": "object", "additionalProperties": true },
|
|
23
15
|
"steps": {
|
|
24
16
|
"type": "array",
|
|
25
17
|
"minItems": 1,
|
|
26
|
-
"items": {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
18
|
+
"items": { "$ref": "#/definitions/Step" }
|
|
19
|
+
},
|
|
20
|
+
"delivery": { "type": "object" }
|
|
30
21
|
},
|
|
31
22
|
"definitions": {
|
|
32
23
|
"Step": {
|
|
33
24
|
"type": "object",
|
|
34
|
-
"required": ["
|
|
25
|
+
"required": ["step_id", "type"],
|
|
35
26
|
"additionalProperties": true,
|
|
36
|
-
"properties": {
|
|
37
|
-
"id": {
|
|
38
|
-
"type": "string",
|
|
39
|
-
"minLength": 1
|
|
40
|
-
},
|
|
41
|
-
"type": {
|
|
42
|
-
"type": "string",
|
|
43
|
-
"enum": ["task", "foreach", "fork_join", "switch", "sub_workflow", "wait", "dispatch"],
|
|
44
|
-
"description": "Recommended: Explicitly specify step type"
|
|
45
|
-
}
|
|
46
|
-
},
|
|
47
27
|
"oneOf": [
|
|
48
28
|
{ "$ref": "#/definitions/TaskStep" },
|
|
49
29
|
{ "$ref": "#/definitions/ForeachStep" },
|
|
50
30
|
{ "$ref": "#/definitions/ForkJoinStep" },
|
|
51
31
|
{ "$ref": "#/definitions/SwitchStep" },
|
|
52
|
-
{ "$ref": "#/definitions/
|
|
32
|
+
{ "$ref": "#/definitions/StepsStep" },
|
|
53
33
|
{ "$ref": "#/definitions/WaitStep" },
|
|
54
34
|
{ "$ref": "#/definitions/DispatchStep" }
|
|
55
35
|
]
|
|
56
36
|
},
|
|
57
37
|
"TaskStep": {
|
|
58
38
|
"type": "object",
|
|
59
|
-
"required": ["
|
|
39
|
+
"required": ["step_id", "type", "service"],
|
|
60
40
|
"additionalProperties": true,
|
|
61
41
|
"properties": {
|
|
62
|
-
"
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
},
|
|
66
|
-
"type":
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
},
|
|
70
|
-
"
|
|
71
|
-
"type": "string"
|
|
72
|
-
},
|
|
73
|
-
"service": {
|
|
74
|
-
"type": "string",
|
|
75
|
-
"description": "Target service name"
|
|
76
|
-
},
|
|
77
|
-
"operation": {
|
|
78
|
-
"type": "string",
|
|
79
|
-
"description": "Operation identifier"
|
|
80
|
-
},
|
|
81
|
-
"action": {
|
|
82
|
-
"type": "string"
|
|
83
|
-
},
|
|
84
|
-
"endpoint": {
|
|
85
|
-
"type": "string"
|
|
86
|
-
},
|
|
87
|
-
"input": {
|
|
88
|
-
"type": "object"
|
|
89
|
-
},
|
|
90
|
-
"output": {
|
|
91
|
-
"type": "object"
|
|
92
|
-
},
|
|
93
|
-
"depends_on": {
|
|
94
|
-
"type": "array",
|
|
95
|
-
"items": {
|
|
96
|
-
"type": "string"
|
|
97
|
-
}
|
|
98
|
-
},
|
|
99
|
-
"retry": {
|
|
100
|
-
"$ref": "#/definitions/Retry"
|
|
101
|
-
},
|
|
102
|
-
"timeoutMs": {
|
|
103
|
-
"type": "integer",
|
|
104
|
-
"minimum": 1
|
|
105
|
-
}
|
|
42
|
+
"step_id": { "type": "string", "minLength": 1 },
|
|
43
|
+
"type": { "type": "string", "const": "task" },
|
|
44
|
+
"service": { "type": "string" },
|
|
45
|
+
"operation": { "type": "string" },
|
|
46
|
+
"input": { "type": "object" },
|
|
47
|
+
"output": { "type": "object" },
|
|
48
|
+
"depends_on": { "type": "array", "items": { "type": "string" } },
|
|
49
|
+
"retry": { "$ref": "#/definitions/Retry" },
|
|
50
|
+
"timeoutMs": { "type": "integer", "minimum": 1 }
|
|
106
51
|
}
|
|
107
52
|
},
|
|
108
53
|
"ForeachStep": {
|
|
109
54
|
"type": "object",
|
|
110
|
-
"required": ["
|
|
55
|
+
"required": ["step_id", "type", "iterator", "body"],
|
|
111
56
|
"additionalProperties": true,
|
|
112
57
|
"properties": {
|
|
113
|
-
"
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
},
|
|
117
|
-
"type":
|
|
118
|
-
"type": "string",
|
|
119
|
-
"const": "foreach"
|
|
120
|
-
},
|
|
121
|
-
"name": {
|
|
122
|
-
"type": "string"
|
|
123
|
-
},
|
|
124
|
-
"iterator": {
|
|
125
|
-
"type": "string"
|
|
126
|
-
},
|
|
127
|
-
"body": {
|
|
128
|
-
"type": "array",
|
|
129
|
-
"minItems": 1,
|
|
130
|
-
"items": {
|
|
131
|
-
"$ref": "#/definitions/Step"
|
|
132
|
-
}
|
|
133
|
-
},
|
|
134
|
-
"output": {
|
|
135
|
-
"type": "object"
|
|
136
|
-
},
|
|
137
|
-
"service": {
|
|
138
|
-
"deprecated": true,
|
|
139
|
-
"description": "DEPRECATED: This field should not be used in foreach steps"
|
|
140
|
-
},
|
|
141
|
-
"input": {
|
|
142
|
-
"deprecated": true,
|
|
143
|
-
"description": "DEPRECATED: This field should not be used in foreach steps"
|
|
144
|
-
}
|
|
58
|
+
"step_id": { "type": "string", "minLength": 1 },
|
|
59
|
+
"type": { "type": "string", "const": "foreach" },
|
|
60
|
+
"iterator": { "type": "string", "minLength": 1 },
|
|
61
|
+
"body": { "type": "array", "minItems": 1, "items": { "$ref": "#/definitions/Step" } },
|
|
62
|
+
"output": { "type": "object" }
|
|
145
63
|
}
|
|
146
64
|
},
|
|
147
65
|
"ForkJoinStep": {
|
|
148
66
|
"type": "object",
|
|
149
|
-
"required": ["
|
|
67
|
+
"required": ["step_id", "type", "branches", "join"],
|
|
150
68
|
"additionalProperties": true,
|
|
151
69
|
"properties": {
|
|
152
|
-
"
|
|
153
|
-
|
|
154
|
-
"minLength": 1
|
|
155
|
-
},
|
|
156
|
-
"type": {
|
|
157
|
-
"type": "string",
|
|
158
|
-
"const": "fork_join"
|
|
159
|
-
},
|
|
160
|
-
"name": {
|
|
161
|
-
"type": "string"
|
|
162
|
-
},
|
|
70
|
+
"step_id": { "type": "string", "minLength": 1 },
|
|
71
|
+
"type": { "type": "string", "const": "fork_join" },
|
|
163
72
|
"branches": {
|
|
164
|
-
"type": "array",
|
|
165
|
-
"minItems": 1,
|
|
166
|
-
"items": {
|
|
167
|
-
"$ref": "#/definitions/Step"
|
|
168
|
-
}
|
|
169
|
-
},
|
|
170
|
-
"join": {
|
|
171
73
|
"type": "object",
|
|
172
|
-
"
|
|
173
|
-
|
|
174
|
-
"type": "string",
|
|
175
|
-
"enum": ["merge", "first", "last", "all", "custom"],
|
|
176
|
-
"default": "merge"
|
|
177
|
-
},
|
|
178
|
-
"output": {
|
|
179
|
-
"type": "object"
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
},
|
|
183
|
-
"service": {
|
|
184
|
-
"deprecated": true,
|
|
185
|
-
"description": "DEPRECATED: This field should not be used in fork_join steps"
|
|
74
|
+
"minProperties": 1,
|
|
75
|
+
"additionalProperties": { "$ref": "#/definitions/Step" }
|
|
186
76
|
},
|
|
187
|
-
"
|
|
188
|
-
"deprecated": true,
|
|
189
|
-
"description": "DEPRECATED: This field should not be used in fork_join steps"
|
|
190
|
-
}
|
|
77
|
+
"join": { "type": "object" }
|
|
191
78
|
}
|
|
192
79
|
},
|
|
193
80
|
"SwitchStep": {
|
|
194
81
|
"type": "object",
|
|
195
|
-
"required": ["
|
|
82
|
+
"required": ["step_id", "type", "expression", "cases"],
|
|
196
83
|
"additionalProperties": true,
|
|
197
84
|
"properties": {
|
|
198
|
-
"
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
},
|
|
202
|
-
"
|
|
203
|
-
"type": "string",
|
|
204
|
-
"const": "switch"
|
|
205
|
-
},
|
|
206
|
-
"name": {
|
|
207
|
-
"type": "string"
|
|
208
|
-
},
|
|
209
|
-
"expression": {
|
|
210
|
-
"type": "string"
|
|
211
|
-
},
|
|
212
|
-
"cases": {
|
|
213
|
-
"type": "object",
|
|
214
|
-
"minProperties": 1,
|
|
215
|
-
"additionalProperties": {
|
|
216
|
-
"$ref": "#/definitions/Step"
|
|
217
|
-
}
|
|
218
|
-
},
|
|
219
|
-
"default": {
|
|
220
|
-
"$ref": "#/definitions/Step",
|
|
221
|
-
"description": "Recommended: Always provide a default case"
|
|
222
|
-
},
|
|
223
|
-
"depends_on": {
|
|
224
|
-
"type": "array",
|
|
225
|
-
"items": {
|
|
226
|
-
"type": "string"
|
|
227
|
-
}
|
|
228
|
-
},
|
|
229
|
-
"service": {
|
|
230
|
-
"deprecated": true,
|
|
231
|
-
"description": "DEPRECATED: This field should not be used in switch steps"
|
|
232
|
-
}
|
|
85
|
+
"step_id": { "type": "string", "minLength": 1 },
|
|
86
|
+
"type": { "type": "string", "const": "switch" },
|
|
87
|
+
"expression": { "type": "string", "minLength": 1 },
|
|
88
|
+
"cases": { "type": "object", "minProperties": 1, "additionalProperties": { "$ref": "#/definitions/Step" } },
|
|
89
|
+
"default": { "$ref": "#/definitions/Step" }
|
|
233
90
|
}
|
|
234
91
|
},
|
|
235
|
-
"
|
|
92
|
+
"StepsStep": {
|
|
236
93
|
"type": "object",
|
|
237
|
-
"required": ["
|
|
94
|
+
"required": ["step_id", "type", "steps"],
|
|
238
95
|
"additionalProperties": true,
|
|
239
96
|
"properties": {
|
|
240
|
-
"
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
}
|
|
244
|
-
"type": {
|
|
245
|
-
"type": "string",
|
|
246
|
-
"const": "sub_workflow"
|
|
247
|
-
},
|
|
248
|
-
"name": {
|
|
249
|
-
"type": "string"
|
|
250
|
-
},
|
|
251
|
-
"input": {
|
|
252
|
-
"type": "object"
|
|
253
|
-
},
|
|
254
|
-
"steps": {
|
|
255
|
-
"type": "array",
|
|
256
|
-
"minItems": 1,
|
|
257
|
-
"items": {
|
|
258
|
-
"$ref": "#/definitions/Step"
|
|
259
|
-
}
|
|
260
|
-
}
|
|
97
|
+
"step_id": { "type": "string", "minLength": 1 },
|
|
98
|
+
"type": { "type": "string", "const": "steps" },
|
|
99
|
+
"steps": { "type": "array", "minItems": 1, "items": { "$ref": "#/definitions/Step" } },
|
|
100
|
+
"max_depth": { "type": "integer", "minimum": 1, "maximum": 10 }
|
|
261
101
|
}
|
|
262
102
|
},
|
|
263
103
|
"WaitStep": {
|
|
264
104
|
"type": "object",
|
|
265
|
-
"required": ["
|
|
105
|
+
"required": ["step_id", "type", "durationMs"],
|
|
266
106
|
"additionalProperties": true,
|
|
267
107
|
"properties": {
|
|
268
|
-
"
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
},
|
|
272
|
-
"type": {
|
|
273
|
-
"type": "string",
|
|
274
|
-
"const": "wait"
|
|
275
|
-
},
|
|
276
|
-
"name": {
|
|
277
|
-
"type": "string"
|
|
278
|
-
},
|
|
279
|
-
"durationMs": {
|
|
280
|
-
"type": "integer",
|
|
281
|
-
"minimum": 0
|
|
282
|
-
},
|
|
283
|
-
"service": {
|
|
284
|
-
"deprecated": true,
|
|
285
|
-
"description": "DEPRECATED: This field should not be used in wait steps"
|
|
286
|
-
},
|
|
287
|
-
"input": {
|
|
288
|
-
"deprecated": true,
|
|
289
|
-
"description": "DEPRECATED: This field should not be used in wait steps"
|
|
290
|
-
},
|
|
291
|
-
"output": {
|
|
292
|
-
"deprecated": true,
|
|
293
|
-
"description": "DEPRECATED: This field should not be used in wait steps"
|
|
294
|
-
}
|
|
108
|
+
"step_id": { "type": "string", "minLength": 1 },
|
|
109
|
+
"type": { "type": "string", "const": "wait" },
|
|
110
|
+
"durationMs": { "type": "integer", "minimum": 0 }
|
|
295
111
|
}
|
|
296
112
|
},
|
|
297
113
|
"DispatchStep": {
|
|
298
114
|
"type": "object",
|
|
299
|
-
"required": ["
|
|
115
|
+
"required": ["step_id", "type", "method", "target"],
|
|
300
116
|
"additionalProperties": true,
|
|
301
117
|
"properties": {
|
|
302
|
-
"
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
}
|
|
306
|
-
"type": {
|
|
307
|
-
"type": "string",
|
|
308
|
-
"const": "dispatch"
|
|
309
|
-
},
|
|
310
|
-
"name": {
|
|
311
|
-
"type": "string"
|
|
312
|
-
},
|
|
313
|
-
"method": {
|
|
314
|
-
"type": "string",
|
|
315
|
-
"enum": ["webhook", "http", "grpc", "kafka", "sqs", "custom"],
|
|
316
|
-
"default": "webhook"
|
|
317
|
-
},
|
|
318
|
-
"target": {
|
|
319
|
-
"type": "string"
|
|
320
|
-
},
|
|
321
|
-
"input": {
|
|
322
|
-
"type": "object"
|
|
323
|
-
},
|
|
324
|
-
"headers": {
|
|
325
|
-
"type": "object"
|
|
326
|
-
},
|
|
327
|
-
"retry": {
|
|
328
|
-
"$ref": "#/definitions/Retry"
|
|
329
|
-
}
|
|
118
|
+
"step_id": { "type": "string", "minLength": 1 },
|
|
119
|
+
"type": { "type": "string", "const": "dispatch" },
|
|
120
|
+
"method": { "type": "string" },
|
|
121
|
+
"target": { "type": "string" }
|
|
330
122
|
}
|
|
331
123
|
},
|
|
332
124
|
"Retry": {
|
|
333
125
|
"type": "object",
|
|
334
|
-
"
|
|
126
|
+
"required": ["maxAttempts", "delayMs"],
|
|
127
|
+
"additionalProperties": false,
|
|
335
128
|
"properties": {
|
|
336
|
-
"maxAttempts": {
|
|
337
|
-
|
|
338
|
-
"minimum": 1
|
|
339
|
-
},
|
|
340
|
-
"delayMs": {
|
|
341
|
-
"type": "integer",
|
|
342
|
-
"minimum": 0
|
|
343
|
-
},
|
|
344
|
-
"backoffMultiplier": {
|
|
345
|
-
"type": "number",
|
|
346
|
-
"minimum": 1.0
|
|
347
|
-
}
|
|
129
|
+
"maxAttempts": { "type": "integer", "minimum": 1 },
|
|
130
|
+
"delayMs": { "type": "integer", "minimum": 0 }
|
|
348
131
|
}
|
|
349
132
|
}
|
|
350
133
|
}
|
|
351
|
-
}
|
|
134
|
+
}
|
|
@@ -1,417 +1,139 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
-
"$id": "https://onlineapps.cz/schemas/cookbook-strict/v2.
|
|
4
|
-
"title": "Cookbook Schema - Strict Mode",
|
|
5
|
-
"description": "Strict validation schema for
|
|
3
|
+
"$id": "https://onlineapps.cz/schemas/cookbook-strict/v2.1.0",
|
|
4
|
+
"title": "Cookbook Schema - Strict Mode (V2.1)",
|
|
5
|
+
"description": "Strict validation schema for V2.1 cookbooks (no additional properties on root).",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"required": ["steps", "version"],
|
|
8
8
|
"additionalProperties": false,
|
|
9
9
|
"properties": {
|
|
10
|
-
"name": {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
},
|
|
16
|
-
"version": {
|
|
17
|
-
"type": "string",
|
|
18
|
-
"pattern": "^\\d+\\.\\d+\\.\\d+$",
|
|
19
|
-
"description": "Semantic version of the cookbook schema"
|
|
20
|
-
},
|
|
21
|
-
"description": {
|
|
22
|
-
"type": "string",
|
|
23
|
-
"maxLength": 500
|
|
24
|
-
},
|
|
25
|
-
"api_input": {
|
|
26
|
-
"type": "object",
|
|
27
|
-
"description": "Initial input data for the workflow"
|
|
28
|
-
},
|
|
29
|
-
"metadata": {
|
|
30
|
-
"type": "object",
|
|
31
|
-
"description": "Additional metadata for debugging and tracking",
|
|
32
|
-
"properties": {
|
|
33
|
-
"created_at": {
|
|
34
|
-
"type": "string",
|
|
35
|
-
"format": "date-time"
|
|
36
|
-
},
|
|
37
|
-
"author": {
|
|
38
|
-
"type": "string"
|
|
39
|
-
},
|
|
40
|
-
"tags": {
|
|
41
|
-
"type": "array",
|
|
42
|
-
"items": {
|
|
43
|
-
"type": "string"
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
},
|
|
10
|
+
"name": { "type": "string", "minLength": 1 },
|
|
11
|
+
"version": { "type": "string", "pattern": "^2\\.\\d+\\.\\d+$" },
|
|
12
|
+
"description": { "type": "string" },
|
|
13
|
+
"api_input": { "type": "object" },
|
|
14
|
+
"metadata": { "type": "object", "additionalProperties": true },
|
|
48
15
|
"steps": {
|
|
49
16
|
"type": "array",
|
|
50
17
|
"minItems": 1,
|
|
51
|
-
"
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
-
}
|
|
18
|
+
"items": { "$ref": "#/definitions/Step" }
|
|
19
|
+
},
|
|
20
|
+
"delivery": { "type": "object" }
|
|
56
21
|
},
|
|
57
22
|
"definitions": {
|
|
58
23
|
"Step": {
|
|
59
24
|
"type": "object",
|
|
60
|
-
"required": ["
|
|
61
|
-
"properties": {
|
|
62
|
-
"id": {
|
|
63
|
-
"type": "string",
|
|
64
|
-
"minLength": 1,
|
|
65
|
-
"maxLength": 100,
|
|
66
|
-
"pattern": "^[a-zA-Z0-9-_]+$"
|
|
67
|
-
},
|
|
68
|
-
"type": {
|
|
69
|
-
"type": "string",
|
|
70
|
-
"enum": ["task", "foreach", "fork_join", "switch", "sub_workflow", "wait", "dispatch"]
|
|
71
|
-
}
|
|
72
|
-
},
|
|
25
|
+
"required": ["step_id", "type"],
|
|
73
26
|
"oneOf": [
|
|
74
27
|
{ "$ref": "#/definitions/TaskStep" },
|
|
75
28
|
{ "$ref": "#/definitions/ForeachStep" },
|
|
76
29
|
{ "$ref": "#/definitions/ForkJoinStep" },
|
|
77
30
|
{ "$ref": "#/definitions/SwitchStep" },
|
|
78
|
-
{ "$ref": "#/definitions/
|
|
31
|
+
{ "$ref": "#/definitions/StepsStep" },
|
|
79
32
|
{ "$ref": "#/definitions/WaitStep" },
|
|
80
33
|
{ "$ref": "#/definitions/DispatchStep" }
|
|
81
34
|
]
|
|
82
35
|
},
|
|
83
36
|
"TaskStep": {
|
|
84
37
|
"type": "object",
|
|
85
|
-
"required": ["
|
|
86
|
-
"additionalProperties":
|
|
38
|
+
"required": ["step_id", "type", "service"],
|
|
39
|
+
"additionalProperties": true,
|
|
87
40
|
"properties": {
|
|
88
|
-
"
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
},
|
|
94
|
-
"type":
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
},
|
|
98
|
-
"
|
|
99
|
-
"type": "string",
|
|
100
|
-
"minLength": 1,
|
|
101
|
-
"maxLength": 200
|
|
102
|
-
},
|
|
103
|
-
"service": {
|
|
104
|
-
"type": "string",
|
|
105
|
-
"minLength": 1,
|
|
106
|
-
"maxLength": 100,
|
|
107
|
-
"pattern": "^[a-zA-Z0-9-_]+$"
|
|
108
|
-
},
|
|
109
|
-
"operation": {
|
|
110
|
-
"type": "string",
|
|
111
|
-
"minLength": 1,
|
|
112
|
-
"maxLength": 100,
|
|
113
|
-
"description": "Operation identifier for the service to execute"
|
|
114
|
-
},
|
|
115
|
-
"action": {
|
|
116
|
-
"type": "string",
|
|
117
|
-
"enum": ["GET", "POST", "PUT", "DELETE", "PATCH"]
|
|
118
|
-
},
|
|
119
|
-
"endpoint": {
|
|
120
|
-
"type": "string",
|
|
121
|
-
"minLength": 1,
|
|
122
|
-
"maxLength": 500
|
|
123
|
-
},
|
|
124
|
-
"input": {
|
|
125
|
-
"type": "object",
|
|
126
|
-
"additionalProperties": {
|
|
127
|
-
"type": "string",
|
|
128
|
-
"minLength": 1
|
|
129
|
-
},
|
|
130
|
-
"description": "Optional input mapping"
|
|
131
|
-
},
|
|
132
|
-
"output": {
|
|
133
|
-
"type": "object",
|
|
134
|
-
"additionalProperties": {
|
|
135
|
-
"type": "string",
|
|
136
|
-
"minLength": 1
|
|
137
|
-
},
|
|
138
|
-
"description": "Optional output mapping"
|
|
139
|
-
},
|
|
140
|
-
"depends_on": {
|
|
141
|
-
"type": "array",
|
|
142
|
-
"items": {
|
|
143
|
-
"type": "string",
|
|
144
|
-
"minLength": 1,
|
|
145
|
-
"maxLength": 100
|
|
146
|
-
},
|
|
147
|
-
"maxItems": 100
|
|
148
|
-
},
|
|
149
|
-
"retry": {
|
|
150
|
-
"$ref": "#/definitions/Retry"
|
|
151
|
-
},
|
|
152
|
-
"timeoutMs": {
|
|
153
|
-
"type": "integer",
|
|
154
|
-
"minimum": 1,
|
|
155
|
-
"maximum": 3600000
|
|
156
|
-
}
|
|
41
|
+
"step_id": { "type": "string", "minLength": 1, "pattern": "^[a-zA-Z0-9_]+$" },
|
|
42
|
+
"type": { "type": "string", "const": "task" },
|
|
43
|
+
"name": { "type": "string", "minLength": 1 },
|
|
44
|
+
"service": { "type": "string", "minLength": 1 },
|
|
45
|
+
"operation": { "type": "string", "minLength": 1 },
|
|
46
|
+
"input": { "type": "object", "additionalProperties": true },
|
|
47
|
+
"output": { "type": "object", "additionalProperties": true },
|
|
48
|
+
"depends_on": { "type": "array", "items": { "type": "string" } },
|
|
49
|
+
"retry": { "$ref": "#/definitions/Retry" },
|
|
50
|
+
"timeoutMs": { "type": "integer", "minimum": 1 },
|
|
51
|
+
"on_error": { "$ref": "#/definitions/ErrorHandler" }
|
|
157
52
|
}
|
|
158
53
|
},
|
|
159
54
|
"ForeachStep": {
|
|
160
55
|
"type": "object",
|
|
161
|
-
"required": ["
|
|
162
|
-
"additionalProperties":
|
|
56
|
+
"required": ["step_id", "type", "iterator", "body"],
|
|
57
|
+
"additionalProperties": true,
|
|
163
58
|
"properties": {
|
|
164
|
-
"
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
},
|
|
170
|
-
"type": {
|
|
171
|
-
"type": "string",
|
|
172
|
-
"const": "foreach"
|
|
173
|
-
},
|
|
174
|
-
"name": {
|
|
175
|
-
"type": "string",
|
|
176
|
-
"minLength": 1,
|
|
177
|
-
"maxLength": 200
|
|
178
|
-
},
|
|
179
|
-
"iterator": {
|
|
180
|
-
"type": "string",
|
|
181
|
-
"minLength": 1,
|
|
182
|
-
"pattern": "^\\$"
|
|
183
|
-
},
|
|
184
|
-
"body": {
|
|
185
|
-
"type": "array",
|
|
186
|
-
"minItems": 1,
|
|
187
|
-
"maxItems": 100,
|
|
188
|
-
"items": {
|
|
189
|
-
"$ref": "#/definitions/Step"
|
|
190
|
-
}
|
|
191
|
-
},
|
|
192
|
-
"output": {
|
|
193
|
-
"type": "object",
|
|
194
|
-
"additionalProperties": {
|
|
195
|
-
"type": "string",
|
|
196
|
-
"minLength": 1
|
|
197
|
-
}
|
|
198
|
-
}
|
|
59
|
+
"step_id": { "type": "string", "minLength": 1, "pattern": "^[a-zA-Z0-9_]+$" },
|
|
60
|
+
"type": { "type": "string", "const": "foreach" },
|
|
61
|
+
"iterator": { "type": "string", "minLength": 1 },
|
|
62
|
+
"body": { "type": "array", "minItems": 1, "items": { "$ref": "#/definitions/Step" } },
|
|
63
|
+
"output": { "type": "object", "additionalProperties": true }
|
|
199
64
|
}
|
|
200
65
|
},
|
|
201
66
|
"ForkJoinStep": {
|
|
202
67
|
"type": "object",
|
|
203
|
-
"required": ["
|
|
204
|
-
"additionalProperties":
|
|
68
|
+
"required": ["step_id", "type", "branches", "join"],
|
|
69
|
+
"additionalProperties": true,
|
|
205
70
|
"properties": {
|
|
206
|
-
"
|
|
207
|
-
|
|
208
|
-
"minLength": 1,
|
|
209
|
-
"maxLength": 100,
|
|
210
|
-
"pattern": "^[a-zA-Z0-9-_]+$"
|
|
211
|
-
},
|
|
212
|
-
"type": {
|
|
213
|
-
"type": "string",
|
|
214
|
-
"const": "fork_join"
|
|
215
|
-
},
|
|
216
|
-
"name": {
|
|
217
|
-
"type": "string",
|
|
218
|
-
"minLength": 1,
|
|
219
|
-
"maxLength": 200
|
|
220
|
-
},
|
|
71
|
+
"step_id": { "type": "string", "minLength": 1, "pattern": "^[a-zA-Z0-9_]+$" },
|
|
72
|
+
"type": { "type": "string", "const": "fork_join" },
|
|
221
73
|
"branches": {
|
|
222
|
-
"type": "
|
|
223
|
-
"
|
|
224
|
-
"
|
|
225
|
-
"items": {
|
|
226
|
-
"$ref": "#/definitions/Step"
|
|
227
|
-
}
|
|
74
|
+
"type": "object",
|
|
75
|
+
"minProperties": 1,
|
|
76
|
+
"additionalProperties": { "$ref": "#/definitions/Step" }
|
|
228
77
|
},
|
|
229
78
|
"join": {
|
|
230
79
|
"type": "object",
|
|
231
80
|
"required": ["strategy"],
|
|
232
|
-
"additionalProperties": false,
|
|
233
81
|
"properties": {
|
|
234
|
-
"strategy": {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
"description": "How to combine branch results"
|
|
238
|
-
},
|
|
239
|
-
"output": {
|
|
240
|
-
"type": "object",
|
|
241
|
-
"additionalProperties": {
|
|
242
|
-
"type": "string",
|
|
243
|
-
"minLength": 1
|
|
244
|
-
}
|
|
245
|
-
},
|
|
246
|
-
"customHandler": {
|
|
247
|
-
"type": "string",
|
|
248
|
-
"description": "Custom handler name when strategy is 'custom'"
|
|
249
|
-
}
|
|
82
|
+
"strategy": { "type": "string", "enum": ["merge", "first", "last", "all", "custom"] },
|
|
83
|
+
"output": { "type": "object" },
|
|
84
|
+
"timeoutMs": { "type": "integer", "minimum": 1 }
|
|
250
85
|
}
|
|
251
86
|
}
|
|
252
87
|
}
|
|
253
88
|
},
|
|
254
89
|
"SwitchStep": {
|
|
255
90
|
"type": "object",
|
|
256
|
-
"required": ["
|
|
257
|
-
"additionalProperties":
|
|
91
|
+
"required": ["step_id", "type", "expression", "cases"],
|
|
92
|
+
"additionalProperties": true,
|
|
258
93
|
"properties": {
|
|
259
|
-
"
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
"maxLength": 100,
|
|
263
|
-
"pattern": "^[a-zA-Z0-9-_]+$"
|
|
264
|
-
},
|
|
265
|
-
"type": {
|
|
266
|
-
"type": "string",
|
|
267
|
-
"const": "switch"
|
|
268
|
-
},
|
|
269
|
-
"name": {
|
|
270
|
-
"type": "string",
|
|
271
|
-
"minLength": 1,
|
|
272
|
-
"maxLength": 200
|
|
273
|
-
},
|
|
274
|
-
"expression": {
|
|
275
|
-
"type": "string",
|
|
276
|
-
"minLength": 1,
|
|
277
|
-
"pattern": "^\\$"
|
|
278
|
-
},
|
|
94
|
+
"step_id": { "type": "string", "minLength": 1, "pattern": "^[a-zA-Z0-9_]+$" },
|
|
95
|
+
"type": { "type": "string", "const": "switch" },
|
|
96
|
+
"expression": { "type": "string", "minLength": 1 },
|
|
279
97
|
"cases": {
|
|
280
98
|
"type": "object",
|
|
281
99
|
"minProperties": 1,
|
|
282
|
-
"
|
|
283
|
-
"additionalProperties": {
|
|
284
|
-
"$ref": "#/definitions/Step"
|
|
285
|
-
}
|
|
286
|
-
},
|
|
287
|
-
"default": {
|
|
288
|
-
"$ref": "#/definitions/Step"
|
|
100
|
+
"additionalProperties": { "$ref": "#/definitions/Step" }
|
|
289
101
|
},
|
|
290
|
-
"
|
|
291
|
-
"type": "array",
|
|
292
|
-
"items": {
|
|
293
|
-
"type": "string",
|
|
294
|
-
"minLength": 1,
|
|
295
|
-
"maxLength": 100
|
|
296
|
-
},
|
|
297
|
-
"maxItems": 100
|
|
298
|
-
}
|
|
102
|
+
"default": { "$ref": "#/definitions/Step" }
|
|
299
103
|
}
|
|
300
104
|
},
|
|
301
|
-
"
|
|
105
|
+
"StepsStep": {
|
|
302
106
|
"type": "object",
|
|
303
|
-
"required": ["
|
|
304
|
-
"additionalProperties":
|
|
107
|
+
"required": ["step_id", "type", "steps"],
|
|
108
|
+
"additionalProperties": true,
|
|
305
109
|
"properties": {
|
|
306
|
-
"
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
"pattern": "^[a-zA-Z0-9-_]+$"
|
|
311
|
-
},
|
|
312
|
-
"type": {
|
|
313
|
-
"type": "string",
|
|
314
|
-
"const": "sub_workflow"
|
|
315
|
-
},
|
|
316
|
-
"name": {
|
|
317
|
-
"type": "string",
|
|
318
|
-
"minLength": 1,
|
|
319
|
-
"maxLength": 200
|
|
320
|
-
},
|
|
321
|
-
"input": {
|
|
322
|
-
"type": "object",
|
|
323
|
-
"additionalProperties": {
|
|
324
|
-
"type": "string",
|
|
325
|
-
"minLength": 1
|
|
326
|
-
},
|
|
327
|
-
"description": "Optional input for sub-workflow"
|
|
328
|
-
},
|
|
329
|
-
"steps": {
|
|
330
|
-
"type": "array",
|
|
331
|
-
"minItems": 1,
|
|
332
|
-
"maxItems": 100,
|
|
333
|
-
"items": {
|
|
334
|
-
"$ref": "#/definitions/Step"
|
|
335
|
-
}
|
|
336
|
-
}
|
|
110
|
+
"step_id": { "type": "string", "minLength": 1, "pattern": "^[a-zA-Z0-9_]+$" },
|
|
111
|
+
"type": { "type": "string", "const": "steps" },
|
|
112
|
+
"steps": { "type": "array", "minItems": 1, "items": { "$ref": "#/definitions/Step" } },
|
|
113
|
+
"max_depth": { "type": "integer", "minimum": 1, "maximum": 10 }
|
|
337
114
|
}
|
|
338
115
|
},
|
|
339
116
|
"WaitStep": {
|
|
340
117
|
"type": "object",
|
|
341
|
-
"required": ["
|
|
342
|
-
"additionalProperties":
|
|
118
|
+
"required": ["step_id", "type", "durationMs"],
|
|
119
|
+
"additionalProperties": true,
|
|
343
120
|
"properties": {
|
|
344
|
-
"
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
"maxLength": 100,
|
|
348
|
-
"pattern": "^[a-zA-Z0-9-_]+$"
|
|
349
|
-
},
|
|
350
|
-
"type": {
|
|
351
|
-
"type": "string",
|
|
352
|
-
"const": "wait"
|
|
353
|
-
},
|
|
354
|
-
"name": {
|
|
355
|
-
"type": "string",
|
|
356
|
-
"minLength": 1,
|
|
357
|
-
"maxLength": 200
|
|
358
|
-
},
|
|
359
|
-
"durationMs": {
|
|
360
|
-
"type": "integer",
|
|
361
|
-
"minimum": 0,
|
|
362
|
-
"maximum": 86400000,
|
|
363
|
-
"description": "Wait duration in milliseconds (max 24 hours)"
|
|
364
|
-
}
|
|
121
|
+
"step_id": { "type": "string", "minLength": 1, "pattern": "^[a-zA-Z0-9_]+$" },
|
|
122
|
+
"type": { "type": "string", "const": "wait" },
|
|
123
|
+
"durationMs": { "type": "integer", "minimum": 0 }
|
|
365
124
|
}
|
|
366
125
|
},
|
|
367
126
|
"DispatchStep": {
|
|
368
127
|
"type": "object",
|
|
369
|
-
"required": ["
|
|
370
|
-
"additionalProperties":
|
|
128
|
+
"required": ["step_id", "type", "method", "target"],
|
|
129
|
+
"additionalProperties": true,
|
|
371
130
|
"properties": {
|
|
372
|
-
"
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
}
|
|
378
|
-
"type": {
|
|
379
|
-
"type": "string",
|
|
380
|
-
"const": "dispatch"
|
|
381
|
-
},
|
|
382
|
-
"name": {
|
|
383
|
-
"type": "string",
|
|
384
|
-
"minLength": 1,
|
|
385
|
-
"maxLength": 200
|
|
386
|
-
},
|
|
387
|
-
"method": {
|
|
388
|
-
"type": "string",
|
|
389
|
-
"enum": ["webhook", "http", "grpc", "kafka", "sqs", "custom"],
|
|
390
|
-
"description": "Dispatch method to use"
|
|
391
|
-
},
|
|
392
|
-
"target": {
|
|
393
|
-
"type": "string",
|
|
394
|
-
"format": "uri",
|
|
395
|
-
"maxLength": 2000
|
|
396
|
-
},
|
|
397
|
-
"input": {
|
|
398
|
-
"type": "object",
|
|
399
|
-
"additionalProperties": {
|
|
400
|
-
"type": "string",
|
|
401
|
-
"minLength": 1
|
|
402
|
-
},
|
|
403
|
-
"description": "Optional dispatch payload"
|
|
404
|
-
},
|
|
405
|
-
"headers": {
|
|
406
|
-
"type": "object",
|
|
407
|
-
"additionalProperties": {
|
|
408
|
-
"type": "string"
|
|
409
|
-
},
|
|
410
|
-
"description": "Optional HTTP headers for webhook/http methods"
|
|
411
|
-
},
|
|
412
|
-
"retry": {
|
|
413
|
-
"$ref": "#/definitions/Retry"
|
|
414
|
-
}
|
|
131
|
+
"step_id": { "type": "string", "minLength": 1, "pattern": "^[a-zA-Z0-9_]+$" },
|
|
132
|
+
"type": { "type": "string", "const": "dispatch" },
|
|
133
|
+
"method": { "type": "string", "enum": ["webhook", "http", "grpc", "kafka", "sqs", "custom"] },
|
|
134
|
+
"target": { "type": "string", "format": "uri" },
|
|
135
|
+
"input": { "type": "object" },
|
|
136
|
+
"retry": { "$ref": "#/definitions/Retry" }
|
|
415
137
|
}
|
|
416
138
|
},
|
|
417
139
|
"Retry": {
|
|
@@ -419,23 +141,23 @@
|
|
|
419
141
|
"required": ["maxAttempts", "delayMs"],
|
|
420
142
|
"additionalProperties": false,
|
|
421
143
|
"properties": {
|
|
422
|
-
"maxAttempts": {
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
"
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
}
|
|
144
|
+
"maxAttempts": { "type": "integer", "minimum": 1 },
|
|
145
|
+
"delayMs": { "type": "integer", "minimum": 0 },
|
|
146
|
+
"backoffMultiplier": { "type": "number", "minimum": 1.0, "maximum": 5.0 }
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
"ErrorHandler": {
|
|
150
|
+
"type": "object",
|
|
151
|
+
"required": ["strategy"],
|
|
152
|
+
"additionalProperties": false,
|
|
153
|
+
"properties": {
|
|
154
|
+
"strategy": { "type": "string", "enum": ["retry", "fail", "continue", "compensate", "fallback"] },
|
|
155
|
+
"retries": { "type": "integer", "minimum": 0, "maximum": 10 },
|
|
156
|
+
"retryDelayMs": { "type": "integer", "minimum": 0 },
|
|
157
|
+
"fallbackStep": { "type": "string" },
|
|
158
|
+
"compensationStep": { "type": "string" },
|
|
159
|
+
"errorOutput": { "type": "string" }
|
|
438
160
|
}
|
|
439
161
|
}
|
|
440
162
|
}
|
|
441
|
-
}
|
|
163
|
+
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Validates:
|
|
6
6
|
* - Top-level structure against JSON Schema (strict: no additional properties)
|
|
7
7
|
* - Recursive validation of each Step object, ensuring all required fields are present and no extraneous fields
|
|
8
|
-
* - Unique `
|
|
8
|
+
* - Unique `step_id` across all steps (including nested steps in foreach, fork_join, switch, steps)
|
|
9
9
|
* - For TaskStep and DispatchStep: `input` and `output` objects each have at least one mapping, all values are non-empty strings
|
|
10
10
|
* - For Retry configurations: `maxAttempts` ≥ 1, `delayMs` ≥ 0
|
|
11
11
|
* - For ForeachStep: `iterator` is a non-empty string, `body` is a non-empty array of valid Steps, optional `output` maps have non-empty string values
|
|
@@ -35,8 +35,7 @@ const loadSchemaFile = (filename) => {
|
|
|
35
35
|
if (fs.existsSync(schemaPath)) {
|
|
36
36
|
return JSON.parse(fs.readFileSync(schemaPath, 'utf-8'));
|
|
37
37
|
}
|
|
38
|
-
|
|
39
|
-
return JSON.parse(fs.readFileSync(path.join(__dirname, '../../schemas/cookbook.v2.schema.json'), 'utf-8'));
|
|
38
|
+
throw new Error(`[cookbook-core] Schema file not found - Expected ${schemaPath}`);
|
|
40
39
|
};
|
|
41
40
|
|
|
42
41
|
const cookbookSchema = loadSchemaFile('cookbook.v2.schema.json');
|
|
@@ -150,18 +149,18 @@ function collectAndValidateUniqueIds(steps, seenIds, path) {
|
|
|
150
149
|
if (typeof step !== 'object' || step === null) {
|
|
151
150
|
throw new CookbookValidationError(`${context} must be an object.`);
|
|
152
151
|
}
|
|
153
|
-
//
|
|
154
|
-
if (!('
|
|
155
|
-
throw new CookbookValidationError(`${context} is missing required property '
|
|
152
|
+
// step_id must exist and be a string
|
|
153
|
+
if (!('step_id' in step)) {
|
|
154
|
+
throw new CookbookValidationError(`${context} is missing required property 'step_id'.`);
|
|
156
155
|
}
|
|
157
|
-
assertNonEmptyString(step.
|
|
158
|
-
if (seenIds.has(step.
|
|
159
|
-
throw new CookbookValidationError(`Duplicate
|
|
156
|
+
assertNonEmptyString(step.step_id, `${context}.step_id`);
|
|
157
|
+
if (seenIds.has(step.step_id)) {
|
|
158
|
+
throw new CookbookValidationError(`Duplicate step_id '${step.step_id}' found at ${context}.`);
|
|
160
159
|
}
|
|
161
|
-
seenIds.add(step.
|
|
160
|
+
seenIds.add(step.step_id);
|
|
162
161
|
|
|
163
162
|
// Recurse into nested steps according to type
|
|
164
|
-
const stepType = step.type
|
|
163
|
+
const stepType = step.type;
|
|
165
164
|
switch (stepType) {
|
|
166
165
|
case 'foreach':
|
|
167
166
|
if (!Array.isArray(step.body)) {
|
|
@@ -170,10 +169,12 @@ function collectAndValidateUniqueIds(steps, seenIds, path) {
|
|
|
170
169
|
collectAndValidateUniqueIds(step.body, seenIds, `${context}.body`);
|
|
171
170
|
break;
|
|
172
171
|
case 'fork_join':
|
|
173
|
-
if (
|
|
174
|
-
throw new CookbookValidationError(`${context} of type 'fork_join' must have 'branches' as an
|
|
172
|
+
if (typeof step.branches !== 'object' || step.branches === null || Array.isArray(step.branches)) {
|
|
173
|
+
throw new CookbookValidationError(`${context} of type 'fork_join' must have 'branches' as an object.`);
|
|
175
174
|
}
|
|
176
|
-
|
|
175
|
+
Object.entries(step.branches).forEach(([branchKey, branchStep]) => {
|
|
176
|
+
collectAndValidateUniqueIds([branchStep], seenIds, `${context}.branches['${branchKey}']`);
|
|
177
|
+
});
|
|
177
178
|
break;
|
|
178
179
|
case 'switch':
|
|
179
180
|
// cases: object mapping keys to Step
|
|
@@ -186,13 +187,13 @@ function collectAndValidateUniqueIds(steps, seenIds, path) {
|
|
|
186
187
|
collectAndValidateUniqueIds([step.default], seenIds, `${context}.default`);
|
|
187
188
|
}
|
|
188
189
|
break;
|
|
189
|
-
case '
|
|
190
|
+
case 'steps':
|
|
190
191
|
if (!Array.isArray(step.steps)) {
|
|
191
|
-
throw new CookbookValidationError(`${context} of type '
|
|
192
|
+
throw new CookbookValidationError(`${context} of type 'steps' must have 'steps' as an array.`);
|
|
192
193
|
}
|
|
193
194
|
collectAndValidateUniqueIds(step.steps, seenIds, `${context}.steps`);
|
|
194
195
|
break;
|
|
195
|
-
// task, wait, dispatch have no nested steps for
|
|
196
|
+
// task, wait, dispatch have no nested steps for step_id collection
|
|
196
197
|
case 'task':
|
|
197
198
|
case 'wait':
|
|
198
199
|
case 'dispatch':
|
|
@@ -215,20 +216,19 @@ function validateStep(step, context, mode = 'default') {
|
|
|
215
216
|
throw new CookbookValidationError(`${context} must be an object.`);
|
|
216
217
|
}
|
|
217
218
|
|
|
218
|
-
//
|
|
219
|
-
if (!('
|
|
220
|
-
throw new CookbookValidationError(`${context} is missing required property '
|
|
219
|
+
// V2.1: step_id and type are required
|
|
220
|
+
if (!('step_id' in step)) {
|
|
221
|
+
throw new CookbookValidationError(`${context} is missing required property 'step_id'.`);
|
|
221
222
|
}
|
|
222
|
-
assertNonEmptyString(step.
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
const stepType = step.type || 'task';
|
|
226
|
-
if (step.type) {
|
|
227
|
-
assertNonEmptyString(step.type, `${context}.type`);
|
|
223
|
+
assertNonEmptyString(step.step_id, `${context}.step_id`);
|
|
224
|
+
if (!('type' in step)) {
|
|
225
|
+
throw new CookbookValidationError(`${context} is missing required property 'type'.`);
|
|
228
226
|
}
|
|
227
|
+
assertNonEmptyString(step.type, `${context}.type`);
|
|
228
|
+
const stepType = step.type;
|
|
229
229
|
|
|
230
|
-
// Ensure no additional properties beyond {
|
|
231
|
-
const commonAllowed = new Set(['
|
|
230
|
+
// Ensure no additional properties beyond { step_id, type, and those allowed per type }
|
|
231
|
+
const commonAllowed = new Set(['step_id', 'type']);
|
|
232
232
|
let allowedProps = new Set(commonAllowed);
|
|
233
233
|
|
|
234
234
|
switch (stepType) {
|
|
@@ -403,14 +403,11 @@ function validateStep(step, context, mode = 'default') {
|
|
|
403
403
|
allowedProps = new Set([...commonAllowed, 'name', 'expression', 'cases', 'default', 'depends_on']);
|
|
404
404
|
break;
|
|
405
405
|
}
|
|
406
|
-
case '
|
|
406
|
+
case 'steps': {
|
|
407
407
|
// Required: steps (input now optional)
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
throw new CookbookValidationError(`${context} of type 'sub_workflow' is missing required property '${prop}'.`);
|
|
412
|
-
}
|
|
413
|
-
});
|
|
408
|
+
if (!('steps' in step)) {
|
|
409
|
+
throw new CookbookValidationError(`${context} of type 'steps' is missing required property 'steps'.`);
|
|
410
|
+
}
|
|
414
411
|
// Validate input map if present
|
|
415
412
|
if ('input' in step) {
|
|
416
413
|
assertNonEmptyStringMap(step.input, `${context}.input`, true);
|
|
@@ -428,8 +425,13 @@ function validateStep(step, context, mode = 'default') {
|
|
|
428
425
|
if ('name' in step) {
|
|
429
426
|
assertNonEmptyString(step.name, `${context}.name`);
|
|
430
427
|
}
|
|
428
|
+
if ('max_depth' in step) {
|
|
429
|
+
if (!Number.isInteger(step.max_depth) || step.max_depth < 1) {
|
|
430
|
+
throw new CookbookValidationError(`${context}.max_depth must be an integer ≥ 1.`);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
431
433
|
|
|
432
|
-
allowedProps = new Set([...commonAllowed, 'name', 'input', 'steps']);
|
|
434
|
+
allowedProps = new Set([...commonAllowed, 'name', 'input', 'steps', 'max_depth']);
|
|
433
435
|
break;
|
|
434
436
|
}
|
|
435
437
|
case 'wait': {
|
|
@@ -15,8 +15,8 @@ function collectAllStepIds(steps, ids = new Set()) {
|
|
|
15
15
|
if (!Array.isArray(steps)) return ids;
|
|
16
16
|
|
|
17
17
|
steps.forEach(step => {
|
|
18
|
-
if (step && typeof step === 'object' && step.
|
|
19
|
-
ids.add(step.
|
|
18
|
+
if (step && typeof step === 'object' && step.step_id) {
|
|
19
|
+
ids.add(step.step_id);
|
|
20
20
|
|
|
21
21
|
// Recursively collect from nested structures
|
|
22
22
|
switch (step.type) {
|
|
@@ -25,7 +25,9 @@ function collectAllStepIds(steps, ids = new Set()) {
|
|
|
25
25
|
break;
|
|
26
26
|
|
|
27
27
|
case 'fork_join':
|
|
28
|
-
if (step.branches
|
|
28
|
+
if (step.branches && typeof step.branches === 'object') {
|
|
29
|
+
Object.values(step.branches).forEach(branchStep => collectAllStepIds([branchStep], ids));
|
|
30
|
+
}
|
|
29
31
|
break;
|
|
30
32
|
|
|
31
33
|
case 'switch':
|
|
@@ -37,7 +39,7 @@ function collectAllStepIds(steps, ids = new Set()) {
|
|
|
37
39
|
if (step.default) collectAllStepIds([step.default], ids);
|
|
38
40
|
break;
|
|
39
41
|
|
|
40
|
-
case '
|
|
42
|
+
case 'steps':
|
|
41
43
|
if (step.steps) collectAllStepIds(step.steps, ids);
|
|
42
44
|
break;
|
|
43
45
|
}
|
|
@@ -83,7 +85,11 @@ function validateDependsOnReferences(cookbook) {
|
|
|
83
85
|
break;
|
|
84
86
|
|
|
85
87
|
case 'fork_join':
|
|
86
|
-
if (step.branches
|
|
88
|
+
if (step.branches && typeof step.branches === 'object') {
|
|
89
|
+
Object.entries(step.branches).forEach(([branchKey, branchStep]) => {
|
|
90
|
+
checkDependsOn([branchStep], `${stepPath}.branches['${branchKey}']`);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
87
93
|
break;
|
|
88
94
|
|
|
89
95
|
case 'switch':
|
|
@@ -95,7 +101,7 @@ function validateDependsOnReferences(cookbook) {
|
|
|
95
101
|
if (step.default) checkDependsOn([step.default], `${stepPath}.default`);
|
|
96
102
|
break;
|
|
97
103
|
|
|
98
|
-
case '
|
|
104
|
+
case 'steps':
|
|
99
105
|
if (step.steps) checkDependsOn(step.steps, `${stepPath}.steps`);
|
|
100
106
|
break;
|
|
101
107
|
}
|
|
@@ -187,8 +193,10 @@ function validateVariableReferences(cookbook) {
|
|
|
187
193
|
break;
|
|
188
194
|
|
|
189
195
|
case 'fork_join':
|
|
190
|
-
if (step.branches) {
|
|
191
|
-
step.branches.forEach((
|
|
196
|
+
if (step.branches && typeof step.branches === 'object') {
|
|
197
|
+
Object.entries(step.branches).forEach(([branchKey, s]) => {
|
|
198
|
+
checkStep(s, `${path}.branches['${branchKey}']`);
|
|
199
|
+
});
|
|
192
200
|
}
|
|
193
201
|
break;
|
|
194
202
|
|
|
@@ -203,7 +211,7 @@ function validateVariableReferences(cookbook) {
|
|
|
203
211
|
}
|
|
204
212
|
break;
|
|
205
213
|
|
|
206
|
-
case '
|
|
214
|
+
case 'steps':
|
|
207
215
|
if (step.steps) {
|
|
208
216
|
step.steps.forEach((s, i) => checkStep(s, `${path}.steps[${i}]`));
|
|
209
217
|
}
|
|
@@ -268,8 +276,10 @@ function validateErrorHandlerReferences(cookbook) {
|
|
|
268
276
|
break;
|
|
269
277
|
|
|
270
278
|
case 'fork_join':
|
|
271
|
-
if (step.branches) {
|
|
272
|
-
step.branches.forEach((
|
|
279
|
+
if (step.branches && typeof step.branches === 'object') {
|
|
280
|
+
Object.entries(step.branches).forEach(([branchKey, s]) => {
|
|
281
|
+
checkStep(s, `${path}.branches['${branchKey}']`);
|
|
282
|
+
});
|
|
273
283
|
}
|
|
274
284
|
break;
|
|
275
285
|
|
|
@@ -284,7 +294,7 @@ function validateErrorHandlerReferences(cookbook) {
|
|
|
284
294
|
}
|
|
285
295
|
break;
|
|
286
296
|
|
|
287
|
-
case '
|
|
297
|
+
case 'steps':
|
|
288
298
|
if (step.steps) {
|
|
289
299
|
step.steps.forEach((s, i) => checkStep(s, `${path}.steps[${i}]`));
|
|
290
300
|
}
|