@substrate-ai/factory 0.19.54 → 0.20.2

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 (491) hide show
  1. package/README.md +39 -0
  2. package/dist/config.d.ts +4 -4
  3. package/package.json +12 -3
  4. package/dist/__tests__/config.test.d.ts +0 -11
  5. package/dist/__tests__/config.test.d.ts.map +0 -1
  6. package/dist/__tests__/config.test.js +0 -215
  7. package/dist/__tests__/config.test.js.map +0 -1
  8. package/dist/__tests__/factory-run-command.test.d.ts +0 -12
  9. package/dist/__tests__/factory-run-command.test.d.ts.map +0 -1
  10. package/dist/__tests__/factory-run-command.test.js +0 -454
  11. package/dist/__tests__/factory-run-command.test.js.map +0 -1
  12. package/dist/__tests__/factory-validate-command.test.d.ts +0 -15
  13. package/dist/__tests__/factory-validate-command.test.d.ts.map +0 -1
  14. package/dist/__tests__/factory-validate-command.test.js +0 -339
  15. package/dist/__tests__/factory-validate-command.test.js.map +0 -1
  16. package/dist/__tests__/fixtures/advanced-cross-project-validation.dot.d.ts +0 -72
  17. package/dist/__tests__/fixtures/advanced-cross-project-validation.dot.d.ts.map +0 -1
  18. package/dist/__tests__/fixtures/advanced-cross-project-validation.dot.js +0 -121
  19. package/dist/__tests__/fixtures/advanced-cross-project-validation.dot.js.map +0 -1
  20. package/dist/__tests__/fixtures/llm-edge-routing.dot.d.ts +0 -28
  21. package/dist/__tests__/fixtures/llm-edge-routing.dot.d.ts.map +0 -1
  22. package/dist/__tests__/fixtures/llm-edge-routing.dot.js +0 -55
  23. package/dist/__tests__/fixtures/llm-edge-routing.dot.js.map +0 -1
  24. package/dist/__tests__/fixtures/manager-loop.dot.d.ts +0 -34
  25. package/dist/__tests__/fixtures/manager-loop.dot.d.ts.map +0 -1
  26. package/dist/__tests__/fixtures/manager-loop.dot.js +0 -61
  27. package/dist/__tests__/fixtures/manager-loop.dot.js.map +0 -1
  28. package/dist/__tests__/fixtures/parallel-fan-out-fan-in.dot.d.ts +0 -42
  29. package/dist/__tests__/fixtures/parallel-fan-out-fan-in.dot.d.ts.map +0 -1
  30. package/dist/__tests__/fixtures/parallel-fan-out-fan-in.dot.js +0 -118
  31. package/dist/__tests__/fixtures/parallel-fan-out-fan-in.dot.js.map +0 -1
  32. package/dist/__tests__/fixtures/subgraph-parent.dot.d.ts +0 -35
  33. package/dist/__tests__/fixtures/subgraph-parent.dot.d.ts.map +0 -1
  34. package/dist/__tests__/fixtures/subgraph-parent.dot.js +0 -69
  35. package/dist/__tests__/fixtures/subgraph-parent.dot.js.map +0 -1
  36. package/dist/__tests__/integration/advanced-graph-events.test.d.ts +0 -19
  37. package/dist/__tests__/integration/advanced-graph-events.test.d.ts.map +0 -1
  38. package/dist/__tests__/integration/advanced-graph-events.test.js +0 -288
  39. package/dist/__tests__/integration/advanced-graph-events.test.js.map +0 -1
  40. package/dist/__tests__/integration/checkpoint-resume.test.d.ts +0 -10
  41. package/dist/__tests__/integration/checkpoint-resume.test.d.ts.map +0 -1
  42. package/dist/__tests__/integration/checkpoint-resume.test.js +0 -125
  43. package/dist/__tests__/integration/checkpoint-resume.test.js.map +0 -1
  44. package/dist/__tests__/integration/conditional-pipeline.test.d.ts +0 -10
  45. package/dist/__tests__/integration/conditional-pipeline.test.d.ts.map +0 -1
  46. package/dist/__tests__/integration/conditional-pipeline.test.js +0 -106
  47. package/dist/__tests__/integration/conditional-pipeline.test.js.map +0 -1
  48. package/dist/__tests__/integration/convergence-validation.test.d.ts +0 -14
  49. package/dist/__tests__/integration/convergence-validation.test.d.ts.map +0 -1
  50. package/dist/__tests__/integration/convergence-validation.test.js +0 -449
  51. package/dist/__tests__/integration/convergence-validation.test.js.map +0 -1
  52. package/dist/__tests__/integration/epic44-coverage-gate.test.d.ts +0 -12
  53. package/dist/__tests__/integration/epic44-coverage-gate.test.d.ts.map +0 -1
  54. package/dist/__tests__/integration/epic44-coverage-gate.test.js +0 -58
  55. package/dist/__tests__/integration/epic44-coverage-gate.test.js.map +0 -1
  56. package/dist/__tests__/integration/epic45-coverage-gate.test.d.ts +0 -11
  57. package/dist/__tests__/integration/epic45-coverage-gate.test.d.ts.map +0 -1
  58. package/dist/__tests__/integration/epic45-coverage-gate.test.js +0 -64
  59. package/dist/__tests__/integration/epic45-coverage-gate.test.js.map +0 -1
  60. package/dist/__tests__/integration/epic46-scenario-primary-executor.test.d.ts +0 -2
  61. package/dist/__tests__/integration/epic46-scenario-primary-executor.test.d.ts.map +0 -1
  62. package/dist/__tests__/integration/epic46-scenario-primary-executor.test.js +0 -285
  63. package/dist/__tests__/integration/epic46-scenario-primary-executor.test.js.map +0 -1
  64. package/dist/__tests__/integration/events.test.d.ts +0 -8
  65. package/dist/__tests__/integration/events.test.d.ts.map +0 -1
  66. package/dist/__tests__/integration/events.test.js +0 -194
  67. package/dist/__tests__/integration/events.test.js.map +0 -1
  68. package/dist/__tests__/integration/graphs.d.ts +0 -59
  69. package/dist/__tests__/integration/graphs.d.ts.map +0 -1
  70. package/dist/__tests__/integration/graphs.js +0 -164
  71. package/dist/__tests__/integration/graphs.js.map +0 -1
  72. package/dist/__tests__/integration/helpers.d.ts +0 -127
  73. package/dist/__tests__/integration/helpers.d.ts.map +0 -1
  74. package/dist/__tests__/integration/helpers.js +0 -167
  75. package/dist/__tests__/integration/helpers.js.map +0 -1
  76. package/dist/__tests__/integration/integrity.test.d.ts +0 -8
  77. package/dist/__tests__/integration/integrity.test.d.ts.map +0 -1
  78. package/dist/__tests__/integration/integrity.test.js +0 -198
  79. package/dist/__tests__/integration/integrity.test.js.map +0 -1
  80. package/dist/__tests__/integration/llm-edge-routing.test.d.ts +0 -21
  81. package/dist/__tests__/integration/llm-edge-routing.test.d.ts.map +0 -1
  82. package/dist/__tests__/integration/llm-edge-routing.test.js +0 -341
  83. package/dist/__tests__/integration/llm-edge-routing.test.js.map +0 -1
  84. package/dist/__tests__/integration/manager-loop.test.d.ts +0 -24
  85. package/dist/__tests__/integration/manager-loop.test.d.ts.map +0 -1
  86. package/dist/__tests__/integration/manager-loop.test.js +0 -276
  87. package/dist/__tests__/integration/manager-loop.test.js.map +0 -1
  88. package/dist/__tests__/integration/multi-type-graph.test.d.ts +0 -10
  89. package/dist/__tests__/integration/multi-type-graph.test.d.ts.map +0 -1
  90. package/dist/__tests__/integration/multi-type-graph.test.js +0 -100
  91. package/dist/__tests__/integration/multi-type-graph.test.js.map +0 -1
  92. package/dist/__tests__/integration/parallel-fan-out-fan-in.test.d.ts +0 -22
  93. package/dist/__tests__/integration/parallel-fan-out-fan-in.test.d.ts.map +0 -1
  94. package/dist/__tests__/integration/parallel-fan-out-fan-in.test.js +0 -515
  95. package/dist/__tests__/integration/parallel-fan-out-fan-in.test.js.map +0 -1
  96. package/dist/__tests__/integration/persistence.test.d.ts +0 -8
  97. package/dist/__tests__/integration/persistence.test.d.ts.map +0 -1
  98. package/dist/__tests__/integration/persistence.test.js +0 -129
  99. package/dist/__tests__/integration/persistence.test.js.map +0 -1
  100. package/dist/__tests__/integration/pipeline-templates-integration.test.d.ts +0 -16
  101. package/dist/__tests__/integration/pipeline-templates-integration.test.d.ts.map +0 -1
  102. package/dist/__tests__/integration/pipeline-templates-integration.test.js +0 -171
  103. package/dist/__tests__/integration/pipeline-templates-integration.test.js.map +0 -1
  104. package/dist/__tests__/integration/scenario-pipeline.test.d.ts +0 -11
  105. package/dist/__tests__/integration/scenario-pipeline.test.d.ts.map +0 -1
  106. package/dist/__tests__/integration/scenario-pipeline.test.js +0 -243
  107. package/dist/__tests__/integration/scenario-pipeline.test.js.map +0 -1
  108. package/dist/__tests__/integration/stylesheet-application.test.d.ts +0 -12
  109. package/dist/__tests__/integration/stylesheet-application.test.d.ts.map +0 -1
  110. package/dist/__tests__/integration/stylesheet-application.test.js +0 -119
  111. package/dist/__tests__/integration/stylesheet-application.test.js.map +0 -1
  112. package/dist/__tests__/integration/subgraph-execution.test.d.ts +0 -24
  113. package/dist/__tests__/integration/subgraph-execution.test.d.ts.map +0 -1
  114. package/dist/__tests__/integration/subgraph-execution.test.js +0 -291
  115. package/dist/__tests__/integration/subgraph-execution.test.js.map +0 -1
  116. package/dist/__tests__/integration/validation-errors.test.d.ts +0 -8
  117. package/dist/__tests__/integration/validation-errors.test.d.ts.map +0 -1
  118. package/dist/__tests__/integration/validation-errors.test.js +0 -150
  119. package/dist/__tests__/integration/validation-errors.test.js.map +0 -1
  120. package/dist/agent/__tests__/loop-detection.test.d.ts +0 -2
  121. package/dist/agent/__tests__/loop-detection.test.d.ts.map +0 -1
  122. package/dist/agent/__tests__/loop-detection.test.js +0 -236
  123. package/dist/agent/__tests__/loop-detection.test.js.map +0 -1
  124. package/dist/agent/__tests__/loop.test.d.ts +0 -2
  125. package/dist/agent/__tests__/loop.test.d.ts.map +0 -1
  126. package/dist/agent/__tests__/loop.test.js +0 -868
  127. package/dist/agent/__tests__/loop.test.js.map +0 -1
  128. package/dist/agent/__tests__/truncation.test.d.ts +0 -2
  129. package/dist/agent/__tests__/truncation.test.d.ts.map +0 -1
  130. package/dist/agent/__tests__/truncation.test.js +0 -276
  131. package/dist/agent/__tests__/truncation.test.js.map +0 -1
  132. package/dist/agent/tools/__tests__/anthropic-tools.test.d.ts +0 -6
  133. package/dist/agent/tools/__tests__/anthropic-tools.test.d.ts.map +0 -1
  134. package/dist/agent/tools/__tests__/anthropic-tools.test.js +0 -49
  135. package/dist/agent/tools/__tests__/anthropic-tools.test.js.map +0 -1
  136. package/dist/agent/tools/__tests__/environment.test.d.ts +0 -6
  137. package/dist/agent/tools/__tests__/environment.test.d.ts.map +0 -1
  138. package/dist/agent/tools/__tests__/environment.test.js +0 -33
  139. package/dist/agent/tools/__tests__/environment.test.js.map +0 -1
  140. package/dist/agent/tools/__tests__/gemini-tools.test.d.ts +0 -6
  141. package/dist/agent/tools/__tests__/gemini-tools.test.d.ts.map +0 -1
  142. package/dist/agent/tools/__tests__/gemini-tools.test.js +0 -98
  143. package/dist/agent/tools/__tests__/gemini-tools.test.js.map +0 -1
  144. package/dist/agent/tools/__tests__/openai-tools.test.d.ts +0 -6
  145. package/dist/agent/tools/__tests__/openai-tools.test.d.ts.map +0 -1
  146. package/dist/agent/tools/__tests__/openai-tools.test.js +0 -53
  147. package/dist/agent/tools/__tests__/openai-tools.test.js.map +0 -1
  148. package/dist/agent/tools/__tests__/patch.test.d.ts +0 -6
  149. package/dist/agent/tools/__tests__/patch.test.d.ts.map +0 -1
  150. package/dist/agent/tools/__tests__/patch.test.js +0 -116
  151. package/dist/agent/tools/__tests__/patch.test.js.map +0 -1
  152. package/dist/agent/tools/__tests__/profiles.test.d.ts +0 -6
  153. package/dist/agent/tools/__tests__/profiles.test.d.ts.map +0 -1
  154. package/dist/agent/tools/__tests__/profiles.test.js +0 -125
  155. package/dist/agent/tools/__tests__/profiles.test.js.map +0 -1
  156. package/dist/agent/tools/__tests__/registry.test.d.ts +0 -6
  157. package/dist/agent/tools/__tests__/registry.test.d.ts.map +0 -1
  158. package/dist/agent/tools/__tests__/registry.test.js +0 -94
  159. package/dist/agent/tools/__tests__/registry.test.js.map +0 -1
  160. package/dist/agent/tools/__tests__/shared.test.d.ts +0 -6
  161. package/dist/agent/tools/__tests__/shared.test.d.ts.map +0 -1
  162. package/dist/agent/tools/__tests__/shared.test.js +0 -131
  163. package/dist/agent/tools/__tests__/shared.test.js.map +0 -1
  164. package/dist/backend/__tests__/direct-backend.test.d.ts +0 -14
  165. package/dist/backend/__tests__/direct-backend.test.d.ts.map +0 -1
  166. package/dist/backend/__tests__/direct-backend.test.js +0 -393
  167. package/dist/backend/__tests__/direct-backend.test.js.map +0 -1
  168. package/dist/backend/__tests__/direct-bootstrap.test.d.ts +0 -7
  169. package/dist/backend/__tests__/direct-bootstrap.test.d.ts.map +0 -1
  170. package/dist/backend/__tests__/direct-bootstrap.test.js +0 -177
  171. package/dist/backend/__tests__/direct-bootstrap.test.js.map +0 -1
  172. package/dist/backend/__tests__/mock-backend.test.d.ts +0 -7
  173. package/dist/backend/__tests__/mock-backend.test.d.ts.map +0 -1
  174. package/dist/backend/__tests__/mock-backend.test.js +0 -273
  175. package/dist/backend/__tests__/mock-backend.test.js.map +0 -1
  176. package/dist/backend/__tests__/parity.test.d.ts +0 -17
  177. package/dist/backend/__tests__/parity.test.d.ts.map +0 -1
  178. package/dist/backend/__tests__/parity.test.js +0 -411
  179. package/dist/backend/__tests__/parity.test.js.map +0 -1
  180. package/dist/context/__tests__/auto-summarizer.test.d.ts +0 -14
  181. package/dist/context/__tests__/auto-summarizer.test.d.ts.map +0 -1
  182. package/dist/context/__tests__/auto-summarizer.test.js +0 -189
  183. package/dist/context/__tests__/auto-summarizer.test.js.map +0 -1
  184. package/dist/context/__tests__/context-cli-command.test.d.ts +0 -7
  185. package/dist/context/__tests__/context-cli-command.test.d.ts.map +0 -1
  186. package/dist/context/__tests__/context-cli-command.test.js +0 -331
  187. package/dist/context/__tests__/context-cli-command.test.js.map +0 -1
  188. package/dist/context/__tests__/pyramid-summary-integration.test.d.ts +0 -2
  189. package/dist/context/__tests__/pyramid-summary-integration.test.d.ts.map +0 -1
  190. package/dist/context/__tests__/pyramid-summary-integration.test.js +0 -533
  191. package/dist/context/__tests__/pyramid-summary-integration.test.js.map +0 -1
  192. package/dist/context/__tests__/summarizer.test.d.ts +0 -2
  193. package/dist/context/__tests__/summarizer.test.d.ts.map +0 -1
  194. package/dist/context/__tests__/summarizer.test.js +0 -189
  195. package/dist/context/__tests__/summarizer.test.js.map +0 -1
  196. package/dist/context/__tests__/summary-cache.test.d.ts +0 -2
  197. package/dist/context/__tests__/summary-cache.test.d.ts.map +0 -1
  198. package/dist/context/__tests__/summary-cache.test.js +0 -214
  199. package/dist/context/__tests__/summary-cache.test.js.map +0 -1
  200. package/dist/context/__tests__/summary-metrics.test.d.ts +0 -2
  201. package/dist/context/__tests__/summary-metrics.test.d.ts.map +0 -1
  202. package/dist/context/__tests__/summary-metrics.test.js +0 -172
  203. package/dist/context/__tests__/summary-metrics.test.js.map +0 -1
  204. package/dist/context/__tests__/summary-types.test.d.ts +0 -2
  205. package/dist/context/__tests__/summary-types.test.d.ts.map +0 -1
  206. package/dist/context/__tests__/summary-types.test.js +0 -130
  207. package/dist/context/__tests__/summary-types.test.js.map +0 -1
  208. package/dist/convergence/__tests__/budget.test.d.ts +0 -6
  209. package/dist/convergence/__tests__/budget.test.d.ts.map +0 -1
  210. package/dist/convergence/__tests__/budget.test.js +0 -187
  211. package/dist/convergence/__tests__/budget.test.js.map +0 -1
  212. package/dist/convergence/__tests__/controller.test.d.ts +0 -9
  213. package/dist/convergence/__tests__/controller.test.d.ts.map +0 -1
  214. package/dist/convergence/__tests__/controller.test.js +0 -585
  215. package/dist/convergence/__tests__/controller.test.js.map +0 -1
  216. package/dist/convergence/__tests__/dual-signal.test.d.ts +0 -14
  217. package/dist/convergence/__tests__/dual-signal.test.d.ts.map +0 -1
  218. package/dist/convergence/__tests__/dual-signal.test.js +0 -123
  219. package/dist/convergence/__tests__/dual-signal.test.js.map +0 -1
  220. package/dist/convergence/__tests__/epic46-integration.test.d.ts +0 -15
  221. package/dist/convergence/__tests__/epic46-integration.test.d.ts.map +0 -1
  222. package/dist/convergence/__tests__/epic46-integration.test.js +0 -522
  223. package/dist/convergence/__tests__/epic46-integration.test.js.map +0 -1
  224. package/dist/convergence/__tests__/plateau.test.d.ts +0 -6
  225. package/dist/convergence/__tests__/plateau.test.d.ts.map +0 -1
  226. package/dist/convergence/__tests__/plateau.test.js +0 -163
  227. package/dist/convergence/__tests__/plateau.test.js.map +0 -1
  228. package/dist/convergence/__tests__/remediation.test.d.ts +0 -11
  229. package/dist/convergence/__tests__/remediation.test.d.ts.map +0 -1
  230. package/dist/convergence/__tests__/remediation.test.js +0 -209
  231. package/dist/convergence/__tests__/remediation.test.js.map +0 -1
  232. package/dist/convergence/__tests__/scenario-primary.test.d.ts +0 -13
  233. package/dist/convergence/__tests__/scenario-primary.test.d.ts.map +0 -1
  234. package/dist/convergence/__tests__/scenario-primary.test.js +0 -183
  235. package/dist/convergence/__tests__/scenario-primary.test.js.map +0 -1
  236. package/dist/factory-command.test.d.ts +0 -8
  237. package/dist/factory-command.test.d.ts.map +0 -1
  238. package/dist/factory-command.test.js +0 -304
  239. package/dist/factory-command.test.js.map +0 -1
  240. package/dist/graph/__tests__/attractor-compliance.test.d.ts +0 -10
  241. package/dist/graph/__tests__/attractor-compliance.test.d.ts.map +0 -1
  242. package/dist/graph/__tests__/attractor-compliance.test.js +0 -766
  243. package/dist/graph/__tests__/attractor-compliance.test.js.map +0 -1
  244. package/dist/graph/__tests__/checkpoint.test.d.ts +0 -8
  245. package/dist/graph/__tests__/checkpoint.test.d.ts.map +0 -1
  246. package/dist/graph/__tests__/checkpoint.test.js +0 -329
  247. package/dist/graph/__tests__/checkpoint.test.js.map +0 -1
  248. package/dist/graph/__tests__/condition-parser.test.d.ts +0 -14
  249. package/dist/graph/__tests__/condition-parser.test.d.ts.map +0 -1
  250. package/dist/graph/__tests__/condition-parser.test.js +0 -406
  251. package/dist/graph/__tests__/condition-parser.test.js.map +0 -1
  252. package/dist/graph/__tests__/context.test.d.ts +0 -14
  253. package/dist/graph/__tests__/context.test.d.ts.map +0 -1
  254. package/dist/graph/__tests__/context.test.js +0 -276
  255. package/dist/graph/__tests__/context.test.js.map +0 -1
  256. package/dist/graph/__tests__/edge-selector-events.test.d.ts +0 -11
  257. package/dist/graph/__tests__/edge-selector-events.test.d.ts.map +0 -1
  258. package/dist/graph/__tests__/edge-selector-events.test.js +0 -184
  259. package/dist/graph/__tests__/edge-selector-events.test.js.map +0 -1
  260. package/dist/graph/__tests__/edge-selector.test.d.ts +0 -6
  261. package/dist/graph/__tests__/edge-selector.test.d.ts.map +0 -1
  262. package/dist/graph/__tests__/edge-selector.test.js +0 -452
  263. package/dist/graph/__tests__/edge-selector.test.js.map +0 -1
  264. package/dist/graph/__tests__/executor-convergence.test.d.ts +0 -12
  265. package/dist/graph/__tests__/executor-convergence.test.d.ts.map +0 -1
  266. package/dist/graph/__tests__/executor-convergence.test.js +0 -432
  267. package/dist/graph/__tests__/executor-convergence.test.js.map +0 -1
  268. package/dist/graph/__tests__/executor-fidelity.test.d.ts +0 -13
  269. package/dist/graph/__tests__/executor-fidelity.test.d.ts.map +0 -1
  270. package/dist/graph/__tests__/executor-fidelity.test.js +0 -335
  271. package/dist/graph/__tests__/executor-fidelity.test.js.map +0 -1
  272. package/dist/graph/__tests__/executor.test.d.ts +0 -14
  273. package/dist/graph/__tests__/executor.test.d.ts.map +0 -1
  274. package/dist/graph/__tests__/executor.test.js +0 -901
  275. package/dist/graph/__tests__/executor.test.js.map +0 -1
  276. package/dist/graph/__tests__/fidelity.test.d.ts +0 -8
  277. package/dist/graph/__tests__/fidelity.test.d.ts.map +0 -1
  278. package/dist/graph/__tests__/fidelity.test.js +0 -135
  279. package/dist/graph/__tests__/fidelity.test.js.map +0 -1
  280. package/dist/graph/__tests__/llm-evaluator.test.d.ts +0 -7
  281. package/dist/graph/__tests__/llm-evaluator.test.d.ts.map +0 -1
  282. package/dist/graph/__tests__/llm-evaluator.test.js +0 -106
  283. package/dist/graph/__tests__/llm-evaluator.test.js.map +0 -1
  284. package/dist/graph/__tests__/parser-chaining.test.d.ts +0 -13
  285. package/dist/graph/__tests__/parser-chaining.test.d.ts.map +0 -1
  286. package/dist/graph/__tests__/parser-chaining.test.js +0 -215
  287. package/dist/graph/__tests__/parser-chaining.test.js.map +0 -1
  288. package/dist/graph/__tests__/parser.test.d.ts +0 -22
  289. package/dist/graph/__tests__/parser.test.d.ts.map +0 -1
  290. package/dist/graph/__tests__/parser.test.js +0 -452
  291. package/dist/graph/__tests__/parser.test.js.map +0 -1
  292. package/dist/graph/__tests__/run-state.test.d.ts +0 -13
  293. package/dist/graph/__tests__/run-state.test.d.ts.map +0 -1
  294. package/dist/graph/__tests__/run-state.test.js +0 -189
  295. package/dist/graph/__tests__/run-state.test.js.map +0 -1
  296. package/dist/graph/__tests__/transformer.test.d.ts +0 -16
  297. package/dist/graph/__tests__/transformer.test.d.ts.map +0 -1
  298. package/dist/graph/__tests__/transformer.test.js +0 -350
  299. package/dist/graph/__tests__/transformer.test.js.map +0 -1
  300. package/dist/graph/__tests__/validator-errors.test.d.ts +0 -15
  301. package/dist/graph/__tests__/validator-errors.test.d.ts.map +0 -1
  302. package/dist/graph/__tests__/validator-errors.test.js +0 -572
  303. package/dist/graph/__tests__/validator-errors.test.js.map +0 -1
  304. package/dist/graph/__tests__/validator-warnings.test.d.ts +0 -15
  305. package/dist/graph/__tests__/validator-warnings.test.d.ts.map +0 -1
  306. package/dist/graph/__tests__/validator-warnings.test.js +0 -363
  307. package/dist/graph/__tests__/validator-warnings.test.js.map +0 -1
  308. package/dist/handlers/__tests__/codergen-handler.test.d.ts +0 -14
  309. package/dist/handlers/__tests__/codergen-handler.test.d.ts.map +0 -1
  310. package/dist/handlers/__tests__/codergen-handler.test.js +0 -442
  311. package/dist/handlers/__tests__/codergen-handler.test.js.map +0 -1
  312. package/dist/handlers/__tests__/fan-in.test.d.ts +0 -14
  313. package/dist/handlers/__tests__/fan-in.test.d.ts.map +0 -1
  314. package/dist/handlers/__tests__/fan-in.test.js +0 -399
  315. package/dist/handlers/__tests__/fan-in.test.js.map +0 -1
  316. package/dist/handlers/__tests__/join-policy.test.d.ts +0 -9
  317. package/dist/handlers/__tests__/join-policy.test.d.ts.map +0 -1
  318. package/dist/handlers/__tests__/join-policy.test.js +0 -201
  319. package/dist/handlers/__tests__/join-policy.test.js.map +0 -1
  320. package/dist/handlers/__tests__/manager-loop.test.d.ts +0 -14
  321. package/dist/handlers/__tests__/manager-loop.test.d.ts.map +0 -1
  322. package/dist/handlers/__tests__/manager-loop.test.js +0 -322
  323. package/dist/handlers/__tests__/manager-loop.test.js.map +0 -1
  324. package/dist/handlers/__tests__/parallel-events.test.d.ts +0 -12
  325. package/dist/handlers/__tests__/parallel-events.test.d.ts.map +0 -1
  326. package/dist/handlers/__tests__/parallel-events.test.js +0 -252
  327. package/dist/handlers/__tests__/parallel-events.test.js.map +0 -1
  328. package/dist/handlers/__tests__/parallel-handler.test.d.ts +0 -14
  329. package/dist/handlers/__tests__/parallel-handler.test.d.ts.map +0 -1
  330. package/dist/handlers/__tests__/parallel-handler.test.js +0 -337
  331. package/dist/handlers/__tests__/parallel-handler.test.js.map +0 -1
  332. package/dist/handlers/__tests__/parallel-join.test.d.ts +0 -9
  333. package/dist/handlers/__tests__/parallel-join.test.d.ts.map +0 -1
  334. package/dist/handlers/__tests__/parallel-join.test.js +0 -267
  335. package/dist/handlers/__tests__/parallel-join.test.js.map +0 -1
  336. package/dist/handlers/__tests__/registry.test.d.ts +0 -14
  337. package/dist/handlers/__tests__/registry.test.d.ts.map +0 -1
  338. package/dist/handlers/__tests__/registry.test.js +0 -315
  339. package/dist/handlers/__tests__/registry.test.js.map +0 -1
  340. package/dist/handlers/__tests__/subgraph-events.test.d.ts +0 -10
  341. package/dist/handlers/__tests__/subgraph-events.test.d.ts.map +0 -1
  342. package/dist/handlers/__tests__/subgraph-events.test.js +0 -189
  343. package/dist/handlers/__tests__/subgraph-events.test.js.map +0 -1
  344. package/dist/handlers/__tests__/subgraph-inheritance.test.d.ts +0 -14
  345. package/dist/handlers/__tests__/subgraph-inheritance.test.d.ts.map +0 -1
  346. package/dist/handlers/__tests__/subgraph-inheritance.test.js +0 -267
  347. package/dist/handlers/__tests__/subgraph-inheritance.test.js.map +0 -1
  348. package/dist/handlers/__tests__/subgraph.test.d.ts +0 -14
  349. package/dist/handlers/__tests__/subgraph.test.d.ts.map +0 -1
  350. package/dist/handlers/__tests__/subgraph.test.js +0 -369
  351. package/dist/handlers/__tests__/subgraph.test.js.map +0 -1
  352. package/dist/handlers/__tests__/tool-handler.test.d.ts +0 -11
  353. package/dist/handlers/__tests__/tool-handler.test.d.ts.map +0 -1
  354. package/dist/handlers/__tests__/tool-handler.test.js +0 -184
  355. package/dist/handlers/__tests__/tool-handler.test.js.map +0 -1
  356. package/dist/handlers/__tests__/tool-scenario.test.d.ts +0 -12
  357. package/dist/handlers/__tests__/tool-scenario.test.d.ts.map +0 -1
  358. package/dist/handlers/__tests__/tool-scenario.test.js +0 -222
  359. package/dist/handlers/__tests__/tool-scenario.test.js.map +0 -1
  360. package/dist/handlers/__tests__/wait-human-handler.test.d.ts +0 -11
  361. package/dist/handlers/__tests__/wait-human-handler.test.d.ts.map +0 -1
  362. package/dist/handlers/__tests__/wait-human-handler.test.js +0 -251
  363. package/dist/handlers/__tests__/wait-human-handler.test.js.map +0 -1
  364. package/dist/llm/__tests__/client.test.d.ts +0 -2
  365. package/dist/llm/__tests__/client.test.d.ts.map +0 -1
  366. package/dist/llm/__tests__/client.test.js +0 -198
  367. package/dist/llm/__tests__/client.test.js.map +0 -1
  368. package/dist/llm/__tests__/types.test.d.ts +0 -2
  369. package/dist/llm/__tests__/types.test.d.ts.map +0 -1
  370. package/dist/llm/__tests__/types.test.js +0 -289
  371. package/dist/llm/__tests__/types.test.js.map +0 -1
  372. package/dist/llm/middleware/__tests__/cost-tracking.test.d.ts +0 -2
  373. package/dist/llm/middleware/__tests__/cost-tracking.test.d.ts.map +0 -1
  374. package/dist/llm/middleware/__tests__/cost-tracking.test.js +0 -73
  375. package/dist/llm/middleware/__tests__/cost-tracking.test.js.map +0 -1
  376. package/dist/llm/middleware/__tests__/logging.test.d.ts +0 -2
  377. package/dist/llm/middleware/__tests__/logging.test.d.ts.map +0 -1
  378. package/dist/llm/middleware/__tests__/logging.test.js +0 -127
  379. package/dist/llm/middleware/__tests__/logging.test.js.map +0 -1
  380. package/dist/llm/middleware/__tests__/retry.test.d.ts +0 -2
  381. package/dist/llm/middleware/__tests__/retry.test.d.ts.map +0 -1
  382. package/dist/llm/middleware/__tests__/retry.test.js +0 -126
  383. package/dist/llm/middleware/__tests__/retry.test.js.map +0 -1
  384. package/dist/llm/providers/__tests__/anthropic.test.d.ts +0 -2
  385. package/dist/llm/providers/__tests__/anthropic.test.d.ts.map +0 -1
  386. package/dist/llm/providers/__tests__/anthropic.test.js +0 -412
  387. package/dist/llm/providers/__tests__/anthropic.test.js.map +0 -1
  388. package/dist/llm/providers/__tests__/gemini.test.d.ts +0 -2
  389. package/dist/llm/providers/__tests__/gemini.test.d.ts.map +0 -1
  390. package/dist/llm/providers/__tests__/gemini.test.js +0 -591
  391. package/dist/llm/providers/__tests__/gemini.test.js.map +0 -1
  392. package/dist/llm/providers/__tests__/openai.test.d.ts +0 -2
  393. package/dist/llm/providers/__tests__/openai.test.d.ts.map +0 -1
  394. package/dist/llm/providers/__tests__/openai.test.js +0 -546
  395. package/dist/llm/providers/__tests__/openai.test.js.map +0 -1
  396. package/dist/persistence/__tests__/factory-queries.test.d.ts +0 -9
  397. package/dist/persistence/__tests__/factory-queries.test.d.ts.map +0 -1
  398. package/dist/persistence/__tests__/factory-queries.test.js +0 -372
  399. package/dist/persistence/__tests__/factory-queries.test.js.map +0 -1
  400. package/dist/persistence/__tests__/factory-schema.test.d.ts +0 -6
  401. package/dist/persistence/__tests__/factory-schema.test.d.ts.map +0 -1
  402. package/dist/persistence/__tests__/factory-schema.test.js +0 -105
  403. package/dist/persistence/__tests__/factory-schema.test.js.map +0 -1
  404. package/dist/scenarios/__tests__/cli-command-list.test.d.ts +0 -7
  405. package/dist/scenarios/__tests__/cli-command-list.test.d.ts.map +0 -1
  406. package/dist/scenarios/__tests__/cli-command-list.test.js +0 -237
  407. package/dist/scenarios/__tests__/cli-command-list.test.js.map +0 -1
  408. package/dist/scenarios/__tests__/cli-command.test.d.ts +0 -11
  409. package/dist/scenarios/__tests__/cli-command.test.d.ts.map +0 -1
  410. package/dist/scenarios/__tests__/cli-command.test.js +0 -275
  411. package/dist/scenarios/__tests__/cli-command.test.js.map +0 -1
  412. package/dist/scenarios/__tests__/integrity-pipeline.test.d.ts +0 -15
  413. package/dist/scenarios/__tests__/integrity-pipeline.test.d.ts.map +0 -1
  414. package/dist/scenarios/__tests__/integrity-pipeline.test.js +0 -318
  415. package/dist/scenarios/__tests__/integrity-pipeline.test.js.map +0 -1
  416. package/dist/scenarios/__tests__/runner-twins.test.d.ts +0 -13
  417. package/dist/scenarios/__tests__/runner-twins.test.d.ts.map +0 -1
  418. package/dist/scenarios/__tests__/runner-twins.test.js +0 -205
  419. package/dist/scenarios/__tests__/runner-twins.test.js.map +0 -1
  420. package/dist/scenarios/__tests__/scorer.test.d.ts +0 -11
  421. package/dist/scenarios/__tests__/scorer.test.d.ts.map +0 -1
  422. package/dist/scenarios/__tests__/scorer.test.js +0 -225
  423. package/dist/scenarios/__tests__/scorer.test.js.map +0 -1
  424. package/dist/scenarios/__tests__/scoring-integration.test.d.ts +0 -8
  425. package/dist/scenarios/__tests__/scoring-integration.test.d.ts.map +0 -1
  426. package/dist/scenarios/__tests__/scoring-integration.test.js +0 -178
  427. package/dist/scenarios/__tests__/scoring-integration.test.js.map +0 -1
  428. package/dist/scenarios/__tests__/store.test.d.ts +0 -5
  429. package/dist/scenarios/__tests__/store.test.d.ts.map +0 -1
  430. package/dist/scenarios/__tests__/store.test.js +0 -169
  431. package/dist/scenarios/__tests__/store.test.js.map +0 -1
  432. package/dist/stylesheet/__tests__/stylesheet.test.d.ts +0 -17
  433. package/dist/stylesheet/__tests__/stylesheet.test.d.ts.map +0 -1
  434. package/dist/stylesheet/__tests__/stylesheet.test.js +0 -368
  435. package/dist/stylesheet/__tests__/stylesheet.test.js.map +0 -1
  436. package/dist/templates/__tests__/templates.test.d.ts +0 -7
  437. package/dist/templates/__tests__/templates.test.d.ts.map +0 -1
  438. package/dist/templates/__tests__/templates.test.js +0 -92
  439. package/dist/templates/__tests__/templates.test.js.map +0 -1
  440. package/dist/twins/__tests__/docker-compose.test.d.ts +0 -13
  441. package/dist/twins/__tests__/docker-compose.test.d.ts.map +0 -1
  442. package/dist/twins/__tests__/docker-compose.test.js +0 -247
  443. package/dist/twins/__tests__/docker-compose.test.js.map +0 -1
  444. package/dist/twins/__tests__/health-monitor.test.d.ts +0 -19
  445. package/dist/twins/__tests__/health-monitor.test.d.ts.map +0 -1
  446. package/dist/twins/__tests__/health-monitor.test.js +0 -301
  447. package/dist/twins/__tests__/health-monitor.test.js.map +0 -1
  448. package/dist/twins/__tests__/integration/e2e.test.d.ts +0 -18
  449. package/dist/twins/__tests__/integration/e2e.test.d.ts.map +0 -1
  450. package/dist/twins/__tests__/integration/e2e.test.js +0 -146
  451. package/dist/twins/__tests__/integration/e2e.test.js.map +0 -1
  452. package/dist/twins/__tests__/integration/health-monitor-integration.test.d.ts +0 -16
  453. package/dist/twins/__tests__/integration/health-monitor-integration.test.d.ts.map +0 -1
  454. package/dist/twins/__tests__/integration/health-monitor-integration.test.js +0 -183
  455. package/dist/twins/__tests__/integration/health-monitor-integration.test.js.map +0 -1
  456. package/dist/twins/__tests__/integration/helpers.d.ts +0 -32
  457. package/dist/twins/__tests__/integration/helpers.d.ts.map +0 -1
  458. package/dist/twins/__tests__/integration/helpers.js +0 -67
  459. package/dist/twins/__tests__/integration/helpers.js.map +0 -1
  460. package/dist/twins/__tests__/integration/lifecycle.test.d.ts +0 -14
  461. package/dist/twins/__tests__/integration/lifecycle.test.d.ts.map +0 -1
  462. package/dist/twins/__tests__/integration/lifecycle.test.js +0 -127
  463. package/dist/twins/__tests__/integration/lifecycle.test.js.map +0 -1
  464. package/dist/twins/__tests__/integration/persistence-integration.test.d.ts +0 -14
  465. package/dist/twins/__tests__/integration/persistence-integration.test.d.ts.map +0 -1
  466. package/dist/twins/__tests__/integration/persistence-integration.test.js +0 -132
  467. package/dist/twins/__tests__/integration/persistence-integration.test.js.map +0 -1
  468. package/dist/twins/__tests__/persistence.test.d.ts +0 -10
  469. package/dist/twins/__tests__/persistence.test.d.ts.map +0 -1
  470. package/dist/twins/__tests__/persistence.test.js +0 -300
  471. package/dist/twins/__tests__/persistence.test.js.map +0 -1
  472. package/dist/twins/__tests__/registry.test.d.ts +0 -7
  473. package/dist/twins/__tests__/registry.test.d.ts.map +0 -1
  474. package/dist/twins/__tests__/registry.test.js +0 -282
  475. package/dist/twins/__tests__/registry.test.js.map +0 -1
  476. package/dist/twins/__tests__/run-state.test.d.ts +0 -9
  477. package/dist/twins/__tests__/run-state.test.d.ts.map +0 -1
  478. package/dist/twins/__tests__/run-state.test.js +0 -112
  479. package/dist/twins/__tests__/run-state.test.js.map +0 -1
  480. package/dist/twins/__tests__/templates-cli.test.d.ts +0 -10
  481. package/dist/twins/__tests__/templates-cli.test.d.ts.map +0 -1
  482. package/dist/twins/__tests__/templates-cli.test.js +0 -187
  483. package/dist/twins/__tests__/templates-cli.test.js.map +0 -1
  484. package/dist/twins/__tests__/templates.test.d.ts +0 -7
  485. package/dist/twins/__tests__/templates.test.d.ts.map +0 -1
  486. package/dist/twins/__tests__/templates.test.js +0 -87
  487. package/dist/twins/__tests__/templates.test.js.map +0 -1
  488. package/dist/twins/__tests__/twins-cli.test.d.ts +0 -11
  489. package/dist/twins/__tests__/twins-cli.test.d.ts.map +0 -1
  490. package/dist/twins/__tests__/twins-cli.test.js +0 -365
  491. package/dist/twins/__tests__/twins-cli.test.js.map +0 -1
