callman-core 1.15.0 → 1.17.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 (134) hide show
  1. package/dist/contract/contractValidator.d.ts.map +1 -1
  2. package/dist/contract/contractValidator.js +19 -2
  3. package/dist/contract/contractValidator.js.map +1 -1
  4. package/dist/contract/xpathResolver.d.ts +7 -0
  5. package/dist/contract/xpathResolver.d.ts.map +1 -0
  6. package/dist/contract/xpathResolver.js +69 -0
  7. package/dist/contract/xpathResolver.js.map +1 -0
  8. package/dist/host/connections.d.ts +3 -0
  9. package/dist/host/connections.d.ts.map +1 -1
  10. package/dist/host/connections.js +3 -0
  11. package/dist/host/connections.js.map +1 -1
  12. package/dist/host/db/connectionMapping.d.ts +3 -1
  13. package/dist/host/db/connectionMapping.d.ts.map +1 -1
  14. package/dist/host/db/connectionMapping.js +3 -0
  15. package/dist/host/db/connectionMapping.js.map +1 -1
  16. package/dist/host/db/db-query-reference.d.ts.map +1 -1
  17. package/dist/host/db/db-query-reference.js +12 -1
  18. package/dist/host/db/db-query-reference.js.map +1 -1
  19. package/dist/host/db/mssqlAdapter.d.ts +8 -0
  20. package/dist/host/db/mssqlAdapter.d.ts.map +1 -0
  21. package/dist/host/db/mssqlAdapter.js +118 -0
  22. package/dist/host/db/mssqlAdapter.js.map +1 -0
  23. package/dist/host/db/queryExecutor.d.ts.map +1 -1
  24. package/dist/host/db/queryExecutor.js +8 -0
  25. package/dist/host/db/queryExecutor.js.map +1 -1
  26. package/dist/host/db/sqlNormalize.d.ts +1 -1
  27. package/dist/host/db/sqlNormalize.d.ts.map +1 -1
  28. package/dist/host/db/types.d.ts +5 -1
  29. package/dist/host/db/types.d.ts.map +1 -1
  30. package/dist/host/index.d.ts +2 -1
  31. package/dist/host/index.d.ts.map +1 -1
  32. package/dist/host/index.js +2 -1
  33. package/dist/host/index.js.map +1 -1
  34. package/dist/host/redis/redisService.d.ts +17 -0
  35. package/dist/host/redis/redisService.d.ts.map +1 -1
  36. package/dist/host/redis/redisService.js +233 -40
  37. package/dist/host/redis/redisService.js.map +1 -1
  38. package/dist/host/redis/types.d.ts +4 -0
  39. package/dist/host/redis/types.d.ts.map +1 -1
  40. package/dist/index.d.ts +4 -0
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +5 -0
  43. package/dist/index.js.map +1 -1
  44. package/dist/scenario-runner/adapters/helpers/request-helpers.d.ts.map +1 -1
  45. package/dist/scenario-runner/adapters/helpers/request-helpers.js +36 -2
  46. package/dist/scenario-runner/adapters/helpers/request-helpers.js.map +1 -1
  47. package/dist/scenario-runner/adapters/request-executor.d.ts.map +1 -1
  48. package/dist/scenario-runner/adapters/request-executor.js +35 -0
  49. package/dist/scenario-runner/adapters/request-executor.js.map +1 -1
  50. package/dist/scenario-runner/adapters/types.d.ts +5 -0
  51. package/dist/scenario-runner/adapters/types.d.ts.map +1 -1
  52. package/dist/scenario-runner/condition.d.ts +4 -0
  53. package/dist/scenario-runner/condition.d.ts.map +1 -1
  54. package/dist/scenario-runner/condition.js +91 -9
  55. package/dist/scenario-runner/condition.js.map +1 -1
  56. package/dist/scenario-runner/conditionTypes.d.ts +14 -0
  57. package/dist/scenario-runner/conditionTypes.d.ts.map +1 -1
  58. package/dist/scenario-runner/extraction.d.ts +10 -0
  59. package/dist/scenario-runner/extraction.d.ts.map +1 -0
  60. package/dist/scenario-runner/extraction.js +147 -0
  61. package/dist/scenario-runner/extraction.js.map +1 -0
  62. package/dist/scenario-runner/graphTraversal.d.ts +3 -0
  63. package/dist/scenario-runner/graphTraversal.d.ts.map +1 -1
  64. package/dist/scenario-runner/graphTraversal.js +4 -0
  65. package/dist/scenario-runner/graphTraversal.js.map +1 -1
  66. package/dist/scenario-runner/index.d.ts +1 -0
  67. package/dist/scenario-runner/index.d.ts.map +1 -1
  68. package/dist/scenario-runner/index.js +2 -0
  69. package/dist/scenario-runner/index.js.map +1 -1
  70. package/dist/scenario-runner/nodePolicy.d.ts.map +1 -1
  71. package/dist/scenario-runner/nodePolicy.js +3 -0
  72. package/dist/scenario-runner/nodePolicy.js.map +1 -1
  73. package/dist/scenario-runner/runScenario.d.ts.map +1 -1
  74. package/dist/scenario-runner/runScenario.js +357 -8
  75. package/dist/scenario-runner/runScenario.js.map +1 -1
  76. package/dist/scenario-runner/types.d.ts +90 -5
  77. package/dist/scenario-runner/types.d.ts.map +1 -1
  78. package/dist/script/types.d.ts +4 -0
  79. package/dist/script/types.d.ts.map +1 -1
  80. package/dist/soap/envelope.d.ts +4 -0
  81. package/dist/soap/envelope.d.ts.map +1 -0
  82. package/dist/soap/envelope.js +25 -0
  83. package/dist/soap/envelope.js.map +1 -0
  84. package/dist/soap/transport.d.ts +22 -0
  85. package/dist/soap/transport.d.ts.map +1 -0
  86. package/dist/soap/transport.js +58 -0
  87. package/dist/soap/transport.js.map +1 -0
  88. package/dist/soap/wsSecurity.d.ts +19 -0
  89. package/dist/soap/wsSecurity.d.ts.map +1 -0
  90. package/dist/soap/wsSecurity.js +66 -0
  91. package/dist/soap/wsSecurity.js.map +1 -0
  92. package/dist-cjs/contract/contractValidator.js +19 -2
  93. package/dist-cjs/contract/contractValidator.js.map +1 -1
  94. package/dist-cjs/contract/xpathResolver.js +76 -0
  95. package/dist-cjs/contract/xpathResolver.js.map +1 -0
  96. package/dist-cjs/host/connections.js +3 -0
  97. package/dist-cjs/host/connections.js.map +1 -1
  98. package/dist-cjs/host/db/connectionMapping.js +3 -0
  99. package/dist-cjs/host/db/connectionMapping.js.map +1 -1
  100. package/dist-cjs/host/db/db-query-reference.js +12 -1
  101. package/dist-cjs/host/db/db-query-reference.js.map +1 -1
  102. package/dist-cjs/host/db/mssqlAdapter.js +124 -0
  103. package/dist-cjs/host/db/mssqlAdapter.js.map +1 -0
  104. package/dist-cjs/host/db/queryExecutor.js +8 -0
  105. package/dist-cjs/host/db/queryExecutor.js.map +1 -1
  106. package/dist-cjs/host/index.js +8 -1
  107. package/dist-cjs/host/index.js.map +1 -1
  108. package/dist-cjs/host/redis/redisService.js +237 -42
  109. package/dist-cjs/host/redis/redisService.js.map +1 -1
  110. package/dist-cjs/index.js +13 -1
  111. package/dist-cjs/index.js.map +1 -1
  112. package/dist-cjs/scenario-runner/adapters/helpers/request-helpers.js +36 -2
  113. package/dist-cjs/scenario-runner/adapters/helpers/request-helpers.js.map +1 -1
  114. package/dist-cjs/scenario-runner/adapters/request-executor.js +35 -0
  115. package/dist-cjs/scenario-runner/adapters/request-executor.js.map +1 -1
  116. package/dist-cjs/scenario-runner/condition.js +93 -10
  117. package/dist-cjs/scenario-runner/condition.js.map +1 -1
  118. package/dist-cjs/scenario-runner/extraction.js +151 -0
  119. package/dist-cjs/scenario-runner/extraction.js.map +1 -0
  120. package/dist-cjs/scenario-runner/graphTraversal.js +5 -1
  121. package/dist-cjs/scenario-runner/graphTraversal.js.map +1 -1
  122. package/dist-cjs/scenario-runner/index.js +2 -0
  123. package/dist-cjs/scenario-runner/index.js.map +1 -1
  124. package/dist-cjs/scenario-runner/nodePolicy.js +3 -0
  125. package/dist-cjs/scenario-runner/nodePolicy.js.map +1 -1
  126. package/dist-cjs/scenario-runner/runScenario.js +355 -6
  127. package/dist-cjs/scenario-runner/runScenario.js.map +1 -1
  128. package/dist-cjs/soap/envelope.js +29 -0
  129. package/dist-cjs/soap/envelope.js.map +1 -0
  130. package/dist-cjs/soap/transport.js +62 -0
  131. package/dist-cjs/soap/transport.js.map +1 -0
  132. package/dist-cjs/soap/wsSecurity.js +74 -0
  133. package/dist-cjs/soap/wsSecurity.js.map +1 -0
  134. package/package.json +6 -2
