@particle-academy/fancy-flow 0.14.0 → 0.15.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.
Files changed (42) hide show
  1. package/README.md +48 -0
  2. package/dist/{capabilities-D-V9Rxv1.d.cts → capabilities-BEbLqnJU.d.cts} +1 -1
  3. package/dist/{capabilities-B6r1Snfj.d.ts → capabilities-CI3gw0C7.d.ts} +1 -1
  4. package/dist/chunk-UEOE6B52.js +39 -0
  5. package/dist/chunk-UEOE6B52.js.map +1 -0
  6. package/dist/{chunk-7U3EP4Q5.js → chunk-ZQBWPYTI.js} +5 -2
  7. package/dist/chunk-ZQBWPYTI.js.map +1 -0
  8. package/dist/engine.cjs +299 -0
  9. package/dist/engine.cjs.map +1 -1
  10. package/dist/engine.d.cts +190 -27
  11. package/dist/engine.d.ts +190 -27
  12. package/dist/engine.js +258 -2
  13. package/dist/engine.js.map +1 -1
  14. package/dist/index.cjs +45 -0
  15. package/dist/index.cjs.map +1 -1
  16. package/dist/index.d.cts +7 -6
  17. package/dist/index.d.ts +7 -6
  18. package/dist/index.js +3 -2
  19. package/dist/index.js.map +1 -1
  20. package/dist/llm/vercel-ai.d.cts +2 -2
  21. package/dist/llm/vercel-ai.d.ts +2 -2
  22. package/dist/pause-9iT4tCEV.d.cts +96 -0
  23. package/dist/pause-9iT4tCEV.d.ts +96 -0
  24. package/dist/registry/index.d.cts +6 -5
  25. package/dist/registry/index.d.ts +6 -5
  26. package/dist/registry.cjs +45 -0
  27. package/dist/registry.cjs.map +1 -1
  28. package/dist/registry.js +2 -1
  29. package/dist/run-flow-ChBi6q-W.d.ts +35 -0
  30. package/dist/run-flow-VKm57Hcj.d.cts +35 -0
  31. package/dist/runtime/index.d.cts +3 -3
  32. package/dist/runtime/index.d.ts +3 -3
  33. package/dist/schema/index.d.cts +1 -1
  34. package/dist/schema/index.d.ts +1 -1
  35. package/dist/{types-H60tQAxr.d.cts → types-D_jOR3Z2.d.cts} +12 -1
  36. package/dist/{types-CnnKPnpt.d.ts → types-NerkPtHS.d.ts} +12 -1
  37. package/dist/{types-fc0fFKkf.d.cts → types-sOmpCitB.d.cts} +1 -1
  38. package/dist/{types-fc0fFKkf.d.ts → types-sOmpCitB.d.ts} +1 -1
  39. package/dist/ux.d.cts +3 -2
  40. package/dist/ux.d.ts +3 -2
  41. package/package.json +1 -1
  42. package/dist/chunk-7U3EP4Q5.js.map +0 -1
package/dist/engine.cjs CHANGED
@@ -182,6 +182,305 @@ function activatedPorts(node, result) {
182
182
  return { ports: declared?.length ? declared : ["out"], value: result };
183
183
  }
184
184
 
