fyflow-orchestration 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,5 @@
1
1
  import {
2
+ ASK_REFUSED_KEYS,
2
3
  BUILT_IN_GATE_TYPES,
3
4
  BUILT_IN_GROUP_TYPES,
4
5
  BUILT_IN_NODE_TYPES,
@@ -21,6 +22,7 @@ import {
21
22
  InvalidRunId,
22
23
  InvalidTaskId,
23
24
  KEY_PATH_RE,
25
+ MESSAGE_OPTION_KEYS,
24
26
  MissingJoinPolicy,
25
27
  MultipleRuns,
26
28
  NODE_KEYS_DECLARED,
@@ -30,6 +32,7 @@ import {
30
32
  NoMatchingEdge,
31
33
  NotStarted,
32
34
  POLICIES,
35
+ POOL_INSTANCE_KEYS,
33
36
  POOL_SPEC_KEYS,
34
37
  PREDICATE_GROUP_KEYS,
35
38
  PREDICATE_LEAF_KEYS,
@@ -41,10 +44,12 @@ import {
41
44
  RETRY_POLICY_KEYS,
42
45
  RegistryError,
43
46
  ResumeRefused,
47
+ RunControlRefused,
44
48
  SCHEMA_KEYWORDS,
45
49
  SCHEMA_TYPES,
46
50
  SchemaRefused,
47
51
  TASK_KEYS_DECLARED,
52
+ TERMINAL_EVENTS,
48
53
  TRANSFORM_KEYS,
49
54
  TypeDefaultRefused,
50
55
  UncloneableResult,
@@ -90,11 +95,14 @@ import {
90
95
  gateTypeCollisions,
91
96
  gateTypeProblem,
92
97
  gateTypesOf,
98
+ givenVerdict,
93
99
  groupsNamedBy,
100
+ innermostMapOf,
94
101
  innermostRegionOf,
95
102
  isDerivation,
96
103
  isSuspended,
97
104
  isTerminal,
105
+ isTerminalEvent,
98
106
  keyPathProblem,
99
107
  languagesOf,
100
108
  lastRecordFor,
@@ -122,6 +130,7 @@ import {
122
130
  resolveRegistry,
123
131
  resumeRefusal,
124
132
  retryPolicyProblem,
133
+ routesOf,
125
134
  sampleOf,
126
135
  schemaProblem,
127
136
  selectEdge,
@@ -134,7 +143,7 @@ import {
134
143
  violatedRules,
135
144
  witness,
136
145
  workersOf
137
- } from "./chunks/chunk-YVOP3DIN.js";
146
+ } from "./chunks/chunk-3U4UZC34.js";
138
147
 
139
148
  // src/definition/assist.ts
140
149
  var TYPES = ["object", "array", "string", "number", "integer", "boolean", "null"];
@@ -174,8 +183,8 @@ async function tryTransform(registry, flow, nodeId, transform, options = {}) {
174
183
  else copy.transform = { lang: transform.lang, expr: transform.expr };
175
184
  return copy;
176
185
  });
177
- const report = await checkWiring(registry, { ...flow, nodes }, options);
178
- return report.verdicts.filter((v) => v.consumer === nodeId);
186
+ const { input, ...wiring } = options;
187
+ return await givenVerdict(registry, { ...flow, nodes }, nodeId, input, wiring);
179
188
  }
180
189
  function schemaOfValue(v) {
181
190
  if (v === void 0) return void 0;
@@ -278,13 +287,8 @@ function signaturesOf(workers) {
278
287
  }
279
288
  var PATH_TYPES = ["*", ...TYPES];
280
289
  var TS_TYPE_NAMES = [...TS_STRUCTURED, ...TS_NAMES.keys()];
281
-
282
- // src/driver/events.ts
283
- var TERMINAL_EVENTS = ["branch.arrived", "branch.exited", "branch.lost"];
284
- function isTerminalEvent(e) {
285
- return TERMINAL_EVENTS.includes(e.type);
286
- }
287
290
  export {
291
+ ASK_REFUSED_KEYS,
288
292
  BUILT_IN_GATE_TYPES,
289
293
  BUILT_IN_GROUP_TYPES,
290
294
  BUILT_IN_NODE_TYPES,
@@ -307,6 +311,7 @@ export {
307
311
  InvalidRunId,
308
312
  InvalidTaskId,
309
313
  KEY_PATH_RE,
314
+ MESSAGE_OPTION_KEYS,
310
315
  MissingJoinPolicy,
311
316
  MultipleRuns,
312
317
  NODE_KEYS_DECLARED,
@@ -317,6 +322,7 @@ export {
317
322
  NotStarted,
318
323
  PATH_TYPES,
319
324
  POLICIES,
325
+ POOL_INSTANCE_KEYS,
320
326
  POOL_SPEC_KEYS,
321
327
  PREDICATE_GROUP_KEYS,
322
328
  PREDICATE_LEAF_KEYS,
@@ -328,6 +334,7 @@ export {
328
334
  RETRY_POLICY_KEYS,
329
335
  RegistryError,
330
336
  ResumeRefused,
337
+ RunControlRefused,
331
338
  SCHEMA_KEYWORDS,
332
339
  SCHEMA_TYPES,
333
340
  SchemaRefused,
@@ -380,7 +387,9 @@ export {
380
387
  gateTypeCollisions,
381
388
  gateTypeProblem,
382
389
  gateTypesOf,
390
+ givenVerdict,
383
391
  groupsNamedBy,
392
+ innermostMapOf,
384
393
  innermostRegionOf,
385
394
  isDerivation,
386
395
  isSuspended,
@@ -414,6 +423,7 @@ export {
414
423
  resolveRegistry,
415
424
  resumeRefusal,
416
425
  retryPolicyProblem,
426
+ routesOf,
417
427
  sampleOf,
418
428
  sampledOutput,
419
429
  schemaOfValue,
@@ -1,7 +1,9 @@
1
1
  import {
2
+ Accumulator,
2
3
  BUILT_IN_NODE_TYPES,
3
4
  ConfigRefused,
4
5
  Driver,
6
+ InvalidDefinition,
5
7
  InvalidTaskId,
6
8
  TypeDefaultRefused,
7
9
  checkGateTypes,
@@ -20,38 +22,22 @@ import {
20
22
  poolsOf,
21
23
  resolveRegistry,
22
24
  resumeRefusal,
23
- step,
25
+ retryPolicyProblem,
26
+ validateDefinition,
24
27
  workersOf
25
- } from "../chunks/chunk-YVOP3DIN.js";
28
+ } from "../chunks/chunk-3U4UZC34.js";
26
29
 
27
30
  // src/scheduler/bootstrap.ts
28
31
  import { WorkerManager } from "fyflow-scheduler";
29
32
 
30
33
  // fyflow-envelope-worker:envelope-worker
31
- var envelope_worker_default = URL.createObjectURL(new Blob(['// src/scheduler/envelope-worker.ts\nvar NotAnEnvelope = class extends Error {\n};\nvar TransformFailed = class extends Error {\n lang;\n expr;\n code;\n constructor(transform, code, why) {\n super(`transform failed \\u2014 ${transform.lang} ${JSON.stringify(transform.expr)}${code !== void 0 ? ` [${code}]` : ""}: ${why}`);\n this.lang = transform.lang;\n this.expr = transform.expr;\n this.code = code;\n }\n};\nfunction unpackEnvelope(payload) {\n if (typeof payload !== "object" || payload === null || Array.isArray(payload)) {\n throw new NotAnEnvelope(`typed pool: the payload is not an envelope { settings, input } \\u2014 got ${brief(payload)}`);\n }\n const p = payload;\n for (const k of ["settings", "input"]) {\n if (!(k in p)) throw new NotAnEnvelope(`typed pool: the payload is not an envelope \\u2014 it has no "${k}" key (keys: ${Object.keys(p).map((x) => `"${x}"`).join(", ") || "none"})`);\n }\n const settings = p.settings;\n if (typeof settings !== "object" || settings === null || Array.isArray(settings)) {\n throw new NotAnEnvelope(`typed pool: the envelope\'s settings is not a plain object \\u2014 got ${brief(settings)}`);\n }\n const env = { settings, input: p.input };\n if ("transform" in p) {\n const t = p.transform;\n const wellFormed = typeof t === "object" && t !== null && !Array.isArray(t) && typeof t.lang === "string" && typeof t.expr === "string" && Object.keys(t).length === 2;\n if (!wellFormed) throw new NotAnEnvelope(`typed pool: the envelope\'s transform is not { lang, expr } \\u2014 got ${brief(p.transform)}`);\n env.transform = { lang: t.lang, expr: t.expr };\n }\n return env;\n}\nfunction brief(v) {\n const s = printable(v);\n return s.length > 120 ? `${s.slice(0, 117)}...` : s;\n}\nfunction printable(v) {\n try {\n const j = JSON.stringify(v);\n if (j !== void 0) return j;\n } catch {\n }\n try {\n return String(v);\n } catch {\n return `an unprintable ${typeof v}`;\n }\n}\nfunction codeOf(e) {\n const c = e?.code;\n return typeof c === "string" && c.length ? c : void 0;\n}\nfunction messageOf(e) {\n if (e instanceof Error) return e.message;\n try {\n return String(e);\n } catch {\n return printable(e);\n }\n}\nvar EnvelopeWorker = class {\n cfg;\n workerContext;\n inner = null;\n /**\n * **Set by `setup()` when the pool config names no worker \u2014 a type that only TRANSFORMS** (ADR 0075).\n * It is a SEPARATE field from `inner === null` on purpose, and that is the whole of this arm\'s\n * correctness: `inner` is null before `setup()` and after `teardown()` too, which are LIFECYCLE\n * states `run()` has refused by name since v0.17, so reading the absence off `inner` would turn\n * *run() before setup()* into *return the input untouched* \u2014 a run that silently hands a worker\'s\n * successor the wrong value instead of failing the task. Measured at W109\'s declaration step: before\n * this item BOTH of those states threw, and neither was an arm.\n */\n noInner = false;\n /**\n * The worker half of each language the pool config names, loaded in `setup()` \u2014 or the reason\n * it did not load, REMEMBERED so that `run()` can fail the task with it (the maintainer\'s call,\n * see `setup()`).\n */\n langs = /* @__PURE__ */ new Map();\n /** Each distinct `(lang, expr)` compiled ONCE per worker instance, never per task \u2014 P17 decision 3. */\n compiled = /* @__PURE__ */ new Map();\n /**\n * **Why this worker could not set up, REMEMBERED \u2014 or `null`** \u2014\n * [W141](../../doc/work-items/w141-a-worker-that-cannot-set-up-fails-its-tasks-and-the-registry-has-one-shape.md),\n * [Q266](../../doc/questions/q266.md), P31 decision 3: W61\'s road for a language, taken for the whole\n * of `setup()`. Set by `setup()` instead of throwing; read FIRST by `run()`, which fails every task\n * with it; cleared by `teardown()`, so a torn-down worker is in its constructed state again.\n */\n setupFailure = null;\n /**\n * Both arguments are kept, and the second is forwarded to the inner worker: the scheduler passes\n * its worker-level context here, and a worker that only accepts `config` silently loses the\n * ability to self-terminate (the scheduler\'s own reference, \xA74).\n */\n constructor(config, workerContext) {\n this.cfg = config;\n this.workerContext = workerContext;\n }\n /**\n * Loads the type\'s worker and runs ITS setup \u2014 a module with no default class is refused by\n * name, as at v0.17 \u2014 then the worker half of every language the pool config names, ONCE per\n * worker instance (P17 decision 3).\n *\n * **A language module that FAILS TO LOAD does not throw here; the failure is remembered and\n * every task whose transform names that language fails at `run()` with it.** Measured at W61\'s\n * declaration, both runtimes, inline and threaded: a wrapper whose `setup()` throws leaves every\n * task of the pool `pending` with `attempts: 0`, the pool restarts its worker, and the scheduler\n * emits nothing a driver could turn into a record \u2014 the failure is reported on the POOL as\n * `worker.setup.failed` and `task.requeue_required`, a worker error and not a task\'s, which is\n * P17 prediction 2\'s stated reversal condition. The maintainer chose this road at W61\'s\n * stop-and-ask (ADR 0051 rule 5): a URL that does not resolve, a module with no default compile\n * function, or an empty URL, each becomes a `TransformFailed` naming the language and the load\n * error, on exactly the tasks that need that language \u2014 a task with no transform, or one in a\n * language that did load, still runs on this worker.\n *\n * **And since W141 the same is true of the TYPE\'s own worker: this method never throws** \u2014 Q266,\n * P31 decision 3, W61\'s road taken for the whole of `setup()`. F1 is a fact about THIS method\n * throwing, whatever threw, and W141 measured it again on 0.5.2 through `createRuntime`: three\n * tasks on a worker whose `setup()` throws stay `pending` for ever in both pool modes, the pool\n * requeueing them on each worker failure until its restart limit, and the run\'s fold says\n * `running`. So every way the type\'s worker can fail to set up \u2014 the module does not load, it\n * exports no default class, its constructor throws, its own `setup()` throws \u2014 and the two\n * pool-config refusals this method made by name, are REMEMBERED in `setupFailure`, and `run()`\n * fails every task on this worker with it: an ordinary task failure, which the run routes per\n * ADR 0004 \u2014 down an `on: ["failed"]` edge on an optional node, parked `user_action` on a\n * non-optional one. **A constructed inner worker is torn down at once**, because the scheduler\n * tears down a worker whose `setup()` threw (its own reference, \xA76: *a failing `setup()` still\n * tears the worker down*) and this class is now the only thing that knows one did. The worker\n * stays up and fails every task until the pool is rebuilt: a setup that failed for a transient\n * reason is not retried here (W141\'s Out).\n */\n async setup() {\n this.setupFailure = null;\n try {\n await this.setupOrThrow();\n } catch (e) {\n this.setupFailure = messageOf(e);\n const inner = this.inner;\n this.inner = null;\n this.noInner = false;\n this.langs.clear();\n if (inner !== null) {\n try {\n await inner.teardown?.();\n } catch (t) {\n this.setupFailure += ` (and its teardown() after the failed setup threw too: ${messageOf(t)})`;\n }\n }\n }\n }\n /** What `setup()` did until W141, throws and all; `setup()` turns each throw into `setupFailure`. */\n async setupOrThrow() {\n if (this.cfg?.transformOnly === true) {\n if (this.cfg.worker !== void 0) {\n throw new Error(`envelope worker: the pool config says transformOnly AND names worker ${brief(this.cfg.worker)} \\u2014 a type either has an inner worker or only transforms`);\n }\n this.noInner = true;\n for (const [name, url] of Object.entries(this.cfg.languages ?? {})) {\n this.langs.set(name, await this.loadLanguage(name, url));\n }\n return;\n }\n if (typeof this.cfg?.worker !== "string" || this.cfg.worker.length === 0) {\n throw new Error(`envelope worker: the pool config names no worker URL \\u2014 got ${brief(this.cfg)}`);\n }\n const workerUrl = this.cfg.worker;\n let mod;\n try {\n mod = await import(workerUrl);\n } catch (e) {\n throw new Error(`envelope worker: the type\'s worker ${JSON.stringify(workerUrl)} did not load: ${messageOf(e)}`);\n }\n if (typeof mod.default !== "function" || !mod.default.prototype) {\n throw new Error(`envelope worker: the type\'s worker ${JSON.stringify(workerUrl)} must export a default class`);\n }\n const Inner = mod.default;\n try {\n this.inner = new Inner(this.cfg.config ?? {}, this.workerContext);\n } catch (e) {\n throw new Error(`envelope worker: the type\'s worker ${JSON.stringify(workerUrl)} threw from its constructor: ${messageOf(e)}`);\n }\n try {\n await this.inner.setup?.();\n } catch (e) {\n throw new Error(`envelope worker: the type\'s worker ${JSON.stringify(workerUrl)} threw from its setup(): ${messageOf(e)}`);\n }\n for (const [name, url] of Object.entries(this.cfg.languages ?? {})) {\n this.langs.set(name, await this.loadLanguage(name, url));\n }\n }\n /** One language\'s worker half, or the Error that stands in for it \u2014 never a throw (see `setup()`). */\n async loadLanguage(name, url) {\n if (typeof url !== "string" || url.length === 0) {\n return new Error(`language "${name}" names no worker module URL in the pool config \\u2014 got ${brief(url)}`);\n }\n try {\n const lm = await import(url);\n if (typeof lm.default !== "function") {\n return new Error(`language "${name}"\'s worker module ${url} must export a default compile function (got ${typeof lm.default})`);\n }\n return lm.default;\n } catch (e) {\n return new Error(`language "${name}"\'s worker module ${url} did not load: ${messageOf(e)}`);\n }\n }\n /**\n * `FD-C5`: the inner worker receives `{ settings, input }` and nothing else. `FD-C6`: when the\n * envelope carries a `transform`, `input` is the evaluation of that expression over the value\n * received, with the node\'s settings bound as `$settings`, and the third key is consumed here.\n */\n async run(payload, context) {\n if (this.setupFailure !== null) {\n throw new Error(`${this.setupFailure} \\u2014 every task on this worker fails with this until the pool is rebuilt`);\n }\n if (this.inner === null && !this.noInner) throw new Error("envelope worker: run() before setup() \\u2014 the inner worker is not loaded");\n const env = unpackEnvelope(payload);\n if (this.noInner) {\n if (env.transform === void 0) {\n throw new NotAnEnvelope("typed pool: this pool`s type names no worker, so the envelope must carry a transform \\u2014 there is nothing else for the task to do (FD-V46 refuses such a node at construction)");\n }\n return await this.transform(env.transform, env.input, env.settings);\n }\n const input = env.transform === void 0 ? env.input : await this.transform(env.transform, env.input, env.settings);\n return await this.inner.run({ settings: env.settings, input }, context);\n }\n /** Evaluates one declared transform; every failure is a `TransformFailed` naming what failed and why. */\n async transform(t, input, settings) {\n const compile = this.langs.get(t.lang);\n if (compile === void 0) {\n const carried = Object.keys(this.cfg.languages ?? {}).map((x) => `"${x}"`).join(", ") || "no languages";\n throw new TransformFailed(t, void 0, `language "${t.lang}" is not loaded in this pool (the pool config carries ${carried}) \\u2014 the bootstrap helper writes an adapter URL for a type some node of which declares a transform, and needs the flow and the languages to do it`);\n }\n if (compile instanceof Error) {\n throw new TransformFailed(t, void 0, `${compile.message} \\u2014 every task transforming in "${t.lang}" on this worker fails with this until the pool is rebuilt`);\n }\n const key = JSON.stringify([t.lang, t.expr]);\n let compiled = this.compiled.get(key);\n if (compiled === void 0) {\n try {\n compiled = compile(t.expr);\n } catch (e) {\n throw new TransformFailed(t, codeOf(e), `the worker half of "${t.lang}" refused the expression the registry half accepted: ${messageOf(e)}`);\n }\n if (typeof compiled?.evaluate !== "function") {\n throw new TransformFailed(t, void 0, `the worker half of "${t.lang}" compiled to ${brief(compiled)}, which has no evaluate(input, settings)`);\n }\n this.compiled.set(key, compiled);\n }\n try {\n return await compiled.evaluate(input, settings);\n } catch (e) {\n throw new TransformFailed(t, codeOf(e), messageOf(e));\n }\n }\n async teardown() {\n await this.inner?.teardown?.();\n this.inner = null;\n this.noInner = false;\n this.setupFailure = null;\n this.langs.clear();\n this.compiled.clear();\n }\n};\nexport {\n NotAnEnvelope,\n TransformFailed,\n EnvelopeWorker as default,\n unpackEnvelope\n};\n'], { type: "text/javascript" }));
34
+ var envelope_worker_default = URL.createObjectURL(new Blob(['// src/scheduler/envelope-worker.ts\nvar NotAnEnvelope = class extends Error {\n};\nvar TransformFailed = class extends Error {\n lang;\n expr;\n code;\n constructor(transform, code, why) {\n super(`transform failed \\u2014 ${transform.lang} ${JSON.stringify(transform.expr)}${code !== void 0 ? ` [${code}]` : ""}: ${why}`);\n this.lang = transform.lang;\n this.expr = transform.expr;\n this.code = code;\n }\n};\nfunction unpackEnvelope(payload) {\n if (typeof payload !== "object" || payload === null || Array.isArray(payload)) {\n throw new NotAnEnvelope(`typed pool: the payload is not an envelope { settings, input } \\u2014 got ${brief(payload)}`);\n }\n const p = payload;\n for (const k of ["settings", "input"]) {\n if (!(k in p)) throw new NotAnEnvelope(`typed pool: the payload is not an envelope \\u2014 it has no "${k}" key (keys: ${Object.keys(p).map((x) => `"${x}"`).join(", ") || "none"})`);\n }\n const settings = p.settings;\n if (typeof settings !== "object" || settings === null || Array.isArray(settings)) {\n throw new NotAnEnvelope(`typed pool: the envelope\'s settings is not a plain object \\u2014 got ${brief(settings)}`);\n }\n const env = { settings, input: p.input };\n if ("transform" in p) {\n const t = p.transform;\n const wellFormed = typeof t === "object" && t !== null && !Array.isArray(t) && typeof t.lang === "string" && typeof t.expr === "string" && Object.keys(t).length === 2;\n if (!wellFormed) throw new NotAnEnvelope(`typed pool: the envelope\'s transform is not { lang, expr } \\u2014 got ${brief(p.transform)}`);\n env.transform = { lang: t.lang, expr: t.expr };\n }\n return env;\n}\nfunction brief(v) {\n const s = printable(v);\n return s.length > 120 ? `${s.slice(0, 117)}...` : s;\n}\nfunction printable(v) {\n try {\n const j = JSON.stringify(v);\n if (j !== void 0) return j;\n } catch {\n }\n try {\n return String(v);\n } catch {\n return `an unprintable ${typeof v}`;\n }\n}\nfunction codeOf(e) {\n const c = e?.code;\n return typeof c === "string" && c.length ? c : void 0;\n}\nfunction messageOf(e) {\n if (e instanceof Error) return e.message;\n try {\n return String(e);\n } catch {\n return printable(e);\n }\n}\nvar EnvelopeWorker = class {\n cfg;\n workerContext;\n inner = null;\n /**\n * **Set by `setup()` when the pool config names no worker \u2014 a type that only TRANSFORMS** (ADR 0075).\n * It is a SEPARATE field from `inner === null` on purpose, and that is the whole of this arm\'s\n * correctness: `inner` is null before `setup()` and after `teardown()` too, which are LIFECYCLE\n * states `run()` has refused by name since v0.17, so reading the absence off `inner` would turn\n * *run() before setup()* into *return the input untouched* \u2014 a run that silently hands a worker\'s\n * successor the wrong value instead of failing the task. Measured at W109\'s declaration step: before\n * this item BOTH of those states threw, and neither was an arm.\n */\n noInner = false;\n /**\n * The worker half of each language the pool config names, loaded in `setup()` \u2014 or the reason\n * it did not load, REMEMBERED so that `run()` can fail the task with it (the maintainer\'s call,\n * see `setup()`).\n */\n langs = /* @__PURE__ */ new Map();\n /** Each distinct `(lang, expr)` compiled ONCE per worker instance, never per task \u2014 P17 decision 3. */\n compiled = /* @__PURE__ */ new Map();\n /**\n * **Why this worker could not set up, REMEMBERED \u2014 or `null`** \u2014\n * [W141](../../doc/work-items/w141-a-worker-that-cannot-set-up-fails-its-tasks-and-the-registry-has-one-shape.md),\n * [Q266](../../doc/questions/q266.md), P31 decision 3: W61\'s road for a language, taken for the whole\n * of `setup()`. Set by `setup()` instead of throwing; read FIRST by `run()`, which fails every task\n * with it; cleared by `teardown()`, so a torn-down worker is in its constructed state again.\n */\n setupFailure = null;\n /**\n * Both arguments are kept, and the second is forwarded to the inner worker: the scheduler passes\n * its worker-level context here, and a worker that only accepts `config` silently loses the\n * ability to self-terminate (the scheduler\'s own reference, \xA74).\n */\n constructor(config, workerContext) {\n this.cfg = config;\n this.workerContext = workerContext;\n }\n /**\n * Loads the type\'s worker and runs ITS setup \u2014 a module with no default class is refused by\n * name, as at v0.17 \u2014 then the worker half of every language the pool config names, ONCE per\n * worker instance (P17 decision 3).\n *\n * **A language module that FAILS TO LOAD does not throw here; the failure is remembered and\n * every task whose transform names that language fails at `run()` with it.** Measured at W61\'s\n * declaration, both runtimes, inline and threaded: a wrapper whose `setup()` throws leaves every\n * task of the pool `pending` with `attempts: 0`, the pool restarts its worker, and the scheduler\n * emits nothing a driver could turn into a record \u2014 the failure is reported on the POOL as\n * `worker.setup.failed` and `task.requeue_required`, a worker error and not a task\'s, which is\n * P17 prediction 2\'s stated reversal condition. The maintainer chose this road at W61\'s\n * stop-and-ask (ADR 0051 rule 5): a URL that does not resolve, a module with no default compile\n * function, or an empty URL, each becomes a `TransformFailed` naming the language and the load\n * error, on exactly the tasks that need that language \u2014 a task with no transform, or one in a\n * language that did load, still runs on this worker.\n *\n * **And since W141 the same is true of the TYPE\'s own worker: this method never throws** \u2014 Q266,\n * P31 decision 3, W61\'s road taken for the whole of `setup()`. F1 is a fact about THIS method\n * throwing, whatever threw, and W141 measured it again on 0.5.2 through `createRuntime`: three\n * tasks on a worker whose `setup()` throws stay `pending` for ever in both pool modes, the pool\n * requeueing them on each worker failure until its restart limit, and the run\'s fold says\n * `running`. So every way the type\'s worker can fail to set up \u2014 the module does not load, it\n * exports no default class, its constructor throws, its own `setup()` throws \u2014 and the two\n * pool-config refusals this method made by name, are REMEMBERED in `setupFailure`, and `run()`\n * fails every task on this worker with it: an ordinary task failure, which the run routes per\n * ADR 0004 \u2014 down an `on: ["failed"]` edge on an optional node, parked `user_action` on a\n * non-optional one. **A constructed inner worker is torn down at once**, because the scheduler\n * tears down a worker whose `setup()` threw (its own reference, \xA76: *a failing `setup()` still\n * tears the worker down*) and this class is now the only thing that knows one did. The worker\n * stays up and fails every task until the pool is rebuilt: a setup that failed for a transient\n * reason is not retried here (W141\'s Out).\n */\n async setup() {\n this.setupFailure = null;\n try {\n await this.setupOrThrow();\n } catch (e) {\n this.setupFailure = messageOf(e);\n const inner = this.inner;\n this.inner = null;\n this.noInner = false;\n this.langs.clear();\n if (inner !== null) {\n try {\n await inner.teardown?.();\n } catch (t) {\n this.setupFailure += ` (and its teardown() after the failed setup threw too: ${messageOf(t)})`;\n }\n }\n }\n }\n /** What `setup()` did until W141, throws and all; `setup()` turns each throw into `setupFailure`. */\n async setupOrThrow() {\n if (this.cfg?.transformOnly === true) {\n if (this.cfg.worker !== void 0) {\n throw new Error(`envelope worker: the pool config says transformOnly AND names worker ${brief(this.cfg.worker)} \\u2014 a type either has an inner worker or only transforms`);\n }\n this.noInner = true;\n for (const [name, url] of Object.entries(this.cfg.languages ?? {})) {\n this.langs.set(name, await this.loadLanguage(name, url));\n }\n return;\n }\n if (typeof this.cfg?.worker !== "string" || this.cfg.worker.length === 0) {\n throw new Error(`envelope worker: the pool config names no worker URL \\u2014 got ${brief(this.cfg)}`);\n }\n const workerUrl = this.cfg.worker;\n let mod;\n try {\n mod = await import(workerUrl);\n } catch (e) {\n throw new Error(`envelope worker: the type\'s worker ${JSON.stringify(workerUrl)} did not load: ${messageOf(e)}`);\n }\n if (typeof mod.default !== "function" || !mod.default.prototype) {\n throw new Error(`envelope worker: the type\'s worker ${JSON.stringify(workerUrl)} must export a default class`);\n }\n const Inner = mod.default;\n try {\n this.inner = new Inner(this.cfg.config ?? {}, this.workerContext);\n } catch (e) {\n throw new Error(`envelope worker: the type\'s worker ${JSON.stringify(workerUrl)} threw from its constructor: ${messageOf(e)}`);\n }\n try {\n await this.inner.setup?.();\n } catch (e) {\n throw new Error(`envelope worker: the type\'s worker ${JSON.stringify(workerUrl)} threw from its setup(): ${messageOf(e)}`);\n }\n for (const [name, url] of Object.entries(this.cfg.languages ?? {})) {\n this.langs.set(name, await this.loadLanguage(name, url));\n }\n }\n /** One language\'s worker half, or the Error that stands in for it \u2014 never a throw (see `setup()`). */\n async loadLanguage(name, url) {\n if (typeof url !== "string" || url.length === 0) {\n return new Error(`language "${name}" names no worker module URL in the pool config \\u2014 got ${brief(url)}`);\n }\n try {\n const lm = await import(url);\n if (typeof lm.default !== "function") {\n return new Error(`language "${name}"\'s worker module ${url} must export a default compile function (got ${typeof lm.default})`);\n }\n return lm.default;\n } catch (e) {\n return new Error(`language "${name}"\'s worker module ${url} did not load: ${messageOf(e)}`);\n }\n }\n /**\n * `FD-C5`: the inner worker receives `{ settings, input }` as its payload, and the scheduler\'s task context as its\n * second argument, passed through untouched (W159). `FD-C6`: when the\n * envelope carries a `transform`, `input` is the evaluation of that expression over the value\n * received, with the node\'s settings bound as `$settings`, and the third key is consumed here.\n */\n async run(payload, context) {\n if (this.setupFailure !== null) {\n throw new Error(`${this.setupFailure} \\u2014 every task on this worker fails with this until the pool is rebuilt`);\n }\n if (this.inner === null && !this.noInner) throw new Error("envelope worker: run() before setup() \\u2014 the inner worker is not loaded");\n const env = unpackEnvelope(payload);\n if (this.noInner) {\n if (env.transform === void 0) {\n throw new NotAnEnvelope("typed pool: this pool`s type names no worker, so the envelope must carry a transform \\u2014 there is nothing else for the task to do (FD-V46 refuses such a node at construction)");\n }\n return await this.transform(env.transform, env.input, env.settings);\n }\n const input = env.transform === void 0 ? env.input : await this.transform(env.transform, env.input, env.settings);\n return await this.inner.run({ settings: env.settings, input }, context);\n }\n /** Evaluates one declared transform; every failure is a `TransformFailed` naming what failed and why. */\n async transform(t, input, settings) {\n const compile = this.langs.get(t.lang);\n if (compile === void 0) {\n const carried = Object.keys(this.cfg.languages ?? {}).map((x) => `"${x}"`).join(", ") || "no languages";\n throw new TransformFailed(t, void 0, `language "${t.lang}" is not loaded in this pool (the pool config carries ${carried}) \\u2014 the bootstrap helper writes every language it is given into every pool, so this one was not in the registry the pool was built from`);\n }\n if (compile instanceof Error) {\n throw new TransformFailed(t, void 0, `${compile.message} \\u2014 every task transforming in "${t.lang}" on this worker fails with this until the pool is rebuilt`);\n }\n const key = JSON.stringify([t.lang, t.expr]);\n let compiled = this.compiled.get(key);\n if (compiled === void 0) {\n try {\n compiled = compile(t.expr);\n } catch (e) {\n throw new TransformFailed(t, codeOf(e), `the worker half of "${t.lang}" refused the expression the registry half accepted: ${messageOf(e)}`);\n }\n if (typeof compiled?.evaluate !== "function") {\n throw new TransformFailed(t, void 0, `the worker half of "${t.lang}" compiled to ${brief(compiled)}, which has no evaluate(input, settings)`);\n }\n this.compiled.set(key, compiled);\n }\n try {\n return await compiled.evaluate(input, settings);\n } catch (e) {\n throw new TransformFailed(t, codeOf(e), messageOf(e));\n }\n }\n async teardown() {\n await this.inner?.teardown?.();\n this.inner = null;\n this.noInner = false;\n this.setupFailure = null;\n this.langs.clear();\n this.compiled.clear();\n }\n};\nexport {\n NotAnEnvelope,\n TransformFailed,\n EnvelopeWorker as default,\n unpackEnvelope\n};\n'], { type: "text/javascript" }));
32
35
 
33
36
  // src/scheduler/bootstrap.ts
34
37
  var ENVELOPE_WORKER_URL = envelope_worker_default;
35
38
  var BootstrapError = class extends Error {
36
39
  };
37
40
  var defaultCreatePool = (scriptUrl, options) => new WorkerManager(scriptUrl, options);
38
- function transformLanguagesByType(flows) {
39
- const out = /* @__PURE__ */ new Map();
40
- for (const flow of flows) {
41
- for (const n of flow.nodes) {
42
- if (n.kind !== "task") continue;
43
- const t = n;
44
- if (t.type === void 0 || t.transform === void 0) continue;
45
- let set = out.get(t.type);
46
- if (set === void 0) {
47
- set = /* @__PURE__ */ new Set();
48
- out.set(t.type, set);
49
- }
50
- set.add(t.transform.lang);
51
- }
52
- }
53
- return out;
54
- }
55
41
  function bootstrap(registry, groups = {}, options = {}) {
56
42
  checkRegistry(registry);
57
43
  const workers = workersOf(registry.workers);
@@ -68,7 +54,7 @@ function bootstrap(registry, groups = {}, options = {}) {
68
54
  for (const [key, inst] of named) {
69
55
  const entry = types.get(inst.type);
70
56
  const def = isDerivation(entry) ? entry : nodeTypeFromDeclaration(entry);
71
- derived[key] = derivePool(key, inst.type, def, def.worker === void 0 ? void 0 : workers.get(def.worker), inst.config, groupKeys, ConfigRefused);
57
+ derived[key] = derivePool(key, inst.type, def, def.worker === void 0 ? void 0 : workers.get(def.worker), inst.config, groupKeys, ConfigRefused, inst.pool);
72
58
  }
73
59
  for (const [name, entry] of types) if (builtIn(name)) one(name, name, entry, (isDerivation(entry) ? entry : nodeTypeFromDeclaration(entry)).config, TypeDefaultRefused);
74
60
  const namedByPools = /* @__PURE__ */ new Set();
@@ -79,28 +65,18 @@ function bootstrap(registry, groups = {}, options = {}) {
79
65
  }
80
66
  }
81
67
  const adapters = languagesOf(options.languages);
82
- const byType = transformLanguagesByType(options.flows ?? []);
83
- const languagesFor = /* @__PURE__ */ new Map();
84
- for (const [name] of types) {
85
- const langs = byType.get(name);
86
- if (langs === void 0 || langs.size === 0) continue;
87
- const urls = {};
88
- for (const lang of [...langs].sort()) {
89
- const adapter = adapters.get(lang);
90
- if (adapter === void 0) {
91
- throw new BootstrapError(`node type "${name}" has a node declaring transform.lang "${lang}", and no adapter was given for it (given: ${[...adapters.keys()].sort().map((x) => `"${x}"`).join(", ") || "none"}) \u2014 the wrapper would meet the transform with nothing to evaluate it`);
92
- }
93
- if (typeof adapter.worker !== "string" || adapter.worker.length === 0) {
94
- throw new BootstrapError(`language "${lang}" names no worker module URL \u2014 LanguageAdapter.worker is the URL of the module whose default export compiles and evaluates it; a function cannot cross to a thread`);
95
- }
96
- urls[lang] = adapter.worker;
68
+ let languages;
69
+ for (const lang of [...adapters.keys()].sort()) {
70
+ const adapter = adapters.get(lang);
71
+ if (typeof adapter?.worker !== "string" || adapter.worker.length === 0) {
72
+ throw new BootstrapError(`language "${lang}" names no worker module URL \u2014 LanguageAdapter.worker is the URL of the module whose default export compiles and evaluates it; a function cannot cross to a thread`);
97
73
  }
98
- languagesFor.set(name, urls);
74
+ (languages ??= {})[lang] = adapter.worker;
99
75
  }
100
76
  const createPool = options.createPool ?? defaultCreatePool;
101
77
  const workerPools = {};
102
78
  for (const [key, p] of Object.entries(derived)) {
103
- workerPools[key] = createPool(ENVELOPE_WORKER_URL, poolOptionsOf(p.spec, p.definition.inline, p.worker?.url, languagesFor.get(p.type)));
79
+ workerPools[key] = createPool(ENVELOPE_WORKER_URL, poolOptionsOf(p.spec, p.definition.inline, p.worker?.url, languages === void 0 ? void 0 : { ...languages }));
104
80
  }
105
81
  return { workerPools, resourceGroups: { ...groups }, derived };
106
82
  }
@@ -122,10 +98,11 @@ var GroupUnusable = class extends RuntimeError {
122
98
  };
123
99
  var GroupNotBuildable = class extends RuntimeError {
124
100
  };
125
- var LanguageNotBuilt = class extends RuntimeError {
126
- };
127
101
  var RunIdInUse = class extends RuntimeError {
128
102
  };
103
+ var LimitKeyMissing = class extends RuntimeError {
104
+ };
105
+ var KEYED_RULE = "FD-R6";
129
106
  var BUILT_IN_GROUP_FACTORIES = {
130
107
  ConcurrentLimitGroup: (config, key) => new ConcurrentLimitGroup(config.limit, key),
131
108
  RateLimitGroup: (config, key) => new RateLimitGroup(config.windows, key),
@@ -161,6 +138,10 @@ function checkGroup(key, typeName, group) {
161
138
  return group;
162
139
  }
163
140
  function createRuntime(document, code = {}, options = {}) {
141
+ if (options.defaultRetryPolicy !== void 0) {
142
+ const problem = retryPolicyProblem(options.defaultRetryPolicy);
143
+ if (problem !== null) throw new InvalidDefinition(["FD-V30"], `defaultRetryPolicy is not a RetryPolicy \u2014 FD-V30: ${problem}`);
144
+ }
164
145
  const resolved = resolveRegistry(document, code);
165
146
  const suppliedPredicateNames = code.predicates === void 0 ? [] : code.predicates instanceof Map ? [...code.predicates.keys()] : code.predicates instanceof Set ? [...code.predicates] : Array.isArray(code.predicates) ? [...code.predicates] : Object.keys(code.predicates);
166
147
  for (const [what, named, given] of [
@@ -192,7 +173,7 @@ function createRuntime(document, code = {}, options = {}) {
192
173
  const nodeTypes = {};
193
174
  for (const [name, def] of resolved.nodeTypes) nodeTypes[name] = def;
194
175
  const pools = {};
195
- for (const [key, p] of resolved.pools) if (key !== p.type) pools[key] = { type: p.type, config: p.config };
176
+ for (const [key, p] of resolved.pools) if (key !== p.type) pools[key] = { type: p.type, config: p.config, ...p.pool !== void 0 ? { pool: p.pool } : {} };
196
177
  const languages = {};
197
178
  for (const [name, adapter] of resolved.languages) languages[name] = adapter;
198
179
  const namedByPools = {};
@@ -201,8 +182,7 @@ function createRuntime(document, code = {}, options = {}) {
201
182
  }
202
183
  const built = bootstrap({ workers, nodeTypes, pools }, namedByPools, {
203
184
  ...options.createPool !== void 0 ? { createPool: options.createPool } : {},
204
- languages,
205
- flows: options.flows ?? []
185
+ languages
206
186
  });
207
187
  const scheduler = (options.createScheduler ?? ((p, g) => new FyflowScheduler(p, g)))(built.workerPools, groups);
208
188
  const predicates = /* @__PURE__ */ new Map();
@@ -220,7 +200,40 @@ function createRuntime(document, code = {}, options = {}) {
220
200
  languages,
221
201
  ...code.gateTypes !== void 0 ? { gateTypes: code.gateTypes } : {}
222
202
  };
223
- const builtLanguages = transformLanguagesByType(options.flows ?? []);
203
+ const validatorRegistry = {
204
+ predicates: new Set(predicates.keys()),
205
+ groups: new Set(registry.groups ?? []),
206
+ nodeTypes: nodeTypesOf(registry.nodeTypes),
207
+ languages: languagesOf(registry.languages),
208
+ workers: workersOf(registry.workers),
209
+ pools: poolsOf(registry.pools),
210
+ ...registry.gateTypes !== void 0 ? { gateTypes: registry.gateTypes } : {}
211
+ };
212
+ const keyedByBuiltIn = /* @__PURE__ */ new Set();
213
+ for (const [key, g] of resolved.groups) if (g.type === "KeyedRateLimitGroup") keyedByBuiltIn.add(key);
214
+ const keyedFindings = (flow) => {
215
+ const nodes = flow?.nodes;
216
+ if (keyedByBuiltIn.size === 0 || typeof nodes?.[Symbol.iterator] !== "function") return [];
217
+ const out = [];
218
+ for (const node of nodes) {
219
+ if (typeof node !== "object" || node === null) continue;
220
+ const n = node;
221
+ if (n.kind !== "task") continue;
222
+ const poolKey = typeof n.pool === "string" ? n.pool : typeof n.type === "string" ? n.type : void 0;
223
+ const fromPool = poolKey === void 0 ? [] : resolved.pools.get(poolKey)?.spec.groups ?? [];
224
+ const fromNode = Array.isArray(n.workerGroups) ? n.workerGroups.filter((g) => typeof g === "string") : [];
225
+ const reached = [.../* @__PURE__ */ new Set([...fromPool, ...fromNode])].filter((g) => keyedByBuiltIn.has(g)).sort();
226
+ if (reached.length === 0) continue;
227
+ const at = typeof n.id === "string" ? n.id : null;
228
+ out.push({
229
+ rule: KEYED_RULE,
230
+ at,
231
+ detail: `node ${at === null ? "<unnamed>" : q(at)} reaches keyed group${reached.length > 1 ? "s" : ""} ${reached.map(q).join(", ")}, built by the built-in KeyedRateLimitGroup factory with no keyFrom \u2014 so every task of it needs a limitKey, which nothing sets unless start is given a createTask, and without one the scheduler's addTask throws "Missing limit key". Two roads work: pass start a createTask that sets limitKey on the task it builds, or declare the group with a code group type (code.groupTypes) whose create() builds a KeyedRateLimitGroup with a keyFrom that derives the key. A key derived from the flow itself is Q248, deferred`
232
+ });
233
+ }
234
+ return out;
235
+ };
236
+ const check = (flow) => [...validateDefinition(flow, validatorRegistry), ...keyedFindings(flow)];
224
237
  let minted = 0;
225
238
  const inUse = /* @__PURE__ */ new Set();
226
239
  const router = settlementRouter(scheduler);
@@ -237,6 +250,7 @@ function createRuntime(document, code = {}, options = {}) {
237
250
  scheduler,
238
251
  pools: built.derived,
239
252
  groups,
253
+ check,
240
254
  start(flow, payload, startOptions = {}) {
241
255
  return openRun(flow, payload, startOptions, null);
242
256
  },
@@ -252,14 +266,12 @@ function createRuntime(document, code = {}, options = {}) {
252
266
  };
253
267
  return runtime;
254
268
  function openRun(flow, payload, startOptions, from) {
255
- for (const [type, langs] of transformLanguagesByType([flow])) {
256
- if (!resolved.nodeTypes.has(type)) continue;
257
- const carried = builtLanguages.get(type) ?? /* @__PURE__ */ new Set();
258
- for (const lang of langs) {
259
- if (!carried.has(lang)) {
260
- throw new LanguageNotBuilt(type, `flow ${q(flow.flowId)} declares transform.lang ${q(lang)} on a node of type ${q(type)}, and this runtime's pools for that type carry ${carried.size ? [...carried].sort().map(q).join(", ") : "no language"} \u2014 pools are built once, so pass every flow this runtime will run as \`flows\` to createRuntime`);
261
- }
262
- }
269
+ const findings = check(flow);
270
+ const definition = findings.filter((f) => f.rule !== KEYED_RULE);
271
+ if (definition.length) throw InvalidDefinition.fromFindings(flow.flowId, definition);
272
+ const keyed = startOptions.createTask === void 0 ? findings.filter((f) => f.rule === KEYED_RULE) : [];
273
+ if (keyed.length) {
274
+ throw new LimitKeyMissing(keyed[0].at ?? "<document>", `flow "${flow.flowId}" cannot start with no createTask \u2014 ` + keyed.map((f) => `${f.rule} at ${f.at ?? "<document>"}: ${f.detail}`).join("; ") + " (ADR 0097)");
263
275
  }
264
276
  const runId = startOptions.runId ?? mint();
265
277
  if (inUse.has(runId)) {
@@ -269,16 +281,75 @@ function createRuntime(document, code = {}, options = {}) {
269
281
  const done = new Promise((resolve) => {
270
282
  settle = resolve;
271
283
  });
272
- let state = emptyState();
284
+ const acc = Accumulator.load(emptyState());
285
+ let cached = null;
286
+ const frozen = /* @__PURE__ */ new WeakSet();
273
287
  let settled = false;
274
288
  let seed = from === null ? null : from.events;
289
+ const own = (input) => {
290
+ if (input.kind !== "outcome" && input.kind !== "resolution") return input;
291
+ const r = input.record;
292
+ return typeof r.result === "object" && r.result !== null ? { kind: input.kind, record: structuredClone(r) } : input;
293
+ };
294
+ const take = (input) => {
295
+ if (acc.apply(own(input))) cached = null;
296
+ };
297
+ const catchUp = () => {
298
+ if (seed === null) return;
299
+ for (const stored of seed) take(stored);
300
+ seed = null;
301
+ };
302
+ const freezeDeep = (root) => {
303
+ const stack = [root];
304
+ while (stack.length > 0) {
305
+ const v = stack.pop();
306
+ if (typeof v !== "object" || v === null || frozen.has(v)) continue;
307
+ frozen.add(v);
308
+ if (ArrayBuffer.isView(v)) continue;
309
+ if (v instanceof Map) for (const [k, x] of v) stack.push(k, x);
310
+ else if (v instanceof Set) for (const x of v) stack.push(x);
311
+ for (const k of Reflect.ownKeys(v)) {
312
+ const d = Object.getOwnPropertyDescriptor(v, k);
313
+ if (d !== void 0 && "value" in d) stack.push(d.value);
314
+ }
315
+ Object.freeze(v);
316
+ }
317
+ return root;
318
+ };
319
+ const current = () => {
320
+ catchUp();
321
+ return cached ??= freezeDeep(acc.materialise());
322
+ };
275
323
  const settleIfTerminal = () => {
276
- if (settled || !isTerminal(state)) return;
324
+ if (settled || !isTerminal({ outcome: acc.outcomeNow() })) return;
277
325
  settled = true;
278
- settle(state);
326
+ const at = current();
327
+ if (at.outcome === "interrupted" && stopping.size > 0) {
328
+ const fin = () => {
329
+ settle(at);
330
+ router.release(runId);
331
+ };
332
+ void Promise.allSettled([...stopping]).then(fin);
333
+ return;
334
+ }
335
+ settle(at);
279
336
  router.release(runId);
280
337
  };
281
- const scheduler2 = router.forRun(runId);
338
+ const view = router.forRun(runId);
339
+ const stopping = /* @__PURE__ */ new Set();
340
+ const viewCancel = view.cancel;
341
+ const scheduler2 = typeof viewCancel === "function" ? {
342
+ ...view,
343
+ cancel: (id, cancelOptions) => {
344
+ const p = viewCancel(id, cancelOptions);
345
+ stopping.add(p);
346
+ const drop = () => {
347
+ stopping.delete(p);
348
+ };
349
+ p.then(drop, drop);
350
+ return p;
351
+ }
352
+ } : view;
282
353
  const driver = new Driver(flow, {
283
354
  runId,
284
355
  scheduler: scheduler2,
@@ -298,16 +369,18 @@ function createRuntime(document, code = {}, options = {}) {
298
369
  // of a run from its first, and it is the well-formed wrong answer the first time a driver's
299
370
  // sequence does not start at this callback's first call.
300
371
  onFoldInput: (input, seq) => {
301
- if (seed !== null) {
302
- for (const stored of seed) state = step(state, stored);
303
- seed = null;
304
- }
305
- state = step(state, input);
372
+ catchUp();
373
+ take(input);
306
374
  queueMicrotask(settleIfTerminal);
307
375
  startOptions.onFoldInput?.(input, seq);
308
376
  },
377
+ ...startOptions.onHeader !== void 0 ? { onHeader: startOptions.onHeader } : {},
309
378
  ...startOptions.onRegionEvent !== void 0 ? { onRegionEvent: startOptions.onRegionEvent } : {},
310
379
  ...startOptions.onRunFailure !== void 0 ? { onRunFailure: startOptions.onRunFailure } : {},
380
+ // **Spread only when given** (`RN-R42`): the driver subscribes the pass-through events for a callback present, so an
381
+ // `undefined` forwarded here must stay ABSENT — and on a runtime the scheduler is subscribed once the first run asks.
382
+ ...startOptions.onTaskMessage !== void 0 ? { onTaskMessage: startOptions.onTaskMessage } : {},
383
+ ...startOptions.onTaskProgress !== void 0 ? { onTaskProgress: startOptions.onTaskProgress } : {},
311
384
  ...startOptions.onUnsatisfiableJoin !== void 0 ? { onUnsatisfiableJoin: startOptions.onUnsatisfiableJoin } : {},
312
385
  ...startOptions.onUnmintedTask !== void 0 ? { onUnmintedTask: startOptions.onUnmintedTask } : {},
313
386
  ...startOptions.onNonconformingResult !== void 0 ? { onNonconformingResult: startOptions.onNonconformingResult } : {},
@@ -321,7 +394,22 @@ function createRuntime(document, code = {}, options = {}) {
321
394
  router.release(runId);
322
395
  throw e;
323
396
  }
324
- return { driver, done, header: driver.checkpoint().header };
397
+ return {
398
+ driver,
399
+ done,
400
+ header: driver.checkpoint().header,
401
+ stop: (stopOptions) => driver.stop(stopOptions),
402
+ state: current,
403
+ outcome: () => {
404
+ catchUp();
405
+ return acc.outcomeNow();
406
+ },
407
+ node: (taskId) => {
408
+ catchUp();
409
+ const entry = acc.entry(taskId);
410
+ return entry === void 0 ? null : freezeDeep(entry);
411
+ }
412
+ };
325
413
  }
326
414
  }
327
415
  function settlementRouter(scheduler) {
@@ -329,7 +417,8 @@ function settlementRouter(scheduler) {
329
417
  const every = /* @__PURE__ */ new Map();
330
418
  const deliver = (name) => (e) => {
331
419
  const detail = "detail" in e ? e.detail : void 0;
332
- const id = typeof detail === "object" && detail !== null ? detail.id : void 0;
420
+ const d = typeof detail === "object" && detail !== null ? detail : void 0;
421
+ const id = typeof d?.id === "string" ? d.id : d?.taskId;
333
422
  let runId = null;
334
423
  if (typeof id === "string") {
335
424
  try {
@@ -355,7 +444,9 @@ function settlementRouter(scheduler) {
355
444
  };
356
445
  return {
357
446
  forRun(runId) {
447
+ const cancel = scheduler.cancel;
358
448
  return {
449
+ ...typeof cancel === "function" ? { cancel: (id, options) => cancel.call(scheduler, id, options) } : {},
359
450
  addTask: (task, options) => scheduler.addTask(task, options),
360
451
  addEventListener(name, listener) {
361
452
  let mine = byRun.get(runId);
@@ -589,7 +680,8 @@ var EnvelopeWorker = class {
589
680
  }
590
681
  }
591
682
  /**
592
- * `FD-C5`: the inner worker receives `{ settings, input }` and nothing else. `FD-C6`: when the
683
+ * `FD-C5`: the inner worker receives `{ settings, input }` as its payload, and the scheduler's task context as its
684
+ * second argument, passed through untouched (W159). `FD-C6`: when the
593
685
  * envelope carries a `transform`, `input` is the evaluation of that expression over the value
594
686
  * received, with the node's settings bound as `$settings`, and the third key is consumed here.
595
687
  */
@@ -613,7 +705,7 @@ var EnvelopeWorker = class {
613
705
  const compile = this.langs.get(t.lang);
614
706
  if (compile === void 0) {
615
707
  const carried = Object.keys(this.cfg.languages ?? {}).map((x) => `"${x}"`).join(", ") || "no languages";
616
- throw new TransformFailed(t, void 0, `language "${t.lang}" is not loaded in this pool (the pool config carries ${carried}) \u2014 the bootstrap helper writes an adapter URL for a type some node of which declares a transform, and needs the flow and the languages to do it`);
708
+ throw new TransformFailed(t, void 0, `language "${t.lang}" is not loaded in this pool (the pool config carries ${carried}) \u2014 the bootstrap helper writes every language it is given into every pool, so this one was not in the registry the pool was built from`);
617
709
  }
618
710
  if (compile instanceof Error) {
619
711
  throw new TransformFailed(t, void 0, `${compile.message} \u2014 every task transforming in "${t.lang}" on this worker fails with this until the pool is rebuilt`);
@@ -654,13 +746,12 @@ export {
654
746
  GROUP_MEMBERS,
655
747
  GroupNotBuildable,
656
748
  GroupUnusable,
657
- LanguageNotBuilt,
749
+ LimitKeyMissing,
658
750
  NotAnEnvelope,
659
751
  RunIdInUse,
660
752
  RuntimeError,
661
753
  TransformFailed,
662
754
  bootstrap,
663
755
  createRuntime,
664
- transformLanguagesByType,
665
756
  unpackEnvelope
666
757
  };