fyflow-orchestration 0.10.0 → 0.11.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,530 @@ import {
145
151
  visibilityKey,
146
152
  witness,
147
153
  workersOf
148
- } from "./chunks/chunk-NA6QQAZ2.js";
154
+ } from "./chunks/chunk-5CVSWDCB.js";
155
+
156
+ // src/definition/definition-schema.ts
157
+ var DRAFT_2020_12 = "https://json-schema.org/draft/2020-12/schema";
158
+ var idOrNonString = () => ({ anyOf: [ref("id"), { not: { type: "string" } }] });
159
+ var ref = (name) => ({ $ref: `#/$defs/${name}` });
160
+ function body(re, name) {
161
+ const s = re.source;
162
+ if (!s.startsWith("^") || !s.endsWith("$") || re.flags !== "") {
163
+ throw new Error(`definitionSchema: ${name} is no longer an anchored, flagless expression (${String(re)}) \u2014 its body cannot be reused as a pattern`);
164
+ }
165
+ return s.slice(1, -1);
166
+ }
167
+ function described(table, key, where) {
168
+ const d = Object.prototype.hasOwnProperty.call(table, key) ? table[key] : void 0;
169
+ if (d === void 0) throw new Error(`definitionSchema: ${where} declares the key "${key}" and no description is written for it`);
170
+ return d;
171
+ }
172
+ var DOC_DESCRIPTIONS = {
173
+ 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.",
174
+ version: "The definition's version, an integer >= 1 (FD-V21), managed by the author. The driver does not interpret it.",
175
+ 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).",
176
+ nodes: "One object per node, order irrelevant. Node ids are unique across the document (FD-V1 \u2014 validateDefinition's)."
177
+ };
178
+ var NODE_DESCRIPTIONS = {
179
+ 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.",
180
+ 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`.',
181
+ 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.",
182
+ 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).",
183
+ settings: "Settings, a plain object.",
184
+ type: "A registered type's name."
185
+ };
186
+ var KIND_DESCRIPTIONS = {
187
+ task: {
188
+ kind: "A `task`: the one kind that is dispatched \u2014 one scheduler task, run by the worker of the node type it names.",
189
+ 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).",
190
+ 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.",
191
+ 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.",
192
+ 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.",
193
+ 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`).",
194
+ retryPolicy: "How the scheduler retries a failed attempt, inside one task id: exactly { maxRetries, backoffMs } (FD-V30).",
195
+ 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.",
196
+ needs: NODE_DESCRIPTIONS.needs
197
+ },
198
+ fanout: {
199
+ kind: "A `fanout`: opens a region of N branches declared HERE, closed by the one `join` naming it. Dispatches nothing.",
200
+ branches: "The region's branch entries: at least two distinct node ids (FD-V9), each a task or an ask, none of them its own join (FD-V9, FD-V12 \u2014 validateDefinition's)."
201
+ },
202
+ map: {
203
+ 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.",
204
+ 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).",
205
+ branch: "The id of the one node each item's instance starts at \u2014 a task or an ask (FD-V25, validateDefinition's).",
206
+ needs: "What the map receives before it reads `over` (spec/flow-definition.md section 3, FD-V26)."
207
+ },
208
+ join: {
209
+ kind: "A `join`: closes the region its `of` opened, by the join policy the driver is given. Dispatches nothing.",
210
+ of: "The id of the `fanout` or `map` whose region this closes (FD-V6, validateDefinition's).",
211
+ edges: "Exactly one edge, carrying neither `when` nor `on` (FD-V7): a join has no outcome for either to read."
212
+ },
213
+ end: {
214
+ kind: "An `end`: terminates one path. No edges.",
215
+ outcome: "A non-empty, author-defined string (FD-V8). The driver does not interpret it."
216
+ },
217
+ gate: {
218
+ 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.",
219
+ 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).`,
220
+ 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.",
221
+ 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."
222
+ },
223
+ ask: {
224
+ 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).`,
225
+ settings: "Opaque settings a UI reads: a plain object (FD-V21). The library never reads them.",
226
+ 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).",
227
+ needs: "What the person is shown as the ask's input (spec/flow-definition.md section 3, FD-V26)."
228
+ }
229
+ };
230
+ var EDGE_DESCRIPTIONS = {
231
+ to: "The id of the node this edge leads to (FD-V2, validateDefinition's).",
232
+ 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.',
233
+ 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.",
234
+ needs: "What this edge's predicate sees beyond its node's outcome \u2014 `needs`' grammar minus \".\" (FD-V26).",
235
+ 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."
236
+ };
237
+ var RETRY_DESCRIPTIONS = {
238
+ maxRetries: "How many times a failed attempt is re-dispatched: an integer >= 0.",
239
+ backoffMs: "Milliseconds between a failed attempt and its retry: a finite number >= 0."
240
+ };
241
+ var TRANSFORM_DESCRIPTIONS = {
242
+ lang: "A registered language's name (FD-V35, validateDefinition's).",
243
+ expr: "Text that language compiles (FD-V36, validateDefinition's): `$` is the value this node receives, `$settings` its effective settings."
244
+ };
245
+ var PREDICATE_DESCRIPTIONS = {
246
+ predicate: "A registered predicate's name (FD-V4, validateDefinition's).",
247
+ path: `A key path over the decision's context, starting at ${PREDICATE_ROOTS.map((r) => `"${r}"`).join(" or ")} and naming a key beyond it (FD-V43).`,
248
+ 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.`,
249
+ value: "The scalar compared against: null, a boolean, a number or a string (FD-V44).",
250
+ 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).",
251
+ length: "Exactly true: compare the LENGTH of the array or string, against a number (FD-V44).",
252
+ all: "A flat group: a non-empty list of comparisons that must all hold (FD-V45).",
253
+ any: "A flat group: a non-empty list of comparisons of which one must hold (FD-V45)."
254
+ };
255
+ var SUBSET_DESCRIPTIONS = {
256
+ type: "One of the seven JSON types, or a non-empty list of distinct ones.",
257
+ required: "Property names an object must carry.",
258
+ properties: "A schema, in the subset, per property name.",
259
+ additionalProperties: "true or false only \u2014 never a schema.",
260
+ enum: "A non-empty list of the values allowed.",
261
+ const: "The one value allowed.",
262
+ minimum: "The smallest number allowed.",
263
+ maximum: "The largest number allowed.",
264
+ minLength: "The shortest string allowed: an integer >= 0.",
265
+ maxLength: "The longest string allowed: an integer >= 0.",
266
+ items: "ONE schema, in the subset, for every item \u2014 never a tuple.",
267
+ minItems: "The fewest items allowed: an integer >= 0.",
268
+ examples: "A non-empty list of values the schema accepts (that each conforms is ADR 0056's check, validateDefinition's).",
269
+ title: "An annotation: a string, constraining nothing (ADR 0098).",
270
+ description: "An annotation: a string, constraining nothing (ADR 0098).",
271
+ readOnly: "An annotation: true or false, constraining nothing (ADR 0098).",
272
+ writeOnly: "An annotation: true or false, constraining nothing (ADR 0098)."
273
+ };
274
+ var NUMERIC_OPS = ["lt", "lte", "gt", "gte"];
275
+ var STRING_OPS = ["startsWith"];
276
+ var ERROR_PART = "!error";
277
+ function nodeKeySchema(kind, key) {
278
+ switch (key) {
279
+ case "id":
280
+ return ref("id");
281
+ case "kind":
282
+ return { const: kind };
283
+ case "type":
284
+ case "pool":
285
+ return { type: "string" };
286
+ case "settings":
287
+ 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} \u2014 no index, no wildcard, no expression (FD-V40)." } } } : { type: "object" };
288
+ case "transform":
289
+ return ref("transform");
290
+ case "optional":
291
+ return { type: "boolean" };
292
+ case "needs":
293
+ return ref("needs");
294
+ case "retryPolicy":
295
+ return ref("retryPolicy");
296
+ case "workerGroups":
297
+ return { type: "array", minItems: 1, uniqueItems: true, items: { type: "string" } };
298
+ // **`branches`' entries and `of` admit a NON-string** — W210's conformity reader, B1: the validator holds both only
299
+ // through the graph (`FD-V2`, `FD-V6` and `FD-V9` read a string and pass anything else over), so `of: null` on a join
300
+ // of no region and `["b", "c", 5]` are ACCEPTED there, and refusing them here would refuse what the validator accepts
301
+ // (ADR 0132 rule 2). A string is held to the id charset. That `FD-V21` should refuse the rest is Q774.
302
+ case "branches":
303
+ return { type: "array", minItems: 2, uniqueItems: true, items: idOrNonString() };
304
+ case "of":
305
+ return idOrNonString();
306
+ case "over":
307
+ case "branch":
308
+ return ref("id");
309
+ case "outcome":
310
+ return { type: "string", minLength: 1 };
311
+ case "answerSchema":
312
+ return ref("subsetSchema");
313
+ case "edges": {
314
+ if (kind === "join") return { type: "array", minItems: 1, maxItems: 1, items: ref("joinEdge") };
315
+ if (kind === "gate") return { type: "array", minItems: 1, items: ref("gateEdge") };
316
+ return { type: "array", minItems: 1, items: ref("edge") };
317
+ }
318
+ }
319
+ throw new Error(`definitionSchema: the "${kind}" kind declares the key "${key}" and no schema is written for it`);
320
+ }
321
+ function edgeKeySchema(key) {
322
+ switch (key) {
323
+ case "to":
324
+ return ref("id");
325
+ case "on":
326
+ return { type: "array", minItems: 1, uniqueItems: true, items: { enum: [...ROUTABLE_STATES] } };
327
+ case "when":
328
+ return ref("when");
329
+ case "needs":
330
+ return ref("edgeNeeds");
331
+ case "route":
332
+ return { type: "string" };
333
+ }
334
+ throw new Error(`definitionSchema: an edge declares the key "${key}" and no schema is written for it`);
335
+ }
336
+ var EDGE_KEYS_BY_SITE = {
337
+ edge: (k) => k !== "route",
338
+ joinEdge: (k) => k !== "route" && k !== "on" && k !== "when",
339
+ gateEdge: (k) => k === "to" || k === "route"
340
+ };
341
+ var SITE_DESCRIPTIONS = {
342
+ edge: "An edge of a task or an ask: { to, on?, when?, needs? }.",
343
+ joinEdge: "A join's one edge: { to, needs? } \u2014 neither `on` nor `when` (FD-V7).",
344
+ gateEdge: "A gate's edge: { to, route? } and nothing else (FD-V41)."
345
+ };
346
+ function edgeSchema(site) {
347
+ const keys = EDGE_KEYS_DECLARED.filter(EDGE_KEYS_BY_SITE[site]);
348
+ return {
349
+ description: SITE_DESCRIPTIONS[site],
350
+ type: "object",
351
+ required: EDGE_KEYS_REQUIRED.filter((k) => keys.includes(k)),
352
+ properties: Object.fromEntries(keys.map((k) => [k, { ...edgeKeySchema(k), description: described(EDGE_DESCRIPTIONS, k, "an edge") }])),
353
+ additionalProperties: false
354
+ };
355
+ }
356
+ function kindSchema(kind) {
357
+ const keys = NODE_KEYS_DECLARED[kind];
358
+ const required = NODE_KEYS_REQUIRED[kind];
359
+ if (keys === void 0 || required === void 0) throw new Error(`definitionSchema: the validator declares no keys for the kind "${kind}"`);
360
+ const own2 = KIND_DESCRIPTIONS[kind] ?? {};
361
+ return {
362
+ description: described(own2, "kind", `the "${kind}" kind`),
363
+ type: "object",
364
+ required: [...required],
365
+ properties: Object.fromEntries(keys.map((k) => [k, {
366
+ ...nodeKeySchema(kind, k),
367
+ description: Object.prototype.hasOwnProperty.call(own2, k) ? own2[k] : described(NODE_DESCRIPTIONS, k, `the "${kind}" kind`)
368
+ }])),
369
+ additionalProperties: false
370
+ };
371
+ }
372
+ function subsetKeywordSchema(keyword) {
373
+ const nonNegInt = { type: "integer", minimum: 0 };
374
+ switch (keyword) {
375
+ case "type":
376
+ return { anyOf: [{ enum: [...SCHEMA_TYPES] }, { type: "array", minItems: 1, uniqueItems: true, items: { enum: [...SCHEMA_TYPES] } }] };
377
+ case "required":
378
+ return { type: "array", uniqueItems: true, items: { type: "string" } };
379
+ case "properties":
380
+ return { type: "object", additionalProperties: ref("subsetSchema") };
381
+ case "additionalProperties":
382
+ return { type: "boolean" };
383
+ case "enum":
384
+ return { type: "array", minItems: 1 };
385
+ case "const":
386
+ return {};
387
+ case "minimum":
388
+ case "maximum":
389
+ return { type: "number" };
390
+ case "minLength":
391
+ case "maxLength":
392
+ case "minItems":
393
+ return nonNegInt;
394
+ case "items":
395
+ return ref("subsetSchema");
396
+ case "examples":
397
+ return { type: "array", minItems: 1 };
398
+ case "title":
399
+ case "description":
400
+ return { type: "string" };
401
+ case "readOnly":
402
+ case "writeOnly":
403
+ return { type: "boolean" };
404
+ }
405
+ throw new Error(`definitionSchema: the schema subset declares the keyword "${keyword}" and no schema is written for it`);
406
+ }
407
+ function needsSchema(withSelf, description) {
408
+ return {
409
+ description,
410
+ type: "object",
411
+ minProperties: 1,
412
+ propertyNames: ref("id"),
413
+ additionalProperties: ref(withSelf ? "reference" : "edgeReference")
414
+ };
415
+ }
416
+ function referenceSchema(withSelf, id) {
417
+ const aimed = `${id}(?:${ERROR_PART})?\\??`;
418
+ const tokens = [...withSelf ? ["\\."] : [], "\\$payload", "\\$item", aimed];
419
+ return {
420
+ 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).`,
421
+ anyOf: [
422
+ { type: "string", pattern: `^(?:${tokens.join("|")})$` },
423
+ { type: "array", minItems: 1, maxItems: 1, items: { type: "string", pattern: `^${aimed}$` } }
424
+ ]
425
+ };
426
+ }
427
+ function leafSchema(id) {
428
+ const path = `^(?:${PREDICATE_ROOTS.join("|")})(?:\\.${id})+$`;
429
+ const scalar = () => ({ type: ["null", "boolean", "number", "string"] });
430
+ const keySchemas = {
431
+ path: { type: "string", pattern: path },
432
+ op: { enum: [...PREDICATE_OPS] },
433
+ value: scalar(),
434
+ default: { anyOf: [scalar(), { type: "array", items: scalar() }] },
435
+ length: { const: true }
436
+ };
437
+ return {
438
+ description: "A declared comparison over the decision's context.",
439
+ type: "object",
440
+ required: [...PREDICATE_LEAF_REQUIRED],
441
+ properties: Object.fromEntries(PREDICATE_LEAF_KEYS.map((k) => {
442
+ const s = keySchemas[k];
443
+ if (s === void 0) throw new Error(`definitionSchema: a comparison declares the key "${k}" and no schema is written for it`);
444
+ return [k, { ...s, description: described(PREDICATE_DESCRIPTIONS, k, "a comparison") }];
445
+ })),
446
+ additionalProperties: false,
447
+ allOf: [
448
+ // `length: true` compares a length — a number — and a length has no prefix; its default stands in for the value.
449
+ {
450
+ if: { required: ["length"] },
451
+ then: { properties: { value: { type: "number" }, op: { not: { enum: [...STRING_OPS] } }, default: { type: ["array", "string"] } } }
452
+ },
453
+ // with no `length`: an ordering operator compares a number, and a default is a scalar.
454
+ {
455
+ if: { not: { required: ["length"] } },
456
+ then: { properties: { default: scalar() } }
457
+ },
458
+ {
459
+ if: { required: ["op"], properties: { op: { enum: [...NUMERIC_OPS] } } },
460
+ then: { anyOf: [{ required: ["length"] }, { properties: { value: { type: "number" } } }] }
461
+ },
462
+ {
463
+ if: { required: ["op"], properties: { op: { enum: [...STRING_OPS] } } },
464
+ then: { properties: { value: { type: "string" } } }
465
+ }
466
+ ]
467
+ };
468
+ }
469
+ function definitionSchema(registry) {
470
+ if (registry !== void 0) checkRegistry(registry);
471
+ const s = staticSchema();
472
+ if (registry !== void 0) specialise(s, registry);
473
+ return s;
474
+ }
475
+ function staticSchema() {
476
+ const id = body(ID_RE_COPY, "ID_RE");
477
+ body(KEY_PATH_RE, "KEY_PATH_RE");
478
+ const defs = {
479
+ id: { description: "A node id: 1-64 characters from [A-Za-z0-9_-] (FD-V16).", type: "string", pattern: `^${id}$` },
480
+ node: {
481
+ description: `A node: one of ${KINDS_COPY.join(", ")}, told apart by \`kind\`.`,
482
+ type: "object",
483
+ required: ["kind"],
484
+ properties: { kind: { enum: [...KINDS_COPY] } },
485
+ allOf: KINDS_COPY.map((k) => ({ if: { required: ["kind"], properties: { kind: { const: k } } }, then: ref(`${k}Node`) }))
486
+ },
487
+ ...Object.fromEntries(KINDS_COPY.map((k) => [`${k}Node`, kindSchema(k)])),
488
+ edge: edgeSchema("edge"),
489
+ joinEdge: edgeSchema("joinEdge"),
490
+ gateEdge: edgeSchema("gateEdge"),
491
+ 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)."),
492
+ edgeNeeds: needsSchema(false, "An edge's `needs`: as a node's, minus \".\" \u2014 its predicate already has the outcome (FD-V26)."),
493
+ reference: referenceSchema(true, id),
494
+ edgeReference: referenceSchema(false, id),
495
+ retryPolicy: {
496
+ description: "Exactly the scheduler's shape (FD-V30).",
497
+ type: "object",
498
+ required: [...RETRY_POLICY_KEYS],
499
+ properties: Object.fromEntries(RETRY_POLICY_KEYS.map((k) => [k, {
500
+ ...k === "maxRetries" ? { type: "integer", minimum: 0 } : { type: "number", minimum: 0 },
501
+ description: described(RETRY_DESCRIPTIONS, k, "a retryPolicy")
502
+ }])),
503
+ additionalProperties: false
504
+ },
505
+ transform: {
506
+ description: "Exactly { lang, expr }, both strings (FD-V37).",
507
+ type: "object",
508
+ required: [...TRANSFORM_KEYS],
509
+ properties: Object.fromEntries(TRANSFORM_KEYS.map((k) => [k, { type: "string", description: described(TRANSFORM_DESCRIPTIONS, k, "a transform") }])),
510
+ additionalProperties: false
511
+ },
512
+ when: {
513
+ description: "One of three shapes, told apart by its keys (FD-V42): a registered name, a declared comparison, or a flat group.",
514
+ oneOf: [ref("predicateName"), ref("comparison"), ref("group")]
515
+ },
516
+ predicateName: {
517
+ description: "A registered predicate, by name (FD-V4's lookup is validateDefinition's).",
518
+ type: "object",
519
+ required: [...PREDICATE_NAME_KEYS],
520
+ properties: Object.fromEntries(PREDICATE_NAME_KEYS.map((k) => [k, { type: "string", description: described(PREDICATE_DESCRIPTIONS, k, "a predicate name") }])),
521
+ additionalProperties: false
522
+ },
523
+ comparison: leafSchema(id),
524
+ group: {
525
+ description: "A FLAT group \u2014 `all` or `any`, never both, never nested, its members comparisons (FD-V45).",
526
+ oneOf: PREDICATE_GROUP_KEYS.map((k) => ({
527
+ type: "object",
528
+ required: [k],
529
+ properties: { [k]: { type: "array", minItems: 1, items: ref("comparison"), description: described(PREDICATE_DESCRIPTIONS, k, "a group") } },
530
+ additionalProperties: false
531
+ }))
532
+ },
533
+ subsetSchema: {
534
+ 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).",
535
+ type: "object",
536
+ properties: Object.fromEntries(SCHEMA_KEYWORDS.map((k) => [k, { ...subsetKeywordSchema(k), description: described(SUBSET_DESCRIPTIONS, k, "the schema subset") }])),
537
+ additionalProperties: false
538
+ }
539
+ };
540
+ return {
541
+ $schema: DRAFT_2020_12,
542
+ title: "FlowDefinition",
543
+ 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.",
544
+ type: "object",
545
+ required: [...DOC_KEYS_DECLARED],
546
+ properties: Object.fromEntries(DOC_KEYS_DECLARED.map((k) => {
547
+ const s = k === "flowId" || k === "entry" ? ref("id") : k === "version" ? { type: "integer", minimum: 1 } : k === "nodes" ? { type: "array", minItems: 1, items: ref("node") } : (() => {
548
+ throw new Error(`definitionSchema: the document declares the key "${k}" and no schema is written for it`);
549
+ })();
550
+ return [k, { ...s, description: described(DOC_DESCRIPTIONS, k, "the document") }];
551
+ })),
552
+ additionalProperties: false,
553
+ $defs: defs
554
+ };
555
+ }
556
+ 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.";
557
+ function relaxed(schema) {
558
+ const walk = (s) => {
559
+ if (typeof s.minLength === "number" && s.minLength > 1) s.minLength = Math.ceil(s.minLength / 2);
560
+ if (isObject(s.properties)) {
561
+ for (const p of Object.values(s.properties)) if (isObject(p)) walk(p);
562
+ }
563
+ if (isObject(s.items)) walk(s.items);
564
+ return s;
565
+ };
566
+ return walk(structuredClone(schema));
567
+ }
568
+ var isObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
569
+ var own = (o, k) => Object.prototype.hasOwnProperty.call(o, k);
570
+ var quoted = (names) => [...names].map((n) => JSON.stringify(n)).join(", ");
571
+ function nodeTypeBranch(name, t, workers, pools) {
572
+ const instances = [...pools].filter(([, p]) => p.type === name).map(([k]) => k);
573
+ const workerName = t.worker;
574
+ const worker = typeof workerName === "string" ? workers.get(workerName) : void 0;
575
+ const properties = {
576
+ pool: {
577
+ description: `A pool of "${name}": its own name (the default instance)${instances.length ? ` or a named instance of it \u2014 ${quoted(instances)}` : ""} (FD-V38).`,
578
+ enum: [name, ...instances]
579
+ }
580
+ };
581
+ const required = [];
582
+ const settingsSchema = worker?.settingsSchema;
583
+ if (settingsSchema !== void 0) {
584
+ const defaults = t.settings ?? {};
585
+ const left = (settingsSchema.required ?? []).filter((k) => !own(defaults, k));
586
+ const settings = {
587
+ 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).",
588
+ type: "object"
589
+ };
590
+ if (isObject(settingsSchema.properties)) {
591
+ settings.properties = Object.fromEntries(Object.entries(settingsSchema.properties).map(([k, p]) => [k, relaxed(p)]));
592
+ }
593
+ if (settingsSchema.additionalProperties === false) settings.additionalProperties = false;
594
+ if (left.length) {
595
+ settings.required = left;
596
+ required.push("settings");
597
+ }
598
+ properties.settings = settings;
599
+ }
600
+ if (onlyTransforms(t)) required.push("transform");
601
+ const branch = {
602
+ 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." : ""),
603
+ properties
604
+ };
605
+ if (required.length) branch.required = required;
606
+ if (worker?.inputSchema !== void 0) branch.$defs = { inputSchema: structuredClone(worker.inputSchema) };
607
+ return branch;
608
+ }
609
+ function gateTypeBranch(name, t) {
610
+ const properties = {};
611
+ const branch = { description: "", properties };
612
+ if (t.settingsSchema !== void 0) {
613
+ properties.settings = relaxed(t.settingsSchema);
614
+ if (findings(t.settingsSchema, {}).length > 0) branch.required = ["settings"];
615
+ }
616
+ if (typeof t.routesFor !== "function") {
617
+ const set = routesOf(t, {});
618
+ if ("problem" in set) throw new Error(`definitionSchema: gate type "${name}" resolves no route set: ${set.problem}`);
619
+ properties.edges = {
620
+ items: {
621
+ properties: {
622
+ route: {
623
+ description: `One of "${name}"'s routes \u2014 ${quoted(set.routes)}; an edge with no \`route\` carries its default, "${set.defaultRoute}" (FD-V41).`,
624
+ enum: [...set.routes]
625
+ }
626
+ }
627
+ }
628
+ };
629
+ 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).`;
630
+ } else {
631
+ 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).`;
632
+ }
633
+ return branch;
634
+ }
635
+ var branchOn = (key, name, then) => ({ if: { required: [key], properties: { [key]: { const: name } } }, then });
636
+ function specialise(s, registry) {
637
+ const defs = s.$defs;
638
+ const nodeTypes = nodeTypesOf(registry.nodeTypes);
639
+ const workers = workersOf(registry.workers);
640
+ const pools = poolsOf(registry.pools);
641
+ const gateTypes = gateTypesOf(registry.gateTypes);
642
+ const groups = [...new Set(registry.groups ?? [])];
643
+ const predicates = [...new Set(registry.predicates)];
644
+ const languages = [...languagesOf(registry.languages).keys()];
645
+ const task = defs.taskNode;
646
+ task.properties.type = {
647
+ ...task.properties.type,
648
+ 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.`,
649
+ enum: [...nodeTypes.keys()]
650
+ };
651
+ task.properties.workerGroups = {
652
+ ...task.properties.workerGroups,
653
+ 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).`,
654
+ items: { type: "string", enum: groups }
655
+ };
656
+ task.allOf = [...nodeTypes].map(([n, t]) => branchOn("type", n, nodeTypeBranch(n, t, workers, pools)));
657
+ const transform = defs.transform;
658
+ transform.properties.lang = {
659
+ ...transform.properties.lang,
660
+ 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).`,
661
+ enum: languages
662
+ };
663
+ const predicateName = defs.predicateName;
664
+ predicateName.properties.predicate = {
665
+ ...predicateName.properties.predicate,
666
+ description: `A predicate the registry declares \u2014 ${predicates.length ? quoted(predicates) : "it declares none"} (FD-V4).`,
667
+ enum: predicates
668
+ };
669
+ const gate = defs.gateNode;
670
+ gate.properties.type = {
671
+ ...gate.properties.type,
672
+ 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.`,
673
+ enum: [...gateTypes.keys()]
674
+ };
675
+ gate.allOf = [...gateTypes].map(([g, t]) => branchOn("type", g, gateTypeBranch(g, t)));
676
+ s.description = REGISTRY_DESCRIPTION;
677
+ }
149
678
 
