@particle-academy/fancy-flow 0.15.1 → 0.16.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.
package/dist/engine.d.cts CHANGED
@@ -1,6 +1,6 @@
1
1
  export { R as RunOptions, a as RunResult, r as runFlow } from './run-flow-VKm57Hcj.cjs';
2
- import { N as NodeExecutor } from './types-sOmpCitB.cjs';
3
- export { A as ActionNodeData, B as BaseNodeData, D as DecisionNodeData, E as ExecutorRegistry, a as FlowEdge, F as FlowGraph, b as FlowNode, c as FlowNodeData, d as FlowNodeKind, e as NodeRunStatus, f as NoteNodeData, O as OutputNodeData, P as PortDescriptor, R as RunEvent, S as SubgraphNodeData, T as TriggerNodeData } from './types-sOmpCitB.cjs';
2
+ import { F as FlowGraph, R as RunEvent, N as NodeExecutor } from './types-sOmpCitB.cjs';
3
+ export { A as ActionNodeData, B as BaseNodeData, D as DecisionNodeData, E as ExecutorRegistry, a as FlowEdge, b as FlowNode, c as FlowNodeData, d as FlowNodeKind, e as NodeRunStatus, f as NoteNodeData, O as OutputNodeData, P as PortDescriptor, S as SubgraphNodeData, T as TriggerNodeData } from './types-sOmpCitB.cjs';
4
4
  import { P as PauseAwaiting } from './pause-9iT4tCEV.cjs';
5
5
  export { L as LEGACY_PAUSE_PREFIXES, a as PAUSE_PREFIX, b as PauseSignal, d as decodePause, e as encodePause, i as isPause, p as pauseForHuman } from './pause-9iT4tCEV.cjs';
6
6
  import '@xyflow/react';
@@ -9,17 +9,22 @@ import '@xyflow/react';
9
9
  * The node package manifest — what a marketplace node declares about itself.
10
10
  *
