@xdelivered/emberflow 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (414) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +153 -0
  3. package/bin/commands.test.ts +185 -0
  4. package/bin/commands.ts +306 -0
  5. package/bin/emberflow.mjs +38 -0
  6. package/bin/init.test.ts +165 -0
  7. package/bin/init.ts +297 -0
  8. package/bin/runtime.mjs +105 -0
  9. package/bin/runtime.test.ts +167 -0
  10. package/bin/skillTargets.test.ts +108 -0
  11. package/bin/skillTargets.ts +36 -0
  12. package/bin/skillTemplates.test.ts +37 -0
  13. package/bin/tsconfig.json +23 -0
  14. package/dist/bin/commands.d.ts +24 -0
  15. package/dist/bin/commands.js +252 -0
  16. package/dist/bin/init.d.ts +17 -0
  17. package/dist/bin/init.js +241 -0
  18. package/dist/bin/runtime.d.mts +39 -0
  19. package/dist/bin/runtime.mjs +96 -0
  20. package/dist/bin/skillTargets.d.ts +13 -0
  21. package/dist/bin/skillTargets.js +22 -0
  22. package/dist/server/agents/claudeAdapter.d.ts +8 -0
  23. package/dist/server/agents/claudeAdapter.js +190 -0
  24. package/dist/server/agents/claudeParse.d.ts +2 -0
  25. package/dist/server/agents/claudeParse.js +162 -0
  26. package/dist/server/agents/codexAdapter.d.ts +13 -0
  27. package/dist/server/agents/codexAdapter.js +174 -0
  28. package/dist/server/agents/codexParse.d.ts +2 -0
  29. package/dist/server/agents/codexParse.js +74 -0
  30. package/dist/server/agents/detect.d.ts +23 -0
  31. package/dist/server/agents/detect.js +35 -0
  32. package/dist/server/agents/gitScope.d.ts +24 -0
  33. package/dist/server/agents/gitScope.js +95 -0
  34. package/dist/server/agents/modelRejectionHint.d.ts +8 -0
  35. package/dist/server/agents/modelRejectionHint.js +12 -0
  36. package/dist/server/agents/prompt.d.ts +79 -0
  37. package/dist/server/agents/prompt.js +364 -0
  38. package/dist/server/agents/runManager.d.ts +90 -0
  39. package/dist/server/agents/runManager.js +259 -0
  40. package/dist/server/agents/types.d.ts +15 -0
  41. package/dist/server/agents/types.js +1 -0
  42. package/dist/server/apiStore.d.ts +60 -0
  43. package/dist/server/apiStore.js +249 -0
  44. package/dist/server/auth/enforce.d.ts +1 -0
  45. package/dist/server/auth/enforce.js +6 -0
  46. package/dist/server/auth/types.d.ts +1 -0
  47. package/dist/server/auth/types.js +1 -0
  48. package/dist/server/auth/verifiers.d.ts +1 -0
  49. package/dist/server/auth/verifiers.js +4 -0
  50. package/dist/server/authAttach.d.ts +19 -0
  51. package/dist/server/authAttach.js +79 -0
  52. package/dist/server/cli.d.ts +7 -0
  53. package/dist/server/cli.js +804 -0
  54. package/dist/server/client.d.ts +130 -0
  55. package/dist/server/client.js +247 -0
  56. package/dist/server/environments.d.ts +103 -0
  57. package/dist/server/environments.js +486 -0
  58. package/dist/server/flowStore.d.ts +37 -0
  59. package/dist/server/flowStore.js +117 -0
  60. package/dist/server/httpOperations.d.ts +32 -0
  61. package/dist/server/httpOperations.js +62 -0
  62. package/dist/server/index.d.ts +1 -0
  63. package/dist/server/index.js +1022 -0
  64. package/dist/server/infrastructure.d.ts +51 -0
  65. package/dist/server/infrastructure.js +116 -0
  66. package/dist/server/login.d.ts +21 -0
  67. package/dist/server/login.js +44 -0
  68. package/dist/server/mcp.d.ts +1 -0
  69. package/dist/server/mcp.js +182 -0
  70. package/dist/server/migrateFlows.d.ts +8 -0
  71. package/dist/server/migrateFlows.js +25 -0
  72. package/dist/server/mockHandler.d.ts +15 -0
  73. package/dist/server/mockHandler.js +35 -0
  74. package/dist/server/nodesPayload.d.ts +14 -0
  75. package/dist/server/nodesPayload.js +14 -0
  76. package/dist/server/normalizeFlow.d.ts +21 -0
  77. package/dist/server/normalizeFlow.js +48 -0
  78. package/dist/server/openBrowser.d.ts +8 -0
  79. package/dist/server/openBrowser.js +16 -0
  80. package/dist/server/operationResult.d.ts +12 -0
  81. package/dist/server/operationResult.js +26 -0
  82. package/dist/server/pathGuard.d.ts +1 -0
  83. package/dist/server/pathGuard.js +16 -0
  84. package/dist/server/pathSafety.d.ts +20 -0
  85. package/dist/server/pathSafety.js +35 -0
  86. package/dist/server/projectConfig.d.ts +47 -0
  87. package/dist/server/projectConfig.js +61 -0
  88. package/dist/server/projectMode.d.ts +26 -0
  89. package/dist/server/projectMode.js +44 -0
  90. package/dist/server/redact.d.ts +6 -0
  91. package/dist/server/redact.js +37 -0
  92. package/dist/server/runRegistry.d.ts +84 -0
  93. package/dist/server/runRegistry.js +200 -0
  94. package/dist/server/runtime/RuntimeAdapter.d.ts +22 -0
  95. package/dist/server/runtime/RuntimeAdapter.js +1 -0
  96. package/dist/server/runtime/expressAdapter.d.ts +19 -0
  97. package/dist/server/runtime/expressAdapter.js +56 -0
  98. package/dist/server/scenarioTest.d.ts +14 -0
  99. package/dist/server/scenarioTest.js +66 -0
  100. package/dist/server/subflowRunner.d.ts +32 -0
  101. package/dist/server/subflowRunner.js +72 -0
  102. package/dist/server/testRunner.d.ts +70 -0
  103. package/dist/server/testRunner.js +151 -0
  104. package/dist/src/config.d.ts +3 -0
  105. package/dist/src/config.js +2 -0
  106. package/dist/src/engine/authVerify.d.ts +33 -0
  107. package/dist/src/engine/authVerify.js +63 -0
  108. package/dist/src/engine/diagnostics.d.ts +62 -0
  109. package/dist/src/engine/diagnostics.js +148 -0
  110. package/dist/src/engine/executor.d.ts +151 -0
  111. package/dist/src/engine/executor.js +684 -0
  112. package/dist/src/engine/httpError.d.ts +7 -0
  113. package/dist/src/engine/httpError.js +14 -0
  114. package/dist/src/engine/index.d.ts +11 -0
  115. package/dist/src/engine/index.js +11 -0
  116. package/dist/src/engine/output.d.ts +11 -0
  117. package/dist/src/engine/output.js +20 -0
  118. package/dist/src/engine/publish.d.ts +30 -0
  119. package/dist/src/engine/publish.js +95 -0
  120. package/dist/src/engine/registry.d.ts +34 -0
  121. package/dist/src/engine/registry.js +67 -0
  122. package/dist/src/engine/scenarios.d.ts +12 -0
  123. package/dist/src/engine/scenarios.js +19 -0
  124. package/dist/src/engine/schemaCheck.d.ts +4 -0
  125. package/dist/src/engine/schemaCheck.js +34 -0
  126. package/dist/src/engine/trace.d.ts +12 -0
  127. package/dist/src/engine/trace.js +12 -0
  128. package/dist/src/engine/types.d.ts +308 -0
  129. package/dist/src/engine/types.js +1 -0
  130. package/dist/src/engine/validation.d.ts +26 -0
  131. package/dist/src/engine/validation.js +318 -0
  132. package/dist/src/flows/anomaly-flows.d.ts +23 -0
  133. package/dist/src/flows/anomaly-flows.js +151 -0
  134. package/dist/src/flows/city-sweep-flow.d.ts +10 -0
  135. package/dist/src/flows/city-sweep-flow.js +127 -0
  136. package/dist/src/flows/login-flow.d.ts +13 -0
  137. package/dist/src/flows/login-flow.js +145 -0
  138. package/dist/src/flows/pradar-flows.d.ts +5 -0
  139. package/dist/src/flows/pradar-flows.js +375 -0
  140. package/dist/src/flows/weather-flow.d.ts +12 -0
  141. package/dist/src/flows/weather-flow.js +150 -0
  142. package/dist/src/lib/pathParams.d.ts +3 -0
  143. package/dist/src/lib/pathParams.js +6 -0
  144. package/dist/src/nodes/anomaly.d.ts +2 -0
  145. package/dist/src/nodes/anomaly.js +556 -0
  146. package/dist/src/nodes/flow-control.d.ts +7 -0
  147. package/dist/src/nodes/flow-control.js +270 -0
  148. package/dist/src/nodes/index.d.ts +3 -0
  149. package/dist/src/nodes/index.js +20 -0
  150. package/dist/src/nodes/login.d.ts +7 -0
  151. package/dist/src/nodes/login.js +158 -0
  152. package/dist/src/nodes/loops.d.ts +11 -0
  153. package/dist/src/nodes/loops.js +60 -0
  154. package/dist/src/nodes/pradar/index.d.ts +7 -0
  155. package/dist/src/nodes/pradar/index.js +9 -0
  156. package/dist/src/nodes/pradar/logic.d.ts +189 -0
  157. package/dist/src/nodes/pradar/logic.js +370 -0
  158. package/dist/src/nodes/pradar/nodes.d.ts +42 -0
  159. package/dist/src/nodes/pradar/nodes.js +801 -0
  160. package/dist/src/nodes/requireAuth.d.ts +20 -0
  161. package/dist/src/nodes/requireAuth.js +48 -0
  162. package/dist/src/nodes/response.d.ts +8 -0
  163. package/dist/src/nodes/response.js +33 -0
  164. package/dist/src/nodes/weather.d.ts +33 -0
  165. package/dist/src/nodes/weather.js +260 -0
  166. package/package.json +92 -0
  167. package/server/agentRoute.test.ts +157 -0
  168. package/server/agents/__fixtures__/fake-claude-writer.mjs +23 -0
  169. package/server/agents/__fixtures__/fake-claude.mjs +55 -0
  170. package/server/agents/__fixtures__/fake-codex-writer.mjs +27 -0
  171. package/server/agents/__fixtures__/fake-codex.mjs +52 -0
  172. package/server/agents/__fixtures__/fake-version-empty.mjs +3 -0
  173. package/server/agents/__fixtures__/fake-version-fail.mjs +4 -0
  174. package/server/agents/__fixtures__/fake-version-garbage.mjs +4 -0
  175. package/server/agents/__fixtures__/fake-version-good.mjs +4 -0
  176. package/server/agents/__fixtures__/fake-version-hang.mjs +3 -0
  177. package/server/agents/claudeAdapter.test.ts +127 -0
  178. package/server/agents/claudeAdapter.ts +212 -0
  179. package/server/agents/claudeParse.ts +193 -0
  180. package/server/agents/codexAdapter.test.ts +125 -0
  181. package/server/agents/codexAdapter.ts +198 -0
  182. package/server/agents/codexParse.test.ts +58 -0
  183. package/server/agents/codexParse.ts +126 -0
  184. package/server/agents/detect.test.ts +65 -0
  185. package/server/agents/detect.ts +43 -0
  186. package/server/agents/gitScope.test.ts +129 -0
  187. package/server/agents/gitScope.ts +110 -0
  188. package/server/agents/modelRejectionHint.test.ts +30 -0
  189. package/server/agents/modelRejectionHint.ts +14 -0
  190. package/server/agents/prompt.test.ts +613 -0
  191. package/server/agents/prompt.ts +527 -0
  192. package/server/agents/runManager.test.ts +251 -0
  193. package/server/agents/runManager.ts +286 -0
  194. package/server/agents/types.ts +17 -0
  195. package/server/apiMount.test.ts +591 -0
  196. package/server/apiStore.test.ts +324 -0
  197. package/server/apiStore.ts +241 -0
  198. package/server/auth/enforce.test.ts +61 -0
  199. package/server/auth/enforce.ts +6 -0
  200. package/server/auth/types.ts +5 -0
  201. package/server/auth/verifiers.test.ts +37 -0
  202. package/server/auth/verifiers.ts +9 -0
  203. package/server/authAttach.test.ts +175 -0
  204. package/server/authAttach.ts +99 -0
  205. package/server/cli.ts +847 -0
  206. package/server/cliAuth.test.ts +229 -0
  207. package/server/cliCreate.test.ts +131 -0
  208. package/server/cliDoctor.test.ts +326 -0
  209. package/server/cliTest.test.ts +735 -0
  210. package/server/client.ts +314 -0
  211. package/server/demoProject.test.ts +29 -0
  212. package/server/diagnosticsRoute.test.ts +169 -0
  213. package/server/environments.test.ts +620 -0
  214. package/server/environments.ts +603 -0
  215. package/server/environmentsRoute.test.ts +385 -0
  216. package/server/errorWorkflow.test.ts +205 -0
  217. package/server/flowStore.test.ts +84 -0
  218. package/server/flowStore.ts +125 -0
  219. package/server/httpOperations.test.ts +377 -0
  220. package/server/httpOperations.ts +96 -0
  221. package/server/index.ts +1111 -0
  222. package/server/infrastructure.test.ts +154 -0
  223. package/server/infrastructure.ts +161 -0
  224. package/server/infrastructureRoute.test.ts +105 -0
  225. package/server/login.test.ts +153 -0
  226. package/server/login.ts +67 -0
  227. package/server/mcp.ts +278 -0
  228. package/server/migrateFlows.test.ts +55 -0
  229. package/server/migrateFlows.ts +23 -0
  230. package/server/mockHandler.test.ts +138 -0
  231. package/server/mockHandler.ts +52 -0
  232. package/server/mockRun.test.ts +367 -0
  233. package/server/mockServe.test.ts +467 -0
  234. package/server/nodesPayload.test.ts +29 -0
  235. package/server/nodesPayload.ts +26 -0
  236. package/server/normalizeFlow.test.ts +80 -0
  237. package/server/normalizeFlow.ts +59 -0
  238. package/server/openBrowser.test.ts +20 -0
  239. package/server/openBrowser.ts +22 -0
  240. package/server/operationResult.test.ts +67 -0
  241. package/server/operationResult.ts +27 -0
  242. package/server/pathGuard.test.ts +19 -0
  243. package/server/pathGuard.ts +12 -0
  244. package/server/pathSafety.test.ts +38 -0
  245. package/server/pathSafety.ts +33 -0
  246. package/server/projectConfig.test.ts +67 -0
  247. package/server/projectConfig.ts +101 -0
  248. package/server/projectMode.test.ts +106 -0
  249. package/server/projectMode.ts +59 -0
  250. package/server/redact.test.ts +88 -0
  251. package/server/redact.ts +35 -0
  252. package/server/redactEgress.test.ts +281 -0
  253. package/server/runRegistry.ts +270 -0
  254. package/server/runsAuth.test.ts +119 -0
  255. package/server/runtime/RuntimeAdapter.ts +24 -0
  256. package/server/runtime/expressAdapter.test.ts +38 -0
  257. package/server/runtime/expressAdapter.ts +70 -0
  258. package/server/scenarioTest.test.ts +148 -0
  259. package/server/scenarioTest.ts +88 -0
  260. package/server/setupStatus.test.ts +127 -0
  261. package/server/subflowRunner.ts +104 -0
  262. package/server/testRunner.ts +216 -0
  263. package/server/tsconfig.json +20 -0
  264. package/server/workflowTestRoute.test.ts +297 -0
  265. package/src/App.tsx +233 -0
  266. package/src/components/AgentConsole.tsx +461 -0
  267. package/src/components/BranchRulesEditor.tsx +156 -0
  268. package/src/components/CreateModal.tsx +275 -0
  269. package/src/components/Dock.tsx +487 -0
  270. package/src/components/EnvironmentDialog.tsx +569 -0
  271. package/src/components/EnvironmentPicker.tsx +357 -0
  272. package/src/components/EnvironmentsDialog.tsx +144 -0
  273. package/src/components/ExecutionPager.tsx +67 -0
  274. package/src/components/InfraPanel.test.tsx +103 -0
  275. package/src/components/InfraPanel.tsx +171 -0
  276. package/src/components/Inspector.tsx +1182 -0
  277. package/src/components/Json.tsx +50 -0
  278. package/src/components/JsonModal.tsx +52 -0
  279. package/src/components/LogMessage.tsx +61 -0
  280. package/src/components/NodeRunModal.tsx +213 -0
  281. package/src/components/RunLogPanel.tsx +397 -0
  282. package/src/components/RunbookView.tsx +1085 -0
  283. package/src/components/Scenarios.tsx +476 -0
  284. package/src/components/SettingsDialog.tsx +229 -0
  285. package/src/components/Sidebar.tsx +436 -0
  286. package/src/components/StatusBar.tsx +302 -0
  287. package/src/components/Toolbar.tsx +663 -0
  288. package/src/components/WelcomeDialog.test.tsx +107 -0
  289. package/src/components/WelcomeDialog.tsx +412 -0
  290. package/src/components/agentMarkdown.test.tsx +91 -0
  291. package/src/components/agentMarkdown.tsx +158 -0
  292. package/src/components/ui/badge.tsx +31 -0
  293. package/src/components/ui/button.tsx +46 -0
  294. package/src/components/ui/combobox.tsx +114 -0
  295. package/src/components/ui/command.tsx +82 -0
  296. package/src/components/ui/dialog.tsx +73 -0
  297. package/src/components/ui/input.tsx +20 -0
  298. package/src/components/ui/popover.tsx +32 -0
  299. package/src/components/ui/resizable.tsx +44 -0
  300. package/src/components/ui/tabs.tsx +36 -0
  301. package/src/config.ts +4 -0
  302. package/src/engine/authVerify.ts +81 -0
  303. package/src/engine/diagnostics.test.ts +437 -0
  304. package/src/engine/diagnostics.ts +211 -0
  305. package/src/engine/executor.test.ts +1339 -0
  306. package/src/engine/executor.ts +840 -0
  307. package/src/engine/httpError.test.ts +12 -0
  308. package/src/engine/httpError.ts +16 -0
  309. package/src/engine/index.ts +11 -0
  310. package/src/engine/output.ts +21 -0
  311. package/src/engine/publish.test.ts +176 -0
  312. package/src/engine/publish.ts +127 -0
  313. package/src/engine/registry.test.ts +64 -0
  314. package/src/engine/registry.ts +80 -0
  315. package/src/engine/scenarios.test.ts +55 -0
  316. package/src/engine/scenarios.ts +29 -0
  317. package/src/engine/schemaCheck.test.ts +21 -0
  318. package/src/engine/schemaCheck.ts +35 -0
  319. package/src/engine/trace.test.ts +19 -0
  320. package/src/engine/trace.ts +23 -0
  321. package/src/engine/types.test.ts +22 -0
  322. package/src/engine/types.ts +316 -0
  323. package/src/engine/validation.test.ts +314 -0
  324. package/src/engine/validation.ts +354 -0
  325. package/src/flows/anomaly-flows.test.ts +81 -0
  326. package/src/flows/anomaly-flows.ts +202 -0
  327. package/src/flows/city-sweep-flow.ts +131 -0
  328. package/src/flows/flows.test.ts +30 -0
  329. package/src/flows/login-flow.ts +150 -0
  330. package/src/flows/pradar-flows.test.ts +204 -0
  331. package/src/flows/pradar-flows.ts +419 -0
  332. package/src/flows/weather-flow.ts +154 -0
  333. package/src/globals.css +147 -0
  334. package/src/lib/pathParams.test.ts +24 -0
  335. package/src/lib/pathParams.ts +6 -0
  336. package/src/lib/registerLens.test.ts +79 -0
  337. package/src/lib/registerLens.ts +59 -0
  338. package/src/lib/runbookModel.test.ts +61 -0
  339. package/src/lib/runbookModel.ts +290 -0
  340. package/src/lib/runbookProjection.test.ts +417 -0
  341. package/src/lib/runbookProjection.ts +254 -0
  342. package/src/lib/useTailAnchor.ts +53 -0
  343. package/src/lib/utils.ts +7 -0
  344. package/src/main.tsx +13 -0
  345. package/src/nodes/anomaly.test.ts +98 -0
  346. package/src/nodes/anomaly.ts +637 -0
  347. package/src/nodes/effects-scan.test.ts +44 -0
  348. package/src/nodes/flow-control.test.ts +280 -0
  349. package/src/nodes/flow-control.ts +305 -0
  350. package/src/nodes/index.ts +22 -0
  351. package/src/nodes/login.test.ts +81 -0
  352. package/src/nodes/login.ts +186 -0
  353. package/src/nodes/loops.ts +69 -0
  354. package/src/nodes/pradar/index.ts +11 -0
  355. package/src/nodes/pradar/logic.test.ts +288 -0
  356. package/src/nodes/pradar/logic.ts +582 -0
  357. package/src/nodes/pradar/nodes.test.ts +146 -0
  358. package/src/nodes/pradar/nodes.ts +955 -0
  359. package/src/nodes/requireAuth.test.ts +75 -0
  360. package/src/nodes/requireAuth.ts +55 -0
  361. package/src/nodes/response.test.ts +33 -0
  362. package/src/nodes/response.ts +39 -0
  363. package/src/nodes/traceDetail.test.ts +39 -0
  364. package/src/nodes/weather.test.ts +65 -0
  365. package/src/nodes/weather.ts +306 -0
  366. package/src/store/agentClient.test.ts +103 -0
  367. package/src/store/agentClient.ts +131 -0
  368. package/src/store/apiTree.test.ts +17 -0
  369. package/src/store/apiTree.ts +73 -0
  370. package/src/store/builderStore.test.ts +1234 -0
  371. package/src/store/builderStore.ts +1879 -0
  372. package/src/store/infraClient.ts +63 -0
  373. package/src/store/nodeMeta.test.ts +24 -0
  374. package/src/store/nodeMeta.ts +21 -0
  375. package/src/store/persistence.test.ts +15 -0
  376. package/src/store/persistence.ts +54 -0
  377. package/src/store/serverRunner.ts +381 -0
  378. package/src/store/setupClient.ts +57 -0
  379. package/studio-dist/assets/ibm-plex-mono-cyrillic-400-normal-BSMlKf0J.woff2 +0 -0
  380. package/studio-dist/assets/ibm-plex-mono-cyrillic-400-normal-CEL4l2ZJ.woff +0 -0
  381. package/studio-dist/assets/ibm-plex-mono-cyrillic-500-normal-Ael50iVv.woff +0 -0
  382. package/studio-dist/assets/ibm-plex-mono-cyrillic-500-normal-Bq9vWWag.woff2 +0 -0
  383. package/studio-dist/assets/ibm-plex-mono-cyrillic-ext-400-normal-DMdlQ8Kv.woff +0 -0
  384. package/studio-dist/assets/ibm-plex-mono-cyrillic-ext-400-normal-xuaO2J-f.woff2 +0 -0
  385. package/studio-dist/assets/ibm-plex-mono-cyrillic-ext-500-normal-BIfNGwUT.woff +0 -0
  386. package/studio-dist/assets/ibm-plex-mono-cyrillic-ext-500-normal-BqneJy0T.woff2 +0 -0
  387. package/studio-dist/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
  388. package/studio-dist/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
  389. package/studio-dist/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
  390. package/studio-dist/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
  391. package/studio-dist/assets/ibm-plex-mono-latin-ext-400-normal-BmRBH3aV.woff2 +0 -0
  392. package/studio-dist/assets/ibm-plex-mono-latin-ext-400-normal-D3D2R8hC.woff +0 -0
  393. package/studio-dist/assets/ibm-plex-mono-latin-ext-500-normal-CAhNIIs5.woff2 +0 -0
  394. package/studio-dist/assets/ibm-plex-mono-latin-ext-500-normal-CZ70TYgx.woff +0 -0
  395. package/studio-dist/assets/ibm-plex-mono-vietnamese-400-normal-BulugwFq.woff2 +0 -0
  396. package/studio-dist/assets/ibm-plex-mono-vietnamese-400-normal-DDuiU_S-.woff +0 -0
  397. package/studio-dist/assets/ibm-plex-mono-vietnamese-500-normal-C8zxqsMH.woff +0 -0
  398. package/studio-dist/assets/ibm-plex-mono-vietnamese-500-normal-DZ4AoWbu.woff2 +0 -0
  399. package/studio-dist/assets/index-DNJwW-hM.css +1 -0
  400. package/studio-dist/assets/index-O26dKRjW.js +65 -0
  401. package/studio-dist/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
  402. package/studio-dist/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
  403. package/studio-dist/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
  404. package/studio-dist/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
  405. package/studio-dist/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
  406. package/studio-dist/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
  407. package/studio-dist/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
  408. package/studio-dist/favicon.svg +1 -0
  409. package/studio-dist/icons.svg +24 -0
  410. package/studio-dist/index.html +14 -0
  411. package/templates/skills/emberflow-basics/SKILL.md +316 -0
  412. package/templates/skills/emberflow-model-process/SKILL.md +209 -0
  413. package/templates/skills/emberflow-new-workflow/SKILL.md +211 -0
  414. package/templates/skills/emberflow-review-workflow/SKILL.md +148 -0
