fyflow-orchestration 0.11.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.
@@ -360,10 +360,32 @@ function describeFindings(fs) {
360
360
  }
361
361
 
362
362
  // src/definition/condition.ts
363
+ var INDEX_SPELLING = "\\[(0|[1-9][0-9]{0,8})\\]";
364
+ var INDEXED_SEGMENT = new RegExp(`^([A-Za-z0-9_-]{1,64})((?:${INDEX_SPELLING})+)$`);
365
+ var INDEX2 = new RegExp(INDEX_SPELLING, "g");
366
+ function keyPathSteps(path) {
367
+ const out = [];
368
+ for (const seg of path.split(".")) {
369
+ const m = INDEXED_SEGMENT.exec(seg);
370
+ if (m === null) {
371
+ out.push({ key: seg });
372
+ continue;
373
+ }
374
+ out.push({ key: m[1] });
375
+ for (const i of m[2].matchAll(INDEX2)) out.push({ index: Number(i[1]) });
376
+ }
377
+ return out;
378
+ }
363
379
  function readKeyPath(value, path) {
364
380
  let v = value;
365
- for (const seg of path.split(".")) {
381
+ for (const step2 of keyPathSteps(path)) {
366
382
  if (typeof v !== "object" || v === null) return void 0;
383
+ if ("index" in step2) {
384
+ if (!Array.isArray(v) || step2.index >= v.length || !Object.prototype.hasOwnProperty.call(v, step2.index)) return void 0;
385
+ v = v[step2.index];
386
+ continue;
387
+ }
388
+ const seg = step2.key;
367
389
  const proto = Object.getPrototypeOf(v);
368
390
  if (proto !== Object.prototype && proto !== null) return void 0;
369
391
  if (!Object.prototype.hasOwnProperty.call(v, seg)) return void 0;
@@ -514,11 +536,12 @@ function languagesOf(x) {
514
536
  if (x instanceof Map) return x;
515
537
  return new Map(Object.entries(x));
516
538
  }
517
- var KEY_PATH_RE = /^[A-Za-z0-9_-]{1,64}(?:\.[A-Za-z0-9_-]{1,64})*$/;
539
+ var KEY_PATH_RE = /^[A-Za-z0-9_-]{1,64}(?:\[(?:0|[1-9][0-9]{0,8})\])*(?:\.[A-Za-z0-9_-]{1,64}(?:\[(?:0|[1-9][0-9]{0,8})\])*)*$/;
540
+ var KEY_PATH_WORDS = "dot-separated object keys, each [A-Za-z0-9_-]{1,64} and optionally followed by array indexes [n] (n is 0 or a number with no leading zero, at most 999999999 \u2014 files[0].series, m[0][2]); no index leading the path, no quoted key, no wildcard, no expression";
518
541
  function keyPathProblem(v) {
519
542
  if (typeof v !== "string") return `settings.key must be a key path string, got ${JSON.stringify(v)}`;
520
543
  if (KEY_PATH_RE.test(v)) return null;
521
- return `settings.key ${JSON.stringify(v)} is not a key path \u2014 dot-separated object keys, each [A-Za-z0-9_-]{1,64}; no array index, no wildcard, no expression (a key that must be computed is computed in the task before the gate)`;
544
+ return `settings.key ${JSON.stringify(v)} is not a key path \u2014 ${KEY_PATH_WORDS} (a key that must be computed is computed in the task before the gate)`;
522
545
  }
523
546
  var GATE_TYPE_KEYS = ["routes", "defaultRoute", "settingsSchema", "keyResolvedRoutes", "routesFor", "settingsProblem", "fn"];
524
547
  var STATIC_ROUTE_KEYS = ["routes", "defaultRoute", "keyResolvedRoutes"];
@@ -770,12 +793,17 @@ function leafProblem(o, where, scope = EDGE_SCOPE) {
770
793
  }
771
794
  const path = o.path;
772
795
  if (typeof path !== "string" || !KEY_PATH_RE.test(path)) {
773
- return { rule: "FD-V43", message: `${where} path ${JSON.stringify(path)} is not a key path \u2014 dot-separated object keys, each [A-Za-z0-9_-]{1,64}; no array index, no wildcard, no expression` };
796
+ return { rule: "FD-V43", message: `${where} path ${JSON.stringify(path)} is not a key path \u2014 ${KEY_PATH_WORDS}` };
774
797
  }
775
- const root = path.split(".")[0];
798
+ const first = path.split(".")[0];
799
+ const bracket = first.indexOf("[");
800
+ const root = bracket === -1 ? first : first.slice(0, bracket);
776
801
  if (!scope.roots.includes(root)) {
777
802
  return { rule: "FD-V43", message: `${where} path ${JSON.stringify(path)} starts at ${JSON.stringify(root)}, ${scope.foreignRoot(root)}` };
778
803
  }
804
+ if (bracket !== -1) {
805
+ return { rule: "FD-V43", message: `${where} path ${JSON.stringify(path)} indexes its root ${JSON.stringify(root)} \u2014 a path names a KEY inside its root first, as a gate's key path names a key inside the value that reached it; an index follows a key (${root}.items[0])` };
806
+ }
779
807
  if (path.split(".").length < 2) {
780
808
  return { rule: "FD-V43", message: `${where} path ${JSON.stringify(path)} is a root alone ${scope.rootAlone(root)}` };
781
809
  }
@@ -1263,8 +1291,15 @@ function validateDefinition(doc, registry) {
1263
1291
  if (nd.edges !== void 0) add("FD-V9", where, "fanout has edges");
1264
1292
  if (nd.branches !== void 0 && !Array.isArray(nd.branches)) {
1265
1293
  add("FD-V21", where, "fanout branches must be an array");
1294
+ } else if (Array.isArray(nd.branches)) {
1295
+ nd.branches.forEach((b, i) => {
1296
+ if (typeof b !== "string") add("FD-V21", where, `fanout branches[${i}] must be a string, got ${describeValue(b)}`);
1297
+ });
1266
1298
  }
1267
1299
  }
1300
+ if (nd.kind === "join" && nd.of !== void 0 && typeof nd.of !== "string") {
1301
+ add("FD-V21", where, `join of must be a string, got ${describeValue(nd.of)}`);
1302
+ }
1268
1303
  if (nd.kind === "map") {
1269
1304
  if (nd.edges !== void 0) add("FD-V25", where, "map has edges");
1270
1305
  if (nd.branch !== void 0 && typeof nd.branch !== "string") {
@@ -2063,7 +2098,7 @@ var typesOf = (s) => {
2063
2098
  };
2064
2099
  var widen = (ts) => ts.flatMap((t) => t === "integer" ? ["integer"] : t === "number" ? ["number", "integer"] : [t]);
2065
2100
  var canon = (v) => JSON.stringify(v);
2066
- var BRANCH_KEY = /^\d+(\.\d+)*$/;
2101
+ var BRANCH_KEY = /^(?:0|[1-9][0-9]*)$/;
2067
2102
  var ERROR_SCHEMA = Object.freeze({ type: ["string", "null"] });
2068
2103
  function fits(P, C, path = "$") {
2069
2104
  const p = P;
@@ -2095,7 +2130,7 @@ function fits(P, C, path = "$") {
2095
2130
  if (!own2(pp, k)) {
2096
2131
  if (record !== void 0 && BRANCH_KEY.test(k)) {
2097
2132
  const r2 = fits(record, cp[k], `${path}.${k}`);
2098
- if (r2.verdict !== "fits") return { verdict: "no", why: `${r2.why} (a branch path the map's record may mint)` };
2133
+ if (r2.verdict !== "fits") return { verdict: "no", why: `${r2.why} (an item index the map's record may mint)` };
2099
2134
  continue;
2100
2135
  }
2101
2136
  if (p.additionalProperties === false) continue;
@@ -2199,14 +2234,21 @@ var Wiring = class {
2199
2234
  * key-resolved route licenses. A step the producer's schema does not describe is left alone rather than
2200
2235
  * invented: `required` on a key no `properties` entry mentions is still a promise the value carries it,
2201
2236
  * which is what the consumer asked about, and describing its SHAPE is not this function's to guess.
2237
+ * **An index step `[n]` raises the array's `minItems` to `n + 1` and ends the walk** (v0.53, ADR 0137) — below.
2202
2238
  */
2203
2239
  requireAlong(schema, key) {
2204
2240
  if (schema === void 0 || key === "") return schema;
2205
- const steps = key.split(".");
2241
+ const steps = keyPathSteps(key);
2206
2242
  const go = (s, i) => {
2207
2243
  if (i >= steps.length || typeof s !== "object" || s === null || Array.isArray(s)) return s;
2208
2244
  const o = s;
2209
- const here = steps[i];
2245
+ const step2 = steps[i];
2246
+ if ("index" in step2) {
2247
+ const floor = step2.index + 1;
2248
+ const had = typeof o.minItems === "number" ? o.minItems : 0;
2249
+ return had >= floor ? o : { ...o, minItems: floor };
2250
+ }
2251
+ const here = step2.key;
2210
2252
  const req = Array.isArray(o.required) ? o.required : [];
2211
2253
  const props = o.properties ?? {};
2212
2254
  const described = own2(props, here);
@@ -2226,9 +2268,10 @@ var Wiring = class {
2226
2268
  * the producer can emit.
2227
2269
  *
2228
2270
  * **The question is per STEP and about the CONTAINER, because that is what `readKeyPath` is** —
2229
- * the DRIVER's own resolution (`src/definition/validate.ts`, W93), where each segment is an OWN key
2230
- * of a PLAIN object and a missing key, a `null`, an array mid-path or a primitive is `undefined`.
2231
- * So two shapes forbid a step and the third is a silence:
2271
+ * the DRIVER's own resolution (`src/definition/condition.ts`, W93), where each KEY step is an OWN key
2272
+ * of a PLAIN object and a missing key, a `null`, an array under a key step or a primitive is `undefined`
2273
+ * — and, since v0.53, each INDEX step `[n]` an element of a REAL array (ADR 0137), asked below in the loop.
2274
+ * So, for a key step, two shapes forbid it and the third is a silence:
2232
2275
  *
2233
2276
  * - **its declared types cannot include `object`** — `name.id` where the producer calls `name` a
2234
2277
  * string. No value the schema admits carries the path.
@@ -2250,13 +2293,21 @@ var Wiring = class {
2250
2293
  * Every rung this repository has is green on a flow that did none of the work it exists for.
2251
2294
  */
2252
2295
  keyPathRefusal(schema, key, gateId, route) {
2253
- const steps = key.split(".");
2296
+ const steps = keyPathSteps(key);
2254
2297
  let s = schema;
2255
- const because = (at, detail) => `gate "${gateId}" is keyed on "${key}", which the producer's schema FORBIDS at ${at}: ${detail} \u2014 so route "${route}" can never be taken, and a run may route every branch by another route, doing none of the work this gate guards`;
2256
- for (let i = 0; i < steps.length; i++) {
2257
- const here = steps[i];
2258
- const at = i === 0 ? "$" : `$.${steps.slice(0, i).join(".")}`;
2298
+ let at = "$";
2299
+ const because = (at2, detail) => `gate "${gateId}" is keyed on "${key}", which the producer's schema FORBIDS at ${at2}: ${detail} \u2014 so route "${route}" can never be taken, and a run may route every branch by another route, doing none of the work this gate guards`;
2300
+ for (const step2 of steps) {
2259
2301
  const ts = typesOf(s);
2302
+ if ("index" in step2) {
2303
+ if (!ts.includes("array")) return because(at, `the schema calls it ${ts.join("|")}, and an index [${step2.index}] reads an element of an array`);
2304
+ if (s.items === void 0) return null;
2305
+ if (!isPlainObject3(s.items)) return null;
2306
+ s = s.items;
2307
+ at = `${at}[${step2.index}]`;
2308
+ continue;
2309
+ }
2310
+ const here = step2.key;
2260
2311
  if (!ts.includes("object")) return because(at, `the schema calls it ${ts.join("|")}, and a key path reads an own key of a plain object`);
2261
2312
  const props = s.properties ?? {};
2262
2313
  if (!own2(props, here)) {
@@ -2269,6 +2320,7 @@ var Wiring = class {
2269
2320
  const next = props[here];
2270
2321
  if (!isPlainObject3(next)) return null;
2271
2322
  s = next;
2323
+ at = at === "$" ? `$.${here}` : `${at}.${here}`;
2272
2324
  }
2273
2325
  return null;
2274
2326
  }
@@ -2361,7 +2413,7 @@ var Wiring = class {
2361
2413
  const members = region.branchOf.get(branch) ?? /* @__PURE__ */ new Set();
2362
2414
  return [...members].map((id) => this.byId.get(id)).filter((n) => (n.kind === "task" || n.kind === "ask" || n.kind === "join" || n.kind === "gate") && (n.edges ?? []).some((e) => e.to === region.joinId));
2363
2415
  }
2364
- /** `JP-R12`'s record for the region a join closes — exact for a fan-out, keyed by minted paths for a map. */
2416
+ /** `JP-R12`'s record for the region a join closes — exact for a fan-out, keyed by entry id; keyed by item index for a map. */
2365
2417
  record(j) {
2366
2418
  const region = this.regions.find((r) => r.joinId === j.id);
2367
2419
  const base = { shape: "record", producer: j.id };
@@ -2385,22 +2437,16 @@ var Wiring = class {
2385
2437
  values.push({ branch: b, schema: firstSchema, sample: first.sample ?? { value: null, source: "witness" } });
2386
2438
  }
2387
2439
  const source = values.every((v2) => v2.sample.source === "example") ? "example" : "witness";
2388
- const enclosing = new Map(this.regions.filter((r) => r.opensWith === "map" && r.members.has(region.fanoutId)).map((r) => [r.fanoutId, 0]));
2389
2440
  if (opener.kind === "fanout") {
2390
- const props = {};
2391
- const sample2 = {};
2392
- for (const v2 of values) {
2393
- const path = branchPathOf(this.regions, v2.branch, enclosing);
2394
- props[path] = v2.schema;
2395
- sample2[path] = v2.sample.value;
2396
- }
2441
+ const props = Object.fromEntries(values.map((v2) => [v2.branch, v2.schema]));
2442
+ const sample2 = Object.fromEntries(values.map((v2) => [v2.branch, v2.sample.value]));
2397
2443
  return { ...base, schema: { type: "object", required: [], properties: props, additionalProperties: false }, sample: { value: sample2, source, arity: values.length } };
2398
2444
  }
2399
2445
  const v = values[0];
2400
2446
  if (!v) return { ...base, unchecked: `map "${opener.id}"'s branch never arrives at "${j.id}"` };
2401
2447
  const arity = this.arrivals(opener.branch)[0]?.sample?.arity ?? 1;
2402
2448
  const sample = {};
2403
- for (let i = 0; i < arity; i++) sample[branchPathOf(this.regions, opener.branch, new Map([...enclosing, [opener.id, i]]))] = v.sample.value;
2449
+ for (let i = 0; i < arity; i++) sample[String(i)] = v.sample.value;
2404
2450
  return {
2405
2451
  ...base,
2406
2452
  schema: { type: "object", additionalProperties: v.schema, branchKeys: true },
@@ -2633,7 +2679,7 @@ var WIRING_SHAPES = ["edge", "branch", "item", "record", "needs", "given"];
2633
2679
  var SEGMENT = /^[A-Za-z0-9_-]{1,64}$/;
2634
2680
  var BRANCH_PATH = /^(?:-|(?:0|[1-9][0-9]*)(?:\.(?:0|[1-9][0-9]*))*)$/;
2635
2681
  var ATTEMPT = /^(?:0|[1-9][0-9]*)$/;
2636
- var INDEX2 = /^(?:0|[1-9][0-9]*)$/;
2682
+ var INDEX3 = /^(?:0|[1-9][0-9]*)$/;
2637
2683
  var InvalidRunId = class extends Error {
2638
2684
  };
2639
2685
  var InvalidTaskId = class extends Error {
@@ -2657,7 +2703,7 @@ function branchPathSegment(branchIndex) {
2657
2703
  throw new InvalidTaskId("branchPath must be null for a node in no region, never an empty path");
2658
2704
  }
2659
2705
  for (const n of parts) {
2660
- if (!INDEX2.test(String(n))) {
2706
+ if (!INDEX3.test(String(n))) {
2661
2707
  throw new InvalidTaskId(`branchPath segment ${JSON.stringify(n)} is not a non-negative integer`);
2662
2708
  }
2663
2709
  }
@@ -2981,7 +3027,7 @@ var RunControlRefused = class extends Error {
2981
3027
  this.reason = reason;
2982
3028
  }
2983
3029
  };
2984
- var CHECKPOINT_FORMAT = 1;
3030
+ var CHECKPOINT_FORMAT = 2;
2985
3031
  var ResumeRefused = class extends Error {
2986
3032
  reason;
2987
3033
  /** The position `halted`, `cancelled` and `diverged` name; `null` for the three a header decides. */
@@ -3180,11 +3226,12 @@ function compareBranchPaths(a, b) {
3180
3226
  }
3181
3227
  return 0;
3182
3228
  }
3183
- function collectArrivals(arrived) {
3184
- const out = {};
3185
- if (!arrived) return out;
3186
- for (const path of [...arrived.keys()].sort(compareBranchPaths)) out[path] = arrived.get(path);
3187
- return out;
3229
+ function collectArrivals(arrived, region) {
3230
+ if (!arrived) return {};
3231
+ return Object.fromEntries([...arrived.keys()].sort(compareBranchPaths).map((path) => {
3232
+ const segment = path.slice(path.lastIndexOf(".") + 1);
3233
+ return [region.opensWith === "fanout" ? region.branches[Number(segment)] : segment, arrived.get(path)];
3234
+ }));
3188
3235
  }
3189
3236
  var EMPTY_INDICES = /* @__PURE__ */ new Map();
3190
3237
  var EMPTY_NEEDS = Object.freeze({});
@@ -3367,7 +3414,8 @@ var Driver = class _Driver {
3367
3414
  firedJoins = /* @__PURE__ */ new Map();
3368
3415
  /**
3369
3416
  * **What each branch carried into its join, keyed by region and then by
3370
- * `branchPath`** — [ADR 0037](../../doc/decisions/0037-what-a-branch-receives-is-a-property-of-the-fan-out-and-what-a-join-carries-is-a-property-of-the-region.md)
3417
+ * `branchPath`** — the FULL path, here where it is recorded; the collection a join hands on is keyed by each
3418
+ * branch's OWN key, derived from these paths by `collectArrivals` at the exit (ADR 0138) —[ADR 0037](../../doc/decisions/0037-what-a-branch-receives-is-a-property-of-the-fan-out-and-what-a-join-carries-is-a-property-of-the-region.md)
3371
3419
  * rule 4, and `JP-R11` in [spec/join-policy.md](../../spec/join-policy.md)
3372
3420
  * section 4.
3373
3421
  *
@@ -6262,7 +6310,7 @@ var Driver = class _Driver {
6262
6310
  fired: true
6263
6311
  });
6264
6312
  if (this.shut()) return;
6265
- const collected = collectArrivals(this.arrivals.get(region.fanoutId)?.get(rp));
6313
+ const collected = collectArrivals(this.arrivals.get(region.fanoutId)?.get(rp), region);
6266
6314
  this.releaseArrivals(region.fanoutId, rp);
6267
6315
  const reading = this.replay?.reading ?? null;
6268
6316
  if (reading !== null && region.joinId === reading.nodeId) {
@@ -151,11 +151,10 @@ import {
151
151
  visibilityKey,
152
152
  witness,
153
153
  workersOf
154
- } from "./chunks/chunk-5CVSWDCB.js";
154
+ } from "./chunks/chunk-4JFQX7DL.js";
155
155
 
156
156
  // src/definition/definition-schema.ts
157
157
  var DRAFT_2020_12 = "https://json-schema.org/draft/2020-12/schema";
158
- var idOrNonString = () => ({ anyOf: [ref("id"), { not: { type: "string" } }] });
159
158
  var ref = (name) => ({ $ref: `#/$defs/${name}` });
160
159
  function body(re, name) {
161
160
  const s = re.source;
@@ -164,6 +163,14 @@ function body(re, name) {
164
163
  }
165
164
  return s.slice(1, -1);
166
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
+ }
167
174
  function described(table, key, where) {
168
175
  const d = Object.prototype.hasOwnProperty.call(table, key) ? table[key] : void 0;
169
176
  if (d === void 0) throw new Error(`definitionSchema: ${where} declares the key "${key}" and no description is written for it`);
@@ -197,7 +204,7 @@ var KIND_DESCRIPTIONS = {
197
204
  },
198
205
  fanout: {
199
206
  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)."
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)."
201
208
  },
202
209
  map: {
203
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.",
@@ -207,7 +214,7 @@ var KIND_DESCRIPTIONS = {
207
214
  },
208
215
  join: {
209
216
  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).",
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.",
211
218
  edges: "Exactly one edge, carrying neither `when` nor `on` (FD-V7): a join has no outcome for either to read."
212
219
  },
213
220
  end: {
@@ -244,7 +251,7 @@ var TRANSFORM_DESCRIPTIONS = {
244
251
  };
245
252
  var PREDICATE_DESCRIPTIONS = {
246
253
  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).`,
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).`,
248
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.`,
249
256
  value: "The scalar compared against: null, a boolean, a number or a string (FD-V44).",
250
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).",
@@ -284,7 +291,7 @@ function nodeKeySchema(kind, key) {
284
291
  case "pool":
285
292
  return { type: "string" };
286
293
  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" };
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" };
288
295
  case "transform":
289
296
  return ref("transform");
290
297
  case "optional":
@@ -295,14 +302,14 @@ function nodeKeySchema(kind, key) {
295
302
  return ref("retryPolicy");
296
303
  case "workerGroups":
297
304
  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.
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.
302
309
  case "branches":
303
- return { type: "array", minItems: 2, uniqueItems: true, items: idOrNonString() };
310
+ return { type: "array", minItems: 2, uniqueItems: true, items: ref("id") };
304
311
  case "of":
305
- return idOrNonString();
312
+ return ref("id");
306
313
  case "over":
307
314
  case "branch":
308
315
  return ref("id");
@@ -425,7 +432,7 @@ function referenceSchema(withSelf, id) {
425
432
  };
426
433
  }
427
434
  function leafSchema(id) {
428
- const path = `^(?:${PREDICATE_ROOTS.join("|")})(?:\\.${id})+$`;
435
+ const path = `^(?:${PREDICATE_ROOTS.join("|")})(?:\\.${keyPathSegment(id)})+$`;
429
436
  const scalar = () => ({ type: ["null", "boolean", "number", "string"] });
430
437
  const keySchemas = {
431
438
  path: { type: "string", pattern: path },
@@ -26,7 +26,7 @@ import {
26
26
  retryPolicyProblem,
27
27
  validateDefinition,
28
28
  workersOf
29
- } from "../chunks/chunk-5CVSWDCB.js";
29
+ } from "../chunks/chunk-4JFQX7DL.js";
30
30
 
31
31
  // src/scheduler/bootstrap.ts
32
32
  import { WorkerManager } from "fyflow-scheduler";