185
+ // src/registry/pause.ts
186
+ var PAUSE_PREFIX = "fancy-flow:pause:";
187
+ var LEGACY_PAUSE_PREFIXES = [
188
+ ["awaiting-approval:", "approval"],
189
+ ["awaiting-input:", "input"]
190
+ ];
191
+ function encodePause(signal) {
192
+ const { nodeId, awaiting, detail } = signal;
193
+ return PAUSE_PREFIX + JSON.stringify(detail === void 0 ? { nodeId, awaiting } : { nodeId, awaiting, detail });
194
+ }
195
+ function decodePause(reason) {
196
+ if (typeof reason !== "string") return null;
197
+ if (reason.startsWith(PAUSE_PREFIX)) {
198
+ const body = reason.slice(PAUSE_PREFIX.length);
199
+ try {
200
+ const parsed = JSON.parse(body);
201
+ if (typeof parsed?.nodeId !== "string" || typeof parsed?.awaiting !== "string") return null;
202
+ return "detail" in parsed ? { nodeId: parsed.nodeId, awaiting: parsed.awaiting, detail: parsed.detail } : { nodeId: parsed.nodeId, awaiting: parsed.awaiting };
203
+ } catch {
204
+ return null;
205
+ }
206
+ }
207
+ for (const [prefix, awaiting] of LEGACY_PAUSE_PREFIXES) {
208
+ if (reason.startsWith(prefix)) {
209
+ return { nodeId: reason.slice(prefix.length), awaiting };
210
+ }
211
+ }
212
+ return null;
213
+ }
214
+ function isPause(reason) {
215
+ return decodePause(reason) !== null;
216
+ }
217
+ function pauseForHuman(ctx, awaiting, detail) {
218
+ return ctx.abort(encodePause({ nodeId: ctx.node.id, awaiting, detail }));
219
+ }
220
+
221
+ // src/marketplace/manifest.ts
222
+ var NODE_MANIFEST_SCHEMA_VERSION = 1;
223
+ var FIRST_PARTY_SCOPE = "@particle-academy/";
224
+ var NAMESPACED_KIND = /^@[a-z0-9][a-z0-9._-]*\/[a-z0-9][a-z0-9._-]*$/i;
225
+ function err(field, message) {
226
+ return { level: "error", field, message };
227
+ }
228
+ function warn(field, message) {
229
+ return { level: "warning", field, message };
230
+ }
231
+ function validateNodeManifest(input) {
232
+ const problems = [];
233
+ if (typeof input !== "object" || input === null || Array.isArray(input)) {
234
+ return { ok: false, problems: [err("", "Manifest must be a JSON object.")] };
235
+ }
236
+ const m = input;
237
+ if (m.schemaVersion !== NODE_MANIFEST_SCHEMA_VERSION) {
238
+ if (typeof m.schemaVersion !== "number") {
239
+ problems.push(err("schemaVersion", `Required, and must be ${NODE_MANIFEST_SCHEMA_VERSION}.`));
240
+ } else {
241
+ return {
242
+ ok: false,
243
+ problems: [
244
+ err(
245
+ "schemaVersion",
246
+ `Unsupported manifest version ${m.schemaVersion}; this fancy-flow understands ${NODE_MANIFEST_SCHEMA_VERSION}. Upgrade fancy-flow to install this node.`
247
+ )
248
+ ]
249
+ };
250
+ }
251
+ }
252
+ if (typeof m.name !== "string" || m.name.trim() === "") {
253
+ problems.push(err("name", "Required \u2014 the package name as installed."));
254
+ }
255
+ if (typeof m.kind !== "string" || m.kind.trim() === "") {
256
+ problems.push(err("kind", "Required \u2014 the canonical kind id this package provides."));
257
+ } else if (!NAMESPACED_KIND.test(m.kind)) {
258
+ problems.push(
259
+ err("kind", `"${m.kind}" must be namespaced as @scope/name \u2014 a bare id makes stored graphs ambiguous, and that is unfixable once documents carry it.`)
260
+ );
261
+ } else if (m.kind.startsWith(FIRST_PARTY_SCOPE)) {
262
+ problems.push(
263
+ warn("kind", `${FIRST_PARTY_SCOPE}* is reserved for first-party nodes; the registry will reject this unless the package is first-party.`)
264
+ );
265
+ }
266
+ if (typeof m.fancyFlow !== "string" || m.fancyFlow.trim() === "") {
267
+ problems.push(err("fancyFlow", "Required \u2014 the semver range of fancy-flow this node targets."));
268
+ }
269
+ if (typeof m.runtimes !== "object" || m.runtimes === null || Array.isArray(m.runtimes)) {
270
+ problems.push(err("runtimes", "Required \u2014 an object of runtime id to entrypoint."));
271
+ } else {
272
+ const entries = Object.entries(m.runtimes);
273
+ if (entries.length === 0) {
274
+ problems.push(err("runtimes", "A node that implements no runtime cannot execute anywhere."));
275
+ }
276
+ for (const [runtime, entry] of entries) {
277
+ if (typeof entry !== "string" || entry.trim() === "") {
278
+ problems.push(err(`runtimes.${runtime}`, "Entrypoint must be a non-empty string."));
279
+ }
280
+ }
281
+ }
282
+ if (typeof m.fixtures !== "string" || m.fixtures.trim() === "") {
283
+ problems.push(
284
+ err("fixtures", "Required \u2014 path to the node's golden fixtures. Every claimed runtime runs them, which is what makes cross-runtime parity verified rather than claimed.")
285
+ );
286
+ }
287
+ if (m.capabilities !== void 0) {
288
+ if (!Array.isArray(m.capabilities) || m.capabilities.some((c) => typeof c !== "string")) {
289
+ problems.push(err("capabilities", "Must be an array of capability id strings."));
290
+ }
291
+ }
292
+ if (m.verified !== void 0) {
293
+ problems.push(
294
+ err("verified", "Assigned by the registry, not the package. Remove it \u2014 a package cannot vouch for itself.")
295
+ );
296
+ }
297
+ const ok = !problems.some((p) => p.level === "error");
298
+ return ok ? { ok, manifest: m, problems } : { ok, problems };
299
+ }
300
+ function checkRuntimeSupport(manifest, hostRuntimes) {
301
+ const provided = Object.keys(manifest.runtimes ?? {});
302
+ const missing = hostRuntimes.filter((r) => !provided.includes(r));
303
+ if (missing.length === 0) return [];
304
+ return [
305
+ err(
306
+ "runtimes",
307
+ `${manifest.kind} implements ${provided.join(", ") || "no runtime"} but this project executes on ${missing.join(", ")}. The node would install, appear in the palette, and then fail to run.`
308
+ )
309
+ ];
310
+ }
311
+ function checkCapabilities(manifest, available) {
312
+ const needed = manifest.capabilities ?? [];
313
+ const missing = needed.filter((c) => available[c] !== true);
314
+ if (missing.length === 0) return [];
315
+ return [
316
+ warn(
317
+ "capabilities",
318
+ `${manifest.kind} needs ${missing.join(", ")} wired on the host. Until then the node will fail at run time rather than at install.`
319
+ )
320
+ ];
321
+ }
322
+
323
+ // src/marketplace/fixtures.ts
324
+ var SUBJECT = "subject";
325
+ var TRIGGER = "trigger";
326
+ var probeId = (port) => `probe:${port}`;
327
+ function buildGraph(kindId, testCase) {
328
+ const subject = {
329
+ id: SUBJECT,
330
+ type: kindId,
331
+ position: { x: 0, y: 100 },
332
+ data: { kind: kindId, config: testCase.config ?? {} }
333
+ };
334
+ const kind = getNodeKind(kindId) ?? void 0;
335
+ const ports = (resolveNodePorts(subject, kind).outputs ?? []).map((p) => p.id);
336
+ const effective = ports.length ? ports : ["out"];
337
+ const nodes = [
338
+ { id: TRIGGER, type: "manual_trigger", position: { x: 0, y: 0 }, data: {} },
339
+ subject,
340
+ ...effective.map(
341
+ (port, i) => ({
342
+ id: probeId(port),
343
+ type: "@particle-academy/transform",
344
+ position: { x: i * 200, y: 200 },
345
+ data: {}
346
+ })
347
+ )
348
+ ];
349
+ const edges = [
350
+ { id: `e:trigger`, source: TRIGGER, target: SUBJECT },
351
+ ...effective.map((port) => ({
352
+ id: `e:${port}`,
353
+ source: SUBJECT,
354
+ sourceHandle: port,
355
+ target: probeId(port)
356
+ }))
357
+ ];
358
+ return { graph: { nodes, edges }, ports: effective };
359
+ }
360
+ function deepEqual(a, b) {
361
+ return JSON.stringify(a) === JSON.stringify(b);
362
+ }
363
+ async function runFixtures(file, executor) {
364
+ const failures = [];
365
+ let passed = 0;
366
+ for (const testCase of file.cases) {
367
+ const { graph } = buildGraph(file.kind, testCase);
368
+ const fired = [];
369
+ let carried;
370
+ const executors = {
371
+ manual_trigger: () => testCase.inputs ?? {},
372
+ "@particle-academy/manual_trigger": () => testCase.inputs ?? {},
373
+ [file.kind]: executor
374
+ };
375
+ for (const node of graph.nodes) {
376
+ if (!node.id.startsWith("probe:")) continue;
377
+ const port = node.id.slice("probe:".length);
378
+ executors[node.id] = ((ctx) => {
379
+ fired.push(port);
380
+ carried = ctx.inputs?.in ?? ctx.inputs;
381
+ return void 0;
382
+ });
383
+ }
384
+ const result = await runFlow(graph, executors, () => {
385
+ });
386
+ const fail = (message) => failures.push({ case: testCase.name, message });
387
+ const expected = testCase.expect;
388
+ let caseOk = true;
389
+ if (expected.pause) {
390
+ const paused = decodePause(result.error);
391
+ if (!paused) {
392
+ caseOk = false;
393
+ fail(`expected a pause awaiting "${expected.pause.awaiting}", got ${result.ok ? "a completed run" : `error: ${result.error}`}`);
394
+ } else {
395
+ if (paused.awaiting !== expected.pause.awaiting) {
396
+ caseOk = false;
397
+ fail(`expected pause awaiting "${expected.pause.awaiting}", got "${paused.awaiting}"`);
398
+ }
399
+ if ("detail" in expected.pause && !deepEqual(paused.detail, expected.pause.detail)) {
400
+ caseOk = false;
401
+ fail(`pause detail mismatch: expected ${JSON.stringify(expected.pause.detail)}, got ${JSON.stringify(paused.detail)}`);
402
+ }
403
+ }
404
+ } else if (expected.error !== void 0) {
405
+ if (result.ok) {
406
+ caseOk = false;
407
+ fail(`expected an error containing "${expected.error}", but the run succeeded`);
408
+ } else if (!String(result.error ?? "").includes(expected.error)) {
409
+ caseOk = false;
410
+ fail(`expected an error containing "${expected.error}", got "${result.error}"`);
411
+ }
412
+ } else {
413
+ if (expected.ports !== void 0) {
414
+ const got = [...fired].sort();
415
+ const want = [...expected.ports].sort();
416
+ if (!deepEqual(got, want)) {
417
+ caseOk = false;
418
+ fail(
419
+ `expected these ports to reach a downstream node: [${want.join(", ")}], but [${got.join(", ")}] did` + (result.ok ? "" : ` (run error: ${result.error})`)
420
+ );
421
+ }
422
+ }
423
+ if ("value" in expected && !deepEqual(carried, expected.value)) {
424
+ caseOk = false;
425
+ fail(`expected the value carried downstream to be ${JSON.stringify(expected.value)}, got ${JSON.stringify(carried)}`);
426
+ }
427
+ }
428
+ if (caseOk) passed += 1;
429
+ }
430
+ return { ok: failures.length === 0, passed, failures };
431
+ }
432
+ function validateFixtureFile(input, expectedKind) {
433
+ const problems = [];
434
+ if (typeof input !== "object" || input === null || Array.isArray(input)) {
435
+ return ["Fixture file must be a JSON object."];
436
+ }
437
+ const f = input;
438
+ if (typeof f.kind !== "string" || f.kind.trim() === "") {
439
+ problems.push("`kind` is required \u2014 the kind these cases exercise.");
440
+ } else if (expectedKind && f.kind !== expectedKind) {
441
+ problems.push(`\`kind\` is "${f.kind}" but the manifest declares "${expectedKind}".`);
442
+ }
443
+ if (!Array.isArray(f.cases) || f.cases.length === 0) {
444
+ problems.push("`cases` must contain at least one case \u2014 an empty fixture file proves nothing.");
445
+ return problems;
446
+ }
447
+ f.cases.forEach((c, i) => {
448
+ const label = `cases[${i}]`;
449
+ if (typeof c !== "object" || c === null) {
450
+ problems.push(`${label} must be an object.`);
451
+ return;
452
+ }
453
+ const testCase = c;
454
+ if (typeof testCase.name !== "string" || testCase.name.trim() === "") {
455
+ problems.push(`${label}.name is required \u2014 a failure report names it.`);
456
+ }
457
+ if (typeof testCase.expect !== "object" || testCase.expect === null) {
458
+ problems.push(`${label}.expect is required \u2014 a case that asserts nothing passes vacuously.`);
459
+ return;
460
+ }
461
+ const expect = testCase.expect;
462
+ if (expect.ports === void 0 && expect.value === void 0 && expect.pause === void 0 && expect.error === void 0) {
463
+ problems.push(`${label}.expect must assert at least one of: ports, value, pause, error.`);
464
+ }
465
+ if (expect.ports !== void 0 && (!Array.isArray(expect.ports) || expect.ports.some((p) => typeof p !== "string"))) {
466
+ problems.push(`${label}.expect.ports must be an array of port id strings.`);
467
+ }
468
+ });
469
+ return problems;
470
+ }
471
+
472
+ exports.LEGACY_PAUSE_PREFIXES = LEGACY_PAUSE_PREFIXES;
473
+ exports.NODE_MANIFEST_SCHEMA_VERSION = NODE_MANIFEST_SCHEMA_VERSION;
474
+ exports.PAUSE_PREFIX = PAUSE_PREFIX;
475
+ exports.checkCapabilities = checkCapabilities;
476
+ exports.checkRuntimeSupport = checkRuntimeSupport;
477
+ exports.decodePause = decodePause;
478
+ exports.encodePause = encodePause;
479
+ exports.isPause = isPause;
480
+ exports.pauseForHuman = pauseForHuman;
481
+ exports.runFixtures = runFixtures;
185
482
  exports.runFlow = runFlow;
483
+ exports.validateFixtureFile = validateFixtureFile;
484
+ exports.validateNodeManifest = validateNodeManifest;
186
485
  //# sourceMappingURL=engine.cjs.map