@@ -0,0 +1,354 @@
1
+ import type { FieldDefinition, WorkflowDefinition, WorkflowEdge, WorkflowNode } from './types';
2
+ import type { NodeRegistry } from './registry';
3
+
4
+ export interface ValidationIssue {
5
+ severity: 'error' | 'warning';
6
+ nodeId?: string;
7
+ message: string;
8
+ }
9
+
10
+ export interface LoopRegion {
11
+ forEachId: string;
12
+ collectId: string;
13
+ /** Loop-body node ids: downstream(forEach) ∩ upstream(collect), excluding both. */
14
+ bodyIds: string[];
15
+ }
16
+
17
+ export function topoSort(flow: WorkflowDefinition): string[] {
18
+ const indegree = new Map<string, number>();
19
+ const adjacency = new Map<string, string[]>();
20
+ for (const n of flow.nodes) {
21
+ indegree.set(n.id, 0);
22
+ adjacency.set(n.id, []);
23
+ }
24
+ for (const e of flow.edges) {
25
+ if (!adjacency.has(e.source) || !indegree.has(e.target)) continue;
26
+ adjacency.get(e.source)!.push(e.target);
27
+ indegree.set(e.target, indegree.get(e.target)! + 1);
28
+ }
29
+ const queue = flow.nodes.filter((n) => indegree.get(n.id) === 0).map((n) => n.id);
30
+ const order: string[] = [];
31
+ while (queue.length > 0) {
32
+ const id = queue.shift()!;
33
+ order.push(id);
34
+ for (const next of adjacency.get(id)!) {
35
+ indegree.set(next, indegree.get(next)! - 1);
36
+ if (indegree.get(next) === 0) queue.push(next);
37
+ }
38
+ }
39
+ if (order.length !== flow.nodes.length) throw new Error('Flow contains a cycle');
40
+ return order;
41
+ }
42
+
43
+ /** All nodes upstream of nodeId (direct parents and further ancestors). */
44
+ export function upstreamNodeIds(flow: WorkflowDefinition, nodeId: string): Set<string> {
45
+ const incoming = new Map<string, string[]>();
46
+ for (const e of flow.edges) {
47
+ if (!incoming.has(e.target)) incoming.set(e.target, []);
48
+ incoming.get(e.target)!.push(e.source);
49
+ }
50
+ const seen = new Set<string>();
51
+ const queue = [...(incoming.get(nodeId) ?? [])];
52
+ while (queue.length > 0) {
53
+ const id = queue.shift()!;
54
+ if (seen.has(id)) continue;
55
+ seen.add(id);
56
+ queue.push(...(incoming.get(id) ?? []));
57
+ }
58
+ return seen;
59
+ }
60
+
61
+ /** All nodes downstream of nodeId (direct children and further descendants). */
62
+ export function downstreamNodeIds(flow: WorkflowDefinition, nodeId: string): Set<string> {
63
+ const outgoing = new Map<string, string[]>();
64
+ for (const e of flow.edges) {
65
+ if (!outgoing.has(e.source)) outgoing.set(e.source, []);
66
+ outgoing.get(e.source)!.push(e.target);
67
+ }
68
+ const seen = new Set<string>();
69
+ const queue = [...(outgoing.get(nodeId) ?? [])];
70
+ while (queue.length > 0) {
71
+ const id = queue.shift()!;
72
+ if (seen.has(id)) continue;
73
+ seen.add(id);
74
+ queue.push(...(outgoing.get(id) ?? []));
75
+ }
76
+ return seen;
77
+ }
78
+
79
+ interface LoopPairing {
80
+ forEachNode: WorkflowNode;
81
+ collectNode: WorkflowNode;
82
+ bodyIds: string[];
83
+ leaks: WorkflowEdge[];
84
+ }
85
+
86
+ /**
87
+ * Matches ForEach nodes to a single downstream Collect (and vice versa) and
88
+ * computes each region's body + any edges that leak out of it. A ForEach/
89
+ * Collect that doesn't pair 1:1 is simply omitted here — validateFlow raises
90
+ * the specific "no Collect"/"multiple Collect"/etc. errors separately.
91
+ */
92
+ function findLoopPairings(flow: WorkflowDefinition): LoopPairing[] {
93
+ const forEachNodes = flow.nodes.filter((n) => n.type === 'ForEach');
94
+ const collectNodes = flow.nodes.filter((n) => n.type === 'Collect');
95
+ const pairings: LoopPairing[] = [];
96
+
97
+ for (const forEachNode of forEachNodes) {
98
+ const downstream = downstreamNodeIds(flow, forEachNode.id);
99
+ const collectsDownstream = collectNodes.filter((c) => downstream.has(c.id));
100
+ if (collectsDownstream.length !== 1) continue;
101
+ const collectNode = collectsDownstream[0];
102
+
103
+ const upstream = upstreamNodeIds(flow, collectNode.id);
104
+ const forEachesUpstream = forEachNodes.filter((f) => upstream.has(f.id));
105
+ if (forEachesUpstream.length !== 1 || forEachesUpstream[0].id !== forEachNode.id) continue;
106
+
107
+ const bodyIds = [...downstream].filter(
108
+ (id) => upstream.has(id) && id !== forEachNode.id && id !== collectNode.id,
109
+ );
110
+ const bodySet = new Set(bodyIds);
111
+ const leaks = flow.edges.filter(
112
+ (e) => bodySet.has(e.source) && !bodySet.has(e.target) && e.target !== collectNode.id,
113
+ );
114
+ pairings.push({ forEachNode, collectNode, bodyIds, leaks });
115
+ }
116
+
117
+ return pairings;
118
+ }
119
+
120
+ /**
121
+ * ForEach node ids that sit downstream of another ForEach node — i.e. one
122
+ * region wraps the other. Deliberately independent of findLoopPairings: a
123
+ * genuinely nested pair almost always also fails the "exactly one Collect
124
+ * descendant" check for the outer ForEach (its downstream includes both the
125
+ * inner and outer Collect), which would otherwise prevent a pairing — and
126
+ * therefore prevent detecting the nesting — from ever being computed.
127
+ */
128
+ function nestedForEachIds(flow: WorkflowDefinition): Set<string> {
129
+ const forEachNodes = flow.nodes.filter((n) => n.type === 'ForEach');
130
+ const nested = new Set<string>();
131
+ for (const outer of forEachNodes) {
132
+ const downstream = downstreamNodeIds(flow, outer.id);
133
+ for (const inner of forEachNodes) {
134
+ if (outer === inner) continue;
135
+ if (downstream.has(inner.id)) {
136
+ nested.add(outer.id);
137
+ nested.add(inner.id);
138
+ }
139
+ }
140
+ }
141
+ return nested;
142
+ }
143
+
144
+ /**
145
+ * Computes ForEach/Collect loop regions for a flow. Only returns regions for
146
+ * valid pairings (1:1 ForEach↔Collect, no leaking edges, not nested) — an
147
+ * invalid flow fails validateFlow and startRun already throws before this
148
+ * would be consulted.
149
+ */
150
+ export function computeLoopRegions(flow: WorkflowDefinition): LoopRegion[] {
151
+ const pairings = findLoopPairings(flow);
152
+ const nested = nestedForEachIds(flow);
153
+ return pairings
154
+ .filter((p) => p.leaks.length === 0 && !nested.has(p.forEachNode.id))
155
+ .map((p) => ({
156
+ forEachId: p.forEachNode.id,
157
+ collectId: p.collectNode.id,
158
+ bodyIds: p.bodyIds,
159
+ }));
160
+ }
161
+
162
+ export function validateFlow(flow: WorkflowDefinition, registry: NodeRegistry): ValidationIssue[] {
163
+ const issues: ValidationIssue[] = [];
164
+ const nodeIds = new Set(flow.nodes.map((n) => n.id));
165
+
166
+ for (const node of flow.nodes) {
167
+ if (!registry.has(node.type)) {
168
+ issues.push({ severity: 'error', nodeId: node.id, message: `Unknown node type: ${node.type}` });
169
+ }
170
+ }
171
+
172
+ for (const edge of flow.edges) {
173
+ for (const end of [edge.source, edge.target]) {
174
+ if (!nodeIds.has(end)) {
175
+ issues.push({ severity: 'error', message: `Edge ${edge.id} references missing node: ${end}` });
176
+ }
177
+ }
178
+ }
179
+
180
+ try {
181
+ topoSort(flow);
182
+ } catch {
183
+ issues.push({ severity: 'error', message: 'Flow contains a cycle' });
184
+ }
185
+
186
+ const ancestorsOf = (nodeId: string): Set<string> => upstreamNodeIds(flow, nodeId);
187
+
188
+ for (const node of flow.nodes) {
189
+ for (const [field, mapping] of Object.entries(node.inputMap ?? {})) {
190
+ if (!nodeIds.has(mapping.sourceNodeId)) {
191
+ issues.push({
192
+ severity: 'error', nodeId: node.id,
193
+ message: `Input "${field}" maps to missing node: ${mapping.sourceNodeId}`,
194
+ });
195
+ } else if (!ancestorsOf(node.id).has(mapping.sourceNodeId)) {
196
+ issues.push({
197
+ severity: 'warning', nodeId: node.id,
198
+ message: `Input "${field}" maps to ${mapping.sourceNodeId}, which is not upstream of this node`,
199
+ });
200
+ }
201
+ }
202
+
203
+ // A Subflow that names its own containing flow would recurse forever; the
204
+ // runtime guard catches cross-flow cycles, but a direct self-call is a
205
+ // static error.
206
+ if (node.type === 'Subflow' && node.config.workflowId === flow.id) {
207
+ issues.push({
208
+ severity: 'error', nodeId: node.id,
209
+ message: 'subflow cannot call its own flow',
210
+ });
211
+ }
212
+
213
+ if (!registry.has(node.type)) continue;
214
+ const definition = registry.get(node.type).definition;
215
+ const inputSchema = definition.inputSchema;
216
+
217
+ // Mutation nodes write observable state — warn so they can be reviewed
218
+ // before running against a protected environment; safe mode dry-runs them.
219
+ if (definition.effects === 'mutation') {
220
+ issues.push({
221
+ severity: 'warning',
222
+ nodeId: node.id,
223
+ message: `Mutation node "${node.label}" performs writes — dry-run under safe mode`,
224
+ });
225
+ }
226
+
227
+ // Mappings to fields the node never declared are silently ignored by
228
+ // implementations — surface them.
229
+ const declared = new Set((inputSchema?.fields ?? []).map((f) => f.name));
230
+ for (const field of Object.keys(node.inputMap ?? {})) {
231
+ if (!declared.has(field)) {
232
+ issues.push({
233
+ severity: 'warning', nodeId: node.id,
234
+ message: `Input "${field}" is mapped but not declared in ${node.type}'s input schema`,
235
+ });
236
+ }
237
+ }
238
+
239
+ for (const field of inputSchema?.fields ?? []) {
240
+ if (!field.required) continue;
241
+ const mapped = node.inputMap?.[field.name] !== undefined;
242
+ const configured = node.config[field.name] !== undefined;
243
+ if (!mapped && !configured) {
244
+ issues.push({
245
+ severity: 'warning', nodeId: node.id,
246
+ message: `Required input "${field.name}" is not mapped or configured`,
247
+ });
248
+ }
249
+ }
250
+ }
251
+
252
+ if (flow.scenarios && flow.scenarios.length > 0) {
253
+ const inputNode = flow.nodes.find((n) => n.type === 'Input');
254
+ if (!inputNode) {
255
+ issues.push({
256
+ severity: 'warning',
257
+ message: 'Flow has scenarios but no Input node — scenario input is never consumed',
258
+ });
259
+ } else {
260
+ const rawFields = inputNode.config?.fields;
261
+ const fields: FieldDefinition[] = Array.isArray(rawFields)
262
+ ? rawFields.filter(
263
+ (f): f is FieldDefinition =>
264
+ f !== null && typeof f === 'object' && typeof (f as { name?: unknown }).name === 'string',
265
+ )
266
+ : [];
267
+ const rawDefaults = inputNode.config?.defaults;
268
+ const defaults: Record<string, unknown> =
269
+ rawDefaults !== null && typeof rawDefaults === 'object' && !Array.isArray(rawDefaults)
270
+ ? (rawDefaults as Record<string, unknown>)
271
+ : {};
272
+ const declaredNames = new Set(fields.map((f) => f.name));
273
+
274
+ for (const scenario of flow.scenarios) {
275
+ for (const field of fields) {
276
+ if (!field.required) continue;
277
+ if (Object.prototype.hasOwnProperty.call(defaults, field.name)) continue;
278
+ if (Object.prototype.hasOwnProperty.call(scenario.input, field.name)) continue;
279
+ issues.push({
280
+ severity: 'warning',
281
+ nodeId: inputNode.id,
282
+ message: `Scenario "${scenario.name}" omits required input field "${field.name}"`,
283
+ });
284
+ }
285
+ for (const key of Object.keys(scenario.input)) {
286
+ if (!declaredNames.has(key)) {
287
+ issues.push({
288
+ severity: 'warning',
289
+ nodeId: inputNode.id,
290
+ message: `Scenario "${scenario.name}" provides undeclared input field "${key}"`,
291
+ });
292
+ }
293
+ }
294
+ }
295
+ }
296
+ }
297
+
298
+ // ── ForEach/Collect loop regions ──────────────────────────────────
299
+ for (const n of flow.nodes) {
300
+ if (n.type !== 'ForEach') continue;
301
+ const downstream = downstreamNodeIds(flow, n.id);
302
+ const collectsDownstream = flow.nodes.filter((c) => c.type === 'Collect' && downstream.has(c.id));
303
+ if (collectsDownstream.length === 0) {
304
+ issues.push({
305
+ severity: 'error', nodeId: n.id,
306
+ message: `ForEach "${n.id}" has no Collect node among its descendants`,
307
+ });
308
+ } else if (collectsDownstream.length > 1) {
309
+ issues.push({
310
+ severity: 'error', nodeId: n.id,
311
+ message: `ForEach "${n.id}" has multiple Collect nodes among its descendants: ${collectsDownstream.map((c) => c.id).join(', ')}`,
312
+ });
313
+ }
314
+ }
315
+
316
+ for (const n of flow.nodes) {
317
+ if (n.type !== 'Collect') continue;
318
+ const upstream = upstreamNodeIds(flow, n.id);
319
+ const forEachesUpstream = flow.nodes.filter((f) => f.type === 'ForEach' && upstream.has(f.id));
320
+ if (forEachesUpstream.length === 0) {
321
+ issues.push({
322
+ severity: 'error', nodeId: n.id,
323
+ message: `Collect "${n.id}" has no ForEach node among its ancestors`,
324
+ });
325
+ } else if (forEachesUpstream.length > 1) {
326
+ issues.push({
327
+ severity: 'error', nodeId: n.id,
328
+ message: `Collect "${n.id}" has multiple ForEach nodes among its ancestors: ${forEachesUpstream.map((f) => f.id).join(', ')}`,
329
+ });
330
+ }
331
+ }
332
+
333
+ const pairings = findLoopPairings(flow);
334
+ for (const p of pairings) {
335
+ for (const edge of p.leaks) {
336
+ issues.push({
337
+ severity: 'error',
338
+ nodeId: edge.source,
339
+ message: `Edge "${edge.id}" leaves the ForEach/Collect region rooted at "${p.forEachNode.id}" without targeting the region or its Collect node "${p.collectNode.id}"`,
340
+ });
341
+ }
342
+ }
343
+
344
+ const nested = nestedForEachIds(flow);
345
+ for (const forEachId of nested) {
346
+ issues.push({
347
+ severity: 'error',
348
+ nodeId: forEachId,
349
+ message: 'Nested ForEach regions are not supported',
350
+ });
351
+ }
352
+
353
+ return issues;
354
+ }
@@ -0,0 +1,81 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { createAnomalyFlows, createDetectEntireFlow } from './anomaly-flows';
3
+ import { createDefaultRegistry } from '../nodes';
4
+ import { startRun, validateFlow } from '../engine';
5
+
6
+ const registry = createDefaultRegistry(0);
7
+
8
+ describe('anomaly flows', () => {
9
+ it('all five validate cleanly against the default registry', () => {
10
+ const flows = createAnomalyFlows();
11
+ expect(flows).toHaveLength(5);
12
+ for (const flow of flows) {
13
+ expect(validateFlow(flow, registry), flow.name).toEqual([]);
14
+ expect(flow.folder).toBe('Anomaly API');
15
+ }
16
+ });
17
+
18
+ it('detect-entire routes to composeIncident when the series carries an anomaly', async () => {
19
+ const flow = createDetectEntireFlow();
20
+ // Pin the live detection so no HTTP is needed; build still runs locally.
21
+ const isAnomaly = Array.from({ length: 24 }, (_, i) => i === 20);
22
+ const run = await startRun({
23
+ flow,
24
+ registry,
25
+ pins: {
26
+ detect: { isAnomaly, expectedValues: [], upperMargins: [], lowerMargins: [], period: 24 },
27
+ },
28
+ }).runToEnd();
29
+
30
+ expect(run.status).toBe('succeeded');
31
+ expect((run.nodeStates.cond.output as { $branch: string }).$branch).toBe('anomalous');
32
+ expect(run.nodeStates.composeIncident.status).toBe('succeeded');
33
+ expect(run.nodeStates.incidentResult.status).toBe('succeeded');
34
+ expect(run.nodeStates.cleanResult.status).toBe('skipped');
35
+ });
36
+
37
+ it('detect-entire falls back to the clean branch when no anomalies are found', async () => {
38
+ const flow = createDetectEntireFlow();
39
+ const isAnomaly = Array.from({ length: 24 }, () => false);
40
+ const run = await startRun({
41
+ flow,
42
+ registry,
43
+ pins: {
44
+ detect: { isAnomaly, expectedValues: [], upperMargins: [], lowerMargins: [], period: 24 },
45
+ },
46
+ }).runToEnd();
47
+
48
+ expect(run.status).toBe('succeeded');
49
+ expect((run.nodeStates.cond.output as { $branch: string }).$branch).toBe('clean');
50
+ expect(run.nodeStates.cleanResult.status).toBe('succeeded');
51
+ expect(run.nodeStates.composeIncident.status).toBe('skipped');
52
+ expect(run.nodeStates.incidentResult.status).toBe('skipped');
53
+ });
54
+
55
+ it('quota flow reproduces the warning-email decision at 850/1000 calls', async () => {
56
+ const flow = createAnomalyFlows().find((f) => f.id === 'anomaly-quota-emails')!;
57
+ const run = await startRun({ flow, registry }).runToEnd();
58
+ expect(run.status).toBe('succeeded');
59
+ const result = run.nodeStates.result.output as { data: { action: string } };
60
+ expect(result.data.action).toBe('warning');
61
+ });
62
+
63
+ it('key lifecycle flow fails with KEY_LIMIT_REACHED at 5 active keys', async () => {
64
+ const flow = createAnomalyFlows().find((f) => f.id === 'anomaly-key-lifecycle')!;
65
+ flow.nodes.find((n) => n.id === 'enforce')!.config.activeKeyCount = 5;
66
+ const run = await startRun({ flow, registry }).runToEnd();
67
+ expect(run.status).toBe('failed');
68
+ expect(run.nodeStates.enforce.error).toContain('KEY_LIMIT_REACHED');
69
+ expect(run.nodeStates.result.status).toBe('skipped');
70
+ });
71
+
72
+ it('key lifecycle flow issues a hashed ef_ak_ key under the limit', async () => {
73
+ const flow = createAnomalyFlows().find((f) => f.id === 'anomaly-key-lifecycle')!;
74
+ const run = await startRun({ flow, registry }).runToEnd();
75
+ expect(run.status).toBe('succeeded');
76
+ const generated = run.nodeStates.generate.output as { rawKey: string; keyPrefix: string; hash: string };
77
+ expect(generated.rawKey).toMatch(/^ef_ak_[0-9a-f]{48}$/);
78
+ expect(generated.keyPrefix).toBe(generated.rawKey.slice(0, 15));
79
+ expect(generated.hash).toMatch(/^[0-9a-f]{64}$/);
80
+ });
81
+ });
@@ -0,0 +1,202 @@
1
+ import type { FieldMapping, WorkflowDefinition, WorkflowEdge, WorkflowNode } from '../engine';
2
+
3
+ const FOLDER = 'Anomaly API';
4
+ const CREATED = '2026-07-02T00:00:00Z';
5
+
6
+ interface NodeSpec {
7
+ id: string;
8
+ type: string;
9
+ label: string;
10
+ config?: Record<string, unknown>;
11
+ inputMap?: Record<string, FieldMapping>;
12
+ position?: { x: number; y: number };
13
+ }
14
+
15
+ function makeFlow(
16
+ id: string,
17
+ name: string,
18
+ specs: NodeSpec[],
19
+ edges: WorkflowEdge[],
20
+ scenarios?: WorkflowDefinition['scenarios'],
21
+ ): WorkflowDefinition {
22
+ const nodes: WorkflowNode[] = specs.map((spec, i) => ({
23
+ id: spec.id,
24
+ type: spec.type,
25
+ label: spec.label,
26
+ position: spec.position ?? { x: 60 + 250 * i, y: 200 },
27
+ config: spec.config ?? {},
28
+ inputMap: spec.inputMap,
29
+ }));
30
+ return {
31
+ id,
32
+ name,
33
+ version: 1,
34
+ folder: FOLDER,
35
+ nodes,
36
+ edges,
37
+ ...(scenarios ? { scenarios } : {}),
38
+ createdAt: CREATED,
39
+ updatedAt: CREATED,
40
+ };
41
+ }
42
+
43
+ const from = (sourceNodeId: string, sourceField: string): FieldMapping => ({ sourceNodeId, sourceField });
44
+
45
+ /**
46
+ * Live call: whole-series detection, then branch on severity.
47
+ * build → detect → summarize → cond(anomalyCount > 0?)
48
+ * ├─ anomalous → composeIncident → incidentResult
49
+ * └─ clean → cleanResult
50
+ *
51
+ * Deviation from spec: the spec's Conditional has two rules (`gt 3 → P1`,
52
+ * `gt 0 → P2`) feeding three downstream paths, but this flow only ever had
53
+ * two downstream branches (anomalous/clean). Kept the original two-branch
54
+ * shape as a single rule `gt 0 → anomalous`, fallback `clean`, rather than
55
+ * inventing a third P1/P2 branch nothing here consumes.
56
+ */
57
+ export function createDetectEntireFlow(): WorkflowDefinition {
58
+ return makeFlow(
59
+ 'anomaly-detect-entire',
60
+ 'Detect · Entire Series',
61
+ [
62
+ { id: 'input', type: 'Input', label: 'Series Request', config: { fields: [{ name: 'points', type: 'number', required: true }, { name: 'spikeIndex', type: 'number' }], defaults: { points: 24, spikeIndex: 20 } }, position: { x: -180, y: 240 } },
63
+ { id: 'build', type: 'BuildSeries', label: 'Build Series', config: { points: 24, baseValue: 100, spikeIndex: 20, spikeFactor: 3 }, inputMap: { points: from('input', 'points'), spikeIndex: from('input', 'spikeIndex') }, position: { x: 60, y: 240 } },
64
+ { id: 'detect', type: 'DetectEntireSeries', label: 'Detect Entire Series', config: { apiKey: 'dev-emberflow', sensitivity: 80 }, inputMap: { series: from('build', 'series') }, position: { x: 300, y: 240 } },
65
+ { id: 'summarize', type: 'SummarizeAnomalies', label: 'Summarize Anomalies', inputMap: { isAnomaly: from('detect', 'isAnomaly'), series: from('build', 'series') }, position: { x: 540, y: 240 } },
66
+ { id: 'cond', type: 'Conditional', label: 'Severity?', config: { branches: [{ name: 'anomalous', op: 'gt', value: 0 }], fallback: 'clean' }, inputMap: { value: from('summarize', 'anomalyCount') }, position: { x: 1020, y: 240 } },
67
+ { id: 'composeIncident', type: 'ComposeIncident', label: 'Compose Incident', inputMap: { headline: from('summarize', 'headline'), anomalyCount: from('summarize', 'anomalyCount') }, position: { x: 1260, y: 120 } },
68
+ { id: 'incidentResult', type: 'Result', label: 'Incident', inputMap: { data: from('composeIncident', '$') }, position: { x: 1500, y: 120 } },
69
+ { id: 'cleanResult', type: 'Result', label: 'All Clear', inputMap: { data: from('summarize', '$') }, position: { x: 1260, y: 360 } },
70
+ ],
71
+ [
72
+ { id: 'e0', source: 'input', target: 'build', targetHandle: 'points' },
73
+ { id: 'e1', source: 'build', target: 'detect', targetHandle: 'series' },
74
+ { id: 'e2', source: 'detect', target: 'summarize', targetHandle: 'isAnomaly' },
75
+ { id: 'e3', source: 'build', target: 'summarize', targetHandle: 'series' },
76
+ { id: 'e4', source: 'summarize', target: 'cond', targetHandle: 'value' },
77
+ { id: 'e6', source: 'cond', target: 'composeIncident', sourceHandle: 'anomalous' },
78
+ { id: 'e7', source: 'cond', target: 'cleanResult', sourceHandle: 'clean' },
79
+ { id: 'e8', source: 'composeIncident', target: 'incidentResult', targetHandle: 'data' },
80
+ ],
81
+ [
82
+ {
83
+ id: 'scn-spike',
84
+ name: 'spike at 20',
85
+ description: '×3 spike — detector flags it, incident branch fires',
86
+ input: { points: 24, spikeIndex: 20 },
87
+ },
88
+ {
89
+ id: 'scn-flat',
90
+ name: 'flat series',
91
+ description: 'no spike (spikeIndex −1) — clean branch, all clear',
92
+ input: { points: 24, spikeIndex: -1 },
93
+ },
94
+ ],
95
+ );
96
+ }
97
+
98
+ /** Live call: streaming-style last-point verdict. */
99
+ export function createDetectLastFlow(): WorkflowDefinition {
100
+ return makeFlow(
101
+ 'anomaly-detect-last',
102
+ 'Detect · Last Point',
103
+ [
104
+ { id: 'build', type: 'BuildSeries', label: 'Build Series (spike at end)', config: { points: 14, baseValue: 50, spikeIndex: 13, spikeFactor: 4 } },
105
+ { id: 'detect', type: 'DetectLastPoint', label: 'Detect Last Point', config: { apiKey: 'dev-emberflow', sensitivity: 80 }, inputMap: { series: from('build', 'series') } },
106
+ { id: 'result', type: 'Result', label: 'Verdict', inputMap: { data: from('detect', '$') } },
107
+ ],
108
+ [
109
+ { id: 'e1', source: 'build', target: 'detect', targetHandle: 'series' },
110
+ { id: 'e2', source: 'detect', target: 'result', targetHandle: 'data' },
111
+ ],
112
+ );
113
+ }
114
+
115
+ /** Live call: detection + Adtributor root-cause analysis. */
116
+ export function createRootCauseFlow(): WorkflowDefinition {
117
+ return makeFlow(
118
+ 'anomaly-root-cause',
119
+ 'Detect · Root Cause',
120
+ [
121
+ { id: 'build', type: 'BuildDimensionalSeries', label: 'Build Dimensional Series', config: { points: 14, baseValue: 100, spikeIndex: 12, culpritSlice: 'paid' } },
122
+ { id: 'detect', type: 'DetectAttributed', label: 'Detect + Attribute', config: { apiKey: 'dev-emberflow', sensitivity: 80, topK: 3 }, inputMap: { series: from('build', 'series') } },
123
+ { id: 'extract', type: 'ExtractRootCause', label: 'Extract Root Cause', inputMap: { rca: from('detect', 'rca') } },
124
+ { id: 'result', type: 'Result', label: 'Root Cause', inputMap: { data: from('extract', '$') } },
125
+ ],
126
+ [
127
+ { id: 'e1', source: 'build', target: 'detect', targetHandle: 'series' },
128
+ { id: 'e2', source: 'detect', target: 'extract', targetHandle: 'rca' },
129
+ { id: 'e3', source: 'extract', target: 'result', targetHandle: 'data' },
130
+ ],
131
+ );
132
+ }
133
+
134
+ /** Replica of today's quota/email business logic (Clerk-gated server-side). */
135
+ export function createQuotaFlow(): WorkflowDefinition {
136
+ return makeFlow(
137
+ 'anomaly-quota-emails',
138
+ 'Quota · Usage & Emails',
139
+ [
140
+ { id: 'input', type: 'Input', label: 'Usage Snapshot', config: { fields: [{ name: 'calls', type: 'number', required: true }], defaults: { calls: 850 } } },
141
+ { id: 'plan', type: 'PlanCatalog', label: 'Plan Catalog', config: { planCode: 'anomaly_free' } },
142
+ { id: 'usage', type: 'UsageState', label: 'Usage State', config: { calls: 850 }, inputMap: { calls: from('input', 'calls'), quota: from('plan', 'quota') } },
143
+ { id: 'classify', type: 'ClassifyQuotaEmail', label: 'Classify Quota Email', config: { alreadyWarned: false, alreadyNotifiedExceeded: false }, inputMap: { warn80: from('usage', 'warn80'), overQuota: from('usage', 'overQuota') } },
144
+ { id: 'result', type: 'Result', label: 'Sweep Decision', inputMap: { data: from('classify', '$') } },
145
+ ],
146
+ [
147
+ { id: 'e0', source: 'input', target: 'usage', targetHandle: 'calls' },
148
+ { id: 'e1', source: 'plan', target: 'usage', targetHandle: 'quota' },
149
+ { id: 'e2', source: 'usage', target: 'classify', targetHandle: 'warn80' },
150
+ { id: 'e3', source: 'classify', target: 'result', targetHandle: 'data' },
151
+ ],
152
+ // Free plan quota is 1,000 calls: the three scenarios straddle the
153
+ // warn-at-80% and exceeded thresholds in classifyUsageRow().
154
+ [
155
+ {
156
+ id: 'scn-quiet',
157
+ name: 'quiet month',
158
+ description: '400/1000 calls — no email',
159
+ input: { calls: 400 },
160
+ },
161
+ {
162
+ id: 'scn-warn',
163
+ name: 'approaching limit',
164
+ description: '850/1000 calls — 80% warning email',
165
+ input: { calls: 850 },
166
+ },
167
+ {
168
+ id: 'scn-over',
169
+ name: 'over quota',
170
+ description: '1200/1000 calls — exceeded email',
171
+ input: { calls: 1200 },
172
+ },
173
+ ],
174
+ );
175
+ }
176
+
177
+ /** Replica of today's key issuance + 5-key limit guard. */
178
+ export function createKeyLifecycleFlow(): WorkflowDefinition {
179
+ return makeFlow(
180
+ 'anomaly-key-lifecycle',
181
+ 'Keys · Issue & Limit',
182
+ [
183
+ { id: 'generate', type: 'GenerateApiKey', label: 'Generate API Key', config: { label: 'production' } },
184
+ { id: 'enforce', type: 'EnforceKeyLimit', label: 'Enforce Key Limit', config: { activeKeyCount: 3 }, inputMap: { keyPrefix: from('generate', 'keyPrefix') } },
185
+ { id: 'result', type: 'Result', label: 'Issued Key', inputMap: { data: from('enforce', '$') } },
186
+ ],
187
+ [
188
+ { id: 'e1', source: 'generate', target: 'enforce', targetHandle: 'keyPrefix' },
189
+ { id: 'e2', source: 'enforce', target: 'result', targetHandle: 'data' },
190
+ ],
191
+ );
192
+ }
193
+
194
+ export function createAnomalyFlows(): WorkflowDefinition[] {
195
+ return [
196
+ createDetectEntireFlow(),
197
+ createDetectLastFlow(),
198
+ createRootCauseFlow(),
199
+ createQuotaFlow(),
200
+ createKeyLifecycleFlow(),
201
+ ];
202
+ }