@@ -1 +1 @@
1
- {"version":3,"file":"nodePolicy.d.ts","sourceRoot":"","sources":["../../src/scenario-runner/nodePolicy.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,EACtB,cAAc,EACd,eAAe,EACf,iBAAiB,EACjB,wBAAwB,EACxB,iBAAiB,EACjB,mBAAmB,EACnB,YAAY,EACZ,2BAA2B,EAC3B,yBAAyB,EACzB,6BAA6B,EAC7B,kBAAkB,EAClB,eAAe,EACf,gBAAgB,EACjB,MAAM,YAAY,CAAC;AAEpB,eAAO,MAAM,qCAAqC,EAAE,2BAOnD,CAAC;AAsDF,eAAO,MAAM,mCAAmC,GAC9C,YAAY,YAAY,GAAG,6BAA6B,KACvD,UAAU,IAAI,yBAAyB,GAAG,6BAI5C,CAAC;AAEF,eAAO,MAAM,oCAAoC,GAC/C,OAAO,OAAO,EACd,WAAU,2BAAmE,KAC5E,2BA+DF,CAAC;AAEF,eAAO,MAAM,oCAAoC,QAAO,2BACJ,CAAC;AAErD,eAAO,MAAM,uCAAuC,GAClD,MAAM,6BAA6B,KAClC,2BAAqF,CAAC;AAEzF,eAAO,MAAM,8BAA8B,GACzC,MAAM,yBAAyB,KAC9B,2BAIA,CAAC;AAEJ,eAAO,MAAM,0BAA0B,GACrC,QAAQ,2BAA2B,KAClC,MAA4E,CAAC;AAEhF,eAAO,MAAM,2BAA2B,GACtC,MAAM,yBAAyB,KAC9B,MAAM,EAeR,CAAC;AAEF,eAAO,MAAM,gCAAgC,GAC3C,KAAK,SACD,mBAAmB,GACnB,cAAc,GACd,gBAAgB,GAChB,kBAAkB,GAClB,iBAAiB,GACjB,iBAAiB,GACjB,wBAAwB,GACxB,eAAe,GACf,sBAAsB,GACtB,eAAe,EAEnB,MAAM,KAAK,EACX,iBAAiB,2BAA2B,KAC3C,KASD,CAAC"}
1
+ {"version":3,"file":"nodePolicy.d.ts","sourceRoot":"","sources":["../../src/scenario-runner/nodePolicy.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,EACtB,cAAc,EACd,eAAe,EACf,iBAAiB,EACjB,wBAAwB,EACxB,iBAAiB,EACjB,mBAAmB,EACnB,YAAY,EACZ,2BAA2B,EAC3B,yBAAyB,EACzB,6BAA6B,EAC7B,kBAAkB,EAClB,eAAe,EACf,gBAAgB,EACjB,MAAM,YAAY,CAAC;AAEpB,eAAO,MAAM,qCAAqC,EAAE,2BAOnD,CAAC;AAyDF,eAAO,MAAM,mCAAmC,GAC9C,YAAY,YAAY,GAAG,6BAA6B,KACvD,UAAU,IAAI,yBAAyB,GAAG,6BAI5C,CAAC;AAEF,eAAO,MAAM,oCAAoC,GAC/C,OAAO,OAAO,EACd,WAAU,2BAAmE,KAC5E,2BA+DF,CAAC;AAEF,eAAO,MAAM,oCAAoC,QAAO,2BACJ,CAAC;AAErD,eAAO,MAAM,uCAAuC,GAClD,MAAM,6BAA6B,KAClC,2BAAqF,CAAC;AAEzF,eAAO,MAAM,8BAA8B,GACzC,MAAM,yBAAyB,KAC9B,2BAIA,CAAC;AAEJ,eAAO,MAAM,0BAA0B,GACrC,QAAQ,2BAA2B,KAClC,MAA4E,CAAC;AAEhF,eAAO,MAAM,2BAA2B,GACtC,MAAM,yBAAyB,KAC9B,MAAM,EAeR,CAAC;AAEF,eAAO,MAAM,gCAAgC,GAC3C,KAAK,SACD,mBAAmB,GACnB,cAAc,GACd,gBAAgB,GAChB,kBAAkB,GAClB,iBAAiB,GACjB,iBAAiB,GACjB,wBAAwB,GACxB,eAAe,GACf,sBAAsB,GACtB,eAAe,EAEnB,MAAM,KAAK,EACX,iBAAiB,2BAA2B,KAC3C,KASD,CAAC"}
@@ -38,6 +38,9 @@ const RECOMMENDED_NEW_NODE_POLICY_PRESETS = {
38
38
  },
39
39
  },
40
40
  "set-vars": STRICT_SCENARIO_NODE_EXECUTION_POLICY,
41
+ // The poll loop IS the retry mechanism for this node type — retry stays
42
+ // off (and is force-disabled at execution time in runScenario).
43
+ poll: STRICT_SCENARIO_NODE_EXECUTION_POLICY,
41
44
  end: STRICT_SCENARIO_NODE_EXECUTION_POLICY,
42
45
  aggregator: STRICT_SCENARIO_NODE_EXECUTION_POLICY,
43
46
  subscenario: STRICT_SCENARIO_NODE_EXECUTION_POLICY,
@@ -1 +1 @@
1
- {"version":3,"file":"nodePolicy.js","sourceRoot":"","sources":["../../src/scenario-runner/nodePolicy.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"nodePolicy.js","sourceRoot":"","sources":["../../src/scenario-runner/nodePolicy.ts"],"names":[],"mappings":"AAiBA,MAAM,CAAC,MAAM,qCAAqC,GAAgC;IAChF,SAAS,EAAE,MAAM;IACjB,KAAK,EAAE;QACL,OAAO,EAAE,KAAK;QACd,WAAW,EAAE,CAAC;QACd,OAAO,EAAE,CAAC;KACX;CACF,CAAC;AAEF,MAAM,mCAAmC,GAGrC;IACF,OAAO,EAAE,qCAAqC;IAC9C,EAAE,EAAE,qCAAqC;IACzC,IAAI,EAAE,qCAAqC;IAC3C,MAAM,EAAE,qCAAqC;IAC7C,sEAAsE;IACtE,SAAS,EAAE,qCAAqC;IAChD,KAAK,EAAE;QACL,SAAS,EAAE,UAAU;QACrB,KAAK,EAAE;YACL,OAAO,EAAE,IAAI;YACb,WAAW,EAAE,CAAC;YACd,OAAO,EAAE,IAAI;SACd;KACF;IACD,KAAK,EAAE;QACL,SAAS,EAAE,UAAU;QACrB,KAAK,EAAE;YACL,OAAO,EAAE,IAAI;YACb,WAAW,EAAE,CAAC;YACd,OAAO,EAAE,IAAI;SACd;KACF;IACD,YAAY,EAAE;QACZ,SAAS,EAAE,UAAU;QACrB,KAAK,EAAE;YACL,OAAO,EAAE,KAAK;YACd,WAAW,EAAE,CAAC;YACd,OAAO,EAAE,CAAC;SACX;KACF;IACD,UAAU,EAAE,qCAAqC;IACjD,wEAAwE;IACxE,gEAAgE;IAChE,IAAI,EAAE,qCAAqC;IAC3C,GAAG,EAAE,qCAAqC;IAC1C,UAAU,EAAE,qCAAqC;IACjD,WAAW,EAAE,qCAAqC;IAClD,cAAc,EAAE,qCAAqC;CACtD,CAAC;AAEF,MAAM,WAAW,GAAG,CAClB,MAAmC,EACN,EAAE,CAAC,CAAC;IACjC,SAAS,EAAE,MAAM,CAAC,SAAS;IAC3B,KAAK,EAAE;QACL,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,OAAO;QAC7B,WAAW,EAAE,MAAM,CAAC,KAAK,CAAC,WAAW;QACrC,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,OAAO;KAC9B;CACF,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,mCAAmC,GAAG,CACjD,UAAwD,EACiB,EAAE;IAC3E,MAAM,IAAI,GAAG,OAAO,UAAU,KAAK,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC;IAE3E,OAAO,IAAI,KAAK,WAAW,CAAC;AAC9B,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,oCAAoC,GAAG,CAClD,KAAc,EACd,WAAwC,qCAAqC,EAChD,EAAE;IAC/B,MAAM,mBAAmB,GACvB,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,KAAK,IAAI;QACd,WAAW,IAAI,KAAK;QACpB,CAAC,KAAK,CAAC,SAAS,KAAK,UAAU,IAAI,KAAK,CAAC,SAAS,KAAK,MAAM,CAAC;QAC5D,CAAC,CAAC,KAAK,CAAC,SAAS;QACjB,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC;IAEzB,MAAM,UAAU,GACd,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,IAAI,KAAK,IAAI,KAAK,CAAC,KAAK;QAC5E,CAAC,CAAC,KAAK,CAAC,KAAK;QACb,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC;IAErB,MAAM,YAAY,GAChB,OAAO,UAAU,KAAK,QAAQ,IAAI,UAAU,KAAK,IAAI,IAAI,SAAS,IAAI,UAAU;QAC9E,CAAC,CAAC,UAAU,CAAC,OAAO,KAAK,IAAI;QAC7B,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC;IAC7B,MAAM,gBAAgB,GACpB,OAAO,UAAU,KAAK,QAAQ,IAAI,UAAU,KAAK,IAAI,IAAI,aAAa,IAAI,UAAU;QAClF,CAAC,CAAC,UAAU,CAAC,WAAW;QACxB,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,WAAW,CAAC;IACjC,MAAM,aAAa,GACjB,OAAO,UAAU,KAAK,QAAQ,IAAI,UAAU,KAAK,IAAI,IAAI,SAAS,IAAI,UAAU;QAC9E,CAAC,CAAC,UAAU,CAAC,OAAO;QACpB,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC;IAE7B,MAAM,eAAe,GACnB,OAAO,UAAU,KAAK,QAAQ,IAAI,UAAU,KAAK,IAAI,IAAI,mBAAmB,IAAI,UAAU;QACxF,CAAC,CAAC,UAAU,CAAC,iBAAiB;QAC9B,CAAC,CAAC,SAAS,CAAC;IAChB,MAAM,gBAAgB,GACpB,OAAO,UAAU,KAAK,QAAQ,IAAI,UAAU,KAAK,IAAI,IAAI,YAAY,IAAI,UAAU;QACjF,CAAC,CAAC,UAAU,CAAC,UAAU;QACvB,CAAC,CAAC,SAAS,CAAC;IAEhB,MAAM,WAAW,GAAG,YAAY;QAC9B,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,gBAAgB,KAAK,QAAQ,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACtF,CAAC,CAAC,CAAC,CAAC;IACN,MAAM,OAAO,GAAG,YAAY;QAC1B,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,aAAa,KAAK,QAAQ,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAChF,CAAC,CAAC,CAAC,CAAC;IACN,4EAA4E;IAC5E,sEAAsE;IACtE,MAAM,iBAAiB,GACrB,YAAY,IAAI,OAAO,eAAe,KAAK,QAAQ,IAAI,eAAe,GAAG,CAAC;QACxE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,eAAe,CAAC;QAC/B,CAAC,CAAC,SAAS,CAAC;IAChB,MAAM,UAAU,GACd,YAAY,IAAI,iBAAiB,IAAI,OAAO,gBAAgB,KAAK,QAAQ,IAAI,gBAAgB,GAAG,CAAC;QAC/F,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC;QACjD,CAAC,CAAC,SAAS,CAAC;IAEhB,OAAO;QACL,SAAS,EAAE,mBAAmB;QAC9B,KAAK,EAAE;YACL,OAAO,EAAE,YAAY;YACrB,WAAW;YACX,OAAO;YACP,GAAG,CAAC,iBAAiB,CAAC,CAAC,CAAC,EAAE,iBAAiB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACnD,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACtC;KACF,CAAC;AACJ,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,oCAAoC,GAAG,GAAgC,EAAE,CACpF,WAAW,CAAC,qCAAqC,CAAC,CAAC;AAErD,MAAM,CAAC,MAAM,uCAAuC,GAAG,CACrD,IAAmC,EACN,EAAE,CAAC,WAAW,CAAC,mCAAmC,CAAC,IAAI,CAAC,CAAC,CAAC;AAEzF,MAAM,CAAC,MAAM,8BAA8B,GAAG,CAC5C,IAA+B,EACF,EAAE,CAC/B,oCAAoC,CAClC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,eAAe,EAChC,oCAAoC,EAAE,CACvC,CAAC;AAEJ,MAAM,CAAC,MAAM,0BAA0B,GAAG,CACxC,MAAmC,EAC3B,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAEhF,MAAM,CAAC,MAAM,2BAA2B,GAAG,CACzC,IAA+B,EACrB,EAAE;IACZ,MAAM,MAAM,GAAG,8BAA8B,CAAC,IAAI,CAAC,CAAC;IACpD,MAAM,MAAM,GAAa,EAAE,CAAC;IAE5B,IAAI,MAAM,CAAC,SAAS,KAAK,UAAU,EAAE,CAAC;QACpC,MAAM,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;IAClC,CAAC;SAAM,CAAC;QACN,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;IAC7B,CAAC;IAED,IAAI,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;QACzB,MAAM,CAAC,IAAI,CAAC,UAAU,0BAA0B,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAC9D,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,gCAAgC,GAAG,CAa9C,IAAW,EACX,eAA4C,EACrC,EAAE,CAAC,CAAC;IACX,GAAG,IAAI;IACP,IAAI,EAAE;QACJ,GAAG,IAAI,CAAC,IAAI;QACZ,MAAM,EAAE;YACN,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM;YACnB,eAAe,EAAE,oCAAoC,CAAC,eAAe,CAAC;SACvE;KACF;CACF,CAAC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"runScenario.d.ts","sourceRoot":"","sources":["../../src/scenario-runner/runScenario.ts"],"names":[],"mappings":"AA0CA,OAAO,KAAK,EAKV,kBAAkB,EAUlB,iBAAiB,EAKlB,MAAM,YAAY,CAAC;AA0rBpB,eAAO,MAAM,WAAW,GAAU,mGAQ/B,kBAAkB,KAAG,OAAO,CAAC,iBAAiB,CAwlDhD,CAAC"}
1
+ {"version":3,"file":"runScenario.d.ts","sourceRoot":"","sources":["../../src/scenario-runner/runScenario.ts"],"names":[],"mappings":"AA+CA,OAAO,KAAK,EAKV,kBAAkB,EAUlB,iBAAiB,EAKlB,MAAM,YAAY,CAAC;AA+sBpB,eAAO,MAAM,WAAW,GAAU,mGAQ/B,kBAAkB,KAAG,OAAO,CAAC,iBAAiB,CAw+DhD,CAAC"}
@@ -1,6 +1,7 @@
1
- import { buildConditionOutputPayload, evaluateConditionConfig, evaluateConditionRule, validateConditionConfig, } from "./condition.js";
1
+ import { buildConditionOutputPayload, evaluateConditionConfig, evaluateConditionRule, resolveConditionRuntimeValue, validateConditionConfig, } from "./condition.js";
2
+ import { applyExtract } from "./extraction.js";
2
3
  import { evaluateAssertionConfig, validateAssertionConfig, } from "./assertion.js";
3
- import { createSubScenarioStepId, buildRuntimeStepDescriptors, DEFAULT_END_LOOP_DELAY_MS, DEFAULT_END_LOOP_ITERATIONS, flattenScenarioNodesForContext, MAX_END_LOOP_ITERATIONS, } from "./graphTraversal.js";
4
+ import { createSubScenarioStepId, buildRuntimeStepDescriptors, DEFAULT_END_LOOP_DELAY_MS, DEFAULT_END_LOOP_ITERATIONS, flattenScenarioNodesForContext, MAX_END_LOOP_ITERATIONS, DEFAULT_POLL_INTERVAL_MS, DEFAULT_POLL_MAX_ATTEMPTS, MAX_POLL_ATTEMPTS, } from "./graphTraversal.js";
4
5
  import { executeWithScenarioNodePolicy } from "./executionPolicyRunner.js";
5
6
  import { getLegacyScenarioNodeExecutionPolicy, getScenarioNodeExecutionPolicy, supportsScenarioNodeExecutionPolicy, } from "./nodePolicy.js";
6
7
  import { buildScenarioExecutionGraph, buildScenarioTemplateValues, collectExclusiveBranchNodeIds, evaluateScenarioExpression, resolveScenarioJsonTemplateString, resolveScenarioTemplateString, } from "./templateResolver.js";
@@ -399,6 +400,19 @@ const updateRecordFromEvent = (records, event) => {
399
400
  });
400
401
  return;
401
402
  }