150
679
  // src/definition/assist.ts
151
680
  var TYPES = ["object", "array", "string", "number", "integer", "boolean", "null"];
@@ -260,11 +789,11 @@ function object(s, indent) {
260
789
  const req = new Set(s.required ?? []);
261
790
  const named = [...props.map(([k]) => k)];
262
791
  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");
792
+ const body2 = all.map(([k, sub]) => `${indent} ${lit(k)}${req.has(k) ? "" : "?"}: ${tsTypeOf(sub, `${indent} `)};`).join("\n");
264
793
  const rest = closed ? "" : `
265
794
  ${indent} [k: string]: unknown;`;
266
795
  return `{
267
- ${body}${rest}
796
+ ${body2}${rest}
268
797
  ${indent}}`;
269
798
  }
270
799
  function signatureOf(name, worker) {
@@ -300,23 +829,28 @@ export {
300
829
  ConfigRefused,
301
830
  DECLARED_POOL_KEYS,
302
831
  DOCUMENT_KEYS,
832
+ DOC_KEYS_DECLARED,
303
833
  DerivationRefused,
304
834
  Driver,
305
835
  DuplicateTaskId,
306
836
  EDGE_KEYS_DECLARED,
837
+ EDGE_KEYS_REQUIRED,
307
838
  GATE_TYPE_KEYS,
308
839
  GateTypeRefused,
309
840
  GroupConfigRefused,
310
841
  GroupTypeMissing,
842
+ ID_RE_COPY as ID_RE,
311
843
  INSTANCE_KEYS,
312
844
  InvalidDefinition,
313
845
  InvalidRunId,
314
846
  InvalidTaskId,
315
847
  KEY_PATH_RE,
848
+ KINDS_COPY as KINDS,
316
849
  MESSAGE_OPTION_KEYS,
317
850
  MissingJoinPolicy,
318
851
  MultipleRuns,
319
852
  NODE_KEYS_DECLARED,
853
+ NODE_KEYS_REQUIRED,
320
854
  NODE_TYPE_DECLARATION_KEYS,
321
855
  NameCollision,
322
856
  NameMissing,
@@ -334,6 +868,7 @@ export {
334
868
  PREDICATE_ROOTS,
335
869
  PoolTypeMissing,
336
870
  RETRY_POLICY_KEYS,
871
+ ROUTABLE_STATES,
337
872
  RegistryError,
338
873
  ResumeRefused,
339
874
  RunControlRefused,
@@ -371,6 +906,7 @@ export {
371
906
  checkRegistry,
372
907
  checkWiring,
373
908
  compileProblem,
909
+ definitionSchema,
374
910
  derivePool,
375
911
  describeFindings,
376
912
  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-5CVSWDCB.js";
30
30
 
31
31
  // src/scheduler/bootstrap.ts
32
32
  import { WorkerManager } from "fyflow-scheduler";