fyflow-orchestration 0.10.0 → 0.12.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.
@@ -9,23 +9,28 @@ import {
9
9
  ConfigRefused,
10
10
  DECLARED_POOL_KEYS,
11
11
  DOCUMENT_KEYS,
12
+ DOC_KEYS_DECLARED,
12
13
  DerivationRefused,
13
14
  Driver,
14
15
  DuplicateTaskId,
15
16
  EDGE_KEYS_DECLARED,
17
+ EDGE_KEYS_REQUIRED,
16
18
  GATE_TYPE_KEYS,
17
19
  GateTypeRefused,
18
20
  GroupConfigRefused,
19
21
  GroupTypeMissing,
22
+ ID_RE_COPY,
20
23
  INSTANCE_KEYS,
21
24
  InvalidDefinition,
22
25
  InvalidRunId,
23
26
  InvalidTaskId,
24
27
  KEY_PATH_RE,
28
+ KINDS_COPY,
25
29
  MESSAGE_OPTION_KEYS,
26
30
  MissingJoinPolicy,
27
31
  MultipleRuns,
28
32
  NODE_KEYS_DECLARED,
33
+ NODE_KEYS_REQUIRED,
29
34
  NODE_TYPE_DECLARATION_KEYS,
30
35
  NameCollision,
31
36
  NameMissing,
@@ -42,6 +47,7 @@ import {
42
47
  PREDICATE_ROOTS,
43
48
  PoolTypeMissing,
44
49
  RETRY_POLICY_KEYS,
50
+ ROUTABLE_STATES,
45
51
  RegistryError,
46
52
  ResumeRefused,
47
53
  RunControlRefused,
@@ -145,7 +151,537 @@ import {
145
151
  visibilityKey,
146
152
  witness,
147
153
  workersOf
148
- } from "./chunks/chunk-NA6QQAZ2.js";
154
+ } from "./chunks/chunk-4JFQX7DL.js";
155
+
156
+ // src/definition/definition-schema.ts
157
+ var DRAFT_2020_12 = "https://json-schema.org/draft/2020-12/schema";
158
+ var ref = (name) => ({ $ref: `#/$defs/${name}` });
159
+ function body(re, name) {
160
+ const s = re.source;
161
+ if (!s.startsWith("^") || !s.endsWith("$") || re.flags !== "") {
162
+ throw new Error(`definitionSchema: ${name} is no longer an anchored, flagless expression (${String(re)}) \u2014 its body cannot be reused as a pattern`);
163
+ }
164
+ return s.slice(1, -1);
165
+ }
166
+ function keyPathSegment(id) {
167
+ const seg = `${id}(?:\\[(?:0|[1-9][0-9]{0,8})\\])*`;
168
+ const whole = body(KEY_PATH_RE, "KEY_PATH_RE");
169
+ if (whole !== `${seg}(?:\\.${seg})*`) {
170
+ throw new Error(`definitionSchema: KEY_PATH_RE (${String(KEY_PATH_RE)}) is no longer one segment pattern repeated \u2014 a comparison's path pattern cannot be derived from it`);
171
+ }
172
+ return seg;
173
+ }
174
+ function described(table, key, where) {
175
+ const d = Object.prototype.hasOwnProperty.call(table, key) ? table[key] : void 0;
176
+ if (d === void 0) throw new Error(`definitionSchema: ${where} declares the key "${key}" and no description is written for it`);
177
+ return d;
178
+ }
179
+ var DOC_DESCRIPTIONS = {
180
+ flowId: "The flow's id: 1-64 characters from [A-Za-z0-9_-] (FD-V16), the charset of spec/task-id.md \u2014 it is a segment of every task id this flow mints.",
181
+ version: "The definition's version, an integer >= 1 (FD-V21), managed by the author. The driver does not interpret it.",
182
+ entry: "The id of the node a run starts at. It must name a node of this document that is neither a join nor an end (FD-V3 \u2014 validateDefinition's, not this schema's).",
183
+ nodes: "One object per node, order irrelevant. Node ids are unique across the document (FD-V1 \u2014 validateDefinition's)."
184
+ };
185
+ var NODE_DESCRIPTIONS = {
186
+ id: "The node's id: 1-64 characters from [A-Za-z0-9_-] (FD-V16), unique in the document (FD-V1). It is a segment of the task id the node mints.",
187
+ kind: 'Which of the seven node kinds this is (FD-V24). There is no "decision" kind: a decision is an edge\'s `when`, and a decision with memory is a `gate`.',
188
+ edges: "Where the run goes from here, in order. The first eligible edge whose `when` holds is taken; an edge with no `on` routes `done` only.",
189
+ needs: "What this node receives instead of the previous node's output: an object whose keys name what the node receives and whose values are references (spec/flow-definition.md section 3, FD-V26). Where each reference points is validateDefinition's (FD-V27-FD-V29, FD-V49).",
190
+ settings: "Settings, a plain object.",
191
+ type: "A registered type's name."
192
+ };
193
+ var KIND_DESCRIPTIONS = {
194
+ task: {
195
+ kind: "A `task`: the one kind that is dispatched \u2014 one scheduler task, run by the worker of the node type it names.",
196
+ type: "The registered NODE type that runs this task (required since v0.19). Which names are registered is the registry's: FD-V32, validateDefinition's. The built-in \"Transform\" runs nothing but the node's own `transform` (FD-V46).",
197
+ pool: "A named pool INSTANCE of this node's type, from the registry's `pools` (FD-V38). Absent: the type's default instance, keyed by the type's name.",
198
+ settings: "Settings handed to the worker in its envelope, merged OVER the type's defaults. A plain object; what its worker's settingsSchema says of them is FD-V33's, validateDefinition's.",
199
+ transform: "An expression the node type's worker wrapper evaluates over this node's input before the worker sees it: exactly { lang, expr } (FD-V37). The language must be registered (FD-V35) and accept the expression (FD-V36) \u2014 validateDefinition's.",
200
+ optional: "true: the FLOW handles this task's failure \u2014 it ends `failed` and may route a `failed` edge. Absent or false: a failure out of retries waits for a person (`user_action`).",
201
+ retryPolicy: "How the scheduler retries a failed attempt, inside one task id: exactly { maxRetries, backoffMs } (FD-V30).",
202
+ workerGroups: "The resource groups the scheduler holds this task to: a non-empty list of distinct group ids (FD-V31). That each id is one the registry's `groups` declares is validateDefinition's.",
203
+ needs: NODE_DESCRIPTIONS.needs
204
+ },
205
+ fanout: {
206
+ kind: "A `fanout`: opens a region of N branches declared HERE, closed by the one `join` naming it. Dispatches nothing.",
207
+ branches: "The region's branch entries: at least two distinct node ids (FD-V9), each a string (FD-V21), each a task or an ask, none of them its own join (FD-V9, FD-V12 \u2014 validateDefinition's)."
208
+ },
209
+ map: {
210
+ kind: "A `map`: opens a region of ONE branch, opened once per item of an array at run time, closed by the one `join` naming it. Dispatches nothing.",
211
+ over: "The key, in the value that reaches the map, holding the array of items: 1-64 characters from [A-Za-z0-9_-] (FD-V25).",
212
+ branch: "The id of the one node each item's instance starts at \u2014 a task or an ask (FD-V25, validateDefinition's).",
213
+ needs: "What the map receives before it reads `over` (spec/flow-definition.md section 3, FD-V26)."
214
+ },
215
+ join: {
216
+ kind: "A `join`: closes the region its `of` opened, by the join policy the driver is given. Dispatches nothing.",
217
+ of: "The id of the `fanout` or `map` whose region this closes: a string (FD-V21); that it names an existing node (FD-V2) and that the node is one (FD-V6) are validateDefinition's.",
218
+ edges: "Exactly one edge, carrying neither `when` nor `on` (FD-V7): a join has no outcome for either to read."
219
+ },
220
+ end: {
221
+ kind: "An `end`: terminates one path. No edges.",
222
+ outcome: "A non-empty, author-defined string (FD-V8). The driver does not interpret it."
223
+ },
224
+ gate: {
225
+ kind: "A `gate`: routes the branch that reaches it by a registered GATE type's decision, and hands on exactly what reached it \u2014 a decision with memory. Dispatches nothing.",
226
+ type: `The registered GATE type that decides: a built-in (${Object.keys(BUILT_IN_GATE_TYPES).sort().map((t) => `"${t}"`).join(", ")}) or one the registry's \`gateTypes\` adds (FD-V39, validateDefinition's).`,
227
+ settings: "Settings the gate type reads: a plain object (FD-V40). A `key`, where present, is a key path the library resolves over what reached the gate. What the type's settingsSchema says of the rest is validateDefinition's.",
228
+ edges: "One edge per route of the gate's type, each carrying `to` and `route` \u2014 an edge with no `route` carries the type's DEFAULT route, which may also be named (FD-V41). That every route is wired exactly once is validateDefinition's."
229
+ },
230
+ ask: {
231
+ kind: `An \`ask\`: waits for a PERSON, whose answer is its output and is routed by its edges as a task's settlement is. Never dispatched. A task's ${ASK_REFUSED_KEYS.map((k) => `\`${k}\``).join(", ")} are refused on an ask (FD-V47).`,
232
+ settings: "Opaque settings a UI reads: a plain object (FD-V21). The library never reads them.",
233
+ answerSchema: "The schema a `done` answer is held to, in the library's built-in schema subset (FD-V48, ADR 0053). That an `examples` entry conforms is validateDefinition's (ADR 0056).",
234
+ needs: "What the person is shown as the ask's input (spec/flow-definition.md section 3, FD-V26)."
235
+ }
236
+ };
237
+ var EDGE_DESCRIPTIONS = {
238
+ to: "The id of the node this edge leads to (FD-V2, validateDefinition's).",
239
+ on: 'The terminal states this edge is eligible for: a non-empty list of distinct "done" / "failed" (FD-V18). Absent: ["done"]. "user_action" suspends rather than routes.',
240
+ when: "The decision: a registered predicate NAME { predicate }, a declared COMPARISON { path, op, value, default?, length? }, or a flat GROUP { all } or { any } of comparisons (FD-V42-FD-V45). Absent: the edge is the default for its state.",
241
+ needs: "What this edge's predicate sees beyond its node's outcome \u2014 `needs`' grammar minus \".\" (FD-V26).",
242
+ route: "On a gate's edge, which of the gate type's routes it carries; absent: the type's DEFAULT route (FD-V41). Refused on every other kind's edge."
243
+ };
244
+ var RETRY_DESCRIPTIONS = {
245
+ maxRetries: "How many times a failed attempt is re-dispatched: an integer >= 0.",
246
+ backoffMs: "Milliseconds between a failed attempt and its retry: a finite number >= 0."
247
+ };
248
+ var TRANSFORM_DESCRIPTIONS = {
249
+ lang: "A registered language's name (FD-V35, validateDefinition's).",
250
+ expr: "Text that language compiles (FD-V36, validateDefinition's): `$` is the value this node receives, `$settings` its effective settings."
251
+ };
252
+ var PREDICATE_DESCRIPTIONS = {
253
+ predicate: "A registered predicate's name (FD-V4, validateDefinition's).",
254
+ path: `A key path over the decision's context, starting at ${PREDICATE_ROOTS.map((r) => `"${r}"`).join(" or ")} and naming a key beyond it; a key past the root may be followed by array indexes [n], the root never (FD-V43).`,
255
+ op: `One of ${PREDICATE_OPS.join(", ")} (FD-V44). The ordering operators compare numbers and "startsWith" a string \u2014 a comparison between kinds is no match, never a coercion.`,
256
+ value: "The scalar compared against: null, a boolean, a number or a string (FD-V44).",
257
+ default: "What is compared where the path does not resolve or resolves to null: a scalar, or \u2014 with `length` \u2014 an array or a string (FD-V44).",
258
+ length: "Exactly true: compare the LENGTH of the array or string, against a number (FD-V44).",
259
+ all: "A flat group: a non-empty list of comparisons that must all hold (FD-V45).",
260
+ any: "A flat group: a non-empty list of comparisons of which one must hold (FD-V45)."
261
+ };
262
+ var SUBSET_DESCRIPTIONS = {
263
+ type: "One of the seven JSON types, or a non-empty list of distinct ones.",
264
+ required: "Property names an object must carry.",
265
+ properties: "A schema, in the subset, per property name.",
266
+ additionalProperties: "true or false only \u2014 never a schema.",
267
+ enum: "A non-empty list of the values allowed.",
268
+ const: "The one value allowed.",
269
+ minimum: "The smallest number allowed.",
270
+ maximum: "The largest number allowed.",
271
+ minLength: "The shortest string allowed: an integer >= 0.",
272
+ maxLength: "The longest string allowed: an integer >= 0.",
273
+ items: "ONE schema, in the subset, for every item \u2014 never a tuple.",
274
+ minItems: "The fewest items allowed: an integer >= 0.",
275
+ examples: "A non-empty list of values the schema accepts (that each conforms is ADR 0056's check, validateDefinition's).",
276
+ title: "An annotation: a string, constraining nothing (ADR 0098).",
277
+ description: "An annotation: a string, constraining nothing (ADR 0098).",
278
+ readOnly: "An annotation: true or false, constraining nothing (ADR 0098).",
279
+ writeOnly: "An annotation: true or false, constraining nothing (ADR 0098)."
280
+ };
281
+ var NUMERIC_OPS = ["lt", "lte", "gt", "gte"];
282
+ var STRING_OPS = ["startsWith"];
283
+ var ERROR_PART = "!error";
284
+ function nodeKeySchema(kind, key) {
285
+ switch (key) {
286
+ case "id":
287
+ return ref("id");
288
+ case "kind":
289
+ return { const: kind };
290
+ case "type":
291
+ case "pool":
292
+ return { type: "string" };
293
+ case "settings":
294
+ return kind === "gate" ? { type: "object", properties: { key: { type: "string", pattern: KEY_PATH_RE.source, description: "A key path: dot-separated object keys, each [A-Za-z0-9_-]{1,64} and optionally followed by array indexes [n] (n is 0 or has no leading zero, at most 999999999; never leading the path) \u2014 files[0].series; no quoted key, no wildcard, no expression (FD-V40)." } } } : { type: "object" };
295
+ case "transform":
296
+ return ref("transform");
297
+ case "optional":
298
+ return { type: "boolean" };
299
+ case "needs":
300
+ return ref("needs");
301
+ case "retryPolicy":
302
+ return ref("retryPolicy");
303
+ case "workerGroups":
304
+ return { type: "array", minItems: 1, uniqueItems: true, items: { type: "string" } };
305
+ // **`branches`' entries and `of` are ids, unconditionally** — v0.52, W215, Q774 resolved (P44 (d1)): `FD-V21` refuses
306
+ // a non-string `of` and a non-string branch entry, so the schema may too (ADR 0132 rule 2). Until then both admitted
307
+ // a non-string here, because the validator accepted one (W210's conformity reader, B1). A string outside the charset
308
+ // names no node — every node id is `FD-V16`'s — so `FD-V2` refuses it.
309
+ case "branches":
310
+ return { type: "array", minItems: 2, uniqueItems: true, items: ref("id") };
311
+ case "of":
312
+ return ref("id");
313
+ case "over":
314
+ case "branch":
315
+ return ref("id");
316
+ case "outcome":
317
+ return { type: "string", minLength: 1 };
318
+ case "answerSchema":
319
+ return ref("subsetSchema");
320
+ case "edges": {
321
+ if (kind === "join") return { type: "array", minItems: 1, maxItems: 1, items: ref("joinEdge") };
322
+ if (kind === "gate") return { type: "array", minItems: 1, items: ref("gateEdge") };
323
+ return { type: "array", minItems: 1, items: ref("edge") };
324
+ }
325
+ }
326
+ throw new Error(`definitionSchema: the "${kind}" kind declares the key "${key}" and no schema is written for it`);
327
+ }
328
+ function edgeKeySchema(key) {
329
+ switch (key) {
330
+ case "to":
331
+ return ref("id");
332
+ case "on":
333
+ return { type: "array", minItems: 1, uniqueItems: true, items: { enum: [...ROUTABLE_STATES] } };
334
+ case "when":
335
+ return ref("when");
336
+ case "needs":
337
+ return ref("edgeNeeds");
338
+ case "route":
339
+ return { type: "string" };
340
+ }
341
+ throw new Error(`definitionSchema: an edge declares the key "${key}" and no schema is written for it`);
342
+ }
343
+ var EDGE_KEYS_BY_SITE = {
344
+ edge: (k) => k !== "route",
345
+ joinEdge: (k) => k !== "route" && k !== "on" && k !== "when",
346
+ gateEdge: (k) => k === "to" || k === "route"
347
+ };
348
+ var SITE_DESCRIPTIONS = {
349
+ edge: "An edge of a task or an ask: { to, on?, when?, needs? }.",
350
+ joinEdge: "A join's one edge: { to, needs? } \u2014 neither `on` nor `when` (FD-V7).",
351
+ gateEdge: "A gate's edge: { to, route? } and nothing else (FD-V41)."
352
+ };
353
+ function edgeSchema(site) {
354
+ const keys = EDGE_KEYS_DECLARED.filter(EDGE_KEYS_BY_SITE[site]);
355
+ return {
356
+ description: SITE_DESCRIPTIONS[site],
357
+ type: "object",
358
+ required: EDGE_KEYS_REQUIRED.filter((k) => keys.includes(k)),
359
+ properties: Object.fromEntries(keys.map((k) => [k, { ...edgeKeySchema(k), description: described(EDGE_DESCRIPTIONS, k, "an edge") }])),
360
+ additionalProperties: false
361
+ };
362
+ }
363
+ function kindSchema(kind) {
364
+ const keys = NODE_KEYS_DECLARED[kind];
365
+ const required = NODE_KEYS_REQUIRED[kind];
366
+ if (keys === void 0 || required === void 0) throw new Error(`definitionSchema: the validator declares no keys for the kind "${kind}"`);
367
+ const own2 = KIND_DESCRIPTIONS[kind] ?? {};
368
+ return {
369
+ description: described(own2, "kind", `the "${kind}" kind`),
370
+ type: "object",
371
+ required: [...required],
372
+ properties: Object.fromEntries(keys.map((k) => [k, {
373
+ ...nodeKeySchema(kind, k),
374
+ description: Object.prototype.hasOwnProperty.call(own2, k) ? own2[k] : described(NODE_DESCRIPTIONS, k, `the "${kind}" kind`)
375
+ }])),
376
+ additionalProperties: false
377
+ };
378
+ }
379
+ function subsetKeywordSchema(keyword) {
380
+ const nonNegInt = { type: "integer", minimum: 0 };
381
+ switch (keyword) {
382
+ case "type":
383
+ return { anyOf: [{ enum: [...SCHEMA_TYPES] }, { type: "array", minItems: 1, uniqueItems: true, items: { enum: [...SCHEMA_TYPES] } }] };
384
+ case "required":
385
+ return { type: "array", uniqueItems: true, items: { type: "string" } };
386
+ case "properties":
387
+ return { type: "object", additionalProperties: ref("subsetSchema") };
388
+ case "additionalProperties":
389
+ return { type: "boolean" };
390
+ case "enum":
391
+ return { type: "array", minItems: 1 };
392
+ case "const":
393
+ return {};
394
+ case "minimum":
395
+ case "maximum":
396
+ return { type: "number" };
397
+ case "minLength":
398
+ case "maxLength":
399
+ case "minItems":
400
+ return nonNegInt;
401
+ case "items":
402
+ return ref("subsetSchema");
403
+ case "examples":
404
+ return { type: "array", minItems: 1 };
405
+ case "title":
406
+ case "description":
407
+ return { type: "string" };
408
+ case "readOnly":
409
+ case "writeOnly":
410
+ return { type: "boolean" };
411
+ }
412
+ throw new Error(`definitionSchema: the schema subset declares the keyword "${keyword}" and no schema is written for it`);
413
+ }
414
+ function needsSchema(withSelf, description) {
415
+ return {
416
+ description,
417
+ type: "object",
418
+ minProperties: 1,
419
+ propertyNames: ref("id"),
420
+ additionalProperties: ref(withSelf ? "reference" : "edgeReference")
421
+ };
422
+ }
423
+ function referenceSchema(withSelf, id) {
424
+ const aimed = `${id}(?:${ERROR_PART})?\\??`;
425
+ const tokens = [...withSelf ? ["\\."] : [], "\\$payload", "\\$item", aimed];
426
+ return {
427
+ description: (withSelf ? '"." (what this node would have received), ' : "") + `"$payload" (what the run entered with), "$item" (the item of the map instance containing this node), a node id (that node's output), or a node id followed by "!error" (that node's error, null where it succeeded) \u2014 each node form with an optional "?" last (the node may not have run: absent, not refused), and as a one-element array (every output of that node visible on this node's path, in branch-path order; where each form is legal is FD-V28's) (FD-V26).`,
428
+ anyOf: [
429
+ { type: "string", pattern: `^(?:${tokens.join("|")})$` },
430
+ { type: "array", minItems: 1, maxItems: 1, items: { type: "string", pattern: `^${aimed}$` } }
431
+ ]
432
+ };
433
+ }
434
+ function leafSchema(id) {
435
+ const path = `^(?:${PREDICATE_ROOTS.join("|")})(?:\\.${keyPathSegment(id)})+$`;
436
+ const scalar = () => ({ type: ["null", "boolean", "number", "string"] });
437
+ const keySchemas = {
438
+ path: { type: "string", pattern: path },
439
+ op: { enum: [...PREDICATE_OPS] },
440
+ value: scalar(),
441
+ default: { anyOf: [scalar(), { type: "array", items: scalar() }] },
442
+ length: { const: true }
443
+ };
444
+ return {
445
+ description: "A declared comparison over the decision's context.",
446
+ type: "object",
447
+ required: [...PREDICATE_LEAF_REQUIRED],
448
+ properties: Object.fromEntries(PREDICATE_LEAF_KEYS.map((k) => {
449
+ const s = keySchemas[k];
450
+ if (s === void 0) throw new Error(`definitionSchema: a comparison declares the key "${k}" and no schema is written for it`);
451
+ return [k, { ...s, description: described(PREDICATE_DESCRIPTIONS, k, "a comparison") }];
452
+ })),
453
+ additionalProperties: false,
454
+ allOf: [
455
+ // `length: true` compares a length — a number — and a length has no prefix; its default stands in for the value.
456
+ {
457
+ if: { required: ["length"] },
458
+ then: { properties: { value: { type: "number" }, op: { not: { enum: [...STRING_OPS] } }, default: { type: ["array", "string"] } } }
459
+ },
460
+ // with no `length`: an ordering operator compares a number, and a default is a scalar.
461
+ {
462
+ if: { not: { required: ["length"] } },
463
+ then: { properties: { default: scalar() } }
464
+ },
465
+ {
466
+ if: { required: ["op"], properties: { op: { enum: [...NUMERIC_OPS] } } },
467
+ then: { anyOf: [{ required: ["length"] }, { properties: { value: { type: "number" } } }] }
468
+ },
469
+ {
470
+ if: { required: ["op"], properties: { op: { enum: [...STRING_OPS] } } },
471
+ then: { properties: { value: { type: "string" } } }
472
+ }
473
+ ]
474
+ };
475
+ }
476
+ function definitionSchema(registry) {
477
+ if (registry !== void 0) checkRegistry(registry);
478
+ const s = staticSchema();
479
+ if (registry !== void 0) specialise(s, registry);
480
+ return s;
481
+ }
482
+ function staticSchema() {
483
+ const id = body(ID_RE_COPY, "ID_RE");
484
+ body(KEY_PATH_RE, "KEY_PATH_RE");
485
+ const defs = {
486
+ id: { description: "A node id: 1-64 characters from [A-Za-z0-9_-] (FD-V16).", type: "string", pattern: `^${id}$` },
487
+ node: {
488
+ description: `A node: one of ${KINDS_COPY.join(", ")}, told apart by \`kind\`.`,
489
+ type: "object",
490
+ required: ["kind"],
491
+ properties: { kind: { enum: [...KINDS_COPY] } },
492
+ allOf: KINDS_COPY.map((k) => ({ if: { required: ["kind"], properties: { kind: { const: k } } }, then: ref(`${k}Node`) }))
493
+ },
494
+ ...Object.fromEntries(KINDS_COPY.map((k) => [`${k}Node`, kindSchema(k)])),
495
+ edge: edgeSchema("edge"),
496
+ joinEdge: edgeSchema("joinEdge"),
497
+ gateEdge: edgeSchema("gateEdge"),
498
+ needs: needsSchema(true, "A node's `needs`: a non-empty object, each key 1-64 characters from [A-Za-z0-9_-], each value a reference (FD-V26)."),
499
+ edgeNeeds: needsSchema(false, "An edge's `needs`: as a node's, minus \".\" \u2014 its predicate already has the outcome (FD-V26)."),
500
+ reference: referenceSchema(true, id),
501
+ edgeReference: referenceSchema(false, id),
502
+ retryPolicy: {
503
+ description: "Exactly the scheduler's shape (FD-V30).",
504
+ type: "object",
505
+ required: [...RETRY_POLICY_KEYS],
506
+ properties: Object.fromEntries(RETRY_POLICY_KEYS.map((k) => [k, {
507
+ ...k === "maxRetries" ? { type: "integer", minimum: 0 } : { type: "number", minimum: 0 },
508
+ description: described(RETRY_DESCRIPTIONS, k, "a retryPolicy")
509
+ }])),
510
+ additionalProperties: false
511
+ },
512
+ transform: {
513
+ description: "Exactly { lang, expr }, both strings (FD-V37).",
514
+ type: "object",
515
+ required: [...TRANSFORM_KEYS],
516
+ properties: Object.fromEntries(TRANSFORM_KEYS.map((k) => [k, { type: "string", description: described(TRANSFORM_DESCRIPTIONS, k, "a transform") }])),
517
+ additionalProperties: false
518
+ },
519
+ when: {
520
+ description: "One of three shapes, told apart by its keys (FD-V42): a registered name, a declared comparison, or a flat group.",
521
+ oneOf: [ref("predicateName"), ref("comparison"), ref("group")]
522
+ },
523
+ predicateName: {
524
+ description: "A registered predicate, by name (FD-V4's lookup is validateDefinition's).",
525
+ type: "object",
526
+ required: [...PREDICATE_NAME_KEYS],
527
+ properties: Object.fromEntries(PREDICATE_NAME_KEYS.map((k) => [k, { type: "string", description: described(PREDICATE_DESCRIPTIONS, k, "a predicate name") }])),
528
+ additionalProperties: false
529
+ },
530
+ comparison: leafSchema(id),
531
+ group: {
532
+ description: "A FLAT group \u2014 `all` or `any`, never both, never nested, its members comparisons (FD-V45).",
533
+ oneOf: PREDICATE_GROUP_KEYS.map((k) => ({
534
+ type: "object",
535
+ required: [k],
536
+ properties: { [k]: { type: "array", minItems: 1, items: ref("comparison"), description: described(PREDICATE_DESCRIPTIONS, k, "a group") } },
537
+ additionalProperties: false
538
+ }))
539
+ },
540
+ subsetSchema: {
541
+ description: "A schema in the library's built-in subset (ADR 0053): a plain object using only these keywords \u2014 any other is refused, never ignored (Q278).",
542
+ type: "object",
543
+ properties: Object.fromEntries(SCHEMA_KEYWORDS.map((k) => [k, { ...subsetKeywordSchema(k), description: described(SUBSET_DESCRIPTIONS, k, "the schema subset") }])),
544
+ additionalProperties: false
545
+ }
546
+ };
547
+ return {
548
+ $schema: DRAFT_2020_12,
549
+ title: "FlowDefinition",
550
+ description: "The STATIC SHAPE of a fyflow-orchestration flow definition (spec/flow-definition.md). It carries every rule a node, an edge or a value decides by its own shape, and none that needs another node or a registry: whether a named node exists and what kind it is, reachability, cycles, regions and joins, where a `needs` reference points, and every registered name \u2014 node types, pools, groups, languages, predicates, gate types and their routes and settings. For all of those, and for the definition as a whole, the AUTHORITY is `validateDefinition(flow, registry)` \u2014 and `runtime.check(flow)` on a runtime. A document this schema refuses, validateDefinition refuses; one it accepts may still be refused there.",
551
+ type: "object",
552
+ required: [...DOC_KEYS_DECLARED],
553
+ properties: Object.fromEntries(DOC_KEYS_DECLARED.map((k) => {
554
+ const s = k === "flowId" || k === "entry" ? ref("id") : k === "version" ? { type: "integer", minimum: 1 } : k === "nodes" ? { type: "array", minItems: 1, items: ref("node") } : (() => {
555
+ throw new Error(`definitionSchema: the document declares the key "${k}" and no schema is written for it`);
556
+ })();
557
+ return [k, { ...s, description: described(DOC_DESCRIPTIONS, k, "the document") }];
558
+ })),
559
+ additionalProperties: false,
560
+ $defs: defs
561
+ };
562
+ }
563
+ var REGISTRY_DESCRIPTION = "The SHAPE of a fyflow-orchestration flow definition SPECIALISED TO ONE REGISTRY (spec/flow-definition.md section 1). Beyond the static shape it carries what the registry decides of a node by itself: a task's `type`, one of the registry's node types (FD-V32), and per type its `pool` (the type's own name or one of its named instances, FD-V38), its `settings` held to the type's worker settingsSchema (FD-V33) and, for a type that only transforms, a required `transform` (FD-V46); `workerGroups` from the registry's groups (FD-V31); `transform.lang` from its languages (FD-V35); a predicate NAME from its predicates (FD-V4); a gate's `type`, one of its gate types (FD-V39), and per type its `settings` held to the type's settingsSchema (FD-V40) and, for a STATIC gate type, each edge's `route` one of its routes (FD-V41). It does NOT cleanly carry, and leaves to the authority: FD-V33 over the EFFECTIVE settings \u2014 a node's settings merged over its type's defaults \u2014 so a settingsSchema's `required` is WEAKENED by the keys the defaults supply, and a default's own value and any top-level keyword but properties, required and additionalProperties are unchecked; a `minLength` above 1 inside an embedded registry schema, relaxed to half rounded up, because the registry's schema subset counts a string's UTF-16 units and JSON Schema its code points; a DERIVED gate type's routes, which its routesFor computes from each gate's settings; FD-V41's every route wired EXACTLY ONCE; a gate type's settingsProblem and a language's compile (FD-V36), which are code; a node type's worker inputSchema, which is RUNTIME input and is carried as an ANNOTATION only, at `$defs.inputSchema` in its type's branch; and every rule that needs another node \u2014 existence, kinds, reachability, cycles, regions, joins, where a `needs` reference points. For all of those, and for the definition as a whole, the AUTHORITY is `validateDefinition(flow, registry)` against THIS registry \u2014 and `runtime.check(flow)` on a runtime. A document this schema refuses, validateDefinition refuses against this registry; one it accepts may still be refused there.";
564
+ function relaxed(schema) {
565
+ const walk = (s) => {
566
+ if (typeof s.minLength === "number" && s.minLength > 1) s.minLength = Math.ceil(s.minLength / 2);
567
+ if (isObject(s.properties)) {
568
+ for (const p of Object.values(s.properties)) if (isObject(p)) walk(p);
569
+ }
570
+ if (isObject(s.items)) walk(s.items);
571
+ return s;
572
+ };
573
+ return walk(structuredClone(schema));
574
+ }
575
+ var isObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
576
+ var own = (o, k) => Object.prototype.hasOwnProperty.call(o, k);
577
+ var quoted = (names) => [...names].map((n) => JSON.stringify(n)).join(", ");
578
+ function nodeTypeBranch(name, t, workers, pools) {
579
+ const instances = [...pools].filter(([, p]) => p.type === name).map(([k]) => k);
580
+ const workerName = t.worker;
581
+ const worker = typeof workerName === "string" ? workers.get(workerName) : void 0;
582
+ const properties = {
583
+ pool: {
584
+ description: `A pool of "${name}": its own name (the default instance)${instances.length ? ` or a named instance of it \u2014 ${quoted(instances)}` : ""} (FD-V38).`,
585
+ enum: [name, ...instances]
586
+ }
587
+ };
588
+ const required = [];
589
+ const settingsSchema = worker?.settingsSchema;
590
+ if (settingsSchema !== void 0) {
591
+ const defaults = t.settings ?? {};
592
+ const left = (settingsSchema.required ?? []).filter((k) => !own(defaults, k));
593
+ const settings = {
594
+ description: `Settings held to worker "${String(workerName)}"'s settingsSchema, merged OVER the type's defaults` + (Object.keys(defaults).length ? ` (${quoted(Object.keys(defaults))} supplied by them)` : "") + " (FD-V33).",
595
+ type: "object"
596
+ };
597
+ if (isObject(settingsSchema.properties)) {
598
+ settings.properties = Object.fromEntries(Object.entries(settingsSchema.properties).map(([k, p]) => [k, relaxed(p)]));
599
+ }
600
+ if (settingsSchema.additionalProperties === false) settings.additionalProperties = false;
601
+ if (left.length) {
602
+ settings.required = left;
603
+ required.push("settings");
604
+ }
605
+ properties.settings = settings;
606
+ }
607
+ if (onlyTransforms(t)) required.push("transform");
608
+ const branch = {
609
+ description: onlyTransforms(t) ? `A task of the built-in type "${name}": it names no worker and runs only its own \`transform\`, which it must declare (FD-V46).` : `A task of node type "${name}", run by worker "${String(workerName)}".` + (worker?.inputSchema !== void 0 ? " What that worker receives as input is described at $defs.inputSchema here \u2014 an ANNOTATION: it is runtime input, and nothing in a definition is held to it." : ""),
610
+ properties
611
+ };
612
+ if (required.length) branch.required = required;
613
+ if (worker?.inputSchema !== void 0) branch.$defs = { inputSchema: structuredClone(worker.inputSchema) };
614
+ return branch;
615
+ }
616
+ function gateTypeBranch(name, t) {
617
+ const properties = {};
618
+ const branch = { description: "", properties };
619
+ if (t.settingsSchema !== void 0) {
620
+ properties.settings = relaxed(t.settingsSchema);
621
+ if (findings(t.settingsSchema, {}).length > 0) branch.required = ["settings"];
622
+ }
623
+ if (typeof t.routesFor !== "function") {
624
+ const set = routesOf(t, {});
625
+ if ("problem" in set) throw new Error(`definitionSchema: gate type "${name}" resolves no route set: ${set.problem}`);
626
+ properties.edges = {
627
+ items: {
628
+ properties: {
629
+ route: {
630
+ description: `One of "${name}"'s routes \u2014 ${quoted(set.routes)}; an edge with no \`route\` carries its default, "${set.defaultRoute}" (FD-V41).`,
631
+ enum: [...set.routes]
632
+ }
633
+ }
634
+ }
635
+ };
636
+ branch.description = `A gate of the STATIC gate type "${name}": routes ${quoted(set.routes)}, the default "${set.defaultRoute}". That each is wired exactly once is validateDefinition's (FD-V41).`;
637
+ } else {
638
+ branch.description = `A gate of the DERIVED gate type "${name}": its routes are computed from each gate's settings, so which \`route\` names exist is validateDefinition's (FD-V41).`;
639
+ }
640
+ return branch;
641
+ }
642
+ var branchOn = (key, name, then) => ({ if: { required: [key], properties: { [key]: { const: name } } }, then });
643
+ function specialise(s, registry) {
644
+ const defs = s.$defs;
645
+ const nodeTypes = nodeTypesOf(registry.nodeTypes);
646
+ const workers = workersOf(registry.workers);
647
+ const pools = poolsOf(registry.pools);
648
+ const gateTypes = gateTypesOf(registry.gateTypes);
649
+ const groups = [...new Set(registry.groups ?? [])];
650
+ const predicates = [...new Set(registry.predicates)];
651
+ const languages = [...languagesOf(registry.languages).keys()];
652
+ const task = defs.taskNode;
653
+ task.properties.type = {
654
+ ...task.properties.type,
655
+ description: `The node type that runs this task: one of the registry's \u2014 ${quoted(nodeTypes.keys())} (FD-V32). What each type adds is its branch in this node's allOf.`,
656
+ enum: [...nodeTypes.keys()]
657
+ };
658
+ task.properties.workerGroups = {
659
+ ...task.properties.workerGroups,
660
+ description: `The resource groups the scheduler holds this task to: a non-empty list of distinct ids of the registry's groups \u2014 ${groups.length ? quoted(groups) : "it declares none"} (FD-V31).`,
661
+ items: { type: "string", enum: groups }
662
+ };
663
+ task.allOf = [...nodeTypes].map(([n, t]) => branchOn("type", n, nodeTypeBranch(n, t, workers, pools)));
664
+ const transform = defs.transform;
665
+ transform.properties.lang = {
666
+ ...transform.properties.lang,
667
+ description: `A language the registry declares \u2014 ${languages.length ? quoted(languages) : "it declares none"} (FD-V35). That it compiles \`expr\` is validateDefinition's (FD-V36).`,
668
+ enum: languages
669
+ };
670
+ const predicateName = defs.predicateName;
671
+ predicateName.properties.predicate = {
672
+ ...predicateName.properties.predicate,
673
+ description: `A predicate the registry declares \u2014 ${predicates.length ? quoted(predicates) : "it declares none"} (FD-V4).`,
674
+ enum: predicates
675
+ };
676
+ const gate = defs.gateNode;
677
+ gate.properties.type = {
678
+ ...gate.properties.type,
679
+ description: `The gate type that decides: one of ${quoted(gateTypes.keys())} \u2014 the built-ins and the registry's \`gateTypes\` (FD-V39). What each adds is its branch in this node's allOf.`,
680
+ enum: [...gateTypes.keys()]
681
+ };
682
+ gate.allOf = [...gateTypes].map(([g, t]) => branchOn("type", g, gateTypeBranch(g, t)));
683
+ s.description = REGISTRY_DESCRIPTION;
684
+ }
149
685
 