403
+ if (event.type === "step:poll") {
404
+ // Tick progress only — the terminal success/fail event owns the rest.
405
+ records.set(event.stepId, {
406
+ ...current,
407
+ stepType: event.stepType,
408
+ title: event.title,
409
+ attempts: event.attempt,
410
+ currentAttempt: event.attempt,
411
+ maxAttempts: event.maxAttempts,
412
+ startedAt: current.startedAt ?? event.at,
413
+ });
414
+ return;
415
+ }
402
416
  if (event.type === "step:condition") {
403
417
  records.set(event.stepId, {
404
418
  ...current,
@@ -443,6 +457,9 @@ const updateRecordFromEvent = (records, event) => {
443
457
  retryDelayMs: event.retryDelayMs ?? current.retryDelayMs,
444
458
  failurePolicy: event.failurePolicy ?? current.failurePolicy,
445
459
  continuedAfterFailure: event.continuedAfterFailure ?? current.continuedAfterFailure,
460
+ errorHandled: event.type === "step:fail"
461
+ ? (event.errorHandled ?? current.errorHandled)
462
+ : current.errorHandled,
446
463
  errors: event.errors ?? current.errors,
447
464
  durationMs: event.durationMs ?? current.durationMs,
448
465
  statusCode: event.statusCode ?? current.statusCode,
@@ -488,6 +505,9 @@ const callHandler = async (handlers, event) => {
488
505
  case "step:retry":
489
506
  await handlers?.onNodeRetry?.(event);
490
507
  return;
508
+ case "step:poll":
509
+ await handlers?.onPollTick?.(event);
510
+ return;
491
511
  case "step:success":
492
512
  await handlers?.onNodeSuccess?.(event);
493
513
  return;
@@ -712,6 +732,38 @@ export const runScenario = async ({ scenario, environment = {}, globals = {}, ru
712
732
  const markRemainingNodesSkipped = async (reason) => {
713
733
  await markNodeIdsSkipped(rootScope, rootScope.graph.nodeMap.keys(), reason);
714
734
  };
735
+ // After a failure is routed into error edges, the node's NORMAL outgoing
736
+ // subtree must not linger idle (an idle source blocks downstream joins in
737
+ // isNodeReady). Skip everything reachable from the normal edges that is
738
+ // NOT also reachable from the taken error edges — shared downstream joins
739
+ // stay runnable.
740
+ const skipUntakenNormalBranches = async (scope, nodeId, errorEdges) => {
741
+ const outgoing = scope.graph.outgoingMap.get(nodeId) ?? [];
742
+ const normalTargets = outgoing
743
+ .filter((edge) => (edge.type ?? "default") !== "error")
744
+ .map((edge) => edge.target);
745
+ if (normalTargets.length === 0) {
746
+ return;
747
+ }
748
+ const collectReachable = (startIds) => {
749
+ const stack = [...startIds];
750
+ const seen = new Set();
751
+ while (stack.length > 0) {
752
+ const current = stack.pop();
753
+ if (!current || seen.has(current)) {
754
+ continue;
755
+ }
756
+ seen.add(current);
757
+ for (const edge of scope.graph.outgoingMap.get(current) ?? []) {
758
+ stack.push(edge.target);
759
+ }
760
+ }
761
+ return seen;
762
+ };
763
+ const reachableFromError = collectReachable(errorEdges.map((edge) => edge.target));
764
+ const untaken = Array.from(collectReachable(normalTargets)).filter((candidate) => !reachableFromError.has(candidate));
765
+ await markNodeIdsSkipped(scope, untaken, "Error branch taken");
766
+ };
715
767
  const markScopeRemainingNodesSkipped = async (scope, reason) => {
716
768
  await markNodeIdsSkipped(scope, scope.graph.nodeMap.keys(), reason);
717
769
  };
@@ -760,11 +812,24 @@ export const runScenario = async ({ scenario, environment = {}, globals = {}, ru
760
812
  if (!node) {
761
813
  return;
762
814
  }
815
+ // Synchronous claim: nothing may await between the idle/ready checks
816
+ // above and this line, or two branches converging on a join could both
817
+ // execute it (each inbound edge calls executeNode once).
818
+ localStatuses.set(stepId, "running");
763
819
  const title = buildTitle(node);
764
820
  const nodeLabel = node.data.label.trim();
765
- const executionPolicy = supportsScenarioNodeExecutionPolicy(node)
821
+ let executionPolicy = supportsScenarioNodeExecutionPolicy(node)
766
822
  ? withNotificationFailurePolicyOverride(node, getScenarioNodeExecutionPolicy(node))
767
823
  : getLegacyScenarioNodeExecutionPolicy();
824
+ if (node.type === "poll") {
825
+ // The poll loop REPLACES per-node retry (nested retry loops would be
826
+ // unexplainable). onFailure — incl. error edges — still governs the
827
+ // timeout outcome.
828
+ executionPolicy = {
829
+ ...executionPolicy,
830
+ retry: { enabled: false, maxAttempts: 0, delayMs: 0 },
831
+ };
832
+ }
768
833
  const executionStartedAtMs = Date.now();
769
834
  let inputData = createInputSnapshot(runtimeContext);
770
835
  let statusCode = null;
@@ -982,6 +1047,223 @@ export const runScenario = async ({ scenario, environment = {}, globals = {}, ru
982
1047
  throw new ScenarioNodeFailureError(redisResult.failureMessage);
983
1048
  }
984
1049
  }
1050
+ if (node.type === "poll") {
1051
+ const pollConfig = node.data.config;
1052
+ const blockingPollIssue = validateConditionConfig(pollConfig.successCondition).find((issue) => issue.severity === "error");
1053
+ if (blockingPollIssue) {
1054
+ throw new ScenarioNodeFailureError(`Poll success condition is invalid: ${blockingPollIssue.message}`);
1055
+ }
1056
+ const pollStartedAtMs = Date.now();
1057
+ const pollMaxAttempts = Math.min(MAX_POLL_ATTEMPTS, Math.max(1, Math.round(pollConfig.maxAttempts ?? DEFAULT_POLL_MAX_ATTEMPTS)));
1058
+ const pollBaseIntervalMs = Math.max(0, Math.round(pollConfig.intervalMs ?? DEFAULT_POLL_INTERVAL_MS));
1059
+ const pollTimeoutMs = typeof pollConfig.timeoutMs === "number" && pollConfig.timeoutMs > 0
1060
+ ? Math.round(pollConfig.timeoutMs)
1061
+ : null;
1062
+ // Thinned emission keeps stored events bounded (~100 tick events).
1063
+ const pollEmitStride = Math.max(1, Math.ceil(pollMaxAttempts / 100));
1064
+ // One tick = the embedded action through the SAME host adapters real
1065
+ // nodes use (desktop IPC / backend drivers both covered), publishing
1066
+ // results into the runtime context exactly like the real node would,
1067
+ // so the success condition AND downstream nodes read the last
1068
+ // attempt's data. Returns the action's error message, or null.
1069
+ const runPollTick = async () => {
1070
+ const tickValues = buildScenarioTemplateValues({
1071
+ environment: runtimeContext.environment,
1072
+ globals: runtimeContext.globals,
1073
+ workflowContext: runtimeContext.workflowContext,
1074
+ nodeMetaByLabel: runtimeContext.nodeMetaByLabel,
1075
+ responseRoot: runtimeContext.responseRoot,
1076
+ dbResult: runtimeContext.dbResult,
1077
+ kafkaEvent: runtimeContext.kafkaEvent,
1078
+ redisResult: runtimeContext.redisResult,
1079
+ });
1080
+ if (pollConfig.actionType === "request") {
1081
+ if (!pollConfig.request) {
1082
+ throw new ScenarioNodeFailureError("Poll node has no request configuration.");
1083
+ }
1084
+ const tickResult = await runtimeAdapters.requestExecutor({
1085
+ runId,
1086
+ stepId,
1087
+ node: {
1088
+ id: node.id,
1089
+ type: "request",
1090
+ position: node.position,
1091
+ data: { label: node.data.label, config: pollConfig.request },
1092
+ },
1093
+ context: createContextSnapshot(runtimeContext),
1094
+ kafkaCapturePlans: [],
1095
+ captureFromTimestampMs: startedAtMs,
1096
+ signal,
1097
+ });
1098
+ if (tickResult.cancelled) {
1099
+ throw new ScenarioStopError();
1100
+ }
1101
+ runtimeContext.environment = { ...tickResult.environment };
1102
+ runtimeContext.globals = { ...tickResult.globals };
1103
+ runtimeContext.lastResponse = tickResult.response;
1104
+ runtimeContext.responseRoot = tickResult.responseRoot;
1105
+ response = tickResult.response;
1106
+ statusCode = tickResult.response.status;
1107
+ testResults = tickResult.testResults;
1108
+ contractResults = tickResult.contractResults;
1109
+ contractSummary = tickResult.contractSummary;
1110
+ contractValidationError = tickResult.contractValidationError;
1111
+ dbAssertionResults = tickResult.dbAssertionResults;
1112
+ redisAssertionResults = tickResult.redisAssertionResults;
1113
+ return tickResult.failureMessage ?? null;
1114
+ }
1115
+ if (pollConfig.actionType === "db") {
1116
+ if (!pollConfig.db) {
1117
+ throw new ScenarioNodeFailureError("Poll node has no database configuration.");
1118
+ }
1119
+ const resolvedQuery = resolveScenarioTemplateString(pollConfig.db.query, tickValues);
1120
+ const resolvedParams = (pollConfig.db.params ?? []).map((param) => resolveScenarioTemplateString(param, tickValues));
1121
+ const tickResult = await runtimeAdapters.dbExecutor({
1122
+ runId,
1123
+ stepId,
1124
+ node: {
1125
+ id: node.id,
1126
+ type: "db",
1127
+ position: node.position,
1128
+ data: { label: node.data.label, config: pollConfig.db },
1129
+ },
1130
+ resolvedQuery,
1131
+ resolvedParams,
1132
+ context: createContextSnapshot(runtimeContext),
1133
+ signal,
1134
+ });
1135
+ runtimeContext.dbResult = tickResult.dbResult;
1136
+ return tickResult.failureMessage ?? null;
1137
+ }
1138
+ if (pollConfig.actionType === "redis") {
1139
+ if (!pollConfig.redis) {
1140
+ throw new ScenarioNodeFailureError("Poll node has no Redis configuration.");
1141
+ }
1142
+ const resolvedCommands = pollConfig.redis.commands
1143
+ .map((command) => resolveScenarioTemplateString(command, tickValues))
1144
+ .filter((command) => command.trim().length > 0);
1145
+ const tickResult = await runtimeAdapters.redisExecutor({
1146
+ runId,
1147
+ stepId,
1148
+ node: {
1149
+ id: node.id,
1150
+ type: "redis",
1151
+ position: node.position,
1152
+ data: { label: node.data.label, config: pollConfig.redis },
1153
+ },
1154
+ resolvedCommands,
1155
+ context: createContextSnapshot(runtimeContext),
1156
+ signal,
1157
+ });
1158
+ runtimeContext.redisResult = tickResult.redisResult;
1159
+ return tickResult.failureMessage ?? null;
1160
+ }
1161
+ if (!pollConfig.script) {
1162
+ throw new ScenarioNodeFailureError("Poll node has no script configuration.");
1163
+ }
1164
+ const tickResult = await runtimeAdapters.scriptExecutor({
1165
+ runId,
1166
+ stepId,
1167
+ node: {
1168
+ id: node.id,
1169
+ type: "script",
1170
+ position: node.position,
1171
+ data: { label: node.data.label, config: pollConfig.script },
1172
+ },
1173
+ context: createContextSnapshot(runtimeContext),
1174
+ signal,
1175
+ });
1176
+ scriptSource = tickResult.scriptSource;
1177
+ scriptTimeoutMs = tickResult.timeoutMs;
1178
+ scriptLogs = tickResult.logs;
1179
+ testResults = tickResult.testResults;
1180
+ applyVariableMutationsToContext(runtimeContext, tickResult.variableMutations);
1181
+ return tickResult.failureMessage ?? null;
1182
+ };
1183
+ let pollAttempt = 0;
1184
+ let pollPassed = false;
1185
+ let lastTickError = null;
1186
+ for (;;) {
1187
+ throwIfStopped(signal);
1188
+ pollAttempt += 1;
1189
+ lastTickError = await runPollTick();
1190
+ if (lastTickError === null) {
1191
+ const evaluation = evaluateConditionConfig(pollConfig.successCondition, {
1192
+ environment: runtimeContext.environment,
1193
+ globals: runtimeContext.globals,
1194
+ workflowContext: runtimeContext.workflowContext,
1195
+ nodeMetaByLabel: runtimeContext.nodeMetaByLabel,
1196
+ responseRoot: runtimeContext.responseRoot,
1197
+ dbResult: runtimeContext.dbResult,
1198
+ kafkaEvent: runtimeContext.kafkaEvent,
1199
+ redisResult: runtimeContext.redisResult,
1200
+ });
1201
+ const conditionPayload = buildConditionOutputPayload(pollConfig.successCondition, evaluation);
1202
+ conditionResult = evaluation.result;
1203
+ conditionExpression = conditionPayload.expression;
1204
+ conditionPreview = conditionPayload.preview;
1205
+ conditionMode = conditionPayload.mode;
1206
+ conditionDebug = conditionPayload.debug;
1207
+ pollPassed = evaluation.result;
1208
+ }
1209
+ else {
1210
+ conditionResult = false;
1211
+ }
1212
+ const pollElapsedNow = Date.now() - pollStartedAtMs;
1213
+ const budgetExhausted = pollAttempt >= pollMaxAttempts ||
1214
+ (pollTimeoutMs !== null && pollElapsedNow >= pollTimeoutMs);
1215
+ const pollBackoff = pollConfig.backoffMultiplier && pollConfig.backoffMultiplier > 1
1216
+ ? Math.min(10, pollConfig.backoffMultiplier)
1217
+ : 1;
1218
+ const nextDelayMs = pollPassed || budgetExhausted
1219
+ ? null
1220
+ : Math.min(pollConfig.maxDelayMs && pollConfig.maxDelayMs > 0
1221
+ ? Math.round(pollConfig.maxDelayMs)
1222
+ : Number.MAX_SAFE_INTEGER, Math.round(pollBaseIntervalMs * Math.pow(pollBackoff, pollAttempt - 1)));
1223
+ if (pollPassed ||
1224
+ budgetExhausted ||
1225
+ pollAttempt === 1 ||
1226
+ pollAttempt % pollEmitStride === 0) {
1227
+ await emit({
1228
+ type: "step:poll",
1229
+ runId,
1230
+ scenarioId: scenario.id,
1231
+ stepId,
1232
+ stepType: node.type,
1233
+ title,
1234
+ at: new Date().toISOString(),
1235
+ attempt: pollAttempt,
1236
+ maxAttempts: pollMaxAttempts,
1237
+ elapsedMs: pollElapsedNow,
1238
+ timeoutMs: pollTimeoutMs,
1239
+ conditionResult: pollPassed,
1240
+ tickError: lastTickError,
1241
+ nextPollDelayMs: nextDelayMs,
1242
+ });
1243
+ }
1244
+ if (pollPassed || budgetExhausted) {
1245
+ break;
1246
+ }
1247
+ await waitForDuration(nextDelayMs ?? pollBaseIntervalMs);
1248
+ }
1249
+ const pollElapsedMs = Date.now() - pollStartedAtMs;
1250
+ outputData = {
1251
+ attempts: pollAttempt,
1252
+ maxAttempts: pollMaxAttempts,
1253
+ elapsedMs: pollElapsedMs,
1254
+ timedOut: !pollPassed,
1255
+ conditionResult: pollPassed,
1256
+ ...(lastTickError ? { lastTickError } : {}),
1257
+ };
1258
+ if (!pollPassed) {
1259
+ if ((pollConfig.onTimeout ?? "fail") === "continue") {
1260
+ shouldMarkPartialSuccess = true;
1261
+ }
1262
+ else {
1263
+ throw new ScenarioNodeFailureError(`Poll did not succeed within ${pollAttempt} attempt(s) (${pollElapsedMs}ms).${lastTickError ? ` Last error: ${lastTickError}` : ""}`);
1264
+ }
1265
+ }
1266
+ }
985
1267
  if (node.type === "notification") {
986
1268
  const resolvedMessage = renderNotificationTemplate(node.data.config.messageTemplate, templateValues).trim();
987
1269
  const resolvedChannelOverride = node.data.config.channelOverride?.trim()
@@ -1267,6 +1549,7 @@ export const runScenario = async ({ scenario, environment = {}, globals = {}, ru
1267
1549
  // may reference a variable set by an earlier one.
1268
1550
  const writtenEnvironment = {};
1269
1551
  const writtenGlobals = {};
1552
+ const extractionOutcomes = [];
1270
1553
  for (const assignment of node.data.config.assignments ?? []) {
1271
1554
  if (!assignment || assignment.enabled === false) {
1272
1555
  continue;
@@ -1285,7 +1568,49 @@ export const runScenario = async ({ scenario, environment = {}, globals = {}, ru
1285
1568
  if (!resolvedKey) {
1286
1569
  continue;
1287
1570
  }
1288
- const resolvedValue = resolveScenarioTemplateString(assignment.value ?? "", rowTemplateValues);
1571
+ let resolvedValue;
1572
+ const extractorType = assignment.extractorType ?? "template";
1573
+ if (extractorType === "jsonpath" || extractorType === "regex") {
1574
+ // Extractor rows resolve `source` with condition-operand path
1575
+ // semantics, then apply the expression. A failed extraction
1576
+ // SKIPS the write (never persists a wrong/empty value
1577
+ // silently) and surfaces the error in outputData.
1578
+ const sourcePath = (assignment.source ?? "").trim();
1579
+ const resolvedSource = resolveConditionRuntimeValue({
1580
+ environment: runtimeContext.environment,
1581
+ globals: runtimeContext.globals,
1582
+ workflowContext: runtimeContext.workflowContext,
1583
+ nodeMetaByLabel: runtimeContext.nodeMetaByLabel,
1584
+ responseRoot: runtimeContext.responseRoot,
1585
+ dbResult: runtimeContext.dbResult,
1586
+ kafkaEvent: runtimeContext.kafkaEvent,
1587
+ redisResult: runtimeContext.redisResult,
1588
+ }, sourcePath);
1589
+ const outcome = applyExtract(resolvedSource.value, {
1590
+ type: extractorType,
1591
+ expression: assignment.expression ?? "",
1592
+ flags: assignment.flags,
1593
+ group: assignment.group,
1594
+ select: assignment.select,
1595
+ });
1596
+ if (outcome.error || typeof outcome.value === "undefined") {
1597
+ extractionOutcomes.push({
1598
+ key: resolvedKey,
1599
+ extractorType,
1600
+ source: sourcePath,
1601
+ error: outcome.error ?? "Extraction produced no value",
1602
+ });
1603
+ continue;
1604
+ }
1605
+ extractionOutcomes.push({ key: resolvedKey, extractorType, source: sourcePath });
1606
+ resolvedValue =
1607
+ typeof outcome.value === "string"
1608
+ ? outcome.value
1609
+ : JSON.stringify(outcome.value);
1610
+ }
1611
+ else {
1612
+ resolvedValue = resolveScenarioTemplateString(assignment.value ?? "", rowTemplateValues);
1613
+ }
1289
1614
  applyVariableMutationsToContext(runtimeContext, [
1290
1615
  {
1291
1616
  scope: assignment.scope === "global" ? "global" : "environment",
@@ -1304,6 +1629,8 @@ export const runScenario = async ({ scenario, environment = {}, globals = {}, ru
1304
1629
  outputData = {
1305
1630
  environment: writtenEnvironment,
1306
1631
  globals: writtenGlobals,
1632
+ // Additive: present only when extractor rows ran.
1633
+ ...(extractionOutcomes.length > 0 ? { extraction: extractionOutcomes } : {}),
1307
1634
  };
1308
1635
  }
1309
1636
  if (node.type === "aggregator") {
@@ -1376,7 +1703,14 @@ export const runScenario = async ({ scenario, environment = {}, globals = {}, ru
1376
1703
  },
1377
1704
  });
1378
1705
  const durationMs = Math.round(Date.now() - executionStartedAtMs);
1379
- if (policyResult.outcome === "failed") {
1706
+ // "error" edges (no-code try/catch): a failure with a drawn handler is
1707
+ // routed into the error branch instead of stopping or continuing on
1708
+ // the normal path — the user's explicit handler overrides onFailure.
1709
+ const errorEdges = policyResult.outcome === "failed" || policyResult.outcome === "continued"
1710
+ ? (scope.graph.outgoingMap.get(nodeId) ?? []).filter((edge) => (edge.type ?? "default") === "error")
1711
+ : [];
1712
+ const failureRouted = errorEdges.length > 0;
1713
+ if (policyResult.outcome === "failed" && !failureRouted) {
1380
1714
  localStatuses.set(stepId, "failed");
1381
1715
  const failedAt = new Date().toISOString();
1382
1716
  const nodeMeta = buildNodeExecutionMeta({
@@ -1449,7 +1783,9 @@ export const runScenario = async ({ scenario, environment = {}, globals = {}, ru
1449
1783
  });
1450
1784
  throw new ScenarioNodeFailureError(policyResult.finalErrorMessage ?? "Scenario node failed");
1451
1785
  }
1452
- if (policyResult.outcome === "continued") {
1786
+ if (policyResult.outcome === "continued" || policyResult.outcome === "failed") {
1787
+ // Either policy-continue, or a failure routed into error edges —
1788
+ // both leave the node terminal as failed_continued so joins unblock.
1453
1789
  degradedStepIds.add(stepId);
1454
1790
  localStatuses.set(stepId, "failed_continued");
1455
1791
  const continuedAt = new Date().toISOString();
@@ -1495,7 +1831,9 @@ export const runScenario = async ({ scenario, environment = {}, globals = {}, ru
1495
1831
  retryEnabled: executionPolicy.retry.enabled,
1496
1832
  retryDelayMs: executionPolicy.retry.delayMs,
1497
1833
  failurePolicy: executionPolicy.onFailure,
1498
- continuedAfterFailure: true,
1834
+ continuedAfterFailure: policyResult.outcome === "continued",
1835
+ errorHandled: failureRouted ? true : undefined,
1836
+ errorEdgeIds: failureRouted ? errorEdges.map((edge) => edge.id) : undefined,
1499
1837
  errors: policyResult.errors,
1500
1838
  nodeMeta,
1501
1839
  errorMessage: policyResult.finalErrorMessage ??
@@ -1667,10 +2005,21 @@ export const runScenario = async ({ scenario, environment = {}, globals = {}, ru
1667
2005
  syncLoopRoots();
1668
2006
  }
1669
2007
  }
2008
+ if (failureRouted) {
2009
+ await skipUntakenNormalBranches(scope, nodeId, errorEdges);
2010
+ for (const edge of errorEdges) {
2011
+ throwIfStopped(signal);
2012
+ await executeNode(edge.target, scope.stepIdForEdge(edge.id), {}, scope);
2013
+ }
2014
+ return;
2015
+ }
1670
2016
  const outgoingEdges = scope.graph.outgoingMap.get(nodeId) ?? [];
1671
2017
  const nextEdges = node.type === "condition"
1672
2018
  ? outgoingEdges.filter((edge) => (conditionResult ? "yes" : "no") === (edge.type ?? "default"))
1673
- : outgoingEdges;
2019
+ : // Error edges are handler paths — never taken on success (or on a
2020
+ // policy-continue without routing). No-op for scenarios that have
2021
+ // no error edges.
2022
+ outgoingEdges.filter((edge) => (edge.type ?? "default") !== "error");
1674
2023
  for (const edge of nextEdges) {
1675
2024
  throwIfStopped(signal);
1676
2025
  await executeNode(edge.target, scope.stepIdForEdge(edge.id), {}, scope);