187
486
  //# sourceMappingURL=engine.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/registry/registry.ts","../src/registry/ports.ts","../src/runtime/run-flow.ts"],"names":[],"mappings":"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","file":"engine.cjs","sourcesContent":["import type { ConfigField, NodeKindDefinition } from \"./types\";\n\nconst kinds = new Map<string, NodeKindDefinition<any, any, any>>();\n/** alias → canonical name. See `resolveKindId`. */\nconst aliases = new Map<string, string>();\nconst listeners = new Set<() => void>();\n\n/**\n * registerNodeKind — install a node kind in the global registry. Returns\n * an `unregister` function. Calling with the same name replaces the prior\n * registration (handy for HMR).\n *\n * A kind's `name` is its CANONICAL id and is what gets written into saved\n * documents. Publish namespaced (`@fancy/llm_branch`, `@acme/salesforce_upsert`)\n * and list any previous bare names in `aliases`, so graphs saved before the\n * rename keep resolving.\n */\nexport function registerNodeKind<TC = any, TI = any, TO = any>(\n definition: NodeKindDefinition<TC, TI, TO>,\n): () => void {\n kinds.set(definition.name, definition as NodeKindDefinition<any, any, any>);\n for (const alias of definition.aliases ?? []) aliases.set(alias, definition.name);\n notify();\n return () => {\n if (kinds.get(definition.name) === (definition as any)) {\n kinds.delete(definition.name);\n for (const alias of definition.aliases ?? []) {\n if (aliases.get(alias) === definition.name) aliases.delete(alias);\n }\n notify();\n }\n };\n}\n\n/**\n * Resolve any id — canonical or alias — to the canonical one, or null.\n *\n * `kind` is persisted inside every saved graph, so a bare name that two\n * packages could both claim is unfixable after the fact: the ambiguous string\n * is already in the document. Canonical ids are namespaced; aliases exist so\n * documents written before namespacing keep opening.\n */\nexport function resolveKindId(id: string): string | null {\n if (kinds.has(id)) return id;\n const canonical = aliases.get(id);\n return canonical && kinds.has(canonical) ? canonical : null;\n}\n\n/** Get a single kind by canonical id or alias, or null. */\nexport function getNodeKind(name: string): NodeKindDefinition | null {\n const canonical = resolveKindId(name);\n return canonical ? ((kinds.get(canonical) as NodeKindDefinition) ?? null) : null;\n}\n\n/** Every id a kind answers to — canonical first. Used to key node-type maps. */\nexport function kindIds(kind: NodeKindDefinition): string[] {\n return [kind.name, ...(kind.aliases ?? [])];\n}\n\n/** List every registered kind, optionally filtered by category. */\nexport function listNodeKinds(category?: string): NodeKindDefinition[] {\n const all = Array.from(kinds.values()) as NodeKindDefinition[];\n return category ? all.filter((k) => k.category === category) : all;\n}\n\n/** Subscribe to registry changes. Returns an unsubscribe function. */\nexport function onNodeKindsChanged(listener: () => void): () => void {\n listeners.add(listener);\n return () => listeners.delete(listener);\n}\n\nfunction notify(): void {\n for (const l of listeners) l();\n}\n\n/** Fill in defaults from a kind's configSchema for newly-created nodes. */\nexport function defaultConfigFor(kind: NodeKindDefinition): Record<string, unknown> {\n const fromKind = kind.defaultConfig ? { ...(kind.defaultConfig as Record<string, unknown>) } : {};\n for (const field of kind.configSchema ?? []) {\n if (fromKind[field.key] !== undefined) continue;\n if (\"default\" in field && (field as any).default !== undefined) {\n fromKind[field.key] = (field as any).default;\n }\n }\n return fromKind;\n}\n\n/**\n * Validate a config object against a kind's schema. Returns an array of\n * issues (empty = valid). Validation is intentionally light — type\n * coercion + required-field checks. Hosts can layer Zod / Ajv on top.\n */\nexport function validateConfig(\n kind: NodeKindDefinition,\n config: Record<string, unknown>,\n): Array<{ key: string; message: string }> {\n const issues: Array<{ key: string; message: string }> = [];\n for (const field of kind.configSchema ?? []) {\n const value = config[field.key];\n if (field.required && (value === undefined || value === null || value === \"\")) {\n issues.push({ key: field.key, message: `${field.label} is required` });\n continue;\n }\n if (value === undefined || value === null) continue;\n const issue = validateField(field, value);\n if (issue) issues.push({ key: field.key, message: issue });\n }\n return issues;\n}\n\nfunction validateField(field: ConfigField, value: unknown): string | null {\n switch (field.type) {\n case \"text\":\n case \"textarea\":\n case \"expression\":\n case \"credential\":\n return typeof value === \"string\" ? null : `${field.label} must be a string`;\n case \"number\": {\n if (typeof value !== \"number\" || !Number.isFinite(value)) return `${field.label} must be a number`;\n if (field.min !== undefined && value < field.min) return `${field.label} must be >= ${field.min}`;\n if (field.max !== undefined && value > field.max) return `${field.label} must be <= ${field.max}`;\n return null;\n }\n case \"switch\":\n return typeof value === \"boolean\" ? null : `${field.label} must be a boolean`;\n case \"select\": {\n const allowed = field.options.map((o) => o.value);\n return allowed.includes(String(value)) ? null : `${field.label} must be one of ${allowed.join(\", \")}`;\n }\n case \"json\":\n return null; // permissive — just JSON-shaped\n case \"repeater\": {\n if (!Array.isArray(value)) return `${field.label} must be a list`;\n if (field.minItems !== undefined && value.length < field.minItems) {\n return `${field.label} needs at least ${field.minItems}`;\n }\n if (field.maxItems !== undefined && value.length > field.maxItems) {\n return `${field.label} allows at most ${field.maxItems}`;\n }\n // Surface the first offending row so the author knows WHICH one.\n for (let i = 0; i < value.length; i++) {\n const row = value[i];\n if (!row || typeof row !== \"object\" || Array.isArray(row)) {\n return `${field.label} item ${i + 1} must be an object`;\n }\n for (const sub of field.fields) {\n const cell = (row as Record<string, unknown>)[sub.key];\n if (sub.required && (cell === undefined || cell === null || cell === \"\")) {\n return `${field.label} item ${i + 1}: ${sub.label} is required`;\n }\n if (cell === undefined || cell === null) continue;\n const issue = validateField(sub, cell);\n if (issue) return `${field.label} item ${i + 1}: ${issue}`;\n }\n }\n return null;\n }\n case \"keyvalue\": {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n return `${field.label} must be a key/value map`;\n }\n const allowed = field.valueOptions?.map((o) => o.value);\n for (const [k, v] of Object.entries(value as Record<string, unknown>)) {\n if (typeof v !== \"string\") return `${field.label}: \"${k}\" must be a string`;\n if (allowed && !allowed.includes(v)) {\n return `${field.label}: \"${k}\" must be one of ${allowed.join(\", \")}`;\n }\n }\n return null;\n }\n case \"document\":\n return null; // opaque to fancy-flow — the host's editor owns its shape\n default:\n return null;\n }\n}\n\n/** Default accents per category. */\nexport function categoryAccent(category: string): string {\n switch (category) {\n case \"trigger\": return \"#10b981\";\n case \"logic\": return \"#f59e0b\";\n case \"data\": return \"#0ea5e9\";\n case \"ai\": return \"#8b5cf6\";\n case \"io\": return \"#3b82f6\";\n case \"human\": return \"#ec4899\";\n case \"output\": return \"#a855f7\";\n default: return \"#71717a\";\n }\n}\n","import type { FlowNode, PortDescriptor } from \"../types\";\nimport type { NodeKindDefinition, PortSpec } from \"./types\";\n\n/**\n * Port resolution — the single place a node's ports are derived, shared by\n * the canvas renderer and the headless runtime.\n *\n * This module is deliberately React-free: `runFlow` (the `/engine` entry)\n * imports it, and the engine bundle must stay free of React.\n *\n * ## Why this is centralized\n *\n * Ports used to be read in two places that disagreed — the canvas consulted\n * `data.outputs ?? kind.outputs`, while the runtime consulted `data.outputs`\n * ONLY and fell back to a single `out` port. A kind that declared branch ports\n * therefore drew correctly and then routed as if it had one output, unless the\n * host remembered to mirror the ports onto every node's `data`. Both callers\n * now go through `resolveNodePorts`, so declared ports and executed ports\n * cannot drift apart.\n */\n\n/**\n * Resolve a `PortSpec` against a config object.\n *\n * A config-driven spec is author-supplied and runs on every render, so a throw\n * is contained: it degrades to \"undeclared\" (letting the caller fall back)\n * rather than taking out the canvas or aborting a run mid-flight.\n */\nexport function resolvePortSpec<TConfig>(\n spec: PortSpec<TConfig> | undefined,\n config: TConfig,\n): PortDescriptor[] | undefined {\n if (spec === undefined) return undefined;\n if (typeof spec !== \"function\") return spec;\n try {\n const resolved = (spec as (c: TConfig) => PortDescriptor[])(config);\n return Array.isArray(resolved) ? resolved : undefined;\n } catch {\n return undefined;\n }\n}\n\n/** Read the config bag off a node, tolerating the FlowNodeData union. */\nexport function nodeConfig(node: Pick<FlowNode, \"data\">): Record<string, unknown> {\n return ((node.data as any)?.config ?? {}) as Record<string, unknown>;\n}\n\n/**\n * Resolve a node's effective ports.\n *\n * Precedence: explicit `data.inputs`/`data.outputs` (a per-node host override)\n * beats the kind's declaration. `undefined` means \"nothing declared\" — the\n * caller applies its own category default.\n */\nexport function resolveNodePorts(\n node: Pick<FlowNode, \"data\">,\n kind?: Pick<NodeKindDefinition<any>, \"inputs\" | \"outputs\">,\n): { inputs?: PortDescriptor[]; outputs?: PortDescriptor[] } {\n const config = nodeConfig(node);\n const data = node.data as any;\n return {\n inputs: data?.inputs ?? resolvePortSpec(kind?.inputs, config),\n outputs: data?.outputs ?? resolvePortSpec(kind?.outputs, config),\n };\n}\n","import type {\n ExecutorRegistry,\n FlowEdge,\n FlowGraph,\n FlowNode,\n NodeExecutor,\n RunEvent,\n} from \"../types\";\n// Both modules are React-free by design — the `/engine` entry must not pull in\n// React. Import them directly rather than via the `registry` barrel, which\n// re-exports the RegistryNode component.\nimport { getNodeKind, kindIds } from \"../registry/registry\";\nimport { resolveNodePorts } from \"../registry/ports\";\n\nexport type RunOptions = {\n /** Stop the run after this many ms. Default: no timeout. */\n timeoutMs?: number;\n /** Abort signal — host can cancel the run. */\n signal?: AbortSignal;\n /** Initial inputs supplied to entry-point nodes (no incoming edges). */\n initialInputs?: Record<string, Record<string, unknown>>;\n /** Nesting depth — set by `subflow` when it runs a child graph. */\n depth?: number;\n};\n\nexport type RunResult = {\n ok: boolean;\n /** Outputs collected per node, keyed by node id. */\n outputs: Record<string, unknown>;\n /** Error captured if any node threw. */\n error?: string;\n};\n\n/**\n * runFlow — topological execution of a FlowGraph against an ExecutorRegistry.\n *\n * Each node runs once, when all upstream nodes have produced outputs on the\n * connected ports. Decision nodes (or any executor that returns `{ branch:\n * 'true' }`) can short-circuit specific output ports — only edges leaving\n * an \"active\" port propagate to downstream nodes.\n *\n * Cycles are detected and abort the run with an error.\n *\n * The `onEvent` callback receives a stream of `RunEvent`s — wire it to a\n * status feed, log panel, or store.\n */\nexport async function runFlow(\n graph: FlowGraph,\n executors: ExecutorRegistry,\n onEvent: (event: RunEvent) => void = () => {},\n options: RunOptions = {},\n): Promise<RunResult> {\n const { signal, initialInputs = {}, timeoutMs, depth = 0 } = options;\n const outputs: Record<string, unknown> = {};\n const portValues = new Map<string, unknown>(); // key: `${nodeId}:${portId}`\n const completed = new Set<string>();\n const errors: string[] = [];\n\n // Topological order via Kahn's algorithm. We allow nodes to run as soon\n // as their incoming edges' source ports have produced values, so the\n // order here is just a deterministic baseline used for cycle detection.\n const order = topoSort(graph);\n if (order === null) {\n const msg = \"Cycle detected in flow graph — aborting.\";\n onEvent({ type: \"run-error\", error: msg });\n return { ok: false, outputs, error: msg };\n }\n\n const incomingByNode = indexIncoming(graph.edges);\n const timer = timeoutMs ? setTimeout(() => errors.push(`Run timed out after ${timeoutMs}ms`), timeoutMs) : null;\n\n onEvent({ type: \"run-start\" });\n\n try {\n for (const node of order) {\n if (signal?.aborted) throw new Error(\"aborted\");\n if (errors.length) break;\n\n const incoming = incomingByNode.get(node.id) ?? [];\n\n // Run a node once any upstream branch reaches it. We iterate in\n // topological order, so by the time we reach this node every upstream\n // node has been processed — each incoming edge is therefore *settled*\n // (active or dead, never still-pending). Requiring ALL incoming edges to\n // be active wrongly skipped MERGE POINTS: when a Decision routes down one\n // branch, the other branch's edge stays dead forever, so an `every` check\n // skipped the shared continuation node and halted the run after the first\n // branch (#1). Run when AT LEAST ONE incoming edge is active —\n // collectInputs() only reads from the active ones. A genuine parallel\n // join still works: in topo order both of its inputs are already active.\n if (incoming.length > 0) {\n const anyActive = incoming.some((e) => portValues.has(`${e.source}:${e.sourceHandle ?? \"out\"}`));\n if (!anyActive) {\n onEvent({ type: \"node-status\", nodeId: node.id, status: \"idle\", text: \"skipped\" });\n continue;\n }\n }\n\n // Note nodes are annotations — never executed.\n if (node.type === \"note\") {\n onEvent({ type: \"node-status\", nodeId: node.id, status: \"idle\", text: \"annotation\" });\n continue;\n }\n\n onEvent({ type: \"node-status\", nodeId: node.id, status: \"running\" });\n\n const inputs = collectInputs(node, incoming, portValues, initialInputs);\n const exec = pickExecutor(executors, node);\n if (!exec) {\n const msg = `No executor registered for kind=${node.type}`;\n errors.push(msg);\n onEvent({ type: \"node-status\", nodeId: node.id, status: \"error\", text: msg });\n onEvent({ type: \"log\", nodeId: node.id, level: \"error\", message: msg });\n break;\n }\n\n try {\n const result = await Promise.resolve(\n exec({\n node,\n inputs,\n abort: (reason) => { throw new Error(reason ?? \"aborted\"); },\n emit: onEvent,\n depth,\n }),\n );\n outputs[node.id] = result;\n\n // Decide which output ports were activated. Three conventions:\n // 1) If result is `{ __port: \"out\", value: x }`, only that port emits.\n // 2) If result has `branch: <portId>`, only that port emits (decision sugar).\n // 3) Otherwise, the value is published on every declared output port.\n const activated = activatedPorts(node, result);\n for (const portId of activated.ports) {\n portValues.set(`${node.id}:${portId}`, activated.value);\n onEvent({ type: \"node-output\", nodeId: node.id, portId, value: activated.value });\n }\n completed.add(node.id);\n onEvent({ type: \"node-status\", nodeId: node.id, status: \"done\" });\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n errors.push(msg);\n onEvent({ type: \"node-status\", nodeId: node.id, status: \"error\", text: msg });\n onEvent({ type: \"log\", nodeId: node.id, level: \"error\", message: msg });\n break;\n }\n }\n } finally {\n if (timer) clearTimeout(timer);\n }\n\n const ok = errors.length === 0;\n onEvent({ type: \"run-end\", ok });\n return ok ? { ok, outputs } : { ok, outputs, error: errors[0] };\n}\n\nfunction indexIncoming(edges: FlowEdge[]): Map<string, FlowEdge[]> {\n const map = new Map<string, FlowEdge[]>();\n for (const e of edges) {\n const list = map.get(e.target) ?? [];\n list.push(e);\n map.set(e.target, list);\n }\n return map;\n}\n\nfunction topoSort(graph: FlowGraph): FlowNode[] | null {\n const inDegree = new Map<string, number>();\n for (const n of graph.nodes) inDegree.set(n.id, 0);\n for (const e of graph.edges) inDegree.set(e.target, (inDegree.get(e.target) ?? 0) + 1);\n const queue: string[] = [];\n for (const [id, d] of inDegree) if (d === 0) queue.push(id);\n const ordered: string[] = [];\n while (queue.length) {\n const id = queue.shift()!;\n ordered.push(id);\n for (const e of graph.edges) {\n if (e.source !== id) continue;\n const next = (inDegree.get(e.target) ?? 0) - 1;\n inDegree.set(e.target, next);\n if (next === 0) queue.push(e.target);\n }\n }\n if (ordered.length !== graph.nodes.length) return null;\n const byId = new Map(graph.nodes.map((n) => [n.id, n]));\n return ordered.map((id) => byId.get(id)!).filter(Boolean);\n}\n\nfunction collectInputs(\n node: FlowNode,\n incoming: FlowEdge[],\n portValues: Map<string, unknown>,\n initial: Record<string, Record<string, unknown>>,\n): Record<string, unknown> {\n const inputs: Record<string, unknown> = { ...(initial[node.id] ?? {}) };\n for (const e of incoming) {\n const portId = e.targetHandle ?? \"in\";\n const val = portValues.get(`${e.source}:${e.sourceHandle ?? \"out\"}`);\n inputs[portId] = val;\n }\n return inputs;\n}\n\nfunction pickExecutor(\n executors: ExecutorRegistry,\n node: FlowNode,\n): NodeExecutor | undefined {\n if (executors[node.id]) return executors[node.id];\n if (node.type && executors[node.type]) return executors[node.type];\n\n // Try every id the kind answers to. Kinds are namespaced (`@fancy/switch_case`)\n // while a host may have bound its executor under the bare name it used before\n // — or vice versa. Without this, the rename would silently stop matching and\n // the node would fall through to `*` or simply not run: a breaking change\n // wearing the costume of a rename.\n const kind = node.type ? getNodeKind(node.type) : null;\n if (kind) {\n for (const id of kindIds(kind)) {\n if (executors[id]) return executors[id];\n }\n }\n\n return executors[\"*\"];\n}\n\nfunction activatedPorts(node: FlowNode, result: unknown): { ports: string[]; value: unknown } {\n if (result && typeof result === \"object\") {\n const r = result as Record<string, unknown>;\n if (typeof r.__port === \"string\") {\n return { ports: [r.__port], value: r.value };\n }\n if (typeof r.branch === \"string\") {\n return { ports: [r.branch], value: r.value ?? r };\n }\n }\n // Resolve through the shared helper so the ports the runtime activates are\n // the same ones the canvas drew — including config-driven ports, which the\n // node's `data` does not carry. Falls back to a lone `out` when a node\n // declares nothing.\n const kind = getNodeKind((node.data as any)?.kind ?? node.type ?? \"\") ?? undefined;\n const declared = resolveNodePorts(node, kind).outputs?.map((p) => p.id);\n return { ports: declared?.length ? declared : [\"out\"], value: result };\n}\n"]}
