fyflow-orchestration 0.7.0 → 0.8.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.
@@ -231,6 +231,17 @@ export declare function branchPathOf(regions: RegionSets[], nodeId: string, item
231
231
  * does.
232
232
  */
233
233
  export declare function regionChainOf(regions: RegionSets[], nodeId: string): string[];
234
+ /**
235
+ * **The map instance a node is IN — the innermost `map` region containing it as a MEMBER** —
236
+ * `spec/flow-definition.md` v0.45, P38 (c), [ADR 0118](../../doc/decisions/0118-a-map-instance-keeps-its-item-while-a-node-of-it-may-still-be-entered.md).
237
+ * What `"$item"` names: the item of the instance this node is entered in. **The region chain's own walk**
238
+ * (`regionChainOf`), read innermost-first for the first region a `map` opened — so a node in a `fanout`
239
+ * nested inside a map instance names the MAP's item, and a `map` NODE names its ENCLOSING instance's item,
240
+ * because an opener is never a member of the region it opens (its branch cannot reach it, `FD-V10`).
241
+ * `undefined` for a node no map region contains — `FD-V49`'s refusal, and the one answer the validator,
242
+ * the driver and the wiring check all read from here, so the three cannot disagree about which item it is.
243
+ */
244
+ export declare function innermostMapOf(regions: RegionSets[], nodeId: string): RegionSets | undefined;
234
245
  /**
235
246
  * **How many leading `branchPath` segments the two nodes have anything to compare
236
247
  * on** — the length of the COMMON PREFIX of their two region chains, and the whole
@@ -327,7 +338,7 @@ export declare function branchIndexOf(regions: RegionSets[], nodeId: string): nu
327
338
  *
328
339
  * | out-arcs of | are | because |
329
340
  * |---|---|---|
330
- * | a `task`'s `edges` | **disjunctive** — exactly one is taken | section 3's routing rule 2 |
341
+ * | a `task`'s `edges`, and — v0.42 — an `ask`'s | **disjunctive** — exactly one is taken | section 3's routing rule 2; an ask routes as a task's settlement does (P35 (h)) |
331
342
  * | a `fanout`'s `branches`, a `map`'s `branch` | **conjunctive** — all are entered | `JP-R5`, which emits every `branch.opened` before any dispatch |
332
343
  *
333
344
  * So a branch entry never *dominates* its own join — every path to the join goes
@@ -79,14 +79,20 @@ export declare class UndeclaredKey extends RegistryError {
79
79
  /** A predicate or language the document NAMES that `code` did not supply, or a group a type names that the document lacks. */
80
80
  export declare class NameMissing extends RegistryError {
81
81
  }
82
- /** A derivation returned something that is not a `PoolSpec`, threw, or produced a pool the runtime refuses (a resident group beside `idleTimeout: 0`). */
82
+ /**
83
+ * A derivation returned something that is not a `PoolSpec`, threw, or produced a pool the runtime refuses: a resident group
84
+ * beside `idleTimeout: 0`, or — since v0.39 ([Q619](../../doc/questions/q619.md) option B) — a group of the built-in
85
+ * `KeyedRateLimitGroup` type held in `residentGroups`, which the scheduler would count in one `""` bucket.
86
+ */
83
87
  export declare class DerivationRefused extends RegistryError {
84
88
  }
85
89
  /**
86
90
  * **A registered GATE type is not a `GateType`** — v0.23 (W92, ADR 0066): not an object; a key beside the
87
- * interface's five; `routes` empty, repeated or outside `FD-V16`'s charset; a `defaultRoute` that is not one
88
- * of them; a `settingsSchema` outside the subset; a `keyResolvedRoutes` that is not an array of its own
89
- * routes, or repeats one (v0.25); or no `fn`. `gateTypeProblem` is the predicate, shared with
91
+ * interface's seven; **static routes AND `routesFor`, or neither** (v0.30, ADR 0092); a `routesFor` or a
92
+ * `settingsProblem` that is not a function (v0.30); `routes` empty, repeated or outside `FD-V16`'s charset; a
93
+ * `defaultRoute` that is not one of them; a `settingsSchema` outside the subset; a `keyResolvedRoutes` that is not
94
+ * an array of its own routes, or repeats one (v0.25); or no `fn`. What a derived type's `routesFor` RETURNS is
95
+ * held at the gate, `FD-V39`, since only a gate has settings. `gateTypeProblem` is the predicate, shared with
90
96
  * `FD-V39`'s second clause, so the registry and the validator cannot disagree about what a gate type IS.
91
97
  */
92
98
  export declare class GateTypeRefused extends RegistryError {
@@ -110,12 +116,21 @@ export declare const WORKER_KEYS: readonly string[];
110
116
  export declare const CHECKED_WORKER_SCHEMAS: readonly string[];
111
117
  /** A document node type's keys. */
112
118
  export declare const NODE_TYPE_DECLARATION_KEYS: readonly string[];
113
- /** A `PoolSpec`'s keys — what a derivation may return. */
119
+ /** A `PoolSpec`'s keys — what a derivation may return. `cancelGraceMs` and `messages` since v0.33 (P33 decision 6, W159). */
114
120
  export declare const POOL_SPEC_KEYS: readonly string[];
115
- /** A document type's `pool` field: the `PoolSpec` minus what the type states beside it. */
121
+ /** A `PoolSpec.messages` object's keys — the scheduler's `MessageOptions`, v0.33. */
122
+ export declare const MESSAGE_OPTION_KEYS: readonly string[];
123
+ /** A document type's `pool` field: the `PoolSpec` minus what the type states beside it — and, since v0.37, a named instance's `pool` (ADR 0099). */
116
124
  export declare const DECLARED_POOL_KEYS: readonly string[];
117
- /** A pool instance's and a group instance's keys. */
125
+ /** A GROUP instance's keys — and the two every instance shares. */
118
126
  export declare const INSTANCE_KEYS: readonly string[];
127
+ /**
128
+ * **A POOL instance's keys: the two, and `pool`** — v0.37,
129
+ * [ADR 0099](../../doc/decisions/0099-a-named-instance-of-a-document-type-may-declare-its-own-pool-options.md),
130
+ * [W165](../../doc/work-items/w165-pool-options-on-a-named-pool-instance.md): the instance's OWN pool options, the
131
+ * `DECLARED_POOL_KEYS`, on an instance of a DOCUMENT type only. A group instance still declares the two alone.
132
+ */
133
+ export declare const POOL_INSTANCE_KEYS: readonly string[];
119
134
  /** A class-form entry is the one whose `pool` is a FUNCTION; a document form's `pool` is an options object or absent. */
120
135
  export declare function isDerivation(t: NodeTypeEntry): t is NodeTypeDefinition;
121
136
  /**
@@ -125,19 +140,38 @@ export declare function isDerivation(t: NodeTypeEntry): t is NodeTypeDefinition;
125
140
  * deliberately not offered — a class is where any other rule lives.
126
141
  */
127
142
  export declare function mergeConfig(base: unknown, over: unknown): unknown;
143
+ /** The pool options a document type declares, and since v0.37 a named instance of one (ADR 0099). */
144
+ type DeclaredPool = NonNullable<NodeTypeDeclaration['pool']>;
128
145
  /**
129
146
  * **The document form of a node type IS the identity derivation** — decision 2: `pool(config)`
130
147
  * merges the instance's config over the declared one, and copies the declared pool fields and the
131
148
  * declared groups. Keys are copied only when declared, so an absent `groups` stays absent (fact 5).
132
149
  * The declaration's shape is checked by `resolveRegistry` before this is called; here it is trusted.
150
+ *
151
+ * **`over` is a NAMED INSTANCE's own pool options** (v0.37, ADR 0099 rules 1-3): merged over the declared `pool` by
152
+ * `mergePool`, and the result is what this derivation hands on. Absent — every type's default instance, and every
153
+ * instance that declares none — the derivation is the one it has always been, option for option (P18 fact 5).
133
154
  */
134
- export declare function nodeTypeFromDeclaration(decl: NodeTypeDeclaration): NodeTypeDefinition;
155
+ export declare function nodeTypeFromDeclaration(decl: NodeTypeDeclaration, over?: DeclaredPool): NodeTypeDefinition;
135
156
  /**
136
157
  * **Why a derivation's output is not a `PoolSpec`, or `null`** — the check BOTH forms pass through
137
158
  * (decision 2's *a class cannot compute around a check*). The scheduler coerces or ignores a wrong
138
159
  * option in silence exactly as it does a retry policy (`FD-V30`'s measurements), so the shape is
139
160
  * pinned here: numbers where numbers, a string list for `groups`, a string list or a `{ key: cost }`
140
- * record for `residentGroups`, and no key beside the six.
161
+ * record for `residentGroups`, and no key beside the eight.
162
+ *
163
+ * **`cancelGraceMs` and `messages` (v0.33, P33 decision 6, W159) are held to the SCHEDULER's own rules**, read
164
+ * at the pin in its `validateGraceMs` and `resolveMessageOptions`, where it throws at `new WorkerManager`:
165
+ * `cancelGraceMs` a number `>= 0` — which `NaN` is not, so no clause of its own refuses it — and `Infinity` admitted, the scheduler's *never force*; `messages` a
166
+ * plain object of `flushIntervalMs` (finite, `> 0`) and `maxBuffered` (an integer `>= 1`), each optional. **A
167
+ * `messages` key that is PRESENT is held whatever its value**, `undefined` included, unlike the top-level keys
168
+ * here: the scheduler reads `cancelGraceMs ?? 2000` but spreads `messages` over its defaults, so a present
169
+ * `undefined` replaces a default and it throws. A key beside the two is refused, as everywhere in this module —
170
+ * and it is not merely ignored by the scheduler: the resolved object is carried to every worker, so on a
171
+ * THREADED pool a stray key holding a value that cannot be cloned fails the task at run time with *could not be
172
+ * cloned* (W159's conformity reader, F1). Refusing it here moves that failure to construction. **Stricter than
173
+ * the scheduler, where it is silent**: a `messages` that is not a plain object (it spreads one into its
174
+ * defaults and reads nothing), and a `cancelGraceMs` of `null` (it reads `?? 2000`), are refused here.
141
175
  */
142
176
  export declare function poolSpecProblem(v: unknown): string | null;
143
177
  /** The group keys a `PoolSpec` names, from both fields — what must exist in `groups`. */
@@ -160,8 +194,15 @@ export declare function poolOptionsOf(spec: PoolSpec, inline: boolean | undefine
160
194
  * its output held to `PoolSpec`'s shape, the derived config against the WORKER's `configSchema`,
161
195
  * every group it names present, and decision 9's refusal — a resident group beside `idleTimeout: 0`
162
196
  * deadlocks a contended group (P18 fact 6, the scheduler reference's own sentence).
197
+ *
198
+ * **`pool` is a named instance's OWN pool options** (v0.37, ADR 0099): the instance is derived by its type's identity
199
+ * derivation with them merged over the type's declared ones (`nodeTypeFromDeclaration(declaration, pool)`), and the
200
+ * OUTPUT takes every check below exactly as any derivation's does — P37 decision 9's *through `derivePool`'s one check*.
201
+ * Its own shape, and its type being a DOCUMENT type, were held by the registry's shape check before this is called, on
202
+ * both roads. The returned `definition` is the TYPE's, as for every instance; `pool` is carried, copied, so a caller that
203
+ * hands the instance on (`createRuntime`, to the bootstrap helper) hands on what was resolved.
163
204
  */
164
- export declare function derivePool(key: string, typeName: string, def: NodeTypeDefinition, worker: WorkerEntry | undefined, config: unknown, groupKeys: ReadonlySet<string>, refuse: new (at: string, m: string) => RegistryError): ResolvedPool;
205
+ export declare function derivePool(key: string, typeName: string, def: NodeTypeDefinition, worker: WorkerEntry | undefined, config: unknown, groupKeys: ReadonlySet<string>, refuse: new (at: string, m: string) => RegistryError, pool?: unknown): ResolvedPool;
165
206
  export declare function checkRegistry(reg: {
166
207
  workers?: Workers;
167
208
  nodeTypes?: NodeTypes;
@@ -183,3 +224,4 @@ export declare function checkGateTypes(x: GateTypes | undefined): void;
183
224
  * schemas, derived `PoolSpec`s and the adapters and factories as given.
184
225
  */
185
226
  export declare function resolveRegistry(doc: RegistryDocument, code?: RegistryCode): ResolvedRegistry;
227
+ export {};
@@ -7,7 +7,8 @@
7
7
  *
8
8
  * A worker entry in the registry document declares up to four schemas (`configSchema`,
9
9
  * `settingsSchema`, `inputSchema`, `outputSchema`); a group type declares one; a node-type class may
10
- * declare one. A worker's first two, a group type's and a class's are CHECKED — at `createRuntime`
10
+ * declare one; and since v0.42 an `ask` NODE in a flow may declare one, `answerSchema`, held to this subset
11
+ * by `schemaProblem` when the flow is validated (`FD-V48`, W179) rather than when a registry is built. A worker's first two, a group type's and a class's are CHECKED — at `createRuntime`
11
12
  * and at `new Driver` — and a worker's last two are declared, carried, enforced on ONE value per run
12
13
  * each since v0.21 (the entry payload at `start()`, a person's resolution at `resolve()` — `FD-R4`, W73),
13
14
  * never per settlement, and since v0.20 confirmed on a run by `scripts/check-workers.mjs` (P19, whose name is *what a schema
@@ -22,8 +23,11 @@
22
23
  * shape one layer up: a document that says one thing and a validator that checks another, at exit 0.
23
24
  * The refusal names the keyword and the path, so a consumer meets a refusal rather than a pass, and
24
25
  * prediction 4's reversal (a real worker needing a keyword outside the subset) is legible the day it
25
- * happens. Annotation keywords (`title`, `description`, `$schema`, `$comment`) are outside the subset
26
- * too and are refused the same way — a consequence ADR 0053 states rather than a whitelist it invents.
26
+ * happens. **ANNOTATION keywords are inside it too** — `examples` (v0.20, ADR 0056) and, since v0.36,
27
+ * `title`, `description`, `readOnly` and `writeOnly`
28
+ * ([ADR 0098](../../doc/decisions/0098-four-annotations-join-the-schema-subset-and-constrain-nothing.md)) —
29
+ * each with its VALUE SHAPE checked and none read by a value check; every other annotation
30
+ * (`deprecated`, `$comment`, `default`, `format`, `$schema`, `$id`) is outside it and refused the same way.
27
31
  *
28
32
  * **What `type` means over JSON data.** `number` and `integer` admit FINITE numbers only: JSON text
29
33
  * cannot carry `NaN` or an infinity, so a schema over JSON data has no reading of them but *not a
@@ -41,15 +45,15 @@
41
45
  /** A JSON Schema in the subset — data, as everything in a registry document is. Checked by `schemaProblem`. */
42
46
  export type JsonSchema = Readonly<Record<string, unknown>>;
43
47
  /**
44
- * **The subset, enumerated** — the twelve keywords P18 fact 1 measured as covering the sample's and
45
- * the corpus's schemas (14 of 14 cases), **and `examples`, the thirteenth, an ANNOTATION** —
48
+ * **The subset, enumerated** — the CONSTRAINING keywords P18 fact 1 measured as covering the sample's
49
+ * and the corpus's schemas, **and the ANNOTATIONS**, first `examples` —
46
50
  * [ADR 0056](../../doc/decisions/0056-an-example-is-an-annotation-validated-against-the-schema-it-sits-on.md),
47
51
  * [W72](../../doc/work-items/w72-the-contract-an-example-beside-every-schema-and-a-worker-run-on-it.md),
48
52
  * the plan's first deliberate extension of the subset. `spec/flow-definition.md` section 3's table
49
- * names the same thirteen, and `test/definition/schema.test.ts` compares the two lists both ways
50
- * (rule 8 obligation (e)'s third shape — a list is a claim about another list).
53
+ * names the same keywords and marks which annotate, and `test/definition/schema.test.ts` compares the
54
+ * two both ways (rule 8 obligation (e)'s third shape — a list is a claim about another list).
51
55
  *
52
- * `examples` differs from the other twelve in ONE way, and the difference is the decision: it
56
+ * `examples` differs from the constraining keywords in ONE way, and the difference is the decision: it
53
57
  * constrains NOTHING when a value is checked (`findings` never reads it) and is itself HELD to the
54
58
  * schema it sits on at construction (`schemaProblem` runs `findings` over each declared example) —
55
59
  * so a worker's declared example is a value the check-time tools may RUN the worker on, and an
@@ -59,6 +63,14 @@ export type JsonSchema = Readonly<Record<string, unknown>>;
59
63
  * self-consistent. The rule is optional — an absent `examples` refuses nothing (the maintainer's
60
64
  * choice, W72's stop-and-ask 1) — and an EMPTY one is refused, because a declared-and-empty list
61
65
  * is the silent kind (`enum`'s own reason).
66
+ *
67
+ * **And the annotations of v0.36** — `title` and `description` (a string), `readOnly` and
68
+ * `writeOnly` (a boolean) —
69
+ * [ADR 0098](../../doc/decisions/0098-four-annotations-join-the-schema-subset-and-constrain-nothing.md),
70
+ * P37 decision 4. Their bar: *an annotation is admitted if it constrains nothing in JSON Schema itself
71
+ * and its value shape is checked*. `schemaProblem` checks the shape; `findings` reads none of them, so
72
+ * **`writeOnly` hides nothing and promises no redaction**, and `readOnly` refuses nothing. They are in
73
+ * THIS list rather than a second one, so the spec's table binds them as it binds every other keyword.
62
74
  */
63
75
  export declare const SCHEMA_KEYWORDS: readonly string[];
64
76
  /** The type names `type` may use — JSON Schema's seven, `integer` included. */
@@ -8,26 +8,42 @@
8
8
  import type { ResourceGroup } from 'fyflow-scheduler';
9
9
  import type { JsonSchema } from './schema.js';
10
10
  export type { JsonSchema } from './schema.js';
11
- /** The terminal states an edge may opt into. `user_action` is refused: it suspends (FD-V18). */
11
+ /**
12
+ * The terminal states an edge may opt into. `user_action` is refused: it suspends (FD-V18). **And
13
+ * `cancelled` is refused too** — a cancel is the consumer's own act and halts the run rather than
14
+ * routing it ([ADR 0094](../../doc/decisions/0094-a-cancelled-task-is-a-terminal-state-that-routes-no-edge-and-ends-its-run-cancelled.md)
15
+ * rule 1), so `FD-V18` still admits exactly these two.
16
+ */
12
17
  export type RoutableState = 'done' | 'failed';
13
- /** Every terminal state a scheduler task can reach. Five library states, three terminal. */
14
- export type TerminalState = 'done' | 'failed' | 'user_action';
18
+ /**
19
+ * Every terminal state a scheduler task can reach. **Six library states, four terminal** — `cancelled`
20
+ * since the scheduler pin W157 moved to, which ends a task through `scheduler.cancel()` alone
21
+ * ([ADR 0094](../../doc/decisions/0094-a-cancelled-task-is-a-terminal-state-that-routes-no-edge-and-ends-its-run-cancelled.md),
22
+ * W158). **Widening this union is BREAKING for a consumer switching on it exhaustively**, on
23
+ * `resumed`'s and `gated`'s precedent: such a switch stops compiling, and one comparing strings does
24
+ * not notice.
25
+ */
26
+ export type TerminalState = 'done' | 'failed' | 'user_action' | 'cancelled';
15
27
  /**
16
28
  * **A declared reference to something the run has produced** —
17
29
  * `spec/flow-definition.md` section 3's `needs`, and
18
30
  * [ADR 0040](../../doc/decisions/0040-what-a-node-can-reach-is-the-runs-own-outputs-and-a-node-id-names-them-relative-to-a-branch-path.md).
19
31
  *
20
- * Three forms, all of them data (ADR 0002 rule 1 — nothing here is a function and
32
+ * Four forms, all of them data (ADR 0002 rule 1 — nothing here is a function and
21
33
  * everything survives `JSON.parse(JSON.stringify(x))`):
22
34
  *
23
35
  * | | |
24
36
  * |---|---|
25
- * | `"."` | what this node would have received anyway — the ordinary edge's value, and for a `map` instance its own item (`JP-R11`). The one token outside `FD-V16`'s charset, so it can never collide with a node id |
37
+ * | `"."` | what this node would have received anyway — the ordinary edge's value, and for a `map` instance its own item (`JP-R11`). Outside `FD-V16`'s charset, so it can never collide with a node id |
38
+ * | `"$payload"` | **the value the RUN entered with** — what `start()` was handed, from any node and any edge (v0.44, [ADR 0117](../../doc/decisions/0117-a-reference-may-name-what-reached-the-run.md)). Always there, so no `?` and no array form. `$` is outside `FD-V16`'s charset, and the whole `$` prefix is reserved: `"$item"` — a map instance's item — is W191's, and any other `$` spelling is `FD-V26` |
26
39
  * | `"score"` | that node's output **on this node's own branch path**. Exactly one, or the run stops (`FD-R3`) |
27
40
  * | `["score"]` | that node's outputs as an **array**, in ascending segment-wise-numeric `branchPath` order. Legal only where the reader is outside a region containing the named node (`FD-V28`) |
28
41
  *
29
42
  * **The array form uses JSON's own array syntax rather than a sigil**, so the
30
- * grammar needs exactly one invented token (`"."`) rather than two.
43
+ * grammar invents tokens only for what has no node id: `"."`, the `?` modifier
44
+ * (v0.13), and — v0.44 — the `$` prefix. *(This read *"exactly one invented token"*
45
+ * until v0.13 and stayed so until W190.)* A `string` covers every token, so the
46
+ * TYPE does not move; `referenceTarget`'s parse does.
31
47
  */
32
48
  export type Reference = string | readonly [string];
33
49
  /**
@@ -37,8 +53,9 @@ export type Reference = string | readonly [string];
37
53
  * object this describes; a node that does not receives what section 3 says an
38
54
  * ordinary edge carries, unchanged.
39
55
  *
40
- * Only a `task` and a `map` may carry it: `FD-V17` already refuses an undeclared
41
- * key on the other three kinds, so that clause needs no rule of its own.
56
+ * Only a `task`, a `map` and — since v0.42 — an `ask` may carry it: `FD-V17` already refuses an
57
+ * undeclared key on the other four kinds, so that clause needs no rule of its own. *(This read "the
58
+ * other three", stale since v0.23's `gate`.)*
42
59
  */
43
60
  export type Needs = Readonly<Record<string, Reference>>;
44
61
  /**
@@ -223,13 +240,7 @@ export type Workers = ReadonlyMap<string, WorkerEntry> | Readonly<Record<string,
223
240
  * validator reads of one is its `type` (`FD-V38`); a `PoolInstance` and a resolved pool both fit.
224
241
  * **Absent means EMPTY**; a type's DEFAULT instance is implicit and never listed here.
225
242
  */
226
- export type Pools = ReadonlyMap<string, {
227
- readonly type: string;
228
- readonly config?: unknown;
229
- }> | Readonly<Record<string, {
230
- readonly type: string;
231
- readonly config?: unknown;
232
- }>>;
243
+ export type Pools = ReadonlyMap<string, Readonly<PoolInstance>> | Readonly<Record<string, Readonly<PoolInstance>>>;
233
244
  /**
234
245
  * **A declared transform: a language NAME and an expression in it** — `spec/flow-definition.md`
235
246
  * section 2's `transform` subsection (v0.18). Data, as everything in a definition is (ADR 0002
@@ -306,6 +317,25 @@ export interface PoolSpec {
306
317
  groups?: readonly string[];
307
318
  /** Group keys held for the WORKER's lifetime rather than per task, at a cost each — never beside `idleTimeout: 0` (decision 9). */
308
319
  residentGroups?: readonly string[] | Readonly<Record<string, number>>;
320
+ /**
321
+ * **Threaded pools: the ms a cancelled task's worker gets to stop by itself before it is forced** — the
322
+ * scheduler's own option, declarable since `spec/flow-definition.md` v0.33 (P33 decision 6, W159). Zero or
323
+ * more, `Infinity` included (the scheduler's *never force*); accepted, and ignored by the scheduler, on an
324
+ * inline pool.
325
+ */
326
+ cancelGraceMs?: number;
327
+ /**
328
+ * **The bounds on a worker's `context.emit()`** — the scheduler's `messages` option, declarable since v0.33
329
+ * (P33 decision 6, W159): events batched for up to `flushIntervalMs` (a finite number > 0), at most
330
+ * `maxBuffered` (an integer >= 1) per buffer between flushes. Each key optional; a key PRESENT is held to its
331
+ * rule whatever its value, `undefined` included, because the scheduler spreads this object over its defaults
332
+ * and a present `undefined` is what it throws on. Written out rather than imported from the scheduler, as
333
+ * `POOL_SPEC_KEYS` is a literal: a key the scheduler adds is admitted by a version of the spec, not by a bump.
334
+ */
335
+ messages?: Readonly<{
336
+ flushIntervalMs?: number;
337
+ maxBuffered?: number;
338
+ }>;
309
339
  }
310
340
  /**
311
341
  * **A node type as a DERIVATION from an instance's config to pool options** — decision 2, the CLASS
@@ -343,7 +373,8 @@ export interface NodeTypeDefinition {
343
373
  * a worker entry; `inline` is the type's; `config` is the default instance's constructor argument;
344
374
  * `pool` carries the declared pool options (a `PoolSpec` minus `config` and `groups`); `groups` are
345
375
  * the pool's group keys; `retryPolicy` and `settings` are what the type declares FOR its nodes. An
346
- * instance's `config` merges shallowly over `config`; every other field is copied as declared.
376
+ * instance's `config` merges shallowly over `config`, and since v0.37 a named instance's own `pool` merges shallowly
377
+ * over `pool` (ADR 0099); every other field is copied as declared, every object in it copied.
347
378
  */
348
379
  export interface NodeTypeDeclaration {
349
380
  /** As `NodeTypeDefinition.worker`: absent only on a built-in that only transforms (ADR 0075). */
@@ -363,6 +394,13 @@ export interface PoolInstance {
363
394
  type: string;
364
395
  /** The instance's config — the derivation's input, merged over the type's for a document type. */
365
396
  config?: unknown;
397
+ /**
398
+ * **The instance's OWN pool options** — v0.37, [ADR 0099](../../doc/decisions/0099-a-named-instance-of-a-document-type-may-declare-its-own-pool-options.md):
399
+ * the keys a type's `pool` may declare, merged SHALLOWLY over its type's (the instance's key winning, a key present
400
+ * with `undefined` absent), every object in them copied. **On an instance of a DOCUMENT type only** — one of a CLASS
401
+ * type is refused (`UndeclaredKey`): the class receives the instance key and owns how its pool is built.
402
+ */
403
+ pool?: NodeTypeDeclaration['pool'];
366
404
  }
367
405
  /** A resource group the runtime constructs — an instance of a group TYPE, by name, with the config its factory's schema admits. */
368
406
  export interface GroupInstance {
@@ -434,6 +472,8 @@ export interface ResolvedPool {
434
472
  config: unknown;
435
473
  /** What the derivation returned, held to `PoolSpec`'s shape and the worker's `configSchema`. */
436
474
  spec: PoolSpec;
475
+ /** A named instance's OWN pool options, COPIED, a key present with `undefined` dropped (it overrides nothing) — v0.37, ADR 0099; absent where it declares none, and on every default instance. */
476
+ pool?: NodeTypeDeclaration['pool'];
437
477
  }
438
478
  /** One resource group, resolved: its factory and its checked config. `create` has NOT been called. */
439
479
  export interface ResolvedGroup {
@@ -552,7 +592,7 @@ export interface MapNode {
552
592
  * against whatever that resolves to.
553
593
  */
554
594
  over: string;
555
- /** **One** branch entry id, whose kind is a `task` (`FD-V25`). Not a list. */
595
+ /** **One** branch entry id, whose kind is a `task` or — since v0.42 — an `ask` (`FD-V25`). Not a list. */
556
596
  branch: string;
557
597
  /**
558
598
  * **What this node receives, and therefore what `over` is a key on** —
@@ -601,7 +641,7 @@ export interface EndNode {
601
641
  export interface GateNode {
602
642
  id: string;
603
643
  kind: 'gate';
604
- /** A registered GATE type's name — a built-in (`distinct`) or `Registry.gateTypes`' (`FD-V39`). */
644
+ /** A registered GATE type's name — a built-in (`distinct`, `if`, `switch`) or `Registry.gateTypes`' (`FD-V39`). */
605
645
  type: string;
606
646
  /**
607
647
  * **What the gate's type is configured with** — a plain object its type's `settingsSchema` accepts
@@ -641,14 +681,54 @@ export interface GateContext {
641
681
  * (P24 decision 2, ADR 0066). Code, not a document entry: `fn` is a function, which a JSON registry
642
682
  * cannot carry (ADR 0002 rule 1), so a consumer's types are registered in `Registry.gateTypes` the way
643
683
  * a language adapter's `compile` is supplied in code.
684
+ *
685
+ * **Its routes are declared STATICALLY or DERIVED from the gate's own settings — exactly one of the two**
686
+ * ([ADR 0092](../../doc/decisions/0092-a-gate-types-routes-are-declared-statically-or-derived-from-its-node.md),
687
+ * v0.30): a `StaticGateType` carries `routes`, `defaultRoute` and `keyResolvedRoutes?`, the same for every
688
+ * gate naming it; a `DerivedGateType` carries `routesFor(settings)` IN PLACE of the three, so two gates of
689
+ * one type may have two route sets. Every reader of a gate's routes reads the gate's RESOLVED set
690
+ * (`routesOf`), never the type's members. A value carrying both, or neither, is refused (`FD-V39`,
691
+ * `GateTypeRefused`).
644
692
  */
645
- export interface GateType {
693
+ export type GateType = StaticGateType | DerivedGateType;
694
+ /** **A gate type whose routes are its NODE's** — `routesFor` in place of `routes`, `defaultRoute` and `keyResolvedRoutes` (ADR 0092). */
695
+ export interface DerivedGateType {
696
+ /**
697
+ * **The routes of ONE gate, from that gate's settings** — `{ routes, defaultRoute, keyResolvedRoutes? }`,
698
+ * held to the clauses a static type's three members are. Called once per gate at instantiation, after
699
+ * `FD-V40` passed, with a COPY of the settings (`{}` when the gate declares none); every reader — `FD-V41`'s
700
+ * count, the default edge, `FD-R5`, the wiring check's guarantee — reads what it returned. **A pure function
701
+ * of the settings**, as `fn` is of its context (Q9): the validator and each driver ask it separately, and a
702
+ * resume asks it again. A throw or a malformed set is refused at the GATE, `FD-V39`.
703
+ */
704
+ routesFor(settings: Readonly<Record<string, unknown>>): Pick<StaticGateType, 'routes' | 'defaultRoute' | 'keyResolvedRoutes'>;
705
+ readonly routes?: never;
706
+ readonly defaultRoute?: never;
707
+ readonly keyResolvedRoutes?: never;
708
+ /** What a gate's `settings` must satisfy, in the built-in subset (ADR 0053). Absent means UNCHECKED (P18 decision 3). */
709
+ readonly settingsSchema?: JsonSchema;
710
+ /**
711
+ * **What the settings must satisfy beyond the schema** — a message naming what is wrong, or `null`. Run by
712
+ * `FD-V40` once the settings are a plain object the `settingsSchema` accepts, with a COPY of them; a message
713
+ * is reported under `FD-V40`, and a throw or a return that is neither a string nor `null` under `FD-V39`
714
+ * (ADR 0092). The subset cannot state a grammar (ADR 0053), so a type that needs one states it here.
715
+ */
716
+ settingsProblem?(settings: Readonly<Record<string, unknown>>): string | null;
717
+ /** The decision: one of THIS GATE's routes, synchronously — a route outside the set `routesFor` returned for the gate HALTS the run (`FD-R5`). */
718
+ fn(ctx: GateContext): string;
719
+ }
720
+ /** **A gate type whose routes are the same for every gate naming it** — the interface as ADR 0066 wrote it; `distinct` is one. */
721
+ export interface StaticGateType {
646
722
  /** Every route `fn` may return, each in `FD-V16`'s charset, distinct, at least one. Every one must be wired by exactly one edge (`FD-V41`). */
647
723
  readonly routes: readonly string[];
648
724
  /** What an edge with no `route` carries. One of `routes` — and NOT necessarily the first. */
649
725
  readonly defaultRoute: string;
726
+ /** Absent on a static type: its routes are declared, not derived (ADR 0092). */
727
+ readonly routesFor?: never;
650
728
  /** What a gate's `settings` must satisfy, in the built-in subset (ADR 0053). Absent means UNCHECKED (P18 decision 3). */
651
729
  readonly settingsSchema?: JsonSchema;
730
+ /** As `DerivedGateType.settingsProblem`: run by `FD-V40` after the schema, its message reported under it. */
731
+ settingsProblem?(settings: Readonly<Record<string, unknown>>): string | null;
652
732
  /**
653
733
  * **The routes on which the gate's KEY PATH resolved** — a subset of `routes` (`FD-V39`'s second clause, as every
654
734
  * other malformed member of this interface is), OPTIONAL, and
@@ -666,12 +746,60 @@ export interface GateType {
666
746
  * and a type must not read this member as a promise about the leaf's TYPE.
667
747
  */
668
748
  readonly keyResolvedRoutes?: readonly string[];
669
- /** The decision: one of `routes`, synchronously. A route not declared STOPS the run by name (P24 decision 8 — `FD-R5`, `UndeclaredRoute`); a throw stops it with the thrown error, as a predicate's does. */
749
+ /** The decision: one of `routes`, synchronously. A route not declared HALTS the run by name (P24 decision 8 — `FD-R5`, `UndeclaredRoute`); a throw halts it with the thrown error, as a predicate's does. */
670
750
  fn(ctx: GateContext): string;
671
751
  }
672
752
  /** A consumer's gate types, keyed by the name a gate's `type` gives. A `Map` or a plain object, as `nodeTypes` is. */
673
753
  export type GateTypes = ReadonlyMap<string, GateType> | Readonly<Record<string, GateType>>;
674
- export type FlowNode = TaskNode | FanoutNode | MapNode | JoinNode | EndNode | GateNode;
754
+ /**
755
+ * **An ASK: a node that waits for a PERSON, by design** — `spec/flow-definition.md` section 2 (v0.42),
756
+ * [P35](../../doc/plans/p35-may-a-node-wait-for-a-person-by-design.md) stop-and-asks (a) and (h),
757
+ * [ADR 0108](../../doc/decisions/0108-an-ask-is-a-seventh-node-kind-with-a-task-id-and-no-dispatch.md).
758
+ *
759
+ * It runs NO worker and is NEVER dispatched, yet it has a TASK ID (`spec/task-id.md` v0.13) — the address a
760
+ * person's answer is given to, `resolve(taskId, …)`, one per map item where it sits in a `map` region. The answer
761
+ * is its OUTPUT: a later node may reference it by `needs` (`FD-V27`), and its edges ROUTE like a task's — `on:
762
+ * ['done' | 'failed']`, a `failed` answer being the person DECLINING, and a `when` over `outcome.result`, the
763
+ * answer. Allowed wherever a `task` is, the entry included. **What a task's WORKER reads it does not carry** —
764
+ * `type`, `pool`, `optional`, `retryPolicy`, `workerGroups` and `transform` are each refused by `FD-V47`, because
765
+ * nothing would read them: there is no worker, no scheduler task and no wrapper to evaluate a transform in
766
+ * (`FD-C6`, P35 decision 6).
767
+ *
768
+ * **BREAKING** (0.8.0, P35 decision 8): `FlowNode` gains a seventh member, so a consumer's `switch` over
769
+ * `node.kind` with a `never` check stops compiling until it names `'ask'`.
770
+ *
771
+ * **Valid since v0.42 and run since W180** — until then every `Driver` refuses the run by name where a branch
772
+ * ENTERS one (`enter()`), as W92's gate was refused until W93.
773
+ */
774
+ export interface AskNode {
775
+ id: string;
776
+ kind: 'ask';
777
+ /**
778
+ * **What a UI shows the person — opaque.** A plain JSON object the library NEVER reads (`FD-V21` refuses one that
779
+ * is not a plain object): the question, a form hint. Read from the definition, as a gate's settings are read by
780
+ * its type.
781
+ */
782
+ settings?: Readonly<Record<string, unknown>>;
783
+ /**
784
+ * **What a `done` answer must satisfy** — a JSON Schema in the built-in subset, held to it at load exactly as a
785
+ * worker's schemas are (`FD-V48`, ADR 0053), its `examples` held to it too (ADR 0056). Absent, an answer is held
786
+ * to nothing. A `failed` answer — a decline — is held to nothing either way. What it holds at run time is W180's
787
+ * (`FD-R4`'s resolve boundary).
788
+ */
789
+ answerSchema?: JsonSchema;
790
+ /**
791
+ * **What reaches the ask** — `FD-C1`, `FD-V26`-`FD-V29`, composed in the driver as a task's is. Optional: without
792
+ * one, the ordinary edge's value. What reached it is what the person is shown, and what W180 keeps.
793
+ */
794
+ needs?: Needs;
795
+ /** Routed like a task's: `on` and `when`, a default edge for every terminal state an edge opts into (`FD-V7`). */
796
+ edges: Edge[];
797
+ }
798
+ /**
799
+ * **Seven kinds since v0.42** (`ask`, W179). **BREAKING for an exhaustive consumer `switch`** each time a member
800
+ * is added — `gate` at 0.6.0, `ask` at 0.8.0.
801
+ */
802
+ export type FlowNode = TaskNode | FanoutNode | MapNode | JoinNode | EndNode | GateNode | AskNode;
675
803
  export interface FlowDefinition {
676
804
  flowId: string;
677
805
  version: number;
@@ -726,7 +854,8 @@ export interface Registry {
726
854
  * **A consumer's GATE types, beside the built-ins** — the sixth set, v0.23 (W92, ADR 0066). **Absent
727
855
  * means the BUILT-INS ALONE** — `distinct` is always there, so this set only ever ADDS; a name that
728
856
  * is a built-in's is refused (`NameCollision` at construction, `FD-V39` reached here directly). The
729
- * validator reads `routes`, `defaultRoute` and `settingsSchema`; the driver reads `fn` too. **A driver
857
+ * validator reads each gate's route set (`routesOf` — the static members, or `routesFor` over the gate's
858
+ * settings, v0.30), `settingsSchema` and `settingsProblem`; the driver reads `fn` too. **A driver
730
859
  * is handed the set as `DriverOptions.gateTypes`** since W93 — the runtime's road is
731
860
  * `runtime.registry.gateTypes`, from `RegistryCode.gateTypes`. *(Until W93 no driver carried it and
732
861
  * every gate flow was refused at construction.)*
@@ -740,7 +869,7 @@ export interface Registry {
740
869
  * legitimately violate more than one rule.
741
870
  */
742
871
  export interface Finding {
743
- /** `FD-V1` … `FD-V46`. Never renumbered, so it is safe to assert on. */
872
+ /** `FD-V1` … `FD-V46` from the validator; `runtime.check` adds `FD-R6` (ADR 0097). Never renumbered, so it is safe to assert on. */
744
873
  rule: string;
745
874
  /** Node id, edge index, or document-level `null`. */
746
875
  at: string | null;