@substrate-ai/factory 0.20.1 → 0.20.3

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 (486) hide show
  1. package/README.md +39 -0
  2. package/package.json +6 -3
  3. package/dist/__tests__/config.test.d.ts +0 -11
  4. package/dist/__tests__/config.test.d.ts.map +0 -1
  5. package/dist/__tests__/config.test.js +0 -215
  6. package/dist/__tests__/config.test.js.map +0 -1
  7. package/dist/__tests__/factory-run-command.test.d.ts +0 -12
  8. package/dist/__tests__/factory-run-command.test.d.ts.map +0 -1
  9. package/dist/__tests__/factory-run-command.test.js +0 -454
  10. package/dist/__tests__/factory-run-command.test.js.map +0 -1
  11. package/dist/__tests__/factory-validate-command.test.d.ts +0 -15
  12. package/dist/__tests__/factory-validate-command.test.d.ts.map +0 -1
  13. package/dist/__tests__/factory-validate-command.test.js +0 -339
  14. package/dist/__tests__/factory-validate-command.test.js.map +0 -1
  15. package/dist/__tests__/fixtures/advanced-cross-project-validation.dot.d.ts +0 -72
  16. package/dist/__tests__/fixtures/advanced-cross-project-validation.dot.d.ts.map +0 -1
  17. package/dist/__tests__/fixtures/advanced-cross-project-validation.dot.js +0 -121
  18. package/dist/__tests__/fixtures/advanced-cross-project-validation.dot.js.map +0 -1
  19. package/dist/__tests__/fixtures/llm-edge-routing.dot.d.ts +0 -28
  20. package/dist/__tests__/fixtures/llm-edge-routing.dot.d.ts.map +0 -1
  21. package/dist/__tests__/fixtures/llm-edge-routing.dot.js +0 -55
  22. package/dist/__tests__/fixtures/llm-edge-routing.dot.js.map +0 -1
  23. package/dist/__tests__/fixtures/manager-loop.dot.d.ts +0 -34
  24. package/dist/__tests__/fixtures/manager-loop.dot.d.ts.map +0 -1
  25. package/dist/__tests__/fixtures/manager-loop.dot.js +0 -61
  26. package/dist/__tests__/fixtures/manager-loop.dot.js.map +0 -1
  27. package/dist/__tests__/fixtures/parallel-fan-out-fan-in.dot.d.ts +0 -42
  28. package/dist/__tests__/fixtures/parallel-fan-out-fan-in.dot.d.ts.map +0 -1
  29. package/dist/__tests__/fixtures/parallel-fan-out-fan-in.dot.js +0 -118
  30. package/dist/__tests__/fixtures/parallel-fan-out-fan-in.dot.js.map +0 -1
  31. package/dist/__tests__/fixtures/subgraph-parent.dot.d.ts +0 -35
  32. package/dist/__tests__/fixtures/subgraph-parent.dot.d.ts.map +0 -1
  33. package/dist/__tests__/fixtures/subgraph-parent.dot.js +0 -69
  34. package/dist/__tests__/fixtures/subgraph-parent.dot.js.map +0 -1
  35. package/dist/__tests__/integration/advanced-graph-events.test.d.ts +0 -19
  36. package/dist/__tests__/integration/advanced-graph-events.test.d.ts.map +0 -1
  37. package/dist/__tests__/integration/advanced-graph-events.test.js +0 -288
  38. package/dist/__tests__/integration/advanced-graph-events.test.js.map +0 -1
  39. package/dist/__tests__/integration/checkpoint-resume.test.d.ts +0 -10
  40. package/dist/__tests__/integration/checkpoint-resume.test.d.ts.map +0 -1
  41. package/dist/__tests__/integration/checkpoint-resume.test.js +0 -125
  42. package/dist/__tests__/integration/checkpoint-resume.test.js.map +0 -1
  43. package/dist/__tests__/integration/conditional-pipeline.test.d.ts +0 -10
  44. package/dist/__tests__/integration/conditional-pipeline.test.d.ts.map +0 -1
  45. package/dist/__tests__/integration/conditional-pipeline.test.js +0 -106
  46. package/dist/__tests__/integration/conditional-pipeline.test.js.map +0 -1
  47. package/dist/__tests__/integration/convergence-validation.test.d.ts +0 -14
  48. package/dist/__tests__/integration/convergence-validation.test.d.ts.map +0 -1
  49. package/dist/__tests__/integration/convergence-validation.test.js +0 -449
  50. package/dist/__tests__/integration/convergence-validation.test.js.map +0 -1
  51. package/dist/__tests__/integration/epic44-coverage-gate.test.d.ts +0 -12
  52. package/dist/__tests__/integration/epic44-coverage-gate.test.d.ts.map +0 -1
  53. package/dist/__tests__/integration/epic44-coverage-gate.test.js +0 -58
  54. package/dist/__tests__/integration/epic44-coverage-gate.test.js.map +0 -1
  55. package/dist/__tests__/integration/epic45-coverage-gate.test.d.ts +0 -11
  56. package/dist/__tests__/integration/epic45-coverage-gate.test.d.ts.map +0 -1
  57. package/dist/__tests__/integration/epic45-coverage-gate.test.js +0 -64
  58. package/dist/__tests__/integration/epic45-coverage-gate.test.js.map +0 -1
  59. package/dist/__tests__/integration/epic46-scenario-primary-executor.test.d.ts +0 -2
  60. package/dist/__tests__/integration/epic46-scenario-primary-executor.test.d.ts.map +0 -1
  61. package/dist/__tests__/integration/epic46-scenario-primary-executor.test.js +0 -285
  62. package/dist/__tests__/integration/epic46-scenario-primary-executor.test.js.map +0 -1
  63. package/dist/__tests__/integration/events.test.d.ts +0 -8
  64. package/dist/__tests__/integration/events.test.d.ts.map +0 -1
  65. package/dist/__tests__/integration/events.test.js +0 -194
  66. package/dist/__tests__/integration/events.test.js.map +0 -1
  67. package/dist/__tests__/integration/graphs.d.ts +0 -59
  68. package/dist/__tests__/integration/graphs.d.ts.map +0 -1
  69. package/dist/__tests__/integration/graphs.js +0 -164
  70. package/dist/__tests__/integration/graphs.js.map +0 -1
  71. package/dist/__tests__/integration/helpers.d.ts +0 -127
  72. package/dist/__tests__/integration/helpers.d.ts.map +0 -1
  73. package/dist/__tests__/integration/helpers.js +0 -167
  74. package/dist/__tests__/integration/helpers.js.map +0 -1
  75. package/dist/__tests__/integration/integrity.test.d.ts +0 -8
  76. package/dist/__tests__/integration/integrity.test.d.ts.map +0 -1
  77. package/dist/__tests__/integration/integrity.test.js +0 -198
  78. package/dist/__tests__/integration/integrity.test.js.map +0 -1
  79. package/dist/__tests__/integration/llm-edge-routing.test.d.ts +0 -21
  80. package/dist/__tests__/integration/llm-edge-routing.test.d.ts.map +0 -1
  81. package/dist/__tests__/integration/llm-edge-routing.test.js +0 -341
  82. package/dist/__tests__/integration/llm-edge-routing.test.js.map +0 -1
  83. package/dist/__tests__/integration/manager-loop.test.d.ts +0 -24
  84. package/dist/__tests__/integration/manager-loop.test.d.ts.map +0 -1
  85. package/dist/__tests__/integration/manager-loop.test.js +0 -276
  86. package/dist/__tests__/integration/manager-loop.test.js.map +0 -1
  87. package/dist/__tests__/integration/multi-type-graph.test.d.ts +0 -10
  88. package/dist/__tests__/integration/multi-type-graph.test.d.ts.map +0 -1
  89. package/dist/__tests__/integration/multi-type-graph.test.js +0 -100
  90. package/dist/__tests__/integration/multi-type-graph.test.js.map +0 -1
  91. package/dist/__tests__/integration/parallel-fan-out-fan-in.test.d.ts +0 -22
  92. package/dist/__tests__/integration/parallel-fan-out-fan-in.test.d.ts.map +0 -1
  93. package/dist/__tests__/integration/parallel-fan-out-fan-in.test.js +0 -515
  94. package/dist/__tests__/integration/parallel-fan-out-fan-in.test.js.map +0 -1
  95. package/dist/__tests__/integration/persistence.test.d.ts +0 -8
  96. package/dist/__tests__/integration/persistence.test.d.ts.map +0 -1
  97. package/dist/__tests__/integration/persistence.test.js +0 -129
  98. package/dist/__tests__/integration/persistence.test.js.map +0 -1
  99. package/dist/__tests__/integration/pipeline-templates-integration.test.d.ts +0 -16
  100. package/dist/__tests__/integration/pipeline-templates-integration.test.d.ts.map +0 -1
  101. package/dist/__tests__/integration/pipeline-templates-integration.test.js +0 -171
  102. package/dist/__tests__/integration/pipeline-templates-integration.test.js.map +0 -1
  103. package/dist/__tests__/integration/scenario-pipeline.test.d.ts +0 -11
  104. package/dist/__tests__/integration/scenario-pipeline.test.d.ts.map +0 -1
  105. package/dist/__tests__/integration/scenario-pipeline.test.js +0 -243
  106. package/dist/__tests__/integration/scenario-pipeline.test.js.map +0 -1
  107. package/dist/__tests__/integration/stylesheet-application.test.d.ts +0 -12
  108. package/dist/__tests__/integration/stylesheet-application.test.d.ts.map +0 -1
  109. package/dist/__tests__/integration/stylesheet-application.test.js +0 -119
  110. package/dist/__tests__/integration/stylesheet-application.test.js.map +0 -1
  111. package/dist/__tests__/integration/subgraph-execution.test.d.ts +0 -24
  112. package/dist/__tests__/integration/subgraph-execution.test.d.ts.map +0 -1
  113. package/dist/__tests__/integration/subgraph-execution.test.js +0 -291
  114. package/dist/__tests__/integration/subgraph-execution.test.js.map +0 -1
  115. package/dist/__tests__/integration/validation-errors.test.d.ts +0 -8
  116. package/dist/__tests__/integration/validation-errors.test.d.ts.map +0 -1
  117. package/dist/__tests__/integration/validation-errors.test.js +0 -150
  118. package/dist/__tests__/integration/validation-errors.test.js.map +0 -1
  119. package/dist/agent/__tests__/loop-detection.test.d.ts +0 -2
  120. package/dist/agent/__tests__/loop-detection.test.d.ts.map +0 -1
  121. package/dist/agent/__tests__/loop-detection.test.js +0 -236
  122. package/dist/agent/__tests__/loop-detection.test.js.map +0 -1
  123. package/dist/agent/__tests__/loop.test.d.ts +0 -2
  124. package/dist/agent/__tests__/loop.test.d.ts.map +0 -1
  125. package/dist/agent/__tests__/loop.test.js +0 -868
  126. package/dist/agent/__tests__/loop.test.js.map +0 -1
  127. package/dist/agent/__tests__/truncation.test.d.ts +0 -2
  128. package/dist/agent/__tests__/truncation.test.d.ts.map +0 -1
  129. package/dist/agent/__tests__/truncation.test.js +0 -276
  130. package/dist/agent/__tests__/truncation.test.js.map +0 -1
  131. package/dist/agent/tools/__tests__/anthropic-tools.test.d.ts +0 -6
  132. package/dist/agent/tools/__tests__/anthropic-tools.test.d.ts.map +0 -1
  133. package/dist/agent/tools/__tests__/anthropic-tools.test.js +0 -49
  134. package/dist/agent/tools/__tests__/anthropic-tools.test.js.map +0 -1
  135. package/dist/agent/tools/__tests__/environment.test.d.ts +0 -6
  136. package/dist/agent/tools/__tests__/environment.test.d.ts.map +0 -1
  137. package/dist/agent/tools/__tests__/environment.test.js +0 -33
  138. package/dist/agent/tools/__tests__/environment.test.js.map +0 -1
  139. package/dist/agent/tools/__tests__/gemini-tools.test.d.ts +0 -6
  140. package/dist/agent/tools/__tests__/gemini-tools.test.d.ts.map +0 -1
  141. package/dist/agent/tools/__tests__/gemini-tools.test.js +0 -98
  142. package/dist/agent/tools/__tests__/gemini-tools.test.js.map +0 -1
  143. package/dist/agent/tools/__tests__/openai-tools.test.d.ts +0 -6
  144. package/dist/agent/tools/__tests__/openai-tools.test.d.ts.map +0 -1
  145. package/dist/agent/tools/__tests__/openai-tools.test.js +0 -53
  146. package/dist/agent/tools/__tests__/openai-tools.test.js.map +0 -1
  147. package/dist/agent/tools/__tests__/patch.test.d.ts +0 -6
  148. package/dist/agent/tools/__tests__/patch.test.d.ts.map +0 -1
  149. package/dist/agent/tools/__tests__/patch.test.js +0 -116
  150. package/dist/agent/tools/__tests__/patch.test.js.map +0 -1
  151. package/dist/agent/tools/__tests__/profiles.test.d.ts +0 -6
  152. package/dist/agent/tools/__tests__/profiles.test.d.ts.map +0 -1
  153. package/dist/agent/tools/__tests__/profiles.test.js +0 -125
  154. package/dist/agent/tools/__tests__/profiles.test.js.map +0 -1
  155. package/dist/agent/tools/__tests__/registry.test.d.ts +0 -6
  156. package/dist/agent/tools/__tests__/registry.test.d.ts.map +0 -1
  157. package/dist/agent/tools/__tests__/registry.test.js +0 -94
  158. package/dist/agent/tools/__tests__/registry.test.js.map +0 -1
  159. package/dist/agent/tools/__tests__/shared.test.d.ts +0 -6
  160. package/dist/agent/tools/__tests__/shared.test.d.ts.map +0 -1
  161. package/dist/agent/tools/__tests__/shared.test.js +0 -131
  162. package/dist/agent/tools/__tests__/shared.test.js.map +0 -1
  163. package/dist/backend/__tests__/direct-backend.test.d.ts +0 -14
  164. package/dist/backend/__tests__/direct-backend.test.d.ts.map +0 -1
  165. package/dist/backend/__tests__/direct-backend.test.js +0 -393
  166. package/dist/backend/__tests__/direct-backend.test.js.map +0 -1
  167. package/dist/backend/__tests__/direct-bootstrap.test.d.ts +0 -7
  168. package/dist/backend/__tests__/direct-bootstrap.test.d.ts.map +0 -1
  169. package/dist/backend/__tests__/direct-bootstrap.test.js +0 -177
  170. package/dist/backend/__tests__/direct-bootstrap.test.js.map +0 -1
  171. package/dist/backend/__tests__/mock-backend.test.d.ts +0 -7
  172. package/dist/backend/__tests__/mock-backend.test.d.ts.map +0 -1
  173. package/dist/backend/__tests__/mock-backend.test.js +0 -273
  174. package/dist/backend/__tests__/mock-backend.test.js.map +0 -1
  175. package/dist/backend/__tests__/parity.test.d.ts +0 -17
  176. package/dist/backend/__tests__/parity.test.d.ts.map +0 -1
  177. package/dist/backend/__tests__/parity.test.js +0 -411
  178. package/dist/backend/__tests__/parity.test.js.map +0 -1
  179. package/dist/context/__tests__/auto-summarizer.test.d.ts +0 -14
  180. package/dist/context/__tests__/auto-summarizer.test.d.ts.map +0 -1
  181. package/dist/context/__tests__/auto-summarizer.test.js +0 -189
  182. package/dist/context/__tests__/auto-summarizer.test.js.map +0 -1
  183. package/dist/context/__tests__/context-cli-command.test.d.ts +0 -7
  184. package/dist/context/__tests__/context-cli-command.test.d.ts.map +0 -1
  185. package/dist/context/__tests__/context-cli-command.test.js +0 -331
  186. package/dist/context/__tests__/context-cli-command.test.js.map +0 -1
  187. package/dist/context/__tests__/pyramid-summary-integration.test.d.ts +0 -2
  188. package/dist/context/__tests__/pyramid-summary-integration.test.d.ts.map +0 -1
  189. package/dist/context/__tests__/pyramid-summary-integration.test.js +0 -533
  190. package/dist/context/__tests__/pyramid-summary-integration.test.js.map +0 -1
  191. package/dist/context/__tests__/summarizer.test.d.ts +0 -2
  192. package/dist/context/__tests__/summarizer.test.d.ts.map +0 -1
  193. package/dist/context/__tests__/summarizer.test.js +0 -189
  194. package/dist/context/__tests__/summarizer.test.js.map +0 -1
  195. package/dist/context/__tests__/summary-cache.test.d.ts +0 -2
  196. package/dist/context/__tests__/summary-cache.test.d.ts.map +0 -1
  197. package/dist/context/__tests__/summary-cache.test.js +0 -214
  198. package/dist/context/__tests__/summary-cache.test.js.map +0 -1
  199. package/dist/context/__tests__/summary-metrics.test.d.ts +0 -2
  200. package/dist/context/__tests__/summary-metrics.test.d.ts.map +0 -1
  201. package/dist/context/__tests__/summary-metrics.test.js +0 -172
  202. package/dist/context/__tests__/summary-metrics.test.js.map +0 -1
  203. package/dist/context/__tests__/summary-types.test.d.ts +0 -2
  204. package/dist/context/__tests__/summary-types.test.d.ts.map +0 -1
  205. package/dist/context/__tests__/summary-types.test.js +0 -130
  206. package/dist/context/__tests__/summary-types.test.js.map +0 -1
  207. package/dist/convergence/__tests__/budget.test.d.ts +0 -6
  208. package/dist/convergence/__tests__/budget.test.d.ts.map +0 -1
  209. package/dist/convergence/__tests__/budget.test.js +0 -187
  210. package/dist/convergence/__tests__/budget.test.js.map +0 -1
  211. package/dist/convergence/__tests__/controller.test.d.ts +0 -9
  212. package/dist/convergence/__tests__/controller.test.d.ts.map +0 -1
  213. package/dist/convergence/__tests__/controller.test.js +0 -585
  214. package/dist/convergence/__tests__/controller.test.js.map +0 -1
  215. package/dist/convergence/__tests__/dual-signal.test.d.ts +0 -14
  216. package/dist/convergence/__tests__/dual-signal.test.d.ts.map +0 -1
  217. package/dist/convergence/__tests__/dual-signal.test.js +0 -123
  218. package/dist/convergence/__tests__/dual-signal.test.js.map +0 -1
  219. package/dist/convergence/__tests__/epic46-integration.test.d.ts +0 -15
  220. package/dist/convergence/__tests__/epic46-integration.test.d.ts.map +0 -1
  221. package/dist/convergence/__tests__/epic46-integration.test.js +0 -522
  222. package/dist/convergence/__tests__/epic46-integration.test.js.map +0 -1
  223. package/dist/convergence/__tests__/plateau.test.d.ts +0 -6
  224. package/dist/convergence/__tests__/plateau.test.d.ts.map +0 -1
  225. package/dist/convergence/__tests__/plateau.test.js +0 -163
  226. package/dist/convergence/__tests__/plateau.test.js.map +0 -1
  227. package/dist/convergence/__tests__/remediation.test.d.ts +0 -11
  228. package/dist/convergence/__tests__/remediation.test.d.ts.map +0 -1
  229. package/dist/convergence/__tests__/remediation.test.js +0 -209
  230. package/dist/convergence/__tests__/remediation.test.js.map +0 -1
  231. package/dist/convergence/__tests__/scenario-primary.test.d.ts +0 -13
  232. package/dist/convergence/__tests__/scenario-primary.test.d.ts.map +0 -1
  233. package/dist/convergence/__tests__/scenario-primary.test.js +0 -183
  234. package/dist/convergence/__tests__/scenario-primary.test.js.map +0 -1
  235. package/dist/factory-command.test.d.ts +0 -8
  236. package/dist/factory-command.test.d.ts.map +0 -1
  237. package/dist/factory-command.test.js +0 -304
  238. package/dist/factory-command.test.js.map +0 -1
  239. package/dist/graph/__tests__/attractor-compliance.test.d.ts +0 -10
  240. package/dist/graph/__tests__/attractor-compliance.test.d.ts.map +0 -1
  241. package/dist/graph/__tests__/attractor-compliance.test.js +0 -766
  242. package/dist/graph/__tests__/attractor-compliance.test.js.map +0 -1
  243. package/dist/graph/__tests__/checkpoint.test.d.ts +0 -8
  244. package/dist/graph/__tests__/checkpoint.test.d.ts.map +0 -1
  245. package/dist/graph/__tests__/checkpoint.test.js +0 -329
  246. package/dist/graph/__tests__/checkpoint.test.js.map +0 -1
  247. package/dist/graph/__tests__/condition-parser.test.d.ts +0 -14
  248. package/dist/graph/__tests__/condition-parser.test.d.ts.map +0 -1
  249. package/dist/graph/__tests__/condition-parser.test.js +0 -406
  250. package/dist/graph/__tests__/condition-parser.test.js.map +0 -1
  251. package/dist/graph/__tests__/context.test.d.ts +0 -14
  252. package/dist/graph/__tests__/context.test.d.ts.map +0 -1
  253. package/dist/graph/__tests__/context.test.js +0 -276
  254. package/dist/graph/__tests__/context.test.js.map +0 -1
  255. package/dist/graph/__tests__/edge-selector-events.test.d.ts +0 -11
  256. package/dist/graph/__tests__/edge-selector-events.test.d.ts.map +0 -1
  257. package/dist/graph/__tests__/edge-selector-events.test.js +0 -184
  258. package/dist/graph/__tests__/edge-selector-events.test.js.map +0 -1
  259. package/dist/graph/__tests__/edge-selector.test.d.ts +0 -6
  260. package/dist/graph/__tests__/edge-selector.test.d.ts.map +0 -1
  261. package/dist/graph/__tests__/edge-selector.test.js +0 -452
  262. package/dist/graph/__tests__/edge-selector.test.js.map +0 -1
  263. package/dist/graph/__tests__/executor-convergence.test.d.ts +0 -12
  264. package/dist/graph/__tests__/executor-convergence.test.d.ts.map +0 -1
  265. package/dist/graph/__tests__/executor-convergence.test.js +0 -432
  266. package/dist/graph/__tests__/executor-convergence.test.js.map +0 -1
  267. package/dist/graph/__tests__/executor-fidelity.test.d.ts +0 -13
  268. package/dist/graph/__tests__/executor-fidelity.test.d.ts.map +0 -1
  269. package/dist/graph/__tests__/executor-fidelity.test.js +0 -335
  270. package/dist/graph/__tests__/executor-fidelity.test.js.map +0 -1
  271. package/dist/graph/__tests__/executor.test.d.ts +0 -14
  272. package/dist/graph/__tests__/executor.test.d.ts.map +0 -1
  273. package/dist/graph/__tests__/executor.test.js +0 -901
  274. package/dist/graph/__tests__/executor.test.js.map +0 -1
  275. package/dist/graph/__tests__/fidelity.test.d.ts +0 -8
  276. package/dist/graph/__tests__/fidelity.test.d.ts.map +0 -1
  277. package/dist/graph/__tests__/fidelity.test.js +0 -135
  278. package/dist/graph/__tests__/fidelity.test.js.map +0 -1
  279. package/dist/graph/__tests__/llm-evaluator.test.d.ts +0 -7
  280. package/dist/graph/__tests__/llm-evaluator.test.d.ts.map +0 -1
  281. package/dist/graph/__tests__/llm-evaluator.test.js +0 -106
  282. package/dist/graph/__tests__/llm-evaluator.test.js.map +0 -1
  283. package/dist/graph/__tests__/parser-chaining.test.d.ts +0 -13
  284. package/dist/graph/__tests__/parser-chaining.test.d.ts.map +0 -1
  285. package/dist/graph/__tests__/parser-chaining.test.js +0 -215
  286. package/dist/graph/__tests__/parser-chaining.test.js.map +0 -1
  287. package/dist/graph/__tests__/parser.test.d.ts +0 -22
  288. package/dist/graph/__tests__/parser.test.d.ts.map +0 -1
  289. package/dist/graph/__tests__/parser.test.js +0 -452
  290. package/dist/graph/__tests__/parser.test.js.map +0 -1
  291. package/dist/graph/__tests__/run-state.test.d.ts +0 -13
  292. package/dist/graph/__tests__/run-state.test.d.ts.map +0 -1
  293. package/dist/graph/__tests__/run-state.test.js +0 -189
  294. package/dist/graph/__tests__/run-state.test.js.map +0 -1
  295. package/dist/graph/__tests__/transformer.test.d.ts +0 -16
  296. package/dist/graph/__tests__/transformer.test.d.ts.map +0 -1
  297. package/dist/graph/__tests__/transformer.test.js +0 -350
  298. package/dist/graph/__tests__/transformer.test.js.map +0 -1
  299. package/dist/graph/__tests__/validator-errors.test.d.ts +0 -15
  300. package/dist/graph/__tests__/validator-errors.test.d.ts.map +0 -1
  301. package/dist/graph/__tests__/validator-errors.test.js +0 -572
  302. package/dist/graph/__tests__/validator-errors.test.js.map +0 -1
  303. package/dist/graph/__tests__/validator-warnings.test.d.ts +0 -15
  304. package/dist/graph/__tests__/validator-warnings.test.d.ts.map +0 -1
  305. package/dist/graph/__tests__/validator-warnings.test.js +0 -363
  306. package/dist/graph/__tests__/validator-warnings.test.js.map +0 -1
  307. package/dist/handlers/__tests__/codergen-handler.test.d.ts +0 -14
  308. package/dist/handlers/__tests__/codergen-handler.test.d.ts.map +0 -1
  309. package/dist/handlers/__tests__/codergen-handler.test.js +0 -442
  310. package/dist/handlers/__tests__/codergen-handler.test.js.map +0 -1
  311. package/dist/handlers/__tests__/fan-in.test.d.ts +0 -14
  312. package/dist/handlers/__tests__/fan-in.test.d.ts.map +0 -1
  313. package/dist/handlers/__tests__/fan-in.test.js +0 -399
  314. package/dist/handlers/__tests__/fan-in.test.js.map +0 -1
  315. package/dist/handlers/__tests__/join-policy.test.d.ts +0 -9
  316. package/dist/handlers/__tests__/join-policy.test.d.ts.map +0 -1
  317. package/dist/handlers/__tests__/join-policy.test.js +0 -201
  318. package/dist/handlers/__tests__/join-policy.test.js.map +0 -1
  319. package/dist/handlers/__tests__/manager-loop.test.d.ts +0 -14
  320. package/dist/handlers/__tests__/manager-loop.test.d.ts.map +0 -1
  321. package/dist/handlers/__tests__/manager-loop.test.js +0 -322
  322. package/dist/handlers/__tests__/manager-loop.test.js.map +0 -1
  323. package/dist/handlers/__tests__/parallel-events.test.d.ts +0 -12
  324. package/dist/handlers/__tests__/parallel-events.test.d.ts.map +0 -1
  325. package/dist/handlers/__tests__/parallel-events.test.js +0 -252
  326. package/dist/handlers/__tests__/parallel-events.test.js.map +0 -1
  327. package/dist/handlers/__tests__/parallel-handler.test.d.ts +0 -14
  328. package/dist/handlers/__tests__/parallel-handler.test.d.ts.map +0 -1
  329. package/dist/handlers/__tests__/parallel-handler.test.js +0 -337
  330. package/dist/handlers/__tests__/parallel-handler.test.js.map +0 -1
  331. package/dist/handlers/__tests__/parallel-join.test.d.ts +0 -9
  332. package/dist/handlers/__tests__/parallel-join.test.d.ts.map +0 -1
  333. package/dist/handlers/__tests__/parallel-join.test.js +0 -267
  334. package/dist/handlers/__tests__/parallel-join.test.js.map +0 -1
  335. package/dist/handlers/__tests__/registry.test.d.ts +0 -14
  336. package/dist/handlers/__tests__/registry.test.d.ts.map +0 -1
  337. package/dist/handlers/__tests__/registry.test.js +0 -315
  338. package/dist/handlers/__tests__/registry.test.js.map +0 -1
  339. package/dist/handlers/__tests__/subgraph-events.test.d.ts +0 -10
  340. package/dist/handlers/__tests__/subgraph-events.test.d.ts.map +0 -1
  341. package/dist/handlers/__tests__/subgraph-events.test.js +0 -189
  342. package/dist/handlers/__tests__/subgraph-events.test.js.map +0 -1
  343. package/dist/handlers/__tests__/subgraph-inheritance.test.d.ts +0 -14
  344. package/dist/handlers/__tests__/subgraph-inheritance.test.d.ts.map +0 -1
  345. package/dist/handlers/__tests__/subgraph-inheritance.test.js +0 -267
  346. package/dist/handlers/__tests__/subgraph-inheritance.test.js.map +0 -1
  347. package/dist/handlers/__tests__/subgraph.test.d.ts +0 -14
  348. package/dist/handlers/__tests__/subgraph.test.d.ts.map +0 -1
  349. package/dist/handlers/__tests__/subgraph.test.js +0 -369
  350. package/dist/handlers/__tests__/subgraph.test.js.map +0 -1
  351. package/dist/handlers/__tests__/tool-handler.test.d.ts +0 -11
  352. package/dist/handlers/__tests__/tool-handler.test.d.ts.map +0 -1
  353. package/dist/handlers/__tests__/tool-handler.test.js +0 -184
  354. package/dist/handlers/__tests__/tool-handler.test.js.map +0 -1
  355. package/dist/handlers/__tests__/tool-scenario.test.d.ts +0 -12
  356. package/dist/handlers/__tests__/tool-scenario.test.d.ts.map +0 -1
  357. package/dist/handlers/__tests__/tool-scenario.test.js +0 -222
  358. package/dist/handlers/__tests__/tool-scenario.test.js.map +0 -1
  359. package/dist/handlers/__tests__/wait-human-handler.test.d.ts +0 -11
  360. package/dist/handlers/__tests__/wait-human-handler.test.d.ts.map +0 -1
  361. package/dist/handlers/__tests__/wait-human-handler.test.js +0 -251
  362. package/dist/handlers/__tests__/wait-human-handler.test.js.map +0 -1
  363. package/dist/llm/__tests__/client.test.d.ts +0 -2
  364. package/dist/llm/__tests__/client.test.d.ts.map +0 -1
  365. package/dist/llm/__tests__/client.test.js +0 -198
  366. package/dist/llm/__tests__/client.test.js.map +0 -1
  367. package/dist/llm/__tests__/types.test.d.ts +0 -2
  368. package/dist/llm/__tests__/types.test.d.ts.map +0 -1
  369. package/dist/llm/__tests__/types.test.js +0 -289
  370. package/dist/llm/__tests__/types.test.js.map +0 -1
  371. package/dist/llm/middleware/__tests__/cost-tracking.test.d.ts +0 -2
  372. package/dist/llm/middleware/__tests__/cost-tracking.test.d.ts.map +0 -1
  373. package/dist/llm/middleware/__tests__/cost-tracking.test.js +0 -73
  374. package/dist/llm/middleware/__tests__/cost-tracking.test.js.map +0 -1
  375. package/dist/llm/middleware/__tests__/logging.test.d.ts +0 -2
  376. package/dist/llm/middleware/__tests__/logging.test.d.ts.map +0 -1
  377. package/dist/llm/middleware/__tests__/logging.test.js +0 -127
  378. package/dist/llm/middleware/__tests__/logging.test.js.map +0 -1
  379. package/dist/llm/middleware/__tests__/retry.test.d.ts +0 -2
  380. package/dist/llm/middleware/__tests__/retry.test.d.ts.map +0 -1
  381. package/dist/llm/middleware/__tests__/retry.test.js +0 -126
  382. package/dist/llm/middleware/__tests__/retry.test.js.map +0 -1
  383. package/dist/llm/providers/__tests__/anthropic.test.d.ts +0 -2
  384. package/dist/llm/providers/__tests__/anthropic.test.d.ts.map +0 -1
  385. package/dist/llm/providers/__tests__/anthropic.test.js +0 -412
  386. package/dist/llm/providers/__tests__/anthropic.test.js.map +0 -1
  387. package/dist/llm/providers/__tests__/gemini.test.d.ts +0 -2
  388. package/dist/llm/providers/__tests__/gemini.test.d.ts.map +0 -1
  389. package/dist/llm/providers/__tests__/gemini.test.js +0 -591
  390. package/dist/llm/providers/__tests__/gemini.test.js.map +0 -1
  391. package/dist/llm/providers/__tests__/openai.test.d.ts +0 -2
  392. package/dist/llm/providers/__tests__/openai.test.d.ts.map +0 -1
  393. package/dist/llm/providers/__tests__/openai.test.js +0 -546
  394. package/dist/llm/providers/__tests__/openai.test.js.map +0 -1
  395. package/dist/persistence/__tests__/factory-queries.test.d.ts +0 -9
  396. package/dist/persistence/__tests__/factory-queries.test.d.ts.map +0 -1
  397. package/dist/persistence/__tests__/factory-queries.test.js +0 -372
  398. package/dist/persistence/__tests__/factory-queries.test.js.map +0 -1
  399. package/dist/persistence/__tests__/factory-schema.test.d.ts +0 -6
  400. package/dist/persistence/__tests__/factory-schema.test.d.ts.map +0 -1
  401. package/dist/persistence/__tests__/factory-schema.test.js +0 -105
  402. package/dist/persistence/__tests__/factory-schema.test.js.map +0 -1
  403. package/dist/scenarios/__tests__/cli-command-list.test.d.ts +0 -7
  404. package/dist/scenarios/__tests__/cli-command-list.test.d.ts.map +0 -1
  405. package/dist/scenarios/__tests__/cli-command-list.test.js +0 -237
  406. package/dist/scenarios/__tests__/cli-command-list.test.js.map +0 -1
  407. package/dist/scenarios/__tests__/cli-command.test.d.ts +0 -11
  408. package/dist/scenarios/__tests__/cli-command.test.d.ts.map +0 -1
  409. package/dist/scenarios/__tests__/cli-command.test.js +0 -275
  410. package/dist/scenarios/__tests__/cli-command.test.js.map +0 -1
  411. package/dist/scenarios/__tests__/integrity-pipeline.test.d.ts +0 -15
  412. package/dist/scenarios/__tests__/integrity-pipeline.test.d.ts.map +0 -1
  413. package/dist/scenarios/__tests__/integrity-pipeline.test.js +0 -318
  414. package/dist/scenarios/__tests__/integrity-pipeline.test.js.map +0 -1
  415. package/dist/scenarios/__tests__/runner-twins.test.d.ts +0 -13
  416. package/dist/scenarios/__tests__/runner-twins.test.d.ts.map +0 -1
  417. package/dist/scenarios/__tests__/runner-twins.test.js +0 -205
  418. package/dist/scenarios/__tests__/runner-twins.test.js.map +0 -1
  419. package/dist/scenarios/__tests__/scorer.test.d.ts +0 -11
  420. package/dist/scenarios/__tests__/scorer.test.d.ts.map +0 -1
  421. package/dist/scenarios/__tests__/scorer.test.js +0 -225
  422. package/dist/scenarios/__tests__/scorer.test.js.map +0 -1
  423. package/dist/scenarios/__tests__/scoring-integration.test.d.ts +0 -8
  424. package/dist/scenarios/__tests__/scoring-integration.test.d.ts.map +0 -1
  425. package/dist/scenarios/__tests__/scoring-integration.test.js +0 -178
  426. package/dist/scenarios/__tests__/scoring-integration.test.js.map +0 -1
  427. package/dist/scenarios/__tests__/store.test.d.ts +0 -5
  428. package/dist/scenarios/__tests__/store.test.d.ts.map +0 -1
  429. package/dist/scenarios/__tests__/store.test.js +0 -169
  430. package/dist/scenarios/__tests__/store.test.js.map +0 -1
  431. package/dist/stylesheet/__tests__/stylesheet.test.d.ts +0 -17
  432. package/dist/stylesheet/__tests__/stylesheet.test.d.ts.map +0 -1
  433. package/dist/stylesheet/__tests__/stylesheet.test.js +0 -368
  434. package/dist/stylesheet/__tests__/stylesheet.test.js.map +0 -1
  435. package/dist/templates/__tests__/templates.test.d.ts +0 -7
  436. package/dist/templates/__tests__/templates.test.d.ts.map +0 -1
  437. package/dist/templates/__tests__/templates.test.js +0 -92
  438. package/dist/templates/__tests__/templates.test.js.map +0 -1
  439. package/dist/twins/__tests__/docker-compose.test.d.ts +0 -13
  440. package/dist/twins/__tests__/docker-compose.test.d.ts.map +0 -1
  441. package/dist/twins/__tests__/docker-compose.test.js +0 -247
  442. package/dist/twins/__tests__/docker-compose.test.js.map +0 -1
  443. package/dist/twins/__tests__/health-monitor.test.d.ts +0 -19
  444. package/dist/twins/__tests__/health-monitor.test.d.ts.map +0 -1
  445. package/dist/twins/__tests__/health-monitor.test.js +0 -301
  446. package/dist/twins/__tests__/health-monitor.test.js.map +0 -1
  447. package/dist/twins/__tests__/integration/e2e.test.d.ts +0 -18
  448. package/dist/twins/__tests__/integration/e2e.test.d.ts.map +0 -1
  449. package/dist/twins/__tests__/integration/e2e.test.js +0 -146
  450. package/dist/twins/__tests__/integration/e2e.test.js.map +0 -1
  451. package/dist/twins/__tests__/integration/helpers.d.ts +0 -32
  452. package/dist/twins/__tests__/integration/helpers.d.ts.map +0 -1
  453. package/dist/twins/__tests__/integration/helpers.js +0 -67
  454. package/dist/twins/__tests__/integration/helpers.js.map +0 -1
  455. package/dist/twins/__tests__/integration/lifecycle.test.d.ts +0 -14
  456. package/dist/twins/__tests__/integration/lifecycle.test.d.ts.map +0 -1
  457. package/dist/twins/__tests__/integration/lifecycle.test.js +0 -127
  458. package/dist/twins/__tests__/integration/lifecycle.test.js.map +0 -1
  459. package/dist/twins/__tests__/integration/persistence-integration.test.d.ts +0 -14
  460. package/dist/twins/__tests__/integration/persistence-integration.test.d.ts.map +0 -1
  461. package/dist/twins/__tests__/integration/persistence-integration.test.js +0 -132
  462. package/dist/twins/__tests__/integration/persistence-integration.test.js.map +0 -1
  463. package/dist/twins/__tests__/persistence.test.d.ts +0 -10
  464. package/dist/twins/__tests__/persistence.test.d.ts.map +0 -1
  465. package/dist/twins/__tests__/persistence.test.js +0 -300
  466. package/dist/twins/__tests__/persistence.test.js.map +0 -1
  467. package/dist/twins/__tests__/registry.test.d.ts +0 -7
  468. package/dist/twins/__tests__/registry.test.d.ts.map +0 -1
  469. package/dist/twins/__tests__/registry.test.js +0 -282
  470. package/dist/twins/__tests__/registry.test.js.map +0 -1
  471. package/dist/twins/__tests__/run-state.test.d.ts +0 -9
  472. package/dist/twins/__tests__/run-state.test.d.ts.map +0 -1
  473. package/dist/twins/__tests__/run-state.test.js +0 -112
  474. package/dist/twins/__tests__/run-state.test.js.map +0 -1
  475. package/dist/twins/__tests__/templates-cli.test.d.ts +0 -10
  476. package/dist/twins/__tests__/templates-cli.test.d.ts.map +0 -1
  477. package/dist/twins/__tests__/templates-cli.test.js +0 -187
  478. package/dist/twins/__tests__/templates-cli.test.js.map +0 -1
  479. package/dist/twins/__tests__/templates.test.d.ts +0 -7
  480. package/dist/twins/__tests__/templates.test.d.ts.map +0 -1
  481. package/dist/twins/__tests__/templates.test.js +0 -87
  482. package/dist/twins/__tests__/templates.test.js.map +0 -1
  483. package/dist/twins/__tests__/twins-cli.test.d.ts +0 -11
  484. package/dist/twins/__tests__/twins-cli.test.d.ts.map +0 -1
  485. package/dist/twins/__tests__/twins-cli.test.js +0 -365
  486. 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