1
+ {"version":3,"sources":["../src/registry/registry.ts","../src/registry/ports.ts","../src/runtime/run-flow.ts","../src/registry/pause.ts","../src/marketplace/manifest.ts","../src/marketplace/fixtures.ts"],"names":[],"mappings":"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","file":"engine.cjs","sourcesContent":["import type { ConfigField, NodeKindDefinition } from \"./types\";\n\nconst kinds = new Map<string, NodeKindDefinition<any, any, any>>();\n/** alias → canonical name. See `resolveKindId`. */\nconst aliases = new Map<string, string>();\nconst listeners = new Set<() => void>();\n\n/**\n * registerNodeKind — install a node kind in the global registry. Returns\n * an `unregister` function. Calling with the same name replaces the prior\n * registration (handy for HMR).\n *\n * A kind's `name` is its CANONICAL id and is what gets written into saved\n * documents. Publish namespaced (`@fancy/llm_branch`, `@acme/salesforce_upsert`)\n * and list any previous bare names in `aliases`, so graphs saved before the\n * rename keep resolving.\n */\nexport function registerNodeKind<TC = any, TI = any, TO = any>(\n definition: NodeKindDefinition<TC, TI, TO>,\n): () => void {\n kinds.set(definition.name, definition as NodeKindDefinition<any, any, any>);\n for (const alias of definition.aliases ?? []) aliases.set(alias, definition.name);\n notify();\n return () => {\n if (kinds.get(definition.name) === (definition as any)) {\n kinds.delete(definition.name);\n for (const alias of definition.aliases ?? []) {\n if (aliases.get(alias) === definition.name) aliases.delete(alias);\n }\n notify();\n }\n };\n}\n\n/**\n * Resolve any id — canonical or alias — to the canonical one, or null.\n *\n * `kind` is persisted inside every saved graph, so a bare name that two\n * packages could both claim is unfixable after the fact: the ambiguous string\n * is already in the document. Canonical ids are namespaced; aliases exist so\n * documents written before namespacing keep opening.\n */\nexport function resolveKindId(id: string): string | null {\n if (kinds.has(id)) return id;\n const canonical = aliases.get(id);\n return canonical && kinds.has(canonical) ? canonical : null;\n}\n\n/** Get a single kind by canonical id or alias, or null. */\nexport function getNodeKind(name: string): NodeKindDefinition | null {\n const canonical = resolveKindId(name);\n return canonical ? ((kinds.get(canonical) as NodeKindDefinition) ?? null) : null;\n}\n\n/** Every id a kind answers to — canonical first. Used to key node-type maps. */\nexport function kindIds(kind: NodeKindDefinition): string[] {\n return [kind.name, ...(kind.aliases ?? [])];\n}\n\n/** List every registered kind, optionally filtered by category. */\nexport function listNodeKinds(category?: string): NodeKindDefinition[] {\n const all = Array.from(kinds.values()) as NodeKindDefinition[];\n return category ? all.filter((k) => k.category === category) : all;\n}\n\n/** Subscribe to registry changes. Returns an unsubscribe function. */\nexport function onNodeKindsChanged(listener: () => void): () => void {\n listeners.add(listener);\n return () => listeners.delete(listener);\n}\n\nfunction notify(): void {\n for (const l of listeners) l();\n}\n\n/** Fill in defaults from a kind's configSchema for newly-created nodes. */\nexport function defaultConfigFor(kind: NodeKindDefinition): Record<string, unknown> {\n const fromKind = kind.defaultConfig ? { ...(kind.defaultConfig as Record<string, unknown>) } : {};\n for (const field of kind.configSchema ?? []) {\n if (fromKind[field.key] !== undefined) continue;\n if (\"default\" in field && (field as any).default !== undefined) {\n fromKind[field.key] = (field as any).default;\n }\n }\n return fromKind;\n}\n\n/**\n * Validate a config object against a kind's schema. Returns an array of\n * issues (empty = valid). Validation is intentionally light — type\n * coercion + required-field checks. Hosts can layer Zod / Ajv on top.\n */\nexport function validateConfig(\n kind: NodeKindDefinition,\n config: Record<string, unknown>,\n): Array<{ key: string; message: string }> {\n const issues: Array<{ key: string; message: string }> = [];\n for (const field of kind.configSchema ?? []) {\n const value = config[field.key];\n if (field.required && (value === undefined || value === null || value === \"\")) {\n issues.push({ key: field.key, message: `${field.label} is required` });\n continue;\n }\n if (value === undefined || value === null) continue;\n const issue = validateField(field, value);\n if (issue) issues.push({ key: field.key, message: issue });\n }\n return issues;\n}\n\nfunction validateField(field: ConfigField, value: unknown): string | null {\n switch (field.type) {\n case \"text\":\n case \"textarea\":\n case \"expression\":\n case \"credential\":\n return typeof value === \"string\" ? null : `${field.label} must be a string`;\n case \"number\": {\n if (typeof value !== \"number\" || !Number.isFinite(value)) return `${field.label} must be a number`;\n if (field.min !== undefined && value < field.min) return `${field.label} must be >= ${field.min}`;\n if (field.max !== undefined && value > field.max) return `${field.label} must be <= ${field.max}`;\n return null;\n }\n case \"switch\":\n return typeof value === \"boolean\" ? null : `${field.label} must be a boolean`;\n case \"select\": {\n const allowed = field.options.map((o) => o.value);\n return allowed.includes(String(value)) ? null : `${field.label} must be one of ${allowed.join(\", \")}`;\n }\n case \"json\":\n return null; // permissive — just JSON-shaped\n case \"repeater\": {\n if (!Array.isArray(value)) return `${field.label} must be a list`;\n if (field.minItems !== undefined && value.length < field.minItems) {\n return `${field.label} needs at least ${field.minItems}`;\n }\n if (field.maxItems !== undefined && value.length > field.maxItems) {\n return `${field.label} allows at most ${field.maxItems}`;\n }\n // Surface the first offending row so the author knows WHICH one.\n for (let i = 0; i < value.length; i++) {\n const row = value[i];\n if (!row || typeof row !== \"object\" || Array.isArray(row)) {\n return `${field.label} item ${i + 1} must be an object`;\n }\n for (const sub of field.fields) {\n const cell = (row as Record<string, unknown>)[sub.key];\n if (sub.required && (cell === undefined || cell === null || cell === \"\")) {\n return `${field.label} item ${i + 1}: ${sub.label} is required`;\n }\n if (cell === undefined || cell === null) continue;\n const issue = validateField(sub, cell);\n if (issue) return `${field.label} item ${i + 1}: ${issue}`;\n }\n }\n return null;\n }\n case \"keyvalue\": {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n return `${field.label} must be a key/value map`;\n }\n const allowed = field.valueOptions?.map((o) => o.value);\n for (const [k, v] of Object.entries(value as Record<string, unknown>)) {\n if (typeof v !== \"string\") return `${field.label}: \"${k}\" must be a string`;\n if (allowed && !allowed.includes(v)) {\n return `${field.label}: \"${k}\" must be one of ${allowed.join(\", \")}`;\n }\n }\n return null;\n }\n case \"document\":\n return null; // opaque to fancy-flow — the host's editor owns its shape\n default:\n return null;\n }\n}\n\n/** Default accents per category. */\nexport function categoryAccent(category: string): string {\n switch (category) {\n case \"trigger\": return \"#10b981\";\n case \"logic\": return \"#f59e0b\";\n case \"data\": return \"#0ea5e9\";\n case \"ai\": return \"#8b5cf6\";\n case \"io\": return \"#3b82f6\";\n case \"human\": return \"#ec4899\";\n case \"output\": return \"#a855f7\";\n default: return \"#71717a\";\n }\n}\n","import type { FlowNode, PortDescriptor } from \"../types\";\nimport type { NodeKindDefinition, PortSpec } from \"./types\";\n\n/**\n * Port resolution — the single place a node's ports are derived, shared by\n * the canvas renderer and the headless runtime.\n *\n * This module is deliberately React-free: `runFlow` (the `/engine` entry)\n * imports it, and the engine bundle must stay free of React.\n *\n * ## Why this is centralized\n *\n * Ports used to be read in two places that disagreed — the canvas consulted\n * `data.outputs ?? kind.outputs`, while the runtime consulted `data.outputs`\n * ONLY and fell back to a single `out` port. A kind that declared branch ports\n * therefore drew correctly and then routed as if it had one output, unless the\n * host remembered to mirror the ports onto every node's `data`. Both callers\n * now go through `resolveNodePorts`, so declared ports and executed ports\n * cannot drift apart.\n */\n\n/**\n * Resolve a `PortSpec` against a config object.\n *\n * A config-driven spec is author-supplied and runs on every render, so a throw\n * is contained: it degrades to \"undeclared\" (letting the caller fall back)\n * rather than taking out the canvas or aborting a run mid-flight.\n */\nexport function resolvePortSpec<TConfig>(\n spec: PortSpec<TConfig> | undefined,\n config: TConfig,\n): PortDescriptor[] | undefined {\n if (spec === undefined) return undefined;\n if (typeof spec !== \"function\") return spec;\n try {\n const resolved = (spec as (c: TConfig) => PortDescriptor[])(config);\n return Array.isArray(resolved) ? resolved : undefined;\n } catch {\n return undefined;\n }\n}\n\n/** Read the config bag off a node, tolerating the FlowNodeData union. */\nexport function nodeConfig(node: Pick<FlowNode, \"data\">): Record<string, unknown> {\n return ((node.data as any)?.config ?? {}) as Record<string, unknown>;\n}\n\n/**\n * Resolve a node's effective ports.\n *\n * Precedence: explicit `data.inputs`/`data.outputs` (a per-node host override)\n * beats the kind's declaration. `undefined` means \"nothing declared\" — the\n * caller applies its own category default.\n */\nexport function resolveNodePorts(\n node: Pick<FlowNode, \"data\">,\n kind?: Pick<NodeKindDefinition<any>, \"inputs\" | \"outputs\">,\n): { inputs?: PortDescriptor[]; outputs?: PortDescriptor[] } {\n const config = nodeConfig(node);\n const data = node.data as any;\n return {\n inputs: data?.inputs ?? resolvePortSpec(kind?.inputs, config),\n outputs: data?.outputs ?? resolvePortSpec(kind?.outputs, config),\n };\n}\n","import type {\n ExecutorRegistry,\n FlowEdge,\n FlowGraph,\n FlowNode,\n NodeExecutor,\n RunEvent,\n} from \"../types\";\n// Both modules are React-free by design — the `/engine` entry must not pull in\n// React. Import them directly rather than via the `registry` barrel, which\n// re-exports the RegistryNode component.\nimport { getNodeKind, kindIds } from \"../registry/registry\";\nimport { resolveNodePorts } from \"../registry/ports\";\n\nexport type RunOptions = {\n /** Stop the run after this many ms. Default: no timeout. */\n timeoutMs?: number;\n /** Abort signal — host can cancel the run. */\n signal?: AbortSignal;\n /** Initial inputs supplied to entry-point nodes (no incoming edges). */\n initialInputs?: Record<string, Record<string, unknown>>;\n /** Nesting depth — set by `subflow` when it runs a child graph. */\n depth?: number;\n};\n\nexport type RunResult = {\n ok: boolean;\n /** Outputs collected per node, keyed by node id. */\n outputs: Record<string, unknown>;\n /** Error captured if any node threw. */\n error?: string;\n};\n\n/**\n * runFlow — topological execution of a FlowGraph against an ExecutorRegistry.\n *\n * Each node runs once, when all upstream nodes have produced outputs on the\n * connected ports. Decision nodes (or any executor that returns `{ branch:\n * 'true' }`) can short-circuit specific output ports — only edges leaving\n * an \"active\" port propagate to downstream nodes.\n *\n * Cycles are detected and abort the run with an error.\n *\n * The `onEvent` callback receives a stream of `RunEvent`s — wire it to a\n * status feed, log panel, or store.\n */\nexport async function runFlow(\n graph: FlowGraph,\n executors: ExecutorRegistry,\n onEvent: (event: RunEvent) => void = () => {},\n options: RunOptions = {},\n): Promise<RunResult> {\n const { signal, initialInputs = {}, timeoutMs, depth = 0 } = options;\n const outputs: Record<string, unknown> = {};\n const portValues = new Map<string, unknown>(); // key: `${nodeId}:${portId}`\n const completed = new Set<string>();\n const errors: string[] = [];\n\n // Topological order via Kahn's algorithm. We allow nodes to run as soon\n // as their incoming edges' source ports have produced values, so the\n // order here is just a deterministic baseline used for cycle detection.\n const order = topoSort(graph);\n if (order === null) {\n const msg = \"Cycle detected in flow graph — aborting.\";\n onEvent({ type: \"run-error\", error: msg });\n return { ok: false, outputs, error: msg };\n }\n\n const incomingByNode = indexIncoming(graph.edges);\n const timer = timeoutMs ? setTimeout(() => errors.push(`Run timed out after ${timeoutMs}ms`), timeoutMs) : null;\n\n onEvent({ type: \"run-start\" });\n\n try {\n for (const node of order) {\n if (signal?.aborted) throw new Error(\"aborted\");\n if (errors.length) break;\n\n const incoming = incomingByNode.get(node.id) ?? [];\n\n // Run a node once any upstream branch reaches it. We iterate in\n // topological order, so by the time we reach this node every upstream\n // node has been processed — each incoming edge is therefore *settled*\n // (active or dead, never still-pending). Requiring ALL incoming edges to\n // be active wrongly skipped MERGE POINTS: when a Decision routes down one\n // branch, the other branch's edge stays dead forever, so an `every` check\n // skipped the shared continuation node and halted the run after the first\n // branch (#1). Run when AT LEAST ONE incoming edge is active —\n // collectInputs() only reads from the active ones. A genuine parallel\n // join still works: in topo order both of its inputs are already active.\n if (incoming.length > 0) {\n const anyActive = incoming.some((e) => portValues.has(`${e.source}:${e.sourceHandle ?? \"out\"}`));\n if (!anyActive) {\n onEvent({ type: \"node-status\", nodeId: node.id, status: \"idle\", text: \"skipped\" });\n continue;\n }\n }\n\n // Note nodes are annotations — never executed.\n if (node.type === \"note\") {\n onEvent({ type: \"node-status\", nodeId: node.id, status: \"idle\", text: \"annotation\" });\n continue;\n }\n\n onEvent({ type: \"node-status\", nodeId: node.id, status: \"running\" });\n\n const inputs = collectInputs(node, incoming, portValues, initialInputs);\n const exec = pickExecutor(executors, node);\n if (!exec) {\n const msg = `No executor registered for kind=${node.type}`;\n errors.push(msg);\n onEvent({ type: \"node-status\", nodeId: node.id, status: \"error\", text: msg });\n onEvent({ type: \"log\", nodeId: node.id, level: \"error\", message: msg });\n break;\n }\n\n try {\n const result = await Promise.resolve(\n exec({\n node,\n inputs,\n abort: (reason) => { throw new Error(reason ?? \"aborted\"); },\n emit: onEvent,\n depth,\n }),\n );\n outputs[node.id] = result;\n\n // Decide which output ports were activated. Three conventions:\n // 1) If result is `{ __port: \"out\", value: x }`, only that port emits.\n // 2) If result has `branch: <portId>`, only that port emits (decision sugar).\n // 3) Otherwise, the value is published on every declared output port.\n const activated = activatedPorts(node, result);\n for (const portId of activated.ports) {\n portValues.set(`${node.id}:${portId}`, activated.value);\n onEvent({ type: \"node-output\", nodeId: node.id, portId, value: activated.value });\n }\n completed.add(node.id);\n onEvent({ type: \"node-status\", nodeId: node.id, status: \"done\" });\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n errors.push(msg);\n onEvent({ type: \"node-status\", nodeId: node.id, status: \"error\", text: msg });\n onEvent({ type: \"log\", nodeId: node.id, level: \"error\", message: msg });\n break;\n }\n }\n } finally {\n if (timer) clearTimeout(timer);\n }\n\n const ok = errors.length === 0;\n onEvent({ type: \"run-end\", ok });\n return ok ? { ok, outputs } : { ok, outputs, error: errors[0] };\n}\n\nfunction indexIncoming(edges: FlowEdge[]): Map<string, FlowEdge[]> {\n const map = new Map<string, FlowEdge[]>();\n for (const e of edges) {\n const list = map.get(e.target) ?? [];\n list.push(e);\n map.set(e.target, list);\n }\n return map;\n}\n\nfunction topoSort(graph: FlowGraph): FlowNode[] | null {\n const inDegree = new Map<string, number>();\n for (const n of graph.nodes) inDegree.set(n.id, 0);\n for (const e of graph.edges) inDegree.set(e.target, (inDegree.get(e.target) ?? 0) + 1);\n const queue: string[] = [];\n for (const [id, d] of inDegree) if (d === 0) queue.push(id);\n const ordered: string[] = [];\n while (queue.length) {\n const id = queue.shift()!;\n ordered.push(id);\n for (const e of graph.edges) {\n if (e.source !== id) continue;\n const next = (inDegree.get(e.target) ?? 0) - 1;\n inDegree.set(e.target, next);\n if (next === 0) queue.push(e.target);\n }\n }\n if (ordered.length !== graph.nodes.length) return null;\n const byId = new Map(graph.nodes.map((n) => [n.id, n]));\n return ordered.map((id) => byId.get(id)!).filter(Boolean);\n}\n\nfunction collectInputs(\n node: FlowNode,\n incoming: FlowEdge[],\n portValues: Map<string, unknown>,\n initial: Record<string, Record<string, unknown>>,\n): Record<string, unknown> {\n const inputs: Record<string, unknown> = { ...(initial[node.id] ?? {}) };\n for (const e of incoming) {\n const portId = e.targetHandle ?? \"in\";\n const val = portValues.get(`${e.source}:${e.sourceHandle ?? \"out\"}`);\n inputs[portId] = val;\n }\n return inputs;\n}\n\nfunction pickExecutor(\n executors: ExecutorRegistry,\n node: FlowNode,\n): NodeExecutor | undefined {\n if (executors[node.id]) return executors[node.id];\n if (node.type && executors[node.type]) return executors[node.type];\n\n // Try every id the kind answers to. Kinds are namespaced (`@fancy/switch_case`)\n // while a host may have bound its executor under the bare name it used before\n // — or vice versa. Without this, the rename would silently stop matching and\n // the node would fall through to `*` or simply not run: a breaking change\n // wearing the costume of a rename.\n const kind = node.type ? getNodeKind(node.type) : null;\n if (kind) {\n for (const id of kindIds(kind)) {\n if (executors[id]) return executors[id];\n }\n }\n\n return executors[\"*\"];\n}\n\nfunction activatedPorts(node: FlowNode, result: unknown): { ports: string[]; value: unknown } {\n if (result && typeof result === \"object\") {\n const r = result as Record<string, unknown>;\n if (typeof r.__port === \"string\") {\n return { ports: [r.__port], value: r.value };\n }\n if (typeof r.branch === \"string\") {\n return { ports: [r.branch], value: r.value ?? r };\n }\n }\n // Resolve through the shared helper so the ports the runtime activates are\n // the same ones the canvas drew — including config-driven ports, which the\n // node's `data` does not carry. Falls back to a lone `out` when a node\n // declares nothing.\n const kind = getNodeKind((node.data as any)?.kind ?? node.type ?? \"\") ?? undefined;\n const declared = resolveNodePorts(node, kind).outputs?.map((p) => p.id);\n return { ports: declared?.length ? declared : [\"out\"], value: result };\n}\n","/**\n * The human-pause contract.\n *\n * A workflow that waits for a person is not an error, but it travels the same\n * channel as one: the executor aborts, the engine records a reason string, and\n * the durable runner decides whether that string meant \"failed\" or \"waiting\".\n *\n * That seam existed before this module, as two `str_starts_with` checks in the\n * Laravel run job against constants owned by two BUILTIN executors. It worked,\n * and it was invisible: a third-party human-input node had no way to announce\n * that it pauses, and nothing stopped a refactor from removing the mechanism\n * out from under published packages. Reported by the MOIC Suite consumer, who\n * needed exactly that and had to reach for a private constant to get it.\n *\n * So the encoding is now public, typed, and versioned by prefix rather than\n * implied. The wire format stays a plain string on purpose — it survives the\n * existing abort → `RunResult.error` path unchanged, crosses a queue boundary,\n * and decodes identically in PHP, none of which a thrown class would do.\n *\n * @see decodePause — the one function a durable runner needs.\n */\n\n/**\n * What the run is waiting for.\n *\n * `approval` and `input` are the shapes both runtimes ship. The type stays open\n * because the whole point is that a marketplace node can define its own —\n * a signature step, a payment confirmation, a review queue — and a runner that\n * does not recognise one should report it rather than guess.\n */\nexport type PauseAwaiting = \"approval\" | \"input\" | (string & {});\n\n/** A run halted, waiting for a person. */\nexport type PauseSignal = {\n /** The node that paused — where a submission gets injected on resume. */\n nodeId: string;\n awaiting: PauseAwaiting;\n /**\n * Kind-supplied context for whoever renders the wait — a form schema, the\n * question being asked, a diff to approve. Must be JSON-serializable: it\n * crosses a queue boundary and, for durable runs, a database column.\n */\n detail?: unknown;\n};\n\n/** Marks a reason string as a pause rather than a failure. */\nexport const PAUSE_PREFIX = \"fancy-flow:pause:\";\n\n/**\n * Reason prefixes shipped before this contract, kept decodable forever.\n *\n * These are what `DurableApprovalExecutor` and `DurableUserInputExecutor`\n * emitted, and they are written into the `error` column of every run that\n * paused under an older version. Dropping them would strand those runs\n * mid-flight — a resume path that only works for new runs is not a resume path.\n */\nexport const LEGACY_PAUSE_PREFIXES: ReadonlyArray<readonly [string, PauseAwaiting]> = [\n [\"awaiting-approval:\", \"approval\"],\n [\"awaiting-input:\", \"input\"],\n];\n\n/**\n * Encode a pause as the reason string an executor aborts with.\n *\n * The payload is JSON rather than delimited fields because a node id may\n * contain a colon, and a positional encoding that breaks on user data is the\n * kind of bug that only shows up in someone else's graph.\n */\nexport function encodePause(signal: PauseSignal): string {\n const { nodeId, awaiting, detail } = signal;\n return PAUSE_PREFIX + JSON.stringify(detail === undefined ? { nodeId, awaiting } : { nodeId, awaiting, detail });\n}\n\n/**\n * Decode a run's error reason into a pause, or null if it was a real failure.\n *\n * This is the whole contract from a runner's side: call it on `result.error`,\n * and if it returns non-null, persist the run as waiting on `signal.nodeId`\n * instead of failing it. Accepts the legacy prefixes, so a runner written\n * against this handles runs that paused under an older version.\n */\nexport function decodePause(reason: string | null | undefined): PauseSignal | null {\n if (typeof reason !== \"string\") return null;\n\n if (reason.startsWith(PAUSE_PREFIX)) {\n const body = reason.slice(PAUSE_PREFIX.length);\n try {\n const parsed = JSON.parse(body) as Partial<PauseSignal>;\n // A malformed payload is a corrupt pause, not a failure to report as a\n // crash — but it is also not something to invent a node id for.\n if (typeof parsed?.nodeId !== \"string\" || typeof parsed?.awaiting !== \"string\") return null;\n return \"detail\" in parsed\n ? { nodeId: parsed.nodeId, awaiting: parsed.awaiting, detail: parsed.detail }\n : { nodeId: parsed.nodeId, awaiting: parsed.awaiting };\n } catch {\n return null;\n }\n }\n\n for (const [prefix, awaiting] of LEGACY_PAUSE_PREFIXES) {\n if (reason.startsWith(prefix)) {\n return { nodeId: reason.slice(prefix.length), awaiting };\n }\n }\n\n return null;\n}\n\n/** True when a run's error reason is actually a pause. */\nexport function isPause(reason: string | null | undefined): boolean {\n return decodePause(reason) !== null;\n}\n\n/**\n * Abort the current node as a pause.\n *\n * Called from inside an executor with its own context. Node authors should\n * reach for this rather than hand-encoding a reason, so the format stays ours\n * to change:\n *\n * ```ts\n * const values = ctx.inputs.values;\n * if (values === undefined) pauseForHuman(ctx, \"input\", { fields });\n * return values;\n * ```\n *\n * Note the `undefined` check — an empty submission (`{}`) is a real answer and\n * must resume. Truthiness here pauses forever on an empty form.\n */\nexport function pauseForHuman(\n ctx: { node: { id: string }; abort: (reason?: string) => never },\n awaiting: PauseAwaiting,\n detail?: unknown,\n): never {\n return ctx.abort(encodePause({ nodeId: ctx.node.id, awaiting, detail }));\n}\n","/**\n * The node package manifest — what a marketplace node declares about itself.\n *\n * A node is not one artifact. It is a kind definition (palette entry, config\n * schema, ports, canvas renderer) plus an executor for EACH runtime the\n * consumer actually runs. A package shipping only a TS executor is unusable to\n * anyone executing on PHP, and today that is invisible until a run fails.\n *\n * So the manifest states which runtimes it implements, and the CLI checks that\n * against the host before installing rather than after. Requested by the MOIC\n * Suite consumer (fancy-flow#2 §2), who runs the editor in TS and executes in\n * PHP and hit exactly this.\n *\n * The manifest is data, not code: the registry, the CLI, and the MCP all read\n * it without executing anything a package author wrote.\n */\n\nimport type { PauseAwaiting } from \"../registry/pause\";\n\n/** The current manifest schema version. Bump only on a breaking shape change. */\nexport const NODE_MANIFEST_SCHEMA_VERSION = 1;\n\n/**\n * A runtime a node can implement.\n *\n * Open rather than a closed union — the PHP and Node twins are what exist\n * today, but the point of a manifest is that a runtime we haven't written can\n * declare itself without a release here.\n */\nexport type NodeRuntimeId = \"ts\" | \"php\" | (string & {});\n\nexport type NodePackageManifest = {\n /** Must equal `NODE_MANIFEST_SCHEMA_VERSION`. */\n schemaVersion: number;\n /** Package name, as installed (`@acme/fancy-flow-salesforce`). */\n name: string;\n /**\n * The canonical kind id this package provides — namespaced, and the string\n * that gets persisted into every document using it.\n */\n kind: string;\n /** Semver range of fancy-flow this node's contract targets. */\n fancyFlow: string;\n /**\n * Per-runtime entrypoints. TS is a module path within the package; PHP is a\n * Composer requirement, because the two ecosystems install differently and\n * pretending otherwise just moves the problem into the CLI.\n */\n runtimes: Partial<Record<NodeRuntimeId, string>>;\n /**\n * Host capabilities this node needs wired before it can run — `llm`,\n * `workflow_resolver`, `document`, or a host-specific one.\n *\n * Declared so the CLI can tell an author what to wire BEFORE install, rather\n * than the node silently no-opping or crashing mid-run.\n */\n capabilities?: string[];\n /**\n * Path to this node's golden fixtures, relative to the package root.\n *\n * REQUIRED. Every runtime the package claims runs these same cases, which is\n * what makes \"behaves identically on both runtimes\" verified rather than\n * asserted. See `./fixtures`.\n */\n fixtures: string;\n /** Declares the node halts for a person. Mirrors `NodeKindDefinition`. */\n pausesForHuman?: PauseAwaiting;\n /** One-line summary — what `search_nodes` matches against. */\n description?: string;\n /**\n * Assigned by the registry, never by the author. Present in a manifest being\n * submitted for publication, it is a claim to a trust signal the author does\n * not get to make.\n */\n verified?: boolean;\n};\n\nexport type ManifestProblem = {\n level: \"error\" | \"warning\";\n field: string;\n message: string;\n};\n\nexport type ManifestValidation = {\n /** True when there are no `error`-level problems. Warnings do not block. */\n ok: boolean;\n manifest?: NodePackageManifest;\n problems: ManifestProblem[];\n};\n\n/** Reserved for first-party packages; the registry rejects other claimants. */\nconst FIRST_PARTY_SCOPE = \"@particle-academy/\";\n\n/** `@scope/name` — the shape 0.11.0 made canonical for kind ids. */\nconst NAMESPACED_KIND = /^@[a-z0-9][a-z0-9._-]*\\/[a-z0-9][a-z0-9._-]*$/i;\n\nfunction err(field: string, message: string): ManifestProblem {\n return { level: \"error\", field, message };\n}\n\nfunction warn(field: string, message: string): ManifestProblem {\n return { level: \"warning\", field, message };\n}\n\n/**\n * Validate a manifest read from disk or a registry.\n *\n * Returns every problem rather than throwing on the first, because an author\n * fixing a package wants the whole list — a validator that reveals one error\n * per run turns a five-minute fix into five round trips.\n */\nexport function validateNodeManifest(input: unknown): ManifestValidation {\n const problems: ManifestProblem[] = [];\n\n if (typeof input !== \"object\" || input === null || Array.isArray(input)) {\n return { ok: false, problems: [err(\"\", \"Manifest must be a JSON object.\")] };\n }\n\n const m = input as Record<string, unknown>;\n\n // Version first: an unknown version means every other check below is\n // guessing at a shape we do not know, so say so plainly instead of\n // half-reading it and reporting confident nonsense about the rest.\n if (m.schemaVersion !== NODE_MANIFEST_SCHEMA_VERSION) {\n if (typeof m.schemaVersion !== \"number\") {\n problems.push(err(\"schemaVersion\", `Required, and must be ${NODE_MANIFEST_SCHEMA_VERSION}.`));\n } else {\n return {\n ok: false,\n problems: [\n err(\n \"schemaVersion\",\n `Unsupported manifest version ${m.schemaVersion}; this fancy-flow understands ${NODE_MANIFEST_SCHEMA_VERSION}. Upgrade fancy-flow to install this node.`,\n ),\n ],\n };\n }\n }\n\n if (typeof m.name !== \"string\" || m.name.trim() === \"\") {\n problems.push(err(\"name\", \"Required — the package name as installed.\"));\n }\n\n if (typeof m.kind !== \"string\" || m.kind.trim() === \"\") {\n problems.push(err(\"kind\", \"Required — the canonical kind id this package provides.\"));\n } else if (!NAMESPACED_KIND.test(m.kind)) {\n // Un-namespaced ids are the one mistake that cannot be fixed after the\n // fact: the ambiguous string is already written into saved documents.\n problems.push(\n err(\"kind\", `\"${m.kind}\" must be namespaced as @scope/name — a bare id makes stored graphs ambiguous, and that is unfixable once documents carry it.`),\n );\n } else if (m.kind.startsWith(FIRST_PARTY_SCOPE)) {\n problems.push(\n warn(\"kind\", `${FIRST_PARTY_SCOPE}* is reserved for first-party nodes; the registry will reject this unless the package is first-party.`),\n );\n }\n\n if (typeof m.fancyFlow !== \"string\" || m.fancyFlow.trim() === \"\") {\n problems.push(err(\"fancyFlow\", \"Required — the semver range of fancy-flow this node targets.\"));\n }\n\n // Runtimes: the check the whole manifest exists for.\n if (typeof m.runtimes !== \"object\" || m.runtimes === null || Array.isArray(m.runtimes)) {\n problems.push(err(\"runtimes\", \"Required — an object of runtime id to entrypoint.\"));\n } else {\n const entries = Object.entries(m.runtimes as Record<string, unknown>);\n if (entries.length === 0) {\n problems.push(err(\"runtimes\", \"A node that implements no runtime cannot execute anywhere.\"));\n }\n for (const [runtime, entry] of entries) {\n if (typeof entry !== \"string\" || entry.trim() === \"\") {\n problems.push(err(`runtimes.${runtime}`, \"Entrypoint must be a non-empty string.\"));\n }\n }\n }\n\n // Fixtures: the publish gate. Cross-runtime drift does not fail loudly —\n // it completes, down one path, with no error — so it has to be caught by\n // something that runs, not by review.\n if (typeof m.fixtures !== \"string\" || m.fixtures.trim() === \"\") {\n problems.push(\n err(\"fixtures\", \"Required — path to the node's golden fixtures. Every claimed runtime runs them, which is what makes cross-runtime parity verified rather than claimed.\"),\n );\n }\n\n if (m.capabilities !== undefined) {\n if (!Array.isArray(m.capabilities) || m.capabilities.some((c) => typeof c !== \"string\")) {\n problems.push(err(\"capabilities\", \"Must be an array of capability id strings.\"));\n }\n }\n\n if (m.verified !== undefined) {\n problems.push(\n err(\"verified\", \"Assigned by the registry, not the package. Remove it — a package cannot vouch for itself.\"),\n );\n }\n\n const ok = !problems.some((p) => p.level === \"error\");\n return ok ? { ok, manifest: m as unknown as NodePackageManifest, problems } : { ok, problems };\n}\n\n/**\n * Check a node against the runtimes a host actually executes on.\n *\n * This is what makes a TS-only package visible to someone running PHP BEFORE\n * they install it rather than at the first run. A missing runtime is an error,\n * not a warning: the node genuinely cannot execute there.\n */\nexport function checkRuntimeSupport(\n manifest: Pick<NodePackageManifest, \"kind\" | \"runtimes\">,\n hostRuntimes: readonly string[],\n): ManifestProblem[] {\n const provided = Object.keys(manifest.runtimes ?? {});\n const missing = hostRuntimes.filter((r) => !provided.includes(r));\n\n if (missing.length === 0) return [];\n\n return [\n err(\n \"runtimes\",\n `${manifest.kind} implements ${provided.join(\", \") || \"no runtime\"} but this project executes on ${missing.join(\", \")}. The node would install, appear in the palette, and then fail to run.`,\n ),\n ];\n}\n\n/**\n * Check that every capability a node needs is actually wired.\n *\n * Pass `capabilityStatus()` from the host. Unwired capabilities are a warning\n * rather than an error — install is the right time to learn what to wire, not\n * a reason to refuse, since wiring usually happens after install.\n */\nexport function checkCapabilities(\n manifest: Pick<NodePackageManifest, \"kind\" | \"capabilities\">,\n available: Readonly<Record<string, boolean>>,\n): ManifestProblem[] {\n const needed = manifest.capabilities ?? [];\n const missing = needed.filter((c) => available[c] !== true);\n\n if (missing.length === 0) return [];\n\n return [\n warn(\n \"capabilities\",\n `${manifest.kind} needs ${missing.join(\", \")} wired on the host. Until then the node will fail at run time rather than at install.`,\n ),\n ];\n}\n","/**\n * Golden fixtures — the parity guarantee, and a publishing requirement.\n *\n * Every runtime a node package claims runs these same JSON cases. That is what\n * makes \"this node behaves identically on TS and PHP\" verified rather than\n * asserted, and it is the one thing a loose collection of repos can never\n * offer.\n *\n * WHY IT IS REQUIRED RATHER THAN ENCOURAGED: cross-runtime drift does not fail\n * loudly. The 0.9.0 port-resolution divergence produced flows that routed\n * correctly in the editor and silently down one path on the server — status\n * `completed`, no error, no exception, nothing to alert on. A guarantee that\n * only holds when an author opts in is not a guarantee.\n *\n * WHAT A CASE ASSERTS, and why it matters more than it looks:\n *\n * A fixture asserts THE DOWNSTREAM NODE EXECUTED — not the port the node\n * recorded. The distinction is the whole lesson of that incident. A test\n * reading back `outputs.router.__port` stays green while no edge fires and\n * the run halts at the branch: the node faithfully recorded its choice, and\n * nothing downstream ever ran. So the runner wires a real probe to every\n * declared port and reports which probes actually executed.\n *\n * The format is plain JSON with no expressions or callbacks, so the PHP runner\n * executes byte-identical cases without an embedded JS engine.\n */\n\nimport { runFlow } from \"../runtime/run-flow\";\nimport { getNodeKind } from \"../registry/registry\";\nimport { resolveNodePorts } from \"../registry/ports\";\nimport { decodePause, type PauseAwaiting } from \"../registry/pause\";\nimport type { FlowGraph, FlowNode, NodeExecutor } from \"../types\";\n\n/** What a case expects to have happened. */\nexport type FixtureExpectation = {\n /**\n * Output ports whose downstream node must have executed — the assertion that\n * catches routing drift. Order-insensitive; the set must match exactly, so a\n * node that fires an extra port fails just as loudly as one that fires none.\n */\n ports?: string[];\n /** The value carried downstream, deep-compared when present. */\n value?: unknown;\n /** The run halted for a person. */\n pause?: { awaiting: PauseAwaiting; detail?: unknown };\n /** The run failed, and the message contains this substring. */\n error?: string;\n};\n\nexport type FixtureCase = {\n /** Human-readable, and what a failure report names. */\n name: string;\n /** Config for the node under test — drives config-derived ports. */\n config?: Record<string, unknown>;\n /** Inputs delivered on the node's input ports. */\n inputs?: Record<string, unknown>;\n expect: FixtureExpectation;\n};\n\nexport type FixtureFile = {\n /** The kind under test. Must match the manifest's `kind`. */\n kind: string;\n cases: FixtureCase[];\n};\n\nexport type FixtureFailure = {\n case: string;\n message: string;\n};\n\nexport type FixtureRunResult = {\n ok: boolean;\n passed: number;\n failures: FixtureFailure[];\n};\n\nconst SUBJECT = \"subject\";\nconst TRIGGER = \"trigger\";\nconst probeId = (port: string) => `probe:${port}`;\n\n/**\n * Build the graph a case runs in: a trigger, the node under test, and one\n * probe per declared output port.\n *\n * The probes are the point. Reading the subject's return value would tell us\n * what it *recorded*; only a probe tells us what actually reached a downstream\n * node through a real edge.\n */\nfunction buildGraph(kindId: string, testCase: FixtureCase): { graph: FlowGraph; ports: string[] } {\n const subject: FlowNode = {\n id: SUBJECT,\n type: kindId,\n position: { x: 0, y: 100 },\n data: { kind: kindId, config: testCase.config ?? {} },\n } as unknown as FlowNode;\n\n const kind = getNodeKind(kindId) ?? undefined;\n const ports = (resolveNodePorts(subject, kind).outputs ?? []).map((p) => p.id);\n const effective = ports.length ? ports : [\"out\"];\n\n const nodes: FlowNode[] = [\n { id: TRIGGER, type: \"manual_trigger\", position: { x: 0, y: 0 }, data: {} } as unknown as FlowNode,\n subject,\n ...effective.map(\n (port, i) =>\n ({\n id: probeId(port),\n type: \"@particle-academy/transform\",\n position: { x: i * 200, y: 200 },\n data: {},\n }) as unknown as FlowNode,\n ),\n ];\n\n const edges = [\n { id: `e:trigger`, source: TRIGGER, target: SUBJECT },\n ...effective.map((port) => ({\n id: `e:${port}`,\n source: SUBJECT,\n sourceHandle: port,\n target: probeId(port),\n })),\n ];\n\n return { graph: { nodes, edges } as FlowGraph, ports: effective };\n}\n\nfunction deepEqual(a: unknown, b: unknown): boolean {\n return JSON.stringify(a) === JSON.stringify(b);\n}\n\n/**\n * Run one fixture file against a kind's executor.\n *\n * `executor` is the node's own; everything else in the graph is supplied here,\n * so a case exercises the node and nothing but the node.\n */\nexport async function runFixtures(\n file: FixtureFile,\n executor: NodeExecutor,\n): Promise<FixtureRunResult> {\n const failures: FixtureFailure[] = [];\n let passed = 0;\n\n for (const testCase of file.cases) {\n const { graph } = buildGraph(file.kind, testCase);\n const fired: string[] = [];\n let carried: unknown;\n\n const executors: Record<string, NodeExecutor> = {\n manual_trigger: () => testCase.inputs ?? {},\n \"@particle-academy/manual_trigger\": () => testCase.inputs ?? {},\n [file.kind]: executor,\n };\n\n // A probe per port, bound by NODE ID — `pickExecutor` checks that before\n // kind, so each probe reports which specific port reached it. Recording\n // here, rather than inspecting the subject's result, is what makes this\n // assert reachability instead of intent.\n for (const node of graph.nodes) {\n if (!node.id.startsWith(\"probe:\")) continue;\n const port = node.id.slice(\"probe:\".length);\n executors[node.id] = ((ctx: { inputs: Record<string, unknown> }) => {\n fired.push(port);\n carried = ctx.inputs?.in ?? ctx.inputs;\n return undefined;\n }) as unknown as NodeExecutor;\n }\n\n const result = await runFlow(graph, executors, () => {});\n const fail = (message: string) => failures.push({ case: testCase.name, message });\n const expected = testCase.expect;\n let caseOk = true;\n\n if (expected.pause) {\n const paused = decodePause(result.error);\n if (!paused) {\n caseOk = false;\n fail(`expected a pause awaiting \"${expected.pause.awaiting}\", got ${result.ok ? \"a completed run\" : `error: ${result.error}`}`);\n } else {\n if (paused.awaiting !== expected.pause.awaiting) {\n caseOk = false;\n fail(`expected pause awaiting \"${expected.pause.awaiting}\", got \"${paused.awaiting}\"`);\n }\n if (\"detail\" in expected.pause && !deepEqual(paused.detail, expected.pause.detail)) {\n caseOk = false;\n fail(`pause detail mismatch: expected ${JSON.stringify(expected.pause.detail)}, got ${JSON.stringify(paused.detail)}`);\n }\n }\n } else if (expected.error !== undefined) {\n if (result.ok) {\n caseOk = false;\n fail(`expected an error containing \"${expected.error}\", but the run succeeded`);\n } else if (!String(result.error ?? \"\").includes(expected.error)) {\n caseOk = false;\n fail(`expected an error containing \"${expected.error}\", got \"${result.error}\"`);\n }\n } else {\n if (expected.ports !== undefined) {\n const got = [...fired].sort();\n const want = [...expected.ports].sort();\n if (!deepEqual(got, want)) {\n caseOk = false;\n // Name the failure in terms of reachability, because that is what\n // the assertion means and what a reader needs to act on.\n fail(\n `expected these ports to reach a downstream node: [${want.join(\", \")}], but [${got.join(\", \")}] did` +\n (result.ok ? \"\" : ` (run error: ${result.error})`),\n );\n }\n }\n if (\"value\" in expected && !deepEqual(carried, expected.value)) {\n caseOk = false;\n fail(`expected the value carried downstream to be ${JSON.stringify(expected.value)}, got ${JSON.stringify(carried)}`);\n }\n }\n\n if (caseOk) passed += 1;\n }\n\n return { ok: failures.length === 0, passed, failures };\n}\n\n/**\n * Validate a fixture file's shape before running it.\n *\n * A package that publishes an empty or malformed fixture file has satisfied the\n * letter of the requirement and none of its purpose, so this is checked at\n * publish rather than trusted.\n */\nexport function validateFixtureFile(input: unknown, expectedKind?: string): string[] {\n const problems: string[] = [];\n\n if (typeof input !== \"object\" || input === null || Array.isArray(input)) {\n return [\"Fixture file must be a JSON object.\"];\n }\n\n const f = input as Record<string, unknown>;\n\n if (typeof f.kind !== \"string\" || f.kind.trim() === \"\") {\n problems.push(\"`kind` is required — the kind these cases exercise.\");\n } else if (expectedKind && f.kind !== expectedKind) {\n problems.push(`\\`kind\\` is \"${f.kind}\" but the manifest declares \"${expectedKind}\".`);\n }\n\n if (!Array.isArray(f.cases) || f.cases.length === 0) {\n problems.push(\"`cases` must contain at least one case — an empty fixture file proves nothing.\");\n return problems;\n }\n\n f.cases.forEach((c: unknown, i: number) => {\n const label = `cases[${i}]`;\n if (typeof c !== \"object\" || c === null) {\n problems.push(`${label} must be an object.`);\n return;\n }\n const testCase = c as Record<string, unknown>;\n if (typeof testCase.name !== \"string\" || testCase.name.trim() === \"\") {\n problems.push(`${label}.name is required — a failure report names it.`);\n }\n if (typeof testCase.expect !== \"object\" || testCase.expect === null) {\n problems.push(`${label}.expect is required — a case that asserts nothing passes vacuously.`);\n return;\n }\n const expect = testCase.expect as Record<string, unknown>;\n if (\n expect.ports === undefined &&\n expect.value === undefined &&\n expect.pause === undefined &&\n expect.error === undefined\n ) {\n problems.push(`${label}.expect must assert at least one of: ports, value, pause, error.`);\n }\n if (expect.ports !== undefined && (!Array.isArray(expect.ports) || expect.ports.some((p) => typeof p !== \"string\"))) {\n problems.push(`${label}.expect.ports must be an array of port id strings.`);\n }\n });\n\n return problems;\n}\n"]}