callman-core 1.15.0 → 1.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.
Files changed (79) hide show
  1. package/dist/host/connections.d.ts +3 -0
  2. package/dist/host/connections.d.ts.map +1 -1
  3. package/dist/host/connections.js +3 -0
  4. package/dist/host/connections.js.map +1 -1
  5. package/dist/host/db/connectionMapping.d.ts +3 -1
  6. package/dist/host/db/connectionMapping.d.ts.map +1 -1
  7. package/dist/host/db/connectionMapping.js +3 -0
  8. package/dist/host/db/connectionMapping.js.map +1 -1
  9. package/dist/host/db/db-query-reference.d.ts.map +1 -1
  10. package/dist/host/db/db-query-reference.js +12 -1
  11. package/dist/host/db/db-query-reference.js.map +1 -1
  12. package/dist/host/db/mssqlAdapter.d.ts +8 -0
  13. package/dist/host/db/mssqlAdapter.d.ts.map +1 -0
  14. package/dist/host/db/mssqlAdapter.js +118 -0
  15. package/dist/host/db/mssqlAdapter.js.map +1 -0
  16. package/dist/host/db/queryExecutor.d.ts.map +1 -1
  17. package/dist/host/db/queryExecutor.js +8 -0
  18. package/dist/host/db/queryExecutor.js.map +1 -1
  19. package/dist/host/db/sqlNormalize.d.ts +1 -1
  20. package/dist/host/db/sqlNormalize.d.ts.map +1 -1
  21. package/dist/host/db/types.d.ts +5 -1
  22. package/dist/host/db/types.d.ts.map +1 -1
  23. package/dist/host/index.d.ts +2 -1
  24. package/dist/host/index.d.ts.map +1 -1
  25. package/dist/host/index.js +2 -1
  26. package/dist/host/index.js.map +1 -1
  27. package/dist/host/redis/redisService.d.ts +17 -0
  28. package/dist/host/redis/redisService.d.ts.map +1 -1
  29. package/dist/host/redis/redisService.js +233 -40
  30. package/dist/host/redis/redisService.js.map +1 -1
  31. package/dist/host/redis/types.d.ts +4 -0
  32. package/dist/host/redis/types.d.ts.map +1 -1
  33. package/dist/scenario-runner/adapters/helpers/request-helpers.d.ts.map +1 -1
  34. package/dist/scenario-runner/adapters/helpers/request-helpers.js +28 -2
  35. package/dist/scenario-runner/adapters/helpers/request-helpers.js.map +1 -1
  36. package/dist/scenario-runner/condition.d.ts +4 -0
  37. package/dist/scenario-runner/condition.d.ts.map +1 -1
  38. package/dist/scenario-runner/condition.js +66 -7
  39. package/dist/scenario-runner/condition.js.map +1 -1
  40. package/dist/scenario-runner/conditionTypes.d.ts +14 -0
  41. package/dist/scenario-runner/conditionTypes.d.ts.map +1 -1
  42. package/dist/scenario-runner/extraction.d.ts +10 -0
  43. package/dist/scenario-runner/extraction.d.ts.map +1 -0
  44. package/dist/scenario-runner/extraction.js +147 -0
  45. package/dist/scenario-runner/extraction.js.map +1 -0
  46. package/dist/scenario-runner/index.d.ts +1 -0
  47. package/dist/scenario-runner/index.d.ts.map +1 -1
  48. package/dist/scenario-runner/index.js +2 -0
  49. package/dist/scenario-runner/index.js.map +1 -1
  50. package/dist/scenario-runner/runScenario.d.ts.map +1 -1
  51. package/dist/scenario-runner/runScenario.js +113 -6
  52. package/dist/scenario-runner/runScenario.js.map +1 -1
  53. package/dist/scenario-runner/types.d.ts +23 -1
  54. package/dist/scenario-runner/types.d.ts.map +1 -1
  55. package/dist-cjs/host/connections.js +3 -0
  56. package/dist-cjs/host/connections.js.map +1 -1
  57. package/dist-cjs/host/db/connectionMapping.js +3 -0
  58. package/dist-cjs/host/db/connectionMapping.js.map +1 -1
  59. package/dist-cjs/host/db/db-query-reference.js +12 -1
  60. package/dist-cjs/host/db/db-query-reference.js.map +1 -1
  61. package/dist-cjs/host/db/mssqlAdapter.js +124 -0
  62. package/dist-cjs/host/db/mssqlAdapter.js.map +1 -0
  63. package/dist-cjs/host/db/queryExecutor.js +8 -0
  64. package/dist-cjs/host/db/queryExecutor.js.map +1 -1
  65. package/dist-cjs/host/index.js +8 -1
  66. package/dist-cjs/host/index.js.map +1 -1
  67. package/dist-cjs/host/redis/redisService.js +237 -42
  68. package/dist-cjs/host/redis/redisService.js.map +1 -1
  69. package/dist-cjs/scenario-runner/adapters/helpers/request-helpers.js +28 -2
  70. package/dist-cjs/scenario-runner/adapters/helpers/request-helpers.js.map +1 -1
  71. package/dist-cjs/scenario-runner/condition.js +68 -8
  72. package/dist-cjs/scenario-runner/condition.js.map +1 -1
  73. package/dist-cjs/scenario-runner/extraction.js +151 -0
  74. package/dist-cjs/scenario-runner/extraction.js.map +1 -0
  75. package/dist-cjs/scenario-runner/index.js +2 -0
  76. package/dist-cjs/scenario-runner/index.js.map +1 -1
  77. package/dist-cjs/scenario-runner/runScenario.js +112 -5
  78. package/dist-cjs/scenario-runner/runScenario.js.map +1 -1
  79. package/package.json +3 -1