11
11
  * A node is not one artifact. It is a kind definition (palette entry, config
12
- * schema, ports, canvas renderer) plus an executor for EACH runtime the
13
- * consumer actually runs. A package shipping only a TS executor is unusable to
14
- * anyone executing on PHP, and today that is invisible until a run fails.
15
- *
16
- * So the manifest states which runtimes it implements, and the CLI checks that
17
- * against the host before installing rather than after. Requested by the MOIC
18
- * Suite consumer (fancy-flow#2 §2), who runs the editor in TS and executes in
19
- * PHP and hit exactly this.
12
+ * schema, ports, renderer) plus an executor for EACH runtime the consumer runs.
13
+ * A package shipping only a TS executor is unusable to anyone executing on PHP,
14
+ * and without a manifest that is invisible until a run fails. Requested by the
15
+ * MOIC Suite consumer (fancy-flow#2 §2), who runs the editor in TS and executes
16
+ * in PHP and hit exactly this.
20
17
  *
21
18
  * The manifest is data, not code: the registry, the CLI, and the MCP all read
22
19
  * it without executing anything a package author wrote.
20
+ *
21
+ * ## Why the engine range lives per runtime
22
+ *
23
+ * The first cut of this manifest carried ONE `fancyFlow` range, and it was
24
+ * wrong: the two engines version independently, so a single range cannot say
25
+ * "needs npm >=0.11 AND php >=0.5". A package supporting both runtimes would
26
+ * install cleanly against a host whose OTHER runtime was too old — the 0.9.0
27
+ * failure shape wearing a manifest. The range belongs inside each runtime.
23
28
  */
24
29
 
25
30
  /** The current manifest schema version. Bump only on a breaking shape change. */
@@ -32,6 +37,38 @@ declare const NODE_MANIFEST_SCHEMA_VERSION = 1;
32
37
  * declare itself without a release here.
33
38
  */
34
39
  type NodeRuntimeId = "ts" | "php" | (string & {});
40
+ /**
41
+ * How one runtime provides this node, and which engine version it needs.
42
+ *
43
+ * `entry` and `package` are both here because the two ecosystems install
44
+ * differently — a module path within the package for TS, a Composer
45
+ * requirement for PHP — and pretending otherwise just moves the problem into
46
+ * the CLI. Exactly one is required.
47
+ */
48
+ type NodeRuntimeSpec = {
49
+ /** Module path within the package (TS-style runtimes). */
50
+ entry?: string;
51
+ /** Dependency requirement resolved by that ecosystem (PHP/Composer-style). */
52
+ package?: string;
53
+ /** Semver range of THIS runtime's engine. Required — see the module note. */
54
+ engine: string;
55
+ };
56
+ /**
57
+ * Whether a capability must be wired for the node to work at all.
58
+ *
59
+ * `required` is checked at AUTHOR time, not run time. The failure that hurts is
60
+ * a node that installs fine, authors fine, and silently no-ops during a run —
61
+ * the same silence as a routing bug. An editor can grey the node and say which
62
+ * capability the host never registered.
63
+ */
64
+ type CapabilityRequirement = "required" | "optional";
65
+ /**
66
+ * Whether a node is safe to run again.
67
+ *
68
+ * Durable runs RETRY. A node that writes needs to say so, or a host has to pick
69
+ * one retry policy for every node and get it wrong somewhere.
70
+ */
71
+ type SideEffects = "none" | "idempotent" | "unsafe-to-replay";
35
72
  type NodePackageManifest = {
36
73
  /** Must equal `NODE_MANIFEST_SCHEMA_VERSION`. */
37
74
  schemaVersion: number;
@@ -42,22 +79,33 @@ type NodePackageManifest = {
42
79
  * that gets persisted into every document using it.
43
80
  */
44
81
  kind: string;
45
- /** Semver range of fancy-flow this node's contract targets. */
46
- fancyFlow: string;
47
82
  /**
48
- * Per-runtime entrypoints. TS is a module path within the package; PHP is a
49
- * Composer requirement, because the two ecosystems install differently and
50
- * pretending otherwise just moves the problem into the CLI.
83
+ * Previous ids this kind still answers to.
84
+ *
85
+ * Core renamed `llm_branch` to `llm_router` and kept every old id working.
86
+ * Third-party packages will rename too, and their documents break the same
87
+ * way core's would have — so packages get the same escape hatch, otherwise
88
+ * only first-party nodes can rename safely.
89
+ */
90
+ aliases?: string[];
91
+ /**
92
+ * Version of this node's CONFIG shape, independent of the document schema.
93
+ *
94
+ * A node's config evolves on its own clock. Without a declared version, every
95
+ * executor accretes hand-written read-fallbacks forever (MOIC carries one for
96
+ * `routes[].key` → `routes[].port`), because nothing canonicalises old config
97
+ * the way import canonicalises kind ids.
51
98
  */
52
- runtimes: Partial<Record<NodeRuntimeId, string>>;
99
+ configVersion?: number;
100
+ /** Per-runtime entrypoints and engine ranges. */
101
+ runtimes: Partial<Record<NodeRuntimeId, NodeRuntimeSpec>>;
53
102
  /**
54
- * Host capabilities this node needs wired before it can run — `llm`,
55
- * `workflow_resolver`, `document`, or a host-specific one.
103
+ * Host capabilities this node needs, and whether each is mandatory.
56
104
  *
57
- * Declared so the CLI can tell an author what to wire BEFORE install, rather
58
- * than the node silently no-opping or crashing mid-run.
105
+ * Declared so the CLI and the editor can say what to wire BEFORE a run,
106
+ * rather than the node silently no-opping or crashing mid-run.
59
107
  */
60
- capabilities?: string[];
108
+ capabilities?: Record<string, CapabilityRequirement>;
61
109
  /**
62
110
  * Path to this node's golden fixtures, relative to the package root.
63
111
  *
@@ -66,8 +114,16 @@ type NodePackageManifest = {
66
114
  * asserted. See `./fixtures`.
67
115
  */
68
116
  fixtures: string;
69
- /** Declares the node halts for a person. Mirrors `NodeKindDefinition`. */
117
+ /**
118
+ * Declares the node halts for a person. Mirrors `NodeKindDefinition`.
119
+ *
120
+ * A host-planning fact, not a node internal: a parent that embeds workflows
121
+ * needs to reject a child that can pause, because a paused child wedges the
122
+ * parent — and discovering that at run time means watching a run park.
123
+ */
70
124
  pausesForHuman?: PauseAwaiting;
125
+ /** Whether this node is safe to replay. See `SideEffects`. */
126
+ sideEffects?: SideEffects;
71
127
  /** One-line summary — what `search_nodes` matches against. */
72
128
  description?: string;
73
129
  /**
@@ -97,19 +153,36 @@ type ManifestValidation = {
97
153
  */
98
154
  declare function validateNodeManifest(input: unknown): ManifestValidation;
99
155
  /**
100
- * Check a node against the runtimes a host actually executes on.
156
+ * Check a node against the runtimes a host executes on, and their versions.
157
+ *
158
+ * Two failures live here, and both are errors because the node genuinely
159
+ * cannot run: a runtime the package does not implement at all, and a runtime
160
+ * it implements against an engine newer than the host's.
101
161
  *
102
- * This is what makes a TS-only package visible to someone running PHP BEFORE
103
- * they install it rather than at the first run. A missing runtime is an error,
104
- * not a warning: the node genuinely cannot execute there.
162
+ * `engineVersions` is optional pass what the host knows. An unchecked range
163
+ * is reported as a warning rather than passed over silently, because "we did
164
+ * not check" and "it is fine" must not look the same.
105
165
  */
106
- declare function checkRuntimeSupport(manifest: Pick<NodePackageManifest, "kind" | "runtimes">, hostRuntimes: readonly string[]): ManifestProblem[];
166
+ declare function checkRuntimeSupport(manifest: Pick<NodePackageManifest, "kind" | "runtimes">, hostRuntimes: readonly string[], engineVersions?: Readonly<Record<string, string>>): ManifestProblem[];
107
167
  /**
108
- * Check that every capability a node needs is actually wired.
168
+ * Minimal semver range check `^x.y.z`, `~x.y.z`, `>=x.y.z`, `x.y.z`, `*`.
109
169
  *
110
- * Pass `capabilityStatus()` from the host. Unwired capabilities are a warning
111
- * rather than an error install is the right time to learn what to wire, not
112
- * a reason to refuse, since wiring usually happens after install.
170
+ * Deliberately small: this runs in the CLI and in CI, and pulling a semver
171
+ * library into the engine for one comparison is not worth the dependency. It
172
+ * covers the forms a manifest actually uses; anything it cannot parse is
173
+ * treated as unsatisfied rather than silently passed, so an unparseable range
174
+ * fails loudly instead of waving a node through.
175
+ */
176
+ declare function satisfiesRange(version: string, range: string): boolean;
177
+ /**
178
+ * Check that every capability a node needs is wired.
179
+ *
180
+ * A `required` capability that is missing is an ERROR — that is the whole point
181
+ * of the requirement level. It is meant to be surfaced at author time, so an
182
+ * editor can grey the node and name what the host never registered, rather than
183
+ * the node installing cleanly and silently no-opping during a run.
184
+ *
185
+ * An `optional` one is a warning: the node still works, with less.
113
186
  */
114
187
  declare function checkCapabilities(manifest: Pick<NodePackageManifest, "kind" | "capabilities">, available: Readonly<Record<string, boolean>>): ManifestProblem[];
115
188
 
@@ -140,6 +213,35 @@ declare function checkCapabilities(manifest: Pick<NodePackageManifest, "kind" |
140
213
  * executes byte-identical cases without an embedded JS engine.
141
214
  */
142
215
 
216
+ /**
217
+ * Deterministic stand-ins for host capabilities, declared as DATA.
218
+ *
219
+ * `llm_router` cannot reach a provider in CI, so a fixture has to supply a fake
220
+ * `LlmClient`. If the stub format is not shared, each runtime stubs
221
+ * differently and the fixtures stop being comparable — parity theatre. So the
222
+ * fixture declares the stub's *behaviour* and both engines construct it
223
+ * identically from the same JSON.
224
+ */
225
+ type FixtureStubs = {
226
+ /** Canned `chooseRoute` answer for the LLM capability. */
227
+ llm_client?: {
228
+ chooseRoute: {
229
+ port: string;
230
+ reason?: string;
231
+ };
232
+ };
233
+ /** Canned workflow resolution, keyed by ref, for `subflow`. */
234
+ workflow_resolver?: Record<string, FlowGraph | null>;
235
+ };
236
+ /** An event a case expects the run to have emitted. */
237
+ type FixtureEventExpectation = {
238
+ type: RunEvent["type"];
239
+ /** Emitting node, when it matters. */
240
+ nodeId?: string;
241
+ level?: "info" | "warn" | "error";
242
+ /** Substring match against the message — full text is too brittle to pin. */
243
+ messageContains?: string;
244
+ };
143
245
  /** What a case expects to have happened. */
144
246
  type FixtureExpectation = {
145
247
  /**
@@ -157,6 +259,29 @@ type FixtureExpectation = {
157
259
  };
158
260
  /** The run failed, and the message contains this substring. */
159
261
  error?: string;
262
+ /**
263
+ * Events the run must have emitted.
264
+ *
265
+ * Emitted events are BEHAVIOUR, not decoration. The hallucinated-port warning
266
+ * is a contract an operator relies on to know a run took the fallback — if
267
+ * one runtime stops emitting it, the guarantee degrades silently. Each entry
268
+ * must match at least one emitted event.
269
+ */
270
+ events?: FixtureEventExpectation[];
271
+ /**
272
+ * The run resumed after a pause and reached this state.
273
+ *
274
+ * Pause/resume is the ONLY path that crosses a persistence boundary, which
275
+ * makes it where two runtimes are most likely to drift — and it had no parity
276
+ * coverage at all while `PausesForHuman` became public API.
277
+ */
278
+ afterResume?: {
279
+ /** Delivered on the paused node's `values` input, as a host would on resume. */
280
+ submit: unknown;
281
+ ports?: string[];
282
+ value?: unknown;
283
+ error?: string;
284
+ };
160
285
  };
161
286
  type FixtureCase = {
162
287
  /** Human-readable, and what a failure report names. */
@@ -180,6 +305,16 @@ type FixtureCase = {
180
305
  ports?: string[];
181
306
  /** Inputs delivered on the node's input ports. */
182
307
  inputs?: Record<string, unknown>;
308
+ /**
309
+ * Run this case against an ALIAS or an older config shape.
310
+ *
311
+ * What proves `aliases` and `configVersion` actually work rather than being
312
+ * declared and rotting. A package that renames its kind should have a case
313
+ * pinning that the old id still resolves.
314
+ */
315
+ legacyKind?: string;
316
+ /** Deterministic capability stand-ins for this case. */
317
+ stubs?: FixtureStubs;
183
318
  expect: FixtureExpectation;
184
319
  };
185
320
  type FixtureFile = {
@@ -212,4 +347,4 @@ declare function runFixtures(file: FixtureFile, executor: NodeExecutor): Promise
212
347
  */
213
348
  declare function validateFixtureFile(input: unknown, expectedKind?: string): string[];
214
349
 
215
- export { type FixtureCase, type FixtureExpectation, type FixtureFailure, type FixtureFile, type FixtureRunResult, type ManifestProblem, type ManifestValidation, NODE_MANIFEST_SCHEMA_VERSION, NodeExecutor, type NodePackageManifest, type NodeRuntimeId, PauseAwaiting, checkCapabilities, checkRuntimeSupport, runFixtures, validateFixtureFile, validateNodeManifest };
350
+ export { type CapabilityRequirement, type FixtureCase, type FixtureEventExpectation, type FixtureExpectation, type FixtureFailure, type FixtureFile, type FixtureRunResult, type FixtureStubs, FlowGraph, type ManifestProblem, type ManifestValidation, NODE_MANIFEST_SCHEMA_VERSION, NodeExecutor, type NodePackageManifest, type NodeRuntimeId, type NodeRuntimeSpec, PauseAwaiting, RunEvent, type SideEffects, checkCapabilities, checkRuntimeSupport, runFixtures, satisfiesRange, validateFixtureFile, validateNodeManifest };
package/dist/engine.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export { R as RunOptions, a as RunResult, r as runFlow } from './run-flow-ChBi6q-W.js';
2
- import { N as NodeExecutor } from './types-sOmpCitB.js';
3
- export { A as ActionNodeData, B as BaseNodeData, D as DecisionNodeData, E as ExecutorRegistry, a as FlowEdge, F as FlowGraph, b as FlowNode, c as FlowNodeData, d as FlowNodeKind, e as NodeRunStatus, f as NoteNodeData, O as OutputNodeData, P as PortDescriptor, R as RunEvent, S as SubgraphNodeData, T as TriggerNodeData } from './types-sOmpCitB.js';
2
+ import { F as FlowGraph, R as RunEvent, N as NodeExecutor } from './types-sOmpCitB.js';
3
+ export { A as ActionNodeData, B as BaseNodeData, D as DecisionNodeData, E as ExecutorRegistry, a as FlowEdge, b as FlowNode, c as FlowNodeData, d as FlowNodeKind, e as NodeRunStatus, f as NoteNodeData, O as OutputNodeData, P as PortDescriptor, S as SubgraphNodeData, T as TriggerNodeData } from './types-sOmpCitB.js';
4
4
  import { P as PauseAwaiting } from './pause-9iT4tCEV.js';
5
5
  export { L as LEGACY_PAUSE_PREFIXES, a as PAUSE_PREFIX, b as PauseSignal, d as decodePause, e as encodePause, i as isPause, p as pauseForHuman } from './pause-9iT4tCEV.js';
6
6
  import '@xyflow/react';
@@ -9,17 +9,22 @@ import '@xyflow/react';
9
9
  * The node package manifest — what a marketplace node declares about itself.
10
10
  *
11
11
  * A node is not one artifact. It is a kind definition (palette entry, config
12
- * schema, ports, canvas renderer) plus an executor for EACH runtime the
13
- * consumer actually runs. A package shipping only a TS executor is unusable to
14
- * anyone executing on PHP, and today that is invisible until a run fails.
15
- *
16
- * So the manifest states which runtimes it implements, and the CLI checks that
17
- * against the host before installing rather than after. Requested by the MOIC
18
- * Suite consumer (fancy-flow#2 §2), who runs the editor in TS and executes in
19
- * PHP and hit exactly this.
12
+ * schema, ports, renderer) plus an executor for EACH runtime the consumer runs.
13
+ * A package shipping only a TS executor is unusable to anyone executing on PHP,
14
+ * and without a manifest that is invisible until a run fails. Requested by the
15
+ * MOIC Suite consumer (fancy-flow#2 §2), who runs the editor in TS and executes
16
+ * in PHP and hit exactly this.
20
17
  *
21
18
  * The manifest is data, not code: the registry, the CLI, and the MCP all read
22
19
  * it without executing anything a package author wrote.
20
+ *
21
+ * ## Why the engine range lives per runtime
22
+ *
23
+ * The first cut of this manifest carried ONE `fancyFlow` range, and it was
24
+ * wrong: the two engines version independently, so a single range cannot say
25
+ * "needs npm >=0.11 AND php >=0.5". A package supporting both runtimes would
26
+ * install cleanly against a host whose OTHER runtime was too old — the 0.9.0
27
+ * failure shape wearing a manifest. The range belongs inside each runtime.
23
28
  */
24
29
 
25
30
  /** The current manifest schema version. Bump only on a breaking shape change. */
@@ -32,6 +37,38 @@ declare const NODE_MANIFEST_SCHEMA_VERSION = 1;
32
37
  * declare itself without a release here.
33
38
  */
34
39
  type NodeRuntimeId = "ts" | "php" | (string & {});
40
+ /**
41
+ * How one runtime provides this node, and which engine version it needs.
42
+ *
43
+ * `entry` and `package` are both here because the two ecosystems install
44
+ * differently — a module path within the package for TS, a Composer
45
+ * requirement for PHP — and pretending otherwise just moves the problem into
46
+ * the CLI. Exactly one is required.
47
+ */
48
+ type NodeRuntimeSpec = {
49
+ /** Module path within the package (TS-style runtimes). */
50
+ entry?: string;
51
+ /** Dependency requirement resolved by that ecosystem (PHP/Composer-style). */
52
+ package?: string;
53
+ /** Semver range of THIS runtime's engine. Required — see the module note. */
54
+ engine: string;
55
+ };
56
+ /**
57
+ * Whether a capability must be wired for the node to work at all.
58
+ *
59
+ * `required` is checked at AUTHOR time, not run time. The failure that hurts is
60
+ * a node that installs fine, authors fine, and silently no-ops during a run —
61
+ * the same silence as a routing bug. An editor can grey the node and say which
62
+ * capability the host never registered.
63
+ */
64
+ type CapabilityRequirement = "required" | "optional";
65
+ /**
66
+ * Whether a node is safe to run again.
67
+ *
68
+ * Durable runs RETRY. A node that writes needs to say so, or a host has to pick
69
+ * one retry policy for every node and get it wrong somewhere.
70
+ */
71
+ type SideEffects = "none" | "idempotent" | "unsafe-to-replay";
35
72
  type NodePackageManifest = {
36
73
  /** Must equal `NODE_MANIFEST_SCHEMA_VERSION`. */
37
74
  schemaVersion: number;
@@ -42,22 +79,33 @@ type NodePackageManifest = {
42
79
  * that gets persisted into every document using it.
43
80
  */
44
81
  kind: string;
45
- /** Semver range of fancy-flow this node's contract targets. */
46
- fancyFlow: string;
47
82
  /**
48
- * Per-runtime entrypoints. TS is a module path within the package; PHP is a
49
- * Composer requirement, because the two ecosystems install differently and
50
- * pretending otherwise just moves the problem into the CLI.
83
+ * Previous ids this kind still answers to.
84
+ *
85
+ * Core renamed `llm_branch` to `llm_router` and kept every old id working.
86
+ * Third-party packages will rename too, and their documents break the same
87
+ * way core's would have — so packages get the same escape hatch, otherwise
88
+ * only first-party nodes can rename safely.
89
+ */
90
+ aliases?: string[];
91
+ /**
92
+ * Version of this node's CONFIG shape, independent of the document schema.
93
+ *
94
+ * A node's config evolves on its own clock. Without a declared version, every
95
+ * executor accretes hand-written read-fallbacks forever (MOIC carries one for
96
+ * `routes[].key` → `routes[].port`), because nothing canonicalises old config
97
+ * the way import canonicalises kind ids.
51
98
  */
52
- runtimes: Partial<Record<NodeRuntimeId, string>>;
99
+ configVersion?: number;
100
+ /** Per-runtime entrypoints and engine ranges. */
101
+ runtimes: Partial<Record<NodeRuntimeId, NodeRuntimeSpec>>;
53
102
  /**
54
- * Host capabilities this node needs wired before it can run — `llm`,
55
- * `workflow_resolver`, `document`, or a host-specific one.
103
+ * Host capabilities this node needs, and whether each is mandatory.
56
104
  *
57
- * Declared so the CLI can tell an author what to wire BEFORE install, rather
58
- * than the node silently no-opping or crashing mid-run.
105
+ * Declared so the CLI and the editor can say what to wire BEFORE a run,
106
+ * rather than the node silently no-opping or crashing mid-run.
59
107
  */
60
- capabilities?: string[];
108
+ capabilities?: Record<string, CapabilityRequirement>;
61
109
  /**
62
110
  * Path to this node's golden fixtures, relative to the package root.
63
111
  *
@@ -66,8 +114,16 @@ type NodePackageManifest = {
66
114
  * asserted. See `./fixtures`.
67
115
  */
68
116
  fixtures: string;
69
- /** Declares the node halts for a person. Mirrors `NodeKindDefinition`. */
117
+ /**
118
+ * Declares the node halts for a person. Mirrors `NodeKindDefinition`.
119
+ *
120
+ * A host-planning fact, not a node internal: a parent that embeds workflows
121
+ * needs to reject a child that can pause, because a paused child wedges the
122
+ * parent — and discovering that at run time means watching a run park.
123
+ */
70
124
  pausesForHuman?: PauseAwaiting;
125
+ /** Whether this node is safe to replay. See `SideEffects`. */
126
+ sideEffects?: SideEffects;
71
127
  /** One-line summary — what `search_nodes` matches against. */
72
128
  description?: string;
73
129
  /**
@@ -97,19 +153,36 @@ type ManifestValidation = {
97
153
  */
98
154
  declare function validateNodeManifest(input: unknown): ManifestValidation;
99
155
  /**
100
- * Check a node against the runtimes a host actually executes on.
156
+ * Check a node against the runtimes a host executes on, and their versions.
157
+ *
158
+ * Two failures live here, and both are errors because the node genuinely
159
+ * cannot run: a runtime the package does not implement at all, and a runtime
160
+ * it implements against an engine newer than the host's.
101
161
  *
102
- * This is what makes a TS-only package visible to someone running PHP BEFORE
103
- * they install it rather than at the first run. A missing runtime is an error,
104
- * not a warning: the node genuinely cannot execute there.
162
+ * `engineVersions` is optional pass what the host knows. An unchecked range
163
+ * is reported as a warning rather than passed over silently, because "we did
164
+ * not check" and "it is fine" must not look the same.
105
165
  */
106
- declare function checkRuntimeSupport(manifest: Pick<NodePackageManifest, "kind" | "runtimes">, hostRuntimes: readonly string[]): ManifestProblem[];
166
+ declare function checkRuntimeSupport(manifest: Pick<NodePackageManifest, "kind" | "runtimes">, hostRuntimes: readonly string[], engineVersions?: Readonly<Record<string, string>>): ManifestProblem[];
107
167
  /**
108
- * Check that every capability a node needs is actually wired.
168
+ * Minimal semver range check `^x.y.z`, `~x.y.z`, `>=x.y.z`, `x.y.z`, `*`.
109
169
  *
110
- * Pass `capabilityStatus()` from the host. Unwired capabilities are a warning
111
- * rather than an error install is the right time to learn what to wire, not
112
- * a reason to refuse, since wiring usually happens after install.
170
+ * Deliberately small: this runs in the CLI and in CI, and pulling a semver
171
+ * library into the engine for one comparison is not worth the dependency. It
172
+ * covers the forms a manifest actually uses; anything it cannot parse is
173
+ * treated as unsatisfied rather than silently passed, so an unparseable range
174
+ * fails loudly instead of waving a node through.
175
+ */
176
+ declare function satisfiesRange(version: string, range: string): boolean;
177
+ /**
178
+ * Check that every capability a node needs is wired.
179
+ *
180
+ * A `required` capability that is missing is an ERROR — that is the whole point
181
+ * of the requirement level. It is meant to be surfaced at author time, so an
182
+ * editor can grey the node and name what the host never registered, rather than
183
+ * the node installing cleanly and silently no-opping during a run.
184
+ *
185
+ * An `optional` one is a warning: the node still works, with less.
113
186
  */
114
187
  declare function checkCapabilities(manifest: Pick<NodePackageManifest, "kind" | "capabilities">, available: Readonly<Record<string, boolean>>): ManifestProblem[];
115
188
 
@@ -140,6 +213,35 @@ declare function checkCapabilities(manifest: Pick<NodePackageManifest, "kind" |
140
213
  * executes byte-identical cases without an embedded JS engine.
141
214
  */
142
215
 
216
+ /**
217
+ * Deterministic stand-ins for host capabilities, declared as DATA.
218
+ *
219
+ * `llm_router` cannot reach a provider in CI, so a fixture has to supply a fake
220
+ * `LlmClient`. If the stub format is not shared, each runtime stubs
221
+ * differently and the fixtures stop being comparable — parity theatre. So the
222
+ * fixture declares the stub's *behaviour* and both engines construct it
223
+ * identically from the same JSON.
224
+ */
225
+ type FixtureStubs = {
226
+ /** Canned `chooseRoute` answer for the LLM capability. */
227
+ llm_client?: {
228
+ chooseRoute: {
229
+ port: string;
230
+ reason?: string;
231
+ };
232
+ };
233
+ /** Canned workflow resolution, keyed by ref, for `subflow`. */
234
+ workflow_resolver?: Record<string, FlowGraph | null>;
235
+ };
236
+ /** An event a case expects the run to have emitted. */
237
+ type FixtureEventExpectation = {
238
+ type: RunEvent["type"];
239
+ /** Emitting node, when it matters. */
240
+ nodeId?: string;
241
+ level?: "info" | "warn" | "error";
242
+ /** Substring match against the message — full text is too brittle to pin. */
243
+ messageContains?: string;
244
+ };
143
245
  /** What a case expects to have happened. */
144
246
  type FixtureExpectation = {
145
247
  /**
@@ -157,6 +259,29 @@ type FixtureExpectation = {
157
259
  };
158
260
  /** The run failed, and the message contains this substring. */
159
261
  error?: string;
262
+ /**
263
+ * Events the run must have emitted.
264
+ *
265
+ * Emitted events are BEHAVIOUR, not decoration. The hallucinated-port warning
266
+ * is a contract an operator relies on to know a run took the fallback — if
267
+ * one runtime stops emitting it, the guarantee degrades silently. Each entry
268
+ * must match at least one emitted event.
269
+ */
270
+ events?: FixtureEventExpectation[];
271
+ /**
272
+ * The run resumed after a pause and reached this state.
273
+ *
274
+ * Pause/resume is the ONLY path that crosses a persistence boundary, which
275
+ * makes it where two runtimes are most likely to drift — and it had no parity
276
+ * coverage at all while `PausesForHuman` became public API.
277
+ */
278
+ afterResume?: {
279
+ /** Delivered on the paused node's `values` input, as a host would on resume. */
280
+ submit: unknown;
281
+ ports?: string[];
282
+ value?: unknown;
283
+ error?: string;
284
+ };
160
285
  };
161
286
  type FixtureCase = {
162
287
  /** Human-readable, and what a failure report names. */
@@ -180,6 +305,16 @@ type FixtureCase = {
180
305
  ports?: string[];
181
306
  /** Inputs delivered on the node's input ports. */
182
307
  inputs?: Record<string, unknown>;
308
+ /**
309
+ * Run this case against an ALIAS or an older config shape.
310
+ *
311
+ * What proves `aliases` and `configVersion` actually work rather than being
312
+ * declared and rotting. A package that renames its kind should have a case
313
+ * pinning that the old id still resolves.
314
+ */
315
+ legacyKind?: string;
316
+ /** Deterministic capability stand-ins for this case. */
317
+ stubs?: FixtureStubs;
183
318
  expect: FixtureExpectation;
184
319
  };
185
320
  type FixtureFile = {
@@ -212,4 +347,4 @@ declare function runFixtures(file: FixtureFile, executor: NodeExecutor): Promise
212
347
  */
213
348
  declare function validateFixtureFile(input: unknown, expectedKind?: string): string[];
214
349
 
215
- export { type FixtureCase, type FixtureExpectation, type FixtureFailure, type FixtureFile, type FixtureRunResult, type ManifestProblem, type ManifestValidation, NODE_MANIFEST_SCHEMA_VERSION, NodeExecutor, type NodePackageManifest, type NodeRuntimeId, PauseAwaiting, checkCapabilities, checkRuntimeSupport, runFixtures, validateFixtureFile, validateNodeManifest };
350
+ export { type CapabilityRequirement, type FixtureCase, type FixtureEventExpectation, type FixtureExpectation, type FixtureFailure, type FixtureFile, type FixtureRunResult, type FixtureStubs, FlowGraph, type ManifestProblem, type ManifestValidation, NODE_MANIFEST_SCHEMA_VERSION, NodeExecutor, type NodePackageManifest, type NodeRuntimeId, type NodeRuntimeSpec, PauseAwaiting, RunEvent, type SideEffects, checkCapabilities, checkRuntimeSupport, runFixtures, satisfiesRange, validateFixtureFile, validateNodeManifest };