@@ -1,901 +0,0 @@
1
- /**
2
- * Unit tests for the graph executor (story 42-14).
3
- *
4
- * Covers all 7 acceptance criteria:
5
- * AC1 — 3-node graph traversal returns SUCCESS
6
- * AC2 — handler exceptions converted to FAIL outcome
7
- * AC3 — retry with exponential backoff (via fake timers)
8
- * AC4 — checkpoint saved after each node; resume from checkpoint
9
- * AC5 — all 6 FactoryEvents emitted with correct payloads
10
- * AC6 — per-node transition overhead < 100ms for 20-node graph
11
- * AC7 — all unit tests pass
12
- */
13
- import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
14
- import { mkdtemp, rm, readFile } from 'node:fs/promises';
15
- import path from 'node:path';
16
- import os from 'node:os';
17
- // ---------------------------------------------------------------------------
18
- // Hoist mock factories so they are available when vi.mock() runs
19
- // ---------------------------------------------------------------------------
20
- const { mockSave, mockLoad, mockResume } = vi.hoisted(() => ({
21
- mockSave: vi.fn().mockResolvedValue(undefined),
22
- mockLoad: vi.fn(),
23
- mockResume: vi.fn(),
24
- }));
25
- const { mockEvaluateGates, mockRecordOutcome, mockCheckGoalGates, mockResolveRetryTarget, mockRecordIterationContext, mockPrepareForIteration } = vi.hoisted(() => ({
26
- mockEvaluateGates: vi.fn().mockReturnValue({ satisfied: true, failingNodes: [] }),
27
- mockRecordOutcome: vi.fn(),
28
- mockCheckGoalGates: vi.fn().mockReturnValue({ satisfied: true, failedGates: [] }),
29
- mockResolveRetryTarget: vi.fn().mockReturnValue(null),
30
- mockRecordIterationContext: vi.fn(),
31
- mockPrepareForIteration: vi.fn().mockResolvedValue([]),
32
- }));
33
- // Mock CheckpointManager to avoid real file I/O in executor tests
34
- vi.mock('../checkpoint.js', () => ({
35
- CheckpointManager: vi.fn().mockImplementation(() => ({
36
- save: mockSave,
37
- load: mockLoad,
38
- resume: mockResume,
39
- })),
40
- }));
41
- // Mock ConvergenceController to avoid implicit reliance on goalGate=false for all nodes.
42
- // Explicit mock ensures tests are not fragile to future nodes with goalGate=true.
43
- // Path is relative to this test file: __tests__/../../convergence/index.js → src/convergence/index.js
44
- // Budget/plateau managers are mocked as no-ops so existing traversal tests are not affected (story 45-8).
45
- vi.mock('../../convergence/index.js', () => ({
46
- createConvergenceController: vi.fn().mockImplementation(() => ({
47
- evaluateGates: mockEvaluateGates,
48
- recordOutcome: mockRecordOutcome,
49
- checkGoalGates: mockCheckGoalGates,
50
- resolveRetryTarget: mockResolveRetryTarget,
51
- recordIterationContext: mockRecordIterationContext,
52
- prepareForIteration: mockPrepareForIteration,
53
- getStoredContexts: vi.fn().mockReturnValue([]),
54
- })),
55
- SessionBudgetManager: vi.fn().mockImplementation(() => ({
56
- checkBudget: vi.fn().mockReturnValue({ allowed: true }),
57
- getElapsedMs: vi.fn().mockReturnValue(0),
58
- reset: vi.fn(),
59
- })),
60
- PipelineBudgetManager: vi.fn().mockImplementation(() => ({
61
- checkBudget: vi.fn().mockReturnValue({ allowed: true }),
62
- addCost: vi.fn(),
63
- getTotalCost: vi.fn().mockReturnValue(0),
64
- reset: vi.fn(),
65
- })),
66
- createPlateauDetector: vi.fn().mockReturnValue({
67
- recordScore: vi.fn(),
68
- isPlateaued: vi.fn().mockReturnValue(false),
69
- getWindow: vi.fn().mockReturnValue(3),
70
- getScores: vi.fn().mockReturnValue([]),
71
- }),
72
- checkPlateauAndEmit: vi.fn().mockReturnValue({ plateaued: false, scores: [] }),
73
- buildRemediationContext: vi.fn().mockReturnValue({
74
- previousFailureReason: '',
75
- scenarioDiff: '',
76
- iterationCount: 0,
77
- satisfactionScoreHistory: [],
78
- fixScope: '',
79
- }),
80
- injectRemediationContext: vi.fn(),
81
- computeBackoffDelay: vi.fn().mockImplementation((attempt) => Math.min(1000 * 2 ** attempt, 30000)),
82
- }));
83
- // Import AFTER mocking
84
- import { createGraphExecutor } from '../executor.js';
85
- import { GraphContext } from '../context.js';
86
- // ---------------------------------------------------------------------------
87
- // Test helpers
88
- // ---------------------------------------------------------------------------
89
- const minimalNode = {
90
- id: '',
91
- label: '',
92
- shape: '',
93
- type: '',
94
- prompt: '',
95
- maxRetries: 0,
96
- goalGate: false,
97
- retryTarget: '',
98
- fallbackRetryTarget: '',
99
- fidelity: '',
100
- threadId: '',
101
- class: '',
102
- timeout: 0,
103
- llmModel: '',
104
- llmProvider: '',
105
- reasoningEffort: '',
106
- autoStatus: false,
107
- allowPartial: false,
108
- toolCommand: '',
109
- backend: '',
110
- };
111
- function makeNode(id, overrides) {
112
- return { ...minimalNode, id, ...overrides };
113
- }
114
- function makeEdge(fromNode, toNode, overrides) {
115
- return {
116
- fromNode,
117
- toNode,
118
- label: '',
119
- condition: '',
120
- weight: 0,
121
- fidelity: '',
122
- threadId: '',
123
- loopRestart: false,
124
- ...overrides,
125
- };
126
- }
127
- /**
128
- * Build a minimal Graph stub conforming to the Graph interface.
129
- * startNodeId and exitNodeId designate which nodes play those roles.
130
- */
131
- function makeGraph(nodeList, edgeList, startNodeId, exitNodeId) {
132
- const nodeMap = new Map(nodeList.map((n) => [n.id, n]));
133
- return {
134
- id: '',
135
- goal: '',
136
- label: '',
137
- modelStylesheet: '',
138
- defaultMaxRetries: 0,
139
- retryTarget: '',
140
- fallbackRetryTarget: '',
141
- defaultFidelity: '',
142
- nodes: nodeMap,
143
- edges: edgeList,
144
- outgoingEdges: (nodeId) => edgeList.filter((e) => e.fromNode === nodeId),
145
- startNode: () => nodeMap.get(startNodeId),
146
- exitNode: () => nodeMap.get(exitNodeId),
147
- };
148
- }
149
- /** Build a mock IHandlerRegistry that returns the given handler for all nodes. */
150
- function makeRegistry(handler) {
151
- return {
152
- register: vi.fn(),
153
- registerShape: vi.fn(),
154
- setDefault: vi.fn(),
155
- resolve: vi.fn().mockReturnValue(handler),
156
- };
157
- }
158
- /** Build a mock TypedEventBus<FactoryEvents> with a spy on emit(). */
159
- function makeEventBus() {
160
- const emit = vi.fn();
161
- const bus = {
162
- emit,
163
- on: vi.fn(),
164
- off: vi.fn(),
165
- };
166
- return { bus, emit };
167
- }
168
- /** Minimal config with fake logsRoot. */
169
- function makeConfig(registry, overrides) {
170
- return {
171
- runId: 'test-run-id',
172
- logsRoot: '/tmp/executor-test',
173
- handlerRegistry: registry,
174
- ...overrides,
175
- };
176
- }
177
- // ---------------------------------------------------------------------------
178
- // Setup
179
- // ---------------------------------------------------------------------------
180
- beforeEach(() => {
181
- vi.clearAllMocks();
182
- mockSave.mockResolvedValue(undefined);
183
- // Reset convergence mocks to default passing state
184
- mockEvaluateGates.mockReturnValue({ satisfied: true, failingNodes: [] });
185
- mockCheckGoalGates.mockReturnValue({ satisfied: true, failedGates: [] });
186
- mockResolveRetryTarget.mockReturnValue(null);
187
- });
188
- // ---------------------------------------------------------------------------
189
- // AC1: 3-node graph traversal returns SUCCESS
190
- // ---------------------------------------------------------------------------
191
- describe('AC1: 3-node graph traversal', () => {
192
- it('dispatches handlers in order and returns SUCCESS', async () => {
193
- const successHandler = vi.fn().mockResolvedValue({ status: 'SUCCESS' });
194
- const registry = makeRegistry(successHandler);
195
- const { bus, emit } = makeEventBus();
196
- const startNode = makeNode('start');
197
- const codergenNode = makeNode('codergen');
198
- const exitNode = makeNode('exit');
199
- const edges = [makeEdge('start', 'codergen'), makeEdge('codergen', 'exit')];
200
- const graph = makeGraph([startNode, codergenNode, exitNode], edges, 'start', 'exit');
201
- const executor = createGraphExecutor();
202
- const outcome = await executor.run(graph, makeConfig(registry, { eventBus: bus }));
203
- expect(outcome.status).toBe('SUCCESS');
204
- // Handler dispatched for start and codergen (not exit — exit terminates loop)
205
- expect(successHandler).toHaveBeenCalledTimes(2);
206
- // graph:node-started emitted once per dispatched node (start, codergen)
207
- const nodeStartedCalls = emit.mock.calls.filter(([event]) => event === 'graph:node-started');
208
- expect(nodeStartedCalls).toHaveLength(2);
209
- // graph:checkpoint-saved emitted once per completed node
210
- const checkpointSavedCalls = emit.mock.calls.filter(([event]) => event === 'graph:checkpoint-saved');
211
- expect(checkpointSavedCalls).toHaveLength(2);
212
- // CheckpointManager.save called once per node
213
- expect(mockSave).toHaveBeenCalledTimes(2);
214
- });
215
- });
216
- // ---------------------------------------------------------------------------
217
- // AC2: Handler exceptions converted to FAIL outcome
218
- // ---------------------------------------------------------------------------
219
- describe('AC2: handler exception converted to FAIL', () => {
220
- it('catches thrown Error and returns { status: FAIL, failureReason }', async () => {
221
- const throwingHandler = vi.fn().mockRejectedValue(new Error('boom'));
222
- const registry = makeRegistry(throwingHandler);
223
- const { bus, emit } = makeEventBus();
224
- // Use a graph where the retry-node has no outgoing edges so the FAIL propagates
225
- const retryNode = makeNode('start');
226
- const exitNode = makeNode('exit');
227
- // No edge from start → exit: forces FAIL from "No outgoing edge"
228
- const graph = makeGraph([retryNode, exitNode], [], 'start', 'exit');
229
- const executor = createGraphExecutor();
230
- const outcome = await executor.run(graph, makeConfig(registry, { eventBus: bus }));
231
- // Handler throws; exception is caught and converted to FAIL
232
- expect(outcome.status).toBe('FAIL');
233
- // failureReason should come from either the exception or the no-edge message
234
- expect(typeof outcome.failureReason).toBe('string');
235
- // graph:node-failed should be emitted for the exception case
236
- const failedCalls = emit.mock.calls.filter(([event]) => event === 'graph:node-failed');
237
- expect(failedCalls).toHaveLength(1);
238
- expect(failedCalls[0][1]).toMatchObject({ nodeId: 'start', failureReason: 'boom' });
239
- });
240
- it('catches non-Error throws and converts to FAIL with string failureReason', async () => {
241
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
242
- const throwingHandler = vi.fn().mockRejectedValue('string error');
243
- const registry = makeRegistry(throwingHandler);
244
- const { bus, emit } = makeEventBus();
245
- const startNode = makeNode('start');
246
- const exitNode = makeNode('exit');
247
- // No edge from start → executor returns FAIL (no outgoing edge)
248
- const graph = makeGraph([startNode, exitNode], [], 'start', 'exit');
249
- const executor = createGraphExecutor();
250
- const outcome = await executor.run(graph, makeConfig(registry, { eventBus: bus }));
251
- // run() returns FAIL (no outgoing edge after the exception)
252
- expect(outcome.status).toBe('FAIL');
253
- // The node-failed event carries the original exception failureReason
254
- const failedCalls = emit.mock.calls.filter(([e]) => e === 'graph:node-failed');
255
- expect(failedCalls).toHaveLength(1);
256
- expect(failedCalls[0][1]).toMatchObject({ failureReason: 'string error' });
257
- });
258
- });
259
- // ---------------------------------------------------------------------------
260
- // AC3: Retry with exponential backoff
261
- // ---------------------------------------------------------------------------
262
- describe('AC3: retry with exponential backoff', () => {
263
- it('retries up to max_retries=2 times (3 total attempts) and emits node-retried twice', async () => {
264
- vi.useFakeTimers();
265
- const failHandler = vi.fn().mockResolvedValue({ status: 'FAIL' });
266
- const registry = makeRegistry(failHandler);
267
- const { bus, emit } = makeEventBus();
268
- // Node with maxRetries=2 (3 total attempts): 1 initial + 2 retries
269
- const retryNode = makeNode('retry-node', { maxRetries: 2 });
270
- const exitNode = makeNode('exit');
271
- // No outgoing edge from retry-node → final run() returns FAIL
272
- const graph = makeGraph([retryNode, exitNode], [], 'retry-node', 'exit');
273
- const executor = createGraphExecutor();
274
- // Start the run — it will pause at each setTimeout
275
- const runPromise = executor.run(graph, makeConfig(registry, { eventBus: bus }));
276
- // Advance all fake timers to allow retries to complete
277
- await vi.runAllTimersAsync();
278
- const outcome = await runPromise;
279
- // Handler called 3 times total (1 initial + 2 retries)
280
- expect(failHandler).toHaveBeenCalledTimes(3);
281
- // graph:node-retried emitted once per retry (before each retry attempt)
282
- const retriedCalls = emit.mock.calls.filter(([event]) => event === 'graph:node-retried');
283
- expect(retriedCalls).toHaveLength(2);
284
- // First retry: attempt=1
285
- expect(retriedCalls[0][1]).toMatchObject({
286
- runId: 'test-run-id',
287
- nodeId: 'retry-node',
288
- attempt: 1,
289
- maxAttempts: 3,
290
- });
291
- // Second retry: attempt=2
292
- expect(retriedCalls[1][1]).toMatchObject({ attempt: 2, maxAttempts: 3 });
293
- // Final outcome is FAIL (no outgoing edge from retry-node)
294
- expect(outcome.status).toBe('FAIL');
295
- vi.useRealTimers();
296
- });
297
- it('increments nodeRetries counter for each retry', async () => {
298
- vi.useFakeTimers();
299
- const failHandler = vi.fn().mockResolvedValue({ status: 'FAIL' });
300
- const registry = makeRegistry(failHandler);
301
- const retryNode = makeNode('my-node', { maxRetries: 1 });
302
- const exitNode = makeNode('exit');
303
- const graph = makeGraph([retryNode, exitNode], [], 'my-node', 'exit');
304
- const executor = createGraphExecutor();
305
- const runPromise = executor.run(graph, makeConfig(registry));
306
- await vi.runAllTimersAsync();
307
- await runPromise;
308
- // save should have been called after the final failed attempt
309
- expect(mockSave).toHaveBeenCalled();
310
- const lastSaveCall = mockSave.mock.calls[mockSave.mock.calls.length - 1];
311
- const saveParams = lastSaveCall[1];
312
- // nodeRetries['my-node'] should be 1 (one retry was done)
313
- expect(saveParams.nodeRetries['my-node']).toBe(1);
314
- vi.useRealTimers();
315
- });
316
- it('does not retry when maxRetries=0 (default)', async () => {
317
- const failHandler = vi.fn().mockResolvedValue({ status: 'FAIL' });
318
- const registry = makeRegistry(failHandler);
319
- const { bus, emit } = makeEventBus();
320
- const startNode = makeNode('start'); // maxRetries: 0 (default)
321
- const exitNode = makeNode('exit');
322
- const graph = makeGraph([startNode, exitNode], [], 'start', 'exit');
323
- const executor = createGraphExecutor();
324
- await executor.run(graph, makeConfig(registry, { eventBus: bus }));
325
- // Handler called exactly once (no retries)
326
- expect(failHandler).toHaveBeenCalledTimes(1);
327
- const retriedCalls = emit.mock.calls.filter(([event]) => event === 'graph:node-retried');
328
- expect(retriedCalls).toHaveLength(0);
329
- });
330
- });
331
- // ---------------------------------------------------------------------------
332
- // AC4: Checkpoint save and resume
333
- // ---------------------------------------------------------------------------
334
- describe('AC4: checkpoint save', () => {
335
- it('calls checkpointManager.save once per completed node with correct currentNode', async () => {
336
- const successHandler = vi.fn().mockResolvedValue({ status: 'SUCCESS' });
337
- const registry = makeRegistry(successHandler);
338
- const startNode = makeNode('start');
339
- const codergenNode = makeNode('codergen');
340
- const exitNode = makeNode('exit');
341
- const edges = [makeEdge('start', 'codergen'), makeEdge('codergen', 'exit')];
342
- const graph = makeGraph([startNode, codergenNode, exitNode], edges, 'start', 'exit');
343
- const executor = createGraphExecutor();
344
- await executor.run(graph, makeConfig(registry));
345
- // 2 saves: after start, after codergen (not after exit)
346
- expect(mockSave).toHaveBeenCalledTimes(2);
347
- const firstSave = mockSave.mock.calls[0][1];
348
- expect(firstSave.currentNode).toBe('start');
349
- expect(firstSave.completedNodes).toContain('start');
350
- const secondSave = mockSave.mock.calls[1][1];
351
- expect(secondSave.currentNode).toBe('codergen');
352
- expect(secondSave.completedNodes).toContain('start');
353
- expect(secondSave.completedNodes).toContain('codergen');
354
- });
355
- });
356
- describe('AC4: checkpoint resume', () => {
357
- it('skips completed nodes and dispatches from the resumed node', async () => {
358
- const handlerSpy = vi.fn().mockResolvedValue({ status: 'SUCCESS' });
359
- const registry = makeRegistry(handlerSpy);
360
- // Graph: start → codergen → exit
361
- const startNode = makeNode('start');
362
- const codergenNode = makeNode('codergen');
363
- const exitNode = makeNode('exit');
364
- const edges = [makeEdge('start', 'codergen'), makeEdge('codergen', 'exit')];
365
- const graph = makeGraph([startNode, codergenNode, exitNode], edges, 'start', 'exit');
366
- // Checkpoint: start was already completed
367
- mockLoad.mockResolvedValue({
368
- timestamp: Date.now(),
369
- currentNode: 'start',
370
- completedNodes: ['start'],
371
- nodeRetries: {},
372
- contextValues: {},
373
- logs: [],
374
- });
375
- // resume() returns a state with start in completedNodes
376
- mockResume.mockReturnValue({
377
- context: new GraphContext(),
378
- completedNodes: new Set(['start']),
379
- nodeRetries: {},
380
- firstResumedNodeFidelity: '',
381
- });
382
- const executor = createGraphExecutor();
383
- const outcome = await executor.run(graph, makeConfig(registry, { checkpointPath: '/tmp/checkpoint.json' }));
384
- expect(outcome.status).toBe('SUCCESS');
385
- // 'start' should NOT have been dispatched (it was already completed)
386
- // Only 'codergen' should have been dispatched
387
- expect(handlerSpy).toHaveBeenCalledTimes(1);
388
- const dispatchedNode = handlerSpy.mock.calls[0][0];
389
- expect(dispatchedNode.id).toBe('codergen');
390
- });
391
- it('re-dispatches currentNode if it was not in completedNodes', async () => {
392
- const handlerSpy = vi.fn().mockResolvedValue({ status: 'SUCCESS' });
393
- const registry = makeRegistry(handlerSpy);
394
- const startNode = makeNode('start');
395
- const codergenNode = makeNode('codergen');
396
- const exitNode = makeNode('exit');
397
- const edges = [makeEdge('start', 'codergen'), makeEdge('codergen', 'exit')];
398
- const graph = makeGraph([startNode, codergenNode, exitNode], edges, 'start', 'exit');
399
- // Checkpoint: start was NOT completed (process interrupted mid-start)
400
- mockLoad.mockResolvedValue({
401
- timestamp: Date.now(),
402
- currentNode: 'start',
403
- completedNodes: [], // start not in completedNodes
404
- nodeRetries: {},
405
- contextValues: {},
406
- logs: [],
407
- });
408
- mockResume.mockReturnValue({
409
- context: new GraphContext(),
410
- completedNodes: new Set(), // empty set
411
- nodeRetries: {},
412
- firstResumedNodeFidelity: '',
413
- });
414
- const executor = createGraphExecutor();
415
- const outcome = await executor.run(graph, makeConfig(registry, { checkpointPath: '/tmp/checkpoint.json' }));
416
- expect(outcome.status).toBe('SUCCESS');
417
- // start AND codergen should have been dispatched (start was re-dispatched)
418
- expect(handlerSpy).toHaveBeenCalledTimes(2);
419
- const firstDispatch = handlerSpy.mock.calls[0][0];
420
- expect(firstDispatch.id).toBe('start');
421
- });
422
- });
423
- // ---------------------------------------------------------------------------
424
- // AC5: All 6 FactoryEvents emitted at the correct points
425
- // ---------------------------------------------------------------------------
426
- describe('AC5: all 6 FactoryEvents emitted correctly', () => {
427
- it('emits all required events with correct runId and payloads', async () => {
428
- const successHandler = vi.fn().mockResolvedValue({ status: 'SUCCESS' });
429
- const registry = makeRegistry(successHandler);
430
- const { bus, emit } = makeEventBus();
431
- // 2-node graph: start → exit
432
- const startNode = makeNode('start', { type: 'start' });
433
- const exitNode = makeNode('exit', { type: 'exit' });
434
- const edges = [makeEdge('start', 'exit', { label: 'proceed' })];
435
- const graph = makeGraph([startNode, exitNode], edges, 'start', 'exit');
436
- const config = makeConfig(registry, { eventBus: bus, runId: 'run-42' });
437
- const executor = createGraphExecutor();
438
- await executor.run(graph, config);
439
- // --- graph:node-started ---
440
- const nodeStarted = emit.mock.calls.filter(([e]) => e === 'graph:node-started');
441
- expect(nodeStarted).toHaveLength(1);
442
- expect(nodeStarted[0][1]).toEqual({ runId: 'run-42', nodeId: 'start', nodeType: 'start' });
443
- // --- graph:node-completed ---
444
- const nodeCompleted = emit.mock.calls.filter(([e]) => e === 'graph:node-completed');
445
- expect(nodeCompleted).toHaveLength(1);
446
- expect(nodeCompleted[0][1]).toMatchObject({ runId: 'run-42', nodeId: 'start' });
447
- // --- graph:checkpoint-saved ---
448
- const checkpointSaved = emit.mock.calls.filter(([e]) => e === 'graph:checkpoint-saved');
449
- expect(checkpointSaved).toHaveLength(1);
450
- expect(checkpointSaved[0][1]).toMatchObject({
451
- runId: 'run-42',
452
- nodeId: 'start',
453
- checkpointPath: '/tmp/executor-test/checkpoint.json',
454
- });
455
- // --- graph:edge-selected ---
456
- const edgeSelected = emit.mock.calls.filter(([e]) => e === 'graph:edge-selected');
457
- expect(edgeSelected).toHaveLength(1);
458
- expect(edgeSelected[0][1]).toMatchObject({
459
- runId: 'run-42',
460
- fromNode: 'start',
461
- toNode: 'exit',
462
- step: 0,
463
- edgeLabel: 'proceed',
464
- });
465
- // --- graph:node-retried: not emitted (no retries) ---
466
- const nodeRetried = emit.mock.calls.filter(([e]) => e === 'graph:node-retried');
467
- expect(nodeRetried).toHaveLength(0);
468
- // --- graph:node-failed: not emitted (success) ---
469
- const nodeFailed = emit.mock.calls.filter(([e]) => e === 'graph:node-failed');
470
- expect(nodeFailed).toHaveLength(0);
471
- });
472
- it('emits graph:node-failed for final FAIL after retries (not graph:node-completed)', async () => {
473
- vi.useFakeTimers();
474
- const failHandler = vi.fn().mockResolvedValue({ status: 'FAIL' });
475
- const registry = makeRegistry(failHandler);
476
- const { bus, emit } = makeEventBus();
477
- const retryNode = makeNode('fail-node', { maxRetries: 1 });
478
- const exitNode = makeNode('exit');
479
- const graph = makeGraph([retryNode, exitNode], [], 'fail-node', 'exit');
480
- const executor = createGraphExecutor();
481
- const runPromise = executor.run(graph, makeConfig(registry, { eventBus: bus }));
482
- await vi.runAllTimersAsync();
483
- await runPromise;
484
- const nodeFailed = emit.mock.calls.filter(([e]) => e === 'graph:node-failed');
485
- expect(nodeFailed).toHaveLength(1);
486
- expect(nodeFailed[0][1]).toMatchObject({
487
- runId: 'test-run-id',
488
- nodeId: 'fail-node',
489
- });
490
- // graph:node-completed must NOT be emitted when final outcome is FAIL
491
- const nodeCompleted = emit.mock.calls.filter(([e]) => e === 'graph:node-completed');
492
- expect(nodeCompleted).toHaveLength(0);
493
- vi.useRealTimers();
494
- });
495
- it('does not include edgeLabel when edge label is empty', async () => {
496
- const successHandler = vi.fn().mockResolvedValue({ status: 'SUCCESS' });
497
- const registry = makeRegistry(successHandler);
498
- const { bus, emit } = makeEventBus();
499
- const startNode = makeNode('start');
500
- const exitNode = makeNode('exit');
501
- // Edge with empty label
502
- const edges = [makeEdge('start', 'exit', { label: '' })];
503
- const graph = makeGraph([startNode, exitNode], edges, 'start', 'exit');
504
- const executor = createGraphExecutor();
505
- await executor.run(graph, makeConfig(registry, { eventBus: bus }));
506
- const edgeSelected = emit.mock.calls.filter(([e]) => e === 'graph:edge-selected');
507
- expect(edgeSelected).toHaveLength(1);
508
- // edgeLabel should not be present (not even as undefined due to exactOptionalPropertyTypes)
509
- expect(edgeSelected[0][1]).not.toHaveProperty('edgeLabel');
510
- });
511
- });
512
- // ---------------------------------------------------------------------------
513
- // AC6: Per-node transition overhead under 100ms
514
- // ---------------------------------------------------------------------------
515
- describe('AC6: performance — per-node overhead < 100ms', () => {
516
- it('20-node linear graph with instant handlers completes within 2000ms total', async () => {
517
- // Build 20-node linear graph: node0 → node1 → ... → node19 → exit
518
- const nodeCount = 20;
519
- const nodes = [];
520
- const edges = [];
521
- for (let i = 0; i < nodeCount; i++) {
522
- nodes.push(makeNode(`node${i}`));
523
- if (i < nodeCount - 1) {
524
- edges.push(makeEdge(`node${i}`, `node${i + 1}`));
525
- }
526
- }
527
- const exitNode = makeNode('exit');
528
- nodes.push(exitNode);
529
- edges.push(makeEdge(`node${nodeCount - 1}`, 'exit'));
530
- const graph = makeGraph(nodes, edges, 'node0', 'exit');
531
- const instantHandler = vi.fn().mockResolvedValue({ status: 'SUCCESS' });
532
- const registry = makeRegistry(instantHandler);
533
- const executor = createGraphExecutor();
534
- const startTime = Date.now();
535
- const outcome = await executor.run(graph, makeConfig(registry));
536
- const elapsed = Date.now() - startTime;
537
- expect(outcome.status).toBe('SUCCESS');
538
- // Per-node overhead = total time / 20 nodes (handlers return instantly, so elapsed ≈ overhead)
539
- const avgPerNode = elapsed / nodeCount;
540
- expect(avgPerNode).toBeLessThan(100);
541
- });
542
- });
543
- // ---------------------------------------------------------------------------
544
- // AC7 / Edge cases (Task 7)
545
- // ---------------------------------------------------------------------------
546
- describe('Edge case: cycle detection', () => {
547
- it('throws when a node is visited more than nodes.size * 3 times', async () => {
548
- const successHandler = vi.fn().mockResolvedValue({ status: 'SUCCESS' });
549
- const registry = makeRegistry(successHandler);
550
- // 2-node graph that forms an infinite loop: start → back → start → ...
551
- // (loopRestart: false so cycle detection triggers)
552
- const startNode = makeNode('start');
553
- const backNode = makeNode('back');
554
- const exitNode = makeNode('exit');
555
- const edges = [
556
- makeEdge('start', 'back'),
557
- makeEdge('back', 'start'), // forms cycle WITHOUT loopRestart
558
- ];
559
- const graph = makeGraph([startNode, backNode, exitNode], edges, 'start', 'exit');
560
- const executor = createGraphExecutor();
561
- await expect(executor.run(graph, makeConfig(registry))).rejects.toThrow('Graph cycle detected');
562
- });
563
- });
564
- describe('Edge case: missing target node', () => {
565
- it('throws descriptive error when edge target node is not in graph', async () => {
566
- const successHandler = vi.fn().mockResolvedValue({ status: 'SUCCESS' });
567
- const registry = makeRegistry(successHandler);
568
- // Edge points to a non-existent node
569
- const startNode = makeNode('start');
570
- const exitNode = makeNode('exit');
571
- const edges = [makeEdge('start', 'nonexistent-node')];
572
- const graph = makeGraph([startNode, exitNode], edges, 'start', 'exit');
573
- const executor = createGraphExecutor();
574
- await expect(executor.run(graph, makeConfig(registry))).rejects.toThrow('Edge target node "nonexistent-node" not found in graph');
575
- });
576
- });
577
- describe('Edge case: no outgoing edges returns FAIL', () => {
578
- it('returns FAIL with descriptive failureReason when edge selection returns null', async () => {
579
- const successHandler = vi.fn().mockResolvedValue({ status: 'SUCCESS' });
580
- const registry = makeRegistry(successHandler);
581
- const startNode = makeNode('start');
582
- const exitNode = makeNode('exit');
583
- // No edges from start
584
- const graph = makeGraph([startNode, exitNode], [], 'start', 'exit');
585
- const executor = createGraphExecutor();
586
- const outcome = await executor.run(graph, makeConfig(registry));
587
- expect(outcome.status).toBe('FAIL');
588
- expect(outcome.failureReason).toContain('No outgoing edge from node start');
589
- });
590
- });
591
- describe('Edge case: omitted eventBus is safe (no-op)', () => {
592
- it('runs without errors when eventBus is undefined', async () => {
593
- const successHandler = vi.fn().mockResolvedValue({ status: 'SUCCESS' });
594
- const registry = makeRegistry(successHandler);
595
- const startNode = makeNode('start');
596
- const exitNode = makeNode('exit');
597
- const edges = [makeEdge('start', 'exit')];
598
- const graph = makeGraph([startNode, exitNode], edges, 'start', 'exit');
599
- // No eventBus in config
600
- const config = makeConfig(registry); // eventBus is undefined
601
- const executor = createGraphExecutor();
602
- const outcome = await executor.run(graph, config);
603
- expect(outcome.status).toBe('SUCCESS');
604
- });
605
- });
606
- describe('Context updates: applied after each node', () => {
607
- it('applies outcome.contextUpdates to context and makes them available for edge selection', async () => {
608
- // First node sets context, second reads it (via condition on edge)
609
- let callCount = 0;
610
- const handler = vi.fn().mockImplementation(async () => {
611
- callCount++;
612
- if (callCount === 1) {
613
- return { status: 'SUCCESS', contextUpdates: { myKey: 'myValue' } };
614
- }
615
- return { status: 'SUCCESS' };
616
- });
617
- const registry = makeRegistry(handler);
618
- const startNode = makeNode('start');
619
- const midNode = makeNode('mid');
620
- const exitNode = makeNode('exit');
621
- const edges = [makeEdge('start', 'mid'), makeEdge('mid', 'exit')];
622
- const graph = makeGraph([startNode, midNode, exitNode], edges, 'start', 'exit');
623
- const executor = createGraphExecutor();
624
- const outcome = await executor.run(graph, makeConfig(registry));
625
- expect(outcome.status).toBe('SUCCESS');
626
- expect(callCount).toBe(2);
627
- });
628
- });
629
- // ---------------------------------------------------------------------------
630
- // allowPartial semantics (story 42-16, AC2/AC3)
631
- // ---------------------------------------------------------------------------
632
- // ---------------------------------------------------------------------------
633
- // AC6: RunStateManager integration — graph.dot and per-node artifacts
634
- // ---------------------------------------------------------------------------
635
- describe('AC6: RunStateManager integration — dotSource writes graph.dot and node artifacts', () => {
636
- let tmpDir;
637
- beforeEach(async () => {
638
- tmpDir = await mkdtemp(path.join(os.tmpdir(), 'executor-ac6-test-'));
639
- });
640
- afterEach(async () => {
641
- await rm(tmpDir, { recursive: true, force: true });
642
- });
643
- it('writes graph.dot with dotSource content and per-node status.json for each dispatched node', async () => {
644
- const dotSource = 'digraph G { start -> exit }';
645
- const successHandler = vi.fn().mockResolvedValue({ status: 'SUCCESS' });
646
- const registry = makeRegistry(successHandler);
647
- const startNode = makeNode('start');
648
- const exitNode = makeNode('exit');
649
- const edges = [makeEdge('start', 'exit')];
650
- const graph = makeGraph([startNode, exitNode], edges, 'start', 'exit');
651
- const executor = createGraphExecutor();
652
- const outcome = await executor.run(graph, makeConfig(registry, { logsRoot: tmpDir, dotSource }));
653
- expect(outcome.status).toBe('SUCCESS');
654
- // AC6 behavior 1: RunStateManager instantiated with logsRoot and initRun called
655
- // before main loop → graph.dot written with dotSource content
656
- const graphDotContent = await readFile(path.join(tmpDir, 'graph.dot'), 'utf8');
657
- expect(graphDotContent).toBe(dotSource);
658
- // AC6 behavior 2: writeNodeArtifacts called per completed node → status.json written
659
- // Only 'start' is dispatched (exit terminates the loop without dispatch)
660
- const statusJsonRaw = await readFile(path.join(tmpDir, 'start', 'status.json'), 'utf8');
661
- const statusJson = JSON.parse(statusJsonRaw);
662
- expect(statusJson).toMatchObject({
663
- nodeId: 'start',
664
- status: 'SUCCESS',
665
- });
666
- expect(typeof statusJson['startedAt']).toBe('number');
667
- expect(typeof statusJson['completedAt']).toBe('number');
668
- expect(typeof statusJson['durationMs']).toBe('number');
669
- });
670
- it('does not write graph.dot when dotSource is omitted (backward-compatible)', async () => {
671
- const successHandler = vi.fn().mockResolvedValue({ status: 'SUCCESS' });
672
- const registry = makeRegistry(successHandler);
673
- const startNode = makeNode('start');
674
- const exitNode = makeNode('exit');
675
- const edges = [makeEdge('start', 'exit')];
676
- const graph = makeGraph([startNode, exitNode], edges, 'start', 'exit');
677
- // No dotSource — RunStateManager should not be instantiated
678
- const executor = createGraphExecutor();
679
- const outcome = await executor.run(graph, makeConfig(registry, { logsRoot: tmpDir }));
680
- expect(outcome.status).toBe('SUCCESS');
681
- // graph.dot must NOT exist (RunStateManager was never created)
682
- await expect(readFile(path.join(tmpDir, 'graph.dot'), 'utf8')).rejects.toThrow();
683
- });
684
- });
685
- describe('allowPartial semantics', () => {
686
- it('node with allowPartial=false, handler returns PARTIAL_SUCCESS → executor treats final node outcome as FAILURE (run returns FAIL)', async () => {
687
- // maxRetries=1 exposes a regression where demotion only fires when attempt >= maxRetries:
688
- // PARTIAL_SUCCESS exits the retry loop immediately (it is not a FAIL, so no retries),
689
- // so the handler is called once at attempt=0 (which is < maxRetries=1). A buggy
690
- // implementation conditioned on attempt >= maxRetries would NOT demote and this test
691
- // would fail (outcome would be PARTIAL_SUCCESS, not FAIL).
692
- const workNode = makeNode('work', { allowPartial: false, maxRetries: 1 });
693
- const exitNode = makeNode('exit');
694
- // Include an edge work → exit, but FAIL routing will short-circuit before edge selection.
695
- const edges = [makeEdge('work', 'exit')];
696
- const graph = makeGraph([workNode, exitNode], edges, 'work', 'exit');
697
- const partialHandler = vi.fn().mockResolvedValue({ status: 'PARTIAL_SUCCESS' });
698
- const registry = makeRegistry(partialHandler);
699
- const executor = createGraphExecutor();
700
- const outcome = await executor.run(graph, makeConfig(registry));
701
- // PARTIAL_SUCCESS exits the loop on the first call (no retries triggered for PARTIAL_SUCCESS).
702
- expect(partialHandler).toHaveBeenCalledTimes(1);
703
- // PARTIAL_SUCCESS is demoted to FAIL because allowPartial=false.
704
- expect(outcome.status).toBe('FAIL');
705
- expect(outcome.failureReason).toContain('allowPartial=false');
706
- });
707
- it('node with allowPartial=true, handler returns PARTIAL_SUCCESS → executor accepts PARTIAL_SUCCESS and continues to exit', async () => {
708
- const workNode = makeNode('work', { allowPartial: true });
709
- const exitNode = makeNode('exit');
710
- const edges = [makeEdge('work', 'exit')];
711
- const graph = makeGraph([workNode, exitNode], edges, 'work', 'exit');
712
- const partialHandler = vi.fn().mockResolvedValue({ status: 'PARTIAL_SUCCESS' });
713
- const registry = makeRegistry(partialHandler);
714
- const executor = createGraphExecutor();
715
- const outcome = await executor.run(graph, makeConfig(registry));
716
- // PARTIAL_SUCCESS is accepted (allowPartial=true); execution continues to exit → SUCCESS.
717
- expect(outcome.status).toBe('SUCCESS');
718
- // Verify PARTIAL_SUCCESS travels to ConvergenceController unchanged (not promoted to SUCCESS),
719
- // directly validating AC3: "not promoted to SUCCESS… goal gates receive the PARTIAL_SUCCESS
720
- // status unchanged".
721
- expect(mockRecordOutcome).toHaveBeenCalledWith('work', 'PARTIAL_SUCCESS');
722
- });
723
- it('node with allowPartial=false, handler returns SUCCESS → normal success, unaffected by allowPartial flag', async () => {
724
- const workNode = makeNode('work', { allowPartial: false });
725
- const exitNode = makeNode('exit');
726
- const edges = [makeEdge('work', 'exit')];
727
- const graph = makeGraph([workNode, exitNode], edges, 'work', 'exit');
728
- const successHandler = vi.fn().mockResolvedValue({ status: 'SUCCESS' });
729
- const registry = makeRegistry(successHandler);
730
- const executor = createGraphExecutor();
731
- const outcome = await executor.run(graph, makeConfig(registry));
732
- // SUCCESS is never demoted regardless of allowPartial.
733
- expect(outcome.status).toBe('SUCCESS');
734
- });
735
- });
736
- // ---------------------------------------------------------------------------
737
- // outcome context injection: executor sets 'outcome' key in context
738
- // ---------------------------------------------------------------------------
739
- describe('outcome context injection', () => {
740
- it('sets outcome=success in context after SUCCESS handler, enabling condition="outcome=success" edges', async () => {
741
- // Graph: start → conditional_node → exit (condition="outcome=success")
742
- const startNode = makeNode('start');
743
- const conditionalNode = makeNode('conditional');
744
- const exitNode = makeNode('exit');
745
- const edges = [
746
- makeEdge('start', 'conditional'),
747
- makeEdge('conditional', 'exit', { condition: 'outcome=success' }),
748
- ];
749
- const graph = makeGraph([startNode, conditionalNode, exitNode], edges, 'start', 'exit');
750
- const successHandler = vi.fn().mockResolvedValue({ status: 'SUCCESS' });
751
- const registry = makeRegistry(successHandler);
752
- const executor = createGraphExecutor();
753
- const outcome = await executor.run(graph, makeConfig(registry));
754
- // Edge condition "outcome=success" matches because executor sets context.outcome = "success"
755
- expect(outcome.status).toBe('SUCCESS');
756
- });
757
- it('sets outcome=fail in context after FAIL handler, enabling condition="outcome=fail" edges', async () => {
758
- // Graph: start → conditional_node → retry_node (condition="outcome=fail") → exit
759
- const startNode = makeNode('start');
760
- const conditionalNode = makeNode('conditional');
761
- const retryNode = makeNode('retry');
762
- const exitNode = makeNode('exit');
763
- const edges = [
764
- makeEdge('start', 'conditional'),
765
- makeEdge('conditional', 'retry', { condition: 'outcome=fail' }),
766
- makeEdge('conditional', 'exit', { condition: 'outcome=success' }),
767
- makeEdge('retry', 'exit'),
768
- ];
769
- const graph = makeGraph([startNode, conditionalNode, retryNode, exitNode], edges, 'start', 'exit');
770
- let callCount = 0;
771
- const handler = vi.fn().mockImplementation(async () => {
772
- callCount++;
773
- // First call (start): SUCCESS. Second call (conditional): FAIL. Third (retry): SUCCESS.
774
- if (callCount === 2)
775
- return { status: 'FAILURE' };
776
- return { status: 'SUCCESS' };
777
- });
778
- const registry = makeRegistry(handler);
779
- const executor = createGraphExecutor();
780
- const outcome = await executor.run(graph, makeConfig(registry));
781
- // conditional returned FAIL → outcome=fail edge routes to retry → retry SUCCESS → exit
782
- expect(outcome.status).toBe('SUCCESS');
783
- expect(handler).toHaveBeenCalledTimes(3); // start, conditional, retry
784
- });
785
- });
786
- // ---------------------------------------------------------------------------
787
- // FAIL routing: conditional edge fallthrough
788
- // ---------------------------------------------------------------------------
789
- describe('FAIL routing conditional edge fallthrough', () => {
790
- it('returns FAIL immediately when node has no conditional outgoing edges', async () => {
791
- const startNode = makeNode('start');
792
- const exitNode = makeNode('exit');
793
- const edges = [makeEdge('start', 'exit')];
794
- const graph = makeGraph([startNode, exitNode], edges, 'start', 'exit');
795
- const failHandler = vi.fn().mockResolvedValue({ status: 'FAILURE', failureReason: 'test fail' });
796
- const registry = makeRegistry(failHandler);
797
- const executor = createGraphExecutor();
798
- const outcome = await executor.run(graph, makeConfig(registry));
799
- expect(outcome.status).toBe('FAIL');
800
- });
801
- it('falls through to edge selection when node has conditional edges', async () => {
802
- // Graph: start → conditional (FAIL) → fallback (condition=outcome=fail) → exit
803
- const startNode = makeNode('start');
804
- const conditionalNode = makeNode('conditional');
805
- const fallbackNode = makeNode('fallback');
806
- const exitNode = makeNode('exit');
807
- const edges = [
808
- makeEdge('start', 'conditional'),
809
- makeEdge('conditional', 'fallback', { condition: 'outcome=fail' }),
810
- makeEdge('conditional', 'exit', { condition: 'outcome=success' }),
811
- makeEdge('fallback', 'exit'),
812
- ];
813
- const graph = makeGraph([startNode, conditionalNode, fallbackNode, exitNode], edges, 'start', 'exit');
814
- let callCount = 0;
815
- const handler = vi.fn().mockImplementation(async () => {
816
- callCount++;
817
- if (callCount === 2)
818
- return { status: 'FAILURE', failureReason: 'needs fixes' };
819
- return { status: 'SUCCESS' };
820
- });
821
- const registry = makeRegistry(handler);
822
- const executor = createGraphExecutor();
823
- const outcome = await executor.run(graph, makeConfig(registry));
824
- // conditional FAIL → falls through to edge selection → outcome=fail → fallback → exit
825
- expect(outcome.status).toBe('SUCCESS');
826
- });
827
- it('failRouteCount caps at defaultMaxRetries and returns FAIL', async () => {
828
- // Graph with a loop: start → conditional (always FAIL) → conditional (condition=outcome=fail)
829
- const startNode = makeNode('start');
830
- const conditionalNode = makeNode('conditional');
831
- const exitNode = makeNode('exit');
832
- const edges = [
833
- makeEdge('start', 'conditional'),
834
- makeEdge('conditional', 'conditional', { condition: 'outcome=fail' }),
835
- makeEdge('conditional', 'exit', { condition: 'outcome=success' }),
836
- ];
837
- // defaultMaxRetries=2 → failRouteCount cap = max(2, 3) = 3
838
- const graph = makeGraph([startNode, conditionalNode, exitNode], edges, 'start', 'exit');
839
- graph.defaultMaxRetries = 2;
840
- let callCount = 0;
841
- const handler = vi.fn().mockImplementation(async () => {
842
- callCount++;
843
- // start (call 1) → SUCCESS, conditional (all subsequent) → FAILURE
844
- if (callCount === 1)
845
- return { status: 'SUCCESS' };
846
- return { status: 'FAILURE', failureReason: 'always fails' };
847
- });
848
- const registry = makeRegistry(handler);
849
- const executor = createGraphExecutor();
850
- const outcome = await executor.run(graph, makeConfig(registry));
851
- // Should return FAIL after exhausting fail route count (cap = max(2, 3) = 3)
852
- expect(outcome.status).toBe('FAIL');
853
- // start(1) + conditional initial(1) + 3 conditional retries via edge = 5
854
- expect(handler).toHaveBeenCalledTimes(5);
855
- });
856
- });
857
- // ---------------------------------------------------------------------------
858
- // Story 49-3: Auto-summarizer convergence integration
859
- // ---------------------------------------------------------------------------
860
- describe('auto-summarizer convergence integration (story 49-3)', () => {
861
- beforeEach(() => {
862
- vi.clearAllMocks();
863
- mockSave.mockResolvedValue(undefined);
864
- mockEvaluateGates.mockReturnValue({ satisfied: true, failingNodes: [] });
865
- mockCheckGoalGates.mockReturnValue({ satisfied: true, failedGates: [] });
866
- mockResolveRetryTarget.mockReturnValue(null);
867
- });
868
- it('recordIterationContext is called after each handler dispatch', async () => {
869
- const handler = vi.fn().mockResolvedValue({ status: 'SUCCESS' });
870
- const registry = makeRegistry(handler);
871
- const graph = makeGraph([makeNode('start'), makeNode('mid'), makeNode('end')], [makeEdge('start', 'mid'), makeEdge('mid', 'end')], 'start', 'end');
872
- const executor = createGraphExecutor();
873
- const outcome = await executor.run(graph, makeConfig(registry));
874
- expect(outcome.status).toBe('SUCCESS');
875
- // recordIterationContext is called after each dispatched handler.
876
- // The exit node completes the loop, so the count depends on how many
877
- // non-exit nodes get dispatched (start, mid dispatched; end exits).
878
- expect(mockRecordIterationContext).toHaveBeenCalled();
879
- // Each call should include an index and content string
880
- expect(mockRecordIterationContext).toHaveBeenCalledWith(expect.objectContaining({ index: expect.any(Number), content: expect.any(String) }));
881
- });
882
- it('prepareForIteration is called before retry dispatch in convergence loop', async () => {
883
- // First call: goal gate unsatisfied → trigger convergence retry
884
- // Second call: goal gate satisfied → exit successfully
885
- mockCheckGoalGates
886
- .mockReturnValueOnce({ satisfied: false, failedGates: ['end'] })
887
- .mockReturnValueOnce({ satisfied: true, failedGates: [] });
888
- mockResolveRetryTarget.mockReturnValue('start');
889
- const handler = vi.fn().mockResolvedValue({ status: 'SUCCESS' });
890
- const registry = makeRegistry(handler);
891
- const graph = makeGraph([makeNode('start'), makeNode('end', { goalGate: true })], [makeEdge('start', 'end')], 'start', 'end');
892
- const executor = createGraphExecutor();
893
- const outcome = await executor.run(graph, makeConfig(registry));
894
- expect(outcome.status).toBe('SUCCESS');
895
- // prepareForIteration should be called at least once (before the retry)
896
- expect(mockPrepareForIteration).toHaveBeenCalled();
897
- // The convergence iteration index should be passed
898
- expect(mockPrepareForIteration).toHaveBeenCalledWith(expect.any(Number));
899
- });
900
- });
901
- //# sourceMappingURL=executor.test.js.map