150
686
  // src/definition/assist.ts
151
687
  var TYPES = ["object", "array", "string", "number", "integer", "boolean", "null"];
@@ -260,11 +796,11 @@ function object(s, indent) {
260
796
  const req = new Set(s.required ?? []);
261
797
  const named = [...props.map(([k]) => k)];
262
798
  const all = [...props, ...[...req].filter((k) => !named.includes(k)).map((k) => [k, {}])];
263
- const body = all.map(([k, sub]) => `${indent} ${lit(k)}${req.has(k) ? "" : "?"}: ${tsTypeOf(sub, `${indent} `)};`).join("\n");
799
+ const body2 = all.map(([k, sub]) => `${indent} ${lit(k)}${req.has(k) ? "" : "?"}: ${tsTypeOf(sub, `${indent} `)};`).join("\n");
264
800
  const rest = closed ? "" : `
265
801
  ${indent} [k: string]: unknown;`;
266
802
  return `{
267
- ${body}${rest}
803
+ ${body2}${rest}
268
804
  ${indent}}`;
269
805
  }
270
806
  function signatureOf(name, worker) {
@@ -300,23 +836,28 @@ export {
300
836
  ConfigRefused,
301
837
  DECLARED_POOL_KEYS,
302
838
  DOCUMENT_KEYS,
839
+ DOC_KEYS_DECLARED,
303
840
  DerivationRefused,
304
841
  Driver,
305
842
  DuplicateTaskId,
306
843
  EDGE_KEYS_DECLARED,
844
+ EDGE_KEYS_REQUIRED,
307
845
  GATE_TYPE_KEYS,
308
846
  GateTypeRefused,
309
847
  GroupConfigRefused,
310
848
  GroupTypeMissing,
849
+ ID_RE_COPY as ID_RE,
311
850
  INSTANCE_KEYS,
312
851
  InvalidDefinition,
313
852
  InvalidRunId,
314
853
  InvalidTaskId,
315
854
  KEY_PATH_RE,
855
+ KINDS_COPY as KINDS,
316
856
  MESSAGE_OPTION_KEYS,
317
857
  MissingJoinPolicy,
318
858
  MultipleRuns,
319
859
  NODE_KEYS_DECLARED,
860
+ NODE_KEYS_REQUIRED,
320
861
  NODE_TYPE_DECLARATION_KEYS,
321
862
  NameCollision,
322
863
  NameMissing,
@@ -334,6 +875,7 @@ export {
334
875
  PREDICATE_ROOTS,
335
876
  PoolTypeMissing,
336
877
  RETRY_POLICY_KEYS,
878
+ ROUTABLE_STATES,
337
879
  RegistryError,
338
880
  ResumeRefused,
339
881
  RunControlRefused,
@@ -371,6 +913,7 @@ export {
371
913
  checkRegistry,
372
914
  checkWiring,
373
915
  compileProblem,
916
+ definitionSchema,
374
917
  derivePool,
375
918
  describeFindings,
376
919
  describePaths,
@@ -26,7 +26,7 @@ import {
26
26
  retryPolicyProblem,
27
27
  validateDefinition,
28
28
  workersOf
29
- } from "../chunks/chunk-NA6QQAZ2.js";
29
+ } from "../chunks/chunk-4JFQX7DL.js";
30
30
 
31
31
  // src/scheduler/bootstrap.ts
32
32
  import { WorkerManager } from "fyflow-scheduler";