@@ -0,0 +1,151 @@
1
+ "use strict";
2
+ // Value extraction for condition operands and set-vars assignments:
3
+ // JSONPath (jsonpath-plus, ALWAYS eval:false — path expressions must never
4
+ // execute code) and regex capture (plain RegExp with hard caps — re2 is a
5
+ // native module and would poison the browser/Electron bundle; the scenario
6
+ // author already runs arbitrary JS in script nodes, so a pathological
7
+ // pattern is not a privilege escalation, only an accident the caps bound).
8
+ //
9
+ // `applyExtract` NEVER throws: failures come back as `{ value: undefined,
10
+ // error }` so callers surface them in reports/debug views instead of
11
+ // failing the whole node on a bad expression.
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.applyExtract = exports.EXTRACT_MATCHES_MAX = exports.EXTRACT_REGEX_INPUT_MAX = exports.EXTRACT_REGEX_PATTERN_MAX = void 0;
14
+ const jsonpath_plus_1 = require("jsonpath-plus");
15
+ exports.EXTRACT_REGEX_PATTERN_MAX = 500;
16
+ exports.EXTRACT_REGEX_INPUT_MAX = 1_000_000;
17
+ exports.EXTRACT_MATCHES_MAX = 1000;
18
+ const failure = (error) => ({ value: undefined, error });
19
+ const toStructuredSource = (source) => {
20
+ if (typeof source === "string") {
21
+ const trimmed = source.trim();
22
+ if (!trimmed) {
23
+ return { error: "JSONPath source is empty" };
24
+ }
25
+ try {
26
+ return { json: JSON.parse(trimmed) };
27
+ }
28
+ catch {
29
+ return { error: "JSONPath source is a string that is not valid JSON" };
30
+ }
31
+ }
32
+ if (source === null || typeof source === "undefined") {
33
+ return { error: "JSONPath source is empty" };
34
+ }
35
+ if (typeof source !== "object") {
36
+ return { error: `JSONPath source must be an object or array, got ${typeof source}` };
37
+ }
38
+ return { json: source };
39
+ };
40
+ const applyJsonPath = (source, extract) => {
41
+ const structured = toStructuredSource(source);
42
+ if ("error" in structured) {
43
+ return failure(structured.error);
44
+ }
45
+ let results;
46
+ try {
47
+ results = (0, jsonpath_plus_1.JSONPath)({
48
+ path: extract.expression,
49
+ json: structured.json,
50
+ eval: false,
51
+ wrap: true,
52
+ });
53
+ }
54
+ catch (error) {
55
+ return failure(`Invalid JSONPath expression: ${error instanceof Error ? error.message : String(error)}`);
56
+ }
57
+ if ((extract.select ?? "first") === "all") {
58
+ return { value: results.slice(0, exports.EXTRACT_MATCHES_MAX) };
59
+ }
60
+ return { value: results.length > 0 ? results[0] : undefined };
61
+ };
62
+ const toRegexInput = (source) => {
63
+ if (typeof source === "string") {
64
+ return source;
65
+ }
66
+ if (source === null || typeof source === "undefined") {
67
+ return "";
68
+ }
69
+ if (typeof source === "number" || typeof source === "boolean") {
70
+ return String(source);
71
+ }
72
+ try {
73
+ return JSON.stringify(source);
74
+ }
75
+ catch {
76
+ return String(source);
77
+ }
78
+ };
79
+ const ALLOWED_REGEX_FLAGS = /^[gimsuy]*$/;
80
+ // Group selection: an explicit `group` is strict; without one, the first
81
+ // capture group wins when the pattern has any, else the whole match.
82
+ const pickGroup = (match, extract) => {
83
+ if (typeof extract.group === "number") {
84
+ if (extract.group < 0 || extract.group >= match.length) {
85
+ return failure(`Capture group ${extract.group} is not present in the match`);
86
+ }
87
+ return { value: match[extract.group] };
88
+ }
89
+ return { value: match.length > 1 ? match[1] : match[0] };
90
+ };
91
+ const applyRegex = (source, extract) => {
92
+ if (extract.expression.length > exports.EXTRACT_REGEX_PATTERN_MAX) {
93
+ return failure(`Regex pattern exceeds ${exports.EXTRACT_REGEX_PATTERN_MAX} characters`);
94
+ }
95
+ const input = toRegexInput(source);
96
+ if (input.length > exports.EXTRACT_REGEX_INPUT_MAX) {
97
+ return failure(`Regex input exceeds ${exports.EXTRACT_REGEX_INPUT_MAX} characters`);
98
+ }
99
+ const rawFlags = extract.flags ?? "";
100
+ if (!ALLOWED_REGEX_FLAGS.test(rawFlags)) {
101
+ return failure(`Invalid regex flags "${rawFlags}" — allowed: g i m s u y`);
102
+ }
103
+ if ((extract.select ?? "first") === "all") {
104
+ let regex;
105
+ try {
106
+ regex = new RegExp(extract.expression, rawFlags.includes("g") ? rawFlags : `${rawFlags}g`);
107
+ }
108
+ catch (error) {
109
+ return failure(`Invalid regex: ${error instanceof Error ? error.message : String(error)}`);
110
+ }
111
+ const values = [];
112
+ for (const match of input.matchAll(regex)) {
113
+ const picked = pickGroup(match, extract);
114
+ if (picked.error) {
115
+ return picked;
116
+ }
117
+ values.push(picked.value);
118
+ if (values.length >= exports.EXTRACT_MATCHES_MAX) {
119
+ break;
120
+ }
121
+ }
122
+ return { value: values };
123
+ }
124
+ let regex;
125
+ try {
126
+ regex = new RegExp(extract.expression, rawFlags.replace("g", ""));
127
+ }
128
+ catch (error) {
129
+ return failure(`Invalid regex: ${error instanceof Error ? error.message : String(error)}`);
130
+ }
131
+ const match = regex.exec(input);
132
+ if (!match) {
133
+ return { value: undefined };
134
+ }
135
+ return pickGroup(match, extract);
136
+ };
137
+ const applyExtract = (source, extract) => {
138
+ if (!extract.expression || !extract.expression.trim()) {
139
+ return failure("Extraction expression is empty");
140
+ }
141
+ try {
142
+ return extract.type === "jsonpath"
143
+ ? applyJsonPath(source, extract)
144
+ : applyRegex(source, extract);
145
+ }
146
+ catch (error) {
147
+ return failure(error instanceof Error ? error.message : String(error));
148
+ }
149
+ };
150
+ exports.applyExtract = applyExtract;
151
+ //# sourceMappingURL=extraction.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extraction.js","sourceRoot":"","sources":["../../src/scenario-runner/extraction.ts"],"names":[],"mappings":"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"}
@@ -33,6 +33,8 @@ Object.defineProperty(exports, "resolveScenarioTemplateString", { enumerable: tr
33
33
  // frontend Scenario Builder can drive their UI from the same source of
34
34
  // truth as the runtime executor.
35
35
  __exportStar(require("./condition.js"), exports);
36
+ // JSONPath/regex extraction shared by condition operands and set-vars rows
37
+ __exportStar(require("./extraction.js"), exports);
36
38
  // Assertion node — a no-code checkpoint built on the condition engine
37
39
  // (createDefaultAssertionConfig / normalize / validate / evaluate).
38
40
  __exportStar(require("./assertion.js"), exports);
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/scenario-runner/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,mDAA+C;AAAtC,6GAAA,WAAW,OAAA;AACpB,yEAAqE;AAA5D,mIAAA,sBAAsB,OAAA;AAC/B,6DAQ+B;AAP7B,kIAAA,2BAA2B,OAAA;AAC3B,kIAAA,2BAA2B,OAAA;AAC3B,oIAAA,6BAA6B,OAAA;AAC7B,iIAAA,0BAA0B,OAAA;AAC1B,qJAAA,8CAA8C,OAAA;AAC9C,wIAAA,iCAAiC,OAAA;AACjC,oIAAA,6BAA6B,OAAA;AAE/B,iEAAiE;AACjE,qEAAqE;AACrE,uEAAuE;AACvE,uEAAuE;AACvE,iCAAiC;AACjC,iDAA+B;AAC/B,sEAAsE;AACtE,oEAAoE;AACpE,iDAA+B;AAC/B,oDAGiC;AAF/B,4HAAA,gCAAgC,OAAA;AAChC,sHAAA,0BAA0B,OAAA;AAE5B,wDAAsC;AACtC,sDAAoC;AACpC,sDAAoC;AACpC,6CAA2B;AAC3B,sDAAoC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/scenario-runner/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,mDAA+C;AAAtC,6GAAA,WAAW,OAAA;AACpB,yEAAqE;AAA5D,mIAAA,sBAAsB,OAAA;AAC/B,6DAQ+B;AAP7B,kIAAA,2BAA2B,OAAA;AAC3B,kIAAA,2BAA2B,OAAA;AAC3B,oIAAA,6BAA6B,OAAA;AAC7B,iIAAA,0BAA0B,OAAA;AAC1B,qJAAA,8CAA8C,OAAA;AAC9C,wIAAA,iCAAiC,OAAA;AACjC,oIAAA,6BAA6B,OAAA;AAE/B,iEAAiE;AACjE,qEAAqE;AACrE,uEAAuE;AACvE,uEAAuE;AACvE,iCAAiC;AACjC,iDAA+B;AAC/B,2EAA2E;AAC3E,kDAAgC;AAChC,sEAAsE;AACtE,oEAAoE;AACpE,iDAA+B;AAC/B,oDAGiC;AAF/B,4HAAA,gCAAgC,OAAA;AAChC,sHAAA,0BAA0B,OAAA;AAE5B,wDAAsC;AACtC,sDAAoC;AACpC,sDAAoC;AACpC,6CAA2B;AAC3B,sDAAoC"}
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.runScenario = void 0;
4
4
  const condition_js_1 = require("./condition.js");
5
+ const extraction_js_1 = require("./extraction.js");
5
6
  const assertion_js_1 = require("./assertion.js");
6
7
  const graphTraversal_js_1 = require("./graphTraversal.js");
7
8
  const executionPolicyRunner_js_1 = require("./executionPolicyRunner.js");
@@ -446,6 +447,9 @@ const updateRecordFromEvent = (records, event) => {
446
447
  retryDelayMs: event.retryDelayMs ?? current.retryDelayMs,
447
448
  failurePolicy: event.failurePolicy ?? current.failurePolicy,
448
449
  continuedAfterFailure: event.continuedAfterFailure ?? current.continuedAfterFailure,
450
+ errorHandled: event.type === "step:fail"
451
+ ? (event.errorHandled ?? current.errorHandled)
452
+ : current.errorHandled,
449
453
  errors: event.errors ?? current.errors,
450
454
  durationMs: event.durationMs ?? current.durationMs,
451
455
  statusCode: event.statusCode ?? current.statusCode,
@@ -715,6 +719,38 @@ const runScenario = async ({ scenario, environment = {}, globals = {}, runtimeAd
715
719
  const markRemainingNodesSkipped = async (reason) => {
716
720
  await markNodeIdsSkipped(rootScope, rootScope.graph.nodeMap.keys(), reason);
717
721
  };
722
+ // After a failure is routed into error edges, the node's NORMAL outgoing
723
+ // subtree must not linger idle (an idle source blocks downstream joins in
724
+ // isNodeReady). Skip everything reachable from the normal edges that is
725
+ // NOT also reachable from the taken error edges — shared downstream joins
726
+ // stay runnable.
727
+ const skipUntakenNormalBranches = async (scope, nodeId, errorEdges) => {
728
+ const outgoing = scope.graph.outgoingMap.get(nodeId) ?? [];
729
+ const normalTargets = outgoing
730
+ .filter((edge) => (edge.type ?? "default") !== "error")
731
+ .map((edge) => edge.target);
732
+ if (normalTargets.length === 0) {
733
+ return;
734
+ }
735
+ const collectReachable = (startIds) => {
736
+ const stack = [...startIds];
737
+ const seen = new Set();
738
+ while (stack.length > 0) {
739
+ const current = stack.pop();
740
+ if (!current || seen.has(current)) {
741
+ continue;
742
+ }
743
+ seen.add(current);
744
+ for (const edge of scope.graph.outgoingMap.get(current) ?? []) {
745
+ stack.push(edge.target);
746
+ }
747
+ }
748
+ return seen;
749
+ };
750
+ const reachableFromError = collectReachable(errorEdges.map((edge) => edge.target));
751
+ const untaken = Array.from(collectReachable(normalTargets)).filter((candidate) => !reachableFromError.has(candidate));
752
+ await markNodeIdsSkipped(scope, untaken, "Error branch taken");
753
+ };
718
754
  const markScopeRemainingNodesSkipped = async (scope, reason) => {
719
755
  await markNodeIdsSkipped(scope, scope.graph.nodeMap.keys(), reason);
720
756
  };
@@ -763,6 +799,10 @@ const runScenario = async ({ scenario, environment = {}, globals = {}, runtimeAd
763
799
  if (!node) {
764
800
  return;
765
801
  }
802
+ // Synchronous claim: nothing may await between the idle/ready checks
803
+ // above and this line, or two branches converging on a join could both
804
+ // execute it (each inbound edge calls executeNode once).
805
+ localStatuses.set(stepId, "running");
766
806
  const title = buildTitle(node);
767
807
  const nodeLabel = node.data.label.trim();
768
808
  const executionPolicy = (0, nodePolicy_js_1.supportsScenarioNodeExecutionPolicy)(node)
@@ -1270,6 +1310,7 @@ const runScenario = async ({ scenario, environment = {}, globals = {}, runtimeAd
1270
1310
  // may reference a variable set by an earlier one.
1271
1311
  const writtenEnvironment = {};
1272
1312
  const writtenGlobals = {};
1313
+ const extractionOutcomes = [];
1273
1314
  for (const assignment of node.data.config.assignments ?? []) {
1274
1315
  if (!assignment || assignment.enabled === false) {
1275
1316
  continue;
@@ -1288,7 +1329,49 @@ const runScenario = async ({ scenario, environment = {}, globals = {}, runtimeAd
1288
1329
  if (!resolvedKey) {
1289
1330
  continue;
1290
1331
  }
1291
- const resolvedValue = (0, templateResolver_js_1.resolveScenarioTemplateString)(assignment.value ?? "", rowTemplateValues);
1332
+ let resolvedValue;
1333
+ const extractorType = assignment.extractorType ?? "template";
1334
+ if (extractorType === "jsonpath" || extractorType === "regex") {
1335
+ // Extractor rows resolve `source` with condition-operand path
1336
+ // semantics, then apply the expression. A failed extraction
1337
+ // SKIPS the write (never persists a wrong/empty value
1338
+ // silently) and surfaces the error in outputData.
1339
+ const sourcePath = (assignment.source ?? "").trim();
1340
+ const resolvedSource = (0, condition_js_1.resolveConditionRuntimeValue)({
1341
+ environment: runtimeContext.environment,
1342
+ globals: runtimeContext.globals,
1343
+ workflowContext: runtimeContext.workflowContext,
1344
+ nodeMetaByLabel: runtimeContext.nodeMetaByLabel,
1345
+ responseRoot: runtimeContext.responseRoot,
1346
+ dbResult: runtimeContext.dbResult,
1347
+ kafkaEvent: runtimeContext.kafkaEvent,
1348
+ redisResult: runtimeContext.redisResult,
1349
+ }, sourcePath);
1350
+ const outcome = (0, extraction_js_1.applyExtract)(resolvedSource.value, {
1351
+ type: extractorType,
1352
+ expression: assignment.expression ?? "",
1353
+ flags: assignment.flags,
1354
+ group: assignment.group,
1355
+ select: assignment.select,
1356
+ });
1357
+ if (outcome.error || typeof outcome.value === "undefined") {
1358
+ extractionOutcomes.push({
1359
+ key: resolvedKey,
1360
+ extractorType,
1361
+ source: sourcePath,
1362
+ error: outcome.error ?? "Extraction produced no value",
1363
+ });
1364
+ continue;
1365
+ }
1366
+ extractionOutcomes.push({ key: resolvedKey, extractorType, source: sourcePath });
1367
+ resolvedValue =
1368
+ typeof outcome.value === "string"
1369
+ ? outcome.value
1370
+ : JSON.stringify(outcome.value);
1371
+ }
1372
+ else {
1373
+ resolvedValue = (0, templateResolver_js_1.resolveScenarioTemplateString)(assignment.value ?? "", rowTemplateValues);
1374
+ }
1292
1375
  applyVariableMutationsToContext(runtimeContext, [
1293
1376
  {
1294
1377
  scope: assignment.scope === "global" ? "global" : "environment",
@@ -1307,6 +1390,8 @@ const runScenario = async ({ scenario, environment = {}, globals = {}, runtimeAd
1307
1390
  outputData = {
1308
1391
  environment: writtenEnvironment,
1309
1392
  globals: writtenGlobals,
1393
+ // Additive: present only when extractor rows ran.
1394
+ ...(extractionOutcomes.length > 0 ? { extraction: extractionOutcomes } : {}),
1310
1395
  };
1311
1396
  }
1312
1397
  if (node.type === "aggregator") {
@@ -1379,7 +1464,14 @@ const runScenario = async ({ scenario, environment = {}, globals = {}, runtimeAd
1379
1464
  },
1380
1465
  });
1381
1466
  const durationMs = Math.round(Date.now() - executionStartedAtMs);
1382
- if (policyResult.outcome === "failed") {
1467
+ // "error" edges (no-code try/catch): a failure with a drawn handler is
1468
+ // routed into the error branch instead of stopping or continuing on
1469
+ // the normal path — the user's explicit handler overrides onFailure.
1470
+ const errorEdges = policyResult.outcome === "failed" || policyResult.outcome === "continued"
1471
+ ? (scope.graph.outgoingMap.get(nodeId) ?? []).filter((edge) => (edge.type ?? "default") === "error")
1472
+ : [];
1473
+ const failureRouted = errorEdges.length > 0;
1474
+ if (policyResult.outcome === "failed" && !failureRouted) {
1383
1475
  localStatuses.set(stepId, "failed");
1384
1476
  const failedAt = new Date().toISOString();
1385
1477
  const nodeMeta = buildNodeExecutionMeta({
@@ -1452,7 +1544,9 @@ const runScenario = async ({ scenario, environment = {}, globals = {}, runtimeAd
1452
1544
  });
1453
1545
  throw new ScenarioNodeFailureError(policyResult.finalErrorMessage ?? "Scenario node failed");
1454
1546
  }
1455
- if (policyResult.outcome === "continued") {
1547
+ if (policyResult.outcome === "continued" || policyResult.outcome === "failed") {
1548
+ // Either policy-continue, or a failure routed into error edges —
1549
+ // both leave the node terminal as failed_continued so joins unblock.
1456
1550
  degradedStepIds.add(stepId);
1457
1551
  localStatuses.set(stepId, "failed_continued");
1458
1552
  const continuedAt = new Date().toISOString();
@@ -1498,7 +1592,9 @@ const runScenario = async ({ scenario, environment = {}, globals = {}, runtimeAd
1498
1592
  retryEnabled: executionPolicy.retry.enabled,
1499
1593
  retryDelayMs: executionPolicy.retry.delayMs,
1500
1594
  failurePolicy: executionPolicy.onFailure,
1501
- continuedAfterFailure: true,
1595
+ continuedAfterFailure: policyResult.outcome === "continued",
1596
+ errorHandled: failureRouted ? true : undefined,
1597
+ errorEdgeIds: failureRouted ? errorEdges.map((edge) => edge.id) : undefined,
1502
1598
  errors: policyResult.errors,
1503
1599
  nodeMeta,
1504
1600
  errorMessage: policyResult.finalErrorMessage ??
@@ -1670,10 +1766,21 @@ const runScenario = async ({ scenario, environment = {}, globals = {}, runtimeAd
1670
1766
  syncLoopRoots();
1671
1767
  }
1672
1768
  }
1769
+ if (failureRouted) {
1770
+ await skipUntakenNormalBranches(scope, nodeId, errorEdges);
1771
+ for (const edge of errorEdges) {
1772
+ throwIfStopped(signal);
1773
+ await executeNode(edge.target, scope.stepIdForEdge(edge.id), {}, scope);
1774
+ }
1775
+ return;
1776
+ }
1673
1777
  const outgoingEdges = scope.graph.outgoingMap.get(nodeId) ?? [];
1674
1778
  const nextEdges = node.type === "condition"
1675
1779
  ? outgoingEdges.filter((edge) => (conditionResult ? "yes" : "no") === (edge.type ?? "default"))
1676
- : outgoingEdges;
1780
+ : // Error edges are handler paths — never taken on success (or on a
1781
+ // policy-continue without routing). No-op for scenarios that have
1782
+ // no error edges.
1783
+ outgoingEdges.filter((edge) => (edge.type ?? "default") !== "error");
1677
1784
  for (const edge of nextEdges) {
1678
1785
  throwIfStopped(signal);
1679
1786
  await executeNode(edge.target, scope.stepIdForEdge(edge.id), {}, scope);