@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.
- package/LICENSE +21 -0
- package/README.md +153 -0
- package/bin/commands.test.ts +185 -0
- package/bin/commands.ts +306 -0
- package/bin/emberflow.mjs +38 -0
- package/bin/init.test.ts +165 -0
- package/bin/init.ts +297 -0
- package/bin/runtime.mjs +105 -0
- package/bin/runtime.test.ts +167 -0
- package/bin/skillTargets.test.ts +108 -0
- package/bin/skillTargets.ts +36 -0
- package/bin/skillTemplates.test.ts +37 -0
- package/bin/tsconfig.json +23 -0
- package/dist/bin/commands.d.ts +24 -0
- package/dist/bin/commands.js +252 -0
- package/dist/bin/init.d.ts +17 -0
- package/dist/bin/init.js +241 -0
- package/dist/bin/runtime.d.mts +39 -0
- package/dist/bin/runtime.mjs +96 -0
- package/dist/bin/skillTargets.d.ts +13 -0
- package/dist/bin/skillTargets.js +22 -0
- package/dist/server/agents/claudeAdapter.d.ts +8 -0
- package/dist/server/agents/claudeAdapter.js +190 -0
- package/dist/server/agents/claudeParse.d.ts +2 -0
- package/dist/server/agents/claudeParse.js +162 -0
- package/dist/server/agents/codexAdapter.d.ts +13 -0
- package/dist/server/agents/codexAdapter.js +174 -0
- package/dist/server/agents/codexParse.d.ts +2 -0
- package/dist/server/agents/codexParse.js +74 -0
- package/dist/server/agents/detect.d.ts +23 -0
- package/dist/server/agents/detect.js +35 -0
- package/dist/server/agents/gitScope.d.ts +24 -0
- package/dist/server/agents/gitScope.js +95 -0
- package/dist/server/agents/modelRejectionHint.d.ts +8 -0
- package/dist/server/agents/modelRejectionHint.js +12 -0
- package/dist/server/agents/prompt.d.ts +79 -0
- package/dist/server/agents/prompt.js +364 -0
- package/dist/server/agents/runManager.d.ts +90 -0
- package/dist/server/agents/runManager.js +259 -0
- package/dist/server/agents/types.d.ts +15 -0
- package/dist/server/agents/types.js +1 -0
- package/dist/server/apiStore.d.ts +60 -0
- package/dist/server/apiStore.js +249 -0
- package/dist/server/auth/enforce.d.ts +1 -0
- package/dist/server/auth/enforce.js +6 -0
- package/dist/server/auth/types.d.ts +1 -0
- package/dist/server/auth/types.js +1 -0
- package/dist/server/auth/verifiers.d.ts +1 -0
- package/dist/server/auth/verifiers.js +4 -0
- package/dist/server/authAttach.d.ts +19 -0
- package/dist/server/authAttach.js +79 -0
- package/dist/server/cli.d.ts +7 -0
- package/dist/server/cli.js +804 -0
- package/dist/server/client.d.ts +130 -0
- package/dist/server/client.js +247 -0
- package/dist/server/environments.d.ts +103 -0
- package/dist/server/environments.js +486 -0
- package/dist/server/flowStore.d.ts +37 -0
- package/dist/server/flowStore.js +117 -0
- package/dist/server/httpOperations.d.ts +32 -0
- package/dist/server/httpOperations.js +62 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +1022 -0
- package/dist/server/infrastructure.d.ts +51 -0
- package/dist/server/infrastructure.js +116 -0
- package/dist/server/login.d.ts +21 -0
- package/dist/server/login.js +44 -0
- package/dist/server/mcp.d.ts +1 -0
- package/dist/server/mcp.js +182 -0
- package/dist/server/migrateFlows.d.ts +8 -0
- package/dist/server/migrateFlows.js +25 -0
- package/dist/server/mockHandler.d.ts +15 -0
- package/dist/server/mockHandler.js +35 -0
- package/dist/server/nodesPayload.d.ts +14 -0
- package/dist/server/nodesPayload.js +14 -0
- package/dist/server/normalizeFlow.d.ts +21 -0
- package/dist/server/normalizeFlow.js +48 -0
- package/dist/server/openBrowser.d.ts +8 -0
- package/dist/server/openBrowser.js +16 -0
- package/dist/server/operationResult.d.ts +12 -0
- package/dist/server/operationResult.js +26 -0
- package/dist/server/pathGuard.d.ts +1 -0
- package/dist/server/pathGuard.js +16 -0
- package/dist/server/pathSafety.d.ts +20 -0
- package/dist/server/pathSafety.js +35 -0
- package/dist/server/projectConfig.d.ts +47 -0
- package/dist/server/projectConfig.js +61 -0
- package/dist/server/projectMode.d.ts +26 -0
- package/dist/server/projectMode.js +44 -0
- package/dist/server/redact.d.ts +6 -0
- package/dist/server/redact.js +37 -0
- package/dist/server/runRegistry.d.ts +84 -0
- package/dist/server/runRegistry.js +200 -0
- package/dist/server/runtime/RuntimeAdapter.d.ts +22 -0
- package/dist/server/runtime/RuntimeAdapter.js +1 -0
- package/dist/server/runtime/expressAdapter.d.ts +19 -0
- package/dist/server/runtime/expressAdapter.js +56 -0
- package/dist/server/scenarioTest.d.ts +14 -0
- package/dist/server/scenarioTest.js +66 -0
- package/dist/server/subflowRunner.d.ts +32 -0
- package/dist/server/subflowRunner.js +72 -0
- package/dist/server/testRunner.d.ts +70 -0
- package/dist/server/testRunner.js +151 -0
- package/dist/src/config.d.ts +3 -0
- package/dist/src/config.js +2 -0
- package/dist/src/engine/authVerify.d.ts +33 -0
- package/dist/src/engine/authVerify.js +63 -0
- package/dist/src/engine/diagnostics.d.ts +62 -0
- package/dist/src/engine/diagnostics.js +148 -0
- package/dist/src/engine/executor.d.ts +151 -0
- package/dist/src/engine/executor.js +684 -0
- package/dist/src/engine/httpError.d.ts +7 -0
- package/dist/src/engine/httpError.js +14 -0
- package/dist/src/engine/index.d.ts +11 -0
- package/dist/src/engine/index.js +11 -0
- package/dist/src/engine/output.d.ts +11 -0
- package/dist/src/engine/output.js +20 -0
- package/dist/src/engine/publish.d.ts +30 -0
- package/dist/src/engine/publish.js +95 -0
- package/dist/src/engine/registry.d.ts +34 -0
- package/dist/src/engine/registry.js +67 -0
- package/dist/src/engine/scenarios.d.ts +12 -0
- package/dist/src/engine/scenarios.js +19 -0
- package/dist/src/engine/schemaCheck.d.ts +4 -0
- package/dist/src/engine/schemaCheck.js +34 -0
- package/dist/src/engine/trace.d.ts +12 -0
- package/dist/src/engine/trace.js +12 -0
- package/dist/src/engine/types.d.ts +308 -0
- package/dist/src/engine/types.js +1 -0
- package/dist/src/engine/validation.d.ts +26 -0
- package/dist/src/engine/validation.js +318 -0
- package/dist/src/flows/anomaly-flows.d.ts +23 -0
- package/dist/src/flows/anomaly-flows.js +151 -0
- package/dist/src/flows/city-sweep-flow.d.ts +10 -0
- package/dist/src/flows/city-sweep-flow.js +127 -0
- package/dist/src/flows/login-flow.d.ts +13 -0
- package/dist/src/flows/login-flow.js +145 -0
- package/dist/src/flows/pradar-flows.d.ts +5 -0
- package/dist/src/flows/pradar-flows.js +375 -0
- package/dist/src/flows/weather-flow.d.ts +12 -0
- package/dist/src/flows/weather-flow.js +150 -0
- package/dist/src/lib/pathParams.d.ts +3 -0
- package/dist/src/lib/pathParams.js +6 -0
- package/dist/src/nodes/anomaly.d.ts +2 -0
- package/dist/src/nodes/anomaly.js +556 -0
- package/dist/src/nodes/flow-control.d.ts +7 -0
- package/dist/src/nodes/flow-control.js +270 -0
- package/dist/src/nodes/index.d.ts +3 -0
- package/dist/src/nodes/index.js +20 -0
- package/dist/src/nodes/login.d.ts +7 -0
- package/dist/src/nodes/login.js +158 -0
- package/dist/src/nodes/loops.d.ts +11 -0
- package/dist/src/nodes/loops.js +60 -0
- package/dist/src/nodes/pradar/index.d.ts +7 -0
- package/dist/src/nodes/pradar/index.js +9 -0
- package/dist/src/nodes/pradar/logic.d.ts +189 -0
- package/dist/src/nodes/pradar/logic.js +370 -0
- package/dist/src/nodes/pradar/nodes.d.ts +42 -0
- package/dist/src/nodes/pradar/nodes.js +801 -0
- package/dist/src/nodes/requireAuth.d.ts +20 -0
- package/dist/src/nodes/requireAuth.js +48 -0
- package/dist/src/nodes/response.d.ts +8 -0
- package/dist/src/nodes/response.js +33 -0
- package/dist/src/nodes/weather.d.ts +33 -0
- package/dist/src/nodes/weather.js +260 -0
- package/package.json +92 -0
- package/server/agentRoute.test.ts +157 -0
- package/server/agents/__fixtures__/fake-claude-writer.mjs +23 -0
- package/server/agents/__fixtures__/fake-claude.mjs +55 -0
- package/server/agents/__fixtures__/fake-codex-writer.mjs +27 -0
- package/server/agents/__fixtures__/fake-codex.mjs +52 -0
- package/server/agents/__fixtures__/fake-version-empty.mjs +3 -0
- package/server/agents/__fixtures__/fake-version-fail.mjs +4 -0
- package/server/agents/__fixtures__/fake-version-garbage.mjs +4 -0
- package/server/agents/__fixtures__/fake-version-good.mjs +4 -0
- package/server/agents/__fixtures__/fake-version-hang.mjs +3 -0
- package/server/agents/claudeAdapter.test.ts +127 -0
- package/server/agents/claudeAdapter.ts +212 -0
- package/server/agents/claudeParse.ts +193 -0
- package/server/agents/codexAdapter.test.ts +125 -0
- package/server/agents/codexAdapter.ts +198 -0
- package/server/agents/codexParse.test.ts +58 -0
- package/server/agents/codexParse.ts +126 -0
- package/server/agents/detect.test.ts +65 -0
- package/server/agents/detect.ts +43 -0
- package/server/agents/gitScope.test.ts +129 -0
- package/server/agents/gitScope.ts +110 -0
- package/server/agents/modelRejectionHint.test.ts +30 -0
- package/server/agents/modelRejectionHint.ts +14 -0
- package/server/agents/prompt.test.ts +613 -0
- package/server/agents/prompt.ts +527 -0
- package/server/agents/runManager.test.ts +251 -0
- package/server/agents/runManager.ts +286 -0
- package/server/agents/types.ts +17 -0
- package/server/apiMount.test.ts +591 -0
- package/server/apiStore.test.ts +324 -0
- package/server/apiStore.ts +241 -0
- package/server/auth/enforce.test.ts +61 -0
- package/server/auth/enforce.ts +6 -0
- package/server/auth/types.ts +5 -0
- package/server/auth/verifiers.test.ts +37 -0
- package/server/auth/verifiers.ts +9 -0
- package/server/authAttach.test.ts +175 -0
- package/server/authAttach.ts +99 -0
- package/server/cli.ts +847 -0
- package/server/cliAuth.test.ts +229 -0
- package/server/cliCreate.test.ts +131 -0
- package/server/cliDoctor.test.ts +326 -0
- package/server/cliTest.test.ts +735 -0
- package/server/client.ts +314 -0
- package/server/demoProject.test.ts +29 -0
- package/server/diagnosticsRoute.test.ts +169 -0
- package/server/environments.test.ts +620 -0
- package/server/environments.ts +603 -0
- package/server/environmentsRoute.test.ts +385 -0
- package/server/errorWorkflow.test.ts +205 -0
- package/server/flowStore.test.ts +84 -0
- package/server/flowStore.ts +125 -0
- package/server/httpOperations.test.ts +377 -0
- package/server/httpOperations.ts +96 -0
- package/server/index.ts +1111 -0
- package/server/infrastructure.test.ts +154 -0
- package/server/infrastructure.ts +161 -0
- package/server/infrastructureRoute.test.ts +105 -0
- package/server/login.test.ts +153 -0
- package/server/login.ts +67 -0
- package/server/mcp.ts +278 -0
- package/server/migrateFlows.test.ts +55 -0
- package/server/migrateFlows.ts +23 -0
- package/server/mockHandler.test.ts +138 -0
- package/server/mockHandler.ts +52 -0
- package/server/mockRun.test.ts +367 -0
- package/server/mockServe.test.ts +467 -0
- package/server/nodesPayload.test.ts +29 -0
- package/server/nodesPayload.ts +26 -0
- package/server/normalizeFlow.test.ts +80 -0
- package/server/normalizeFlow.ts +59 -0
- package/server/openBrowser.test.ts +20 -0
- package/server/openBrowser.ts +22 -0
- package/server/operationResult.test.ts +67 -0
- package/server/operationResult.ts +27 -0
- package/server/pathGuard.test.ts +19 -0
- package/server/pathGuard.ts +12 -0
- package/server/pathSafety.test.ts +38 -0
- package/server/pathSafety.ts +33 -0
- package/server/projectConfig.test.ts +67 -0
- package/server/projectConfig.ts +101 -0
- package/server/projectMode.test.ts +106 -0
- package/server/projectMode.ts +59 -0
- package/server/redact.test.ts +88 -0
- package/server/redact.ts +35 -0
- package/server/redactEgress.test.ts +281 -0
- package/server/runRegistry.ts +270 -0
- package/server/runsAuth.test.ts +119 -0
- package/server/runtime/RuntimeAdapter.ts +24 -0
- package/server/runtime/expressAdapter.test.ts +38 -0
- package/server/runtime/expressAdapter.ts +70 -0
- package/server/scenarioTest.test.ts +148 -0
- package/server/scenarioTest.ts +88 -0
- package/server/setupStatus.test.ts +127 -0
- package/server/subflowRunner.ts +104 -0
- package/server/testRunner.ts +216 -0
- package/server/tsconfig.json +20 -0
- package/server/workflowTestRoute.test.ts +297 -0
- package/src/App.tsx +233 -0
- package/src/components/AgentConsole.tsx +461 -0
- package/src/components/BranchRulesEditor.tsx +156 -0
- package/src/components/CreateModal.tsx +275 -0
- package/src/components/Dock.tsx +487 -0
- package/src/components/EnvironmentDialog.tsx +569 -0
- package/src/components/EnvironmentPicker.tsx +357 -0
- package/src/components/EnvironmentsDialog.tsx +144 -0
- package/src/components/ExecutionPager.tsx +67 -0
- package/src/components/InfraPanel.test.tsx +103 -0
- package/src/components/InfraPanel.tsx +171 -0
- package/src/components/Inspector.tsx +1182 -0
- package/src/components/Json.tsx +50 -0
- package/src/components/JsonModal.tsx +52 -0
- package/src/components/LogMessage.tsx +61 -0
- package/src/components/NodeRunModal.tsx +213 -0
- package/src/components/RunLogPanel.tsx +397 -0
- package/src/components/RunbookView.tsx +1085 -0
- package/src/components/Scenarios.tsx +476 -0
- package/src/components/SettingsDialog.tsx +229 -0
- package/src/components/Sidebar.tsx +436 -0
- package/src/components/StatusBar.tsx +302 -0
- package/src/components/Toolbar.tsx +663 -0
- package/src/components/WelcomeDialog.test.tsx +107 -0
- package/src/components/WelcomeDialog.tsx +412 -0
- package/src/components/agentMarkdown.test.tsx +91 -0
- package/src/components/agentMarkdown.tsx +158 -0
- package/src/components/ui/badge.tsx +31 -0
- package/src/components/ui/button.tsx +46 -0
- package/src/components/ui/combobox.tsx +114 -0
- package/src/components/ui/command.tsx +82 -0
- package/src/components/ui/dialog.tsx +73 -0
- package/src/components/ui/input.tsx +20 -0
- package/src/components/ui/popover.tsx +32 -0
- package/src/components/ui/resizable.tsx +44 -0
- package/src/components/ui/tabs.tsx +36 -0
- package/src/config.ts +4 -0
- package/src/engine/authVerify.ts +81 -0
- package/src/engine/diagnostics.test.ts +437 -0
- package/src/engine/diagnostics.ts +211 -0
- package/src/engine/executor.test.ts +1339 -0
- package/src/engine/executor.ts +840 -0
- package/src/engine/httpError.test.ts +12 -0
- package/src/engine/httpError.ts +16 -0
- package/src/engine/index.ts +11 -0
- package/src/engine/output.ts +21 -0
- package/src/engine/publish.test.ts +176 -0
- package/src/engine/publish.ts +127 -0
- package/src/engine/registry.test.ts +64 -0
- package/src/engine/registry.ts +80 -0
- package/src/engine/scenarios.test.ts +55 -0
- package/src/engine/scenarios.ts +29 -0
- package/src/engine/schemaCheck.test.ts +21 -0
- package/src/engine/schemaCheck.ts +35 -0
- package/src/engine/trace.test.ts +19 -0
- package/src/engine/trace.ts +23 -0
- package/src/engine/types.test.ts +22 -0
- package/src/engine/types.ts +316 -0
- package/src/engine/validation.test.ts +314 -0
- package/src/engine/validation.ts +354 -0
- package/src/flows/anomaly-flows.test.ts +81 -0
- package/src/flows/anomaly-flows.ts +202 -0
- package/src/flows/city-sweep-flow.ts +131 -0
- package/src/flows/flows.test.ts +30 -0
- package/src/flows/login-flow.ts +150 -0
- package/src/flows/pradar-flows.test.ts +204 -0
- package/src/flows/pradar-flows.ts +419 -0
- package/src/flows/weather-flow.ts +154 -0
- package/src/globals.css +147 -0
- package/src/lib/pathParams.test.ts +24 -0
- package/src/lib/pathParams.ts +6 -0
- package/src/lib/registerLens.test.ts +79 -0
- package/src/lib/registerLens.ts +59 -0
- package/src/lib/runbookModel.test.ts +61 -0
- package/src/lib/runbookModel.ts +290 -0
- package/src/lib/runbookProjection.test.ts +417 -0
- package/src/lib/runbookProjection.ts +254 -0
- package/src/lib/useTailAnchor.ts +53 -0
- package/src/lib/utils.ts +7 -0
- package/src/main.tsx +13 -0
- package/src/nodes/anomaly.test.ts +98 -0
- package/src/nodes/anomaly.ts +637 -0
- package/src/nodes/effects-scan.test.ts +44 -0
- package/src/nodes/flow-control.test.ts +280 -0
- package/src/nodes/flow-control.ts +305 -0
- package/src/nodes/index.ts +22 -0
- package/src/nodes/login.test.ts +81 -0
- package/src/nodes/login.ts +186 -0
- package/src/nodes/loops.ts +69 -0
- package/src/nodes/pradar/index.ts +11 -0
- package/src/nodes/pradar/logic.test.ts +288 -0
- package/src/nodes/pradar/logic.ts +582 -0
- package/src/nodes/pradar/nodes.test.ts +146 -0
- package/src/nodes/pradar/nodes.ts +955 -0
- package/src/nodes/requireAuth.test.ts +75 -0
- package/src/nodes/requireAuth.ts +55 -0
- package/src/nodes/response.test.ts +33 -0
- package/src/nodes/response.ts +39 -0
- package/src/nodes/traceDetail.test.ts +39 -0
- package/src/nodes/weather.test.ts +65 -0
- package/src/nodes/weather.ts +306 -0
- package/src/store/agentClient.test.ts +103 -0
- package/src/store/agentClient.ts +131 -0
- package/src/store/apiTree.test.ts +17 -0
- package/src/store/apiTree.ts +73 -0
- package/src/store/builderStore.test.ts +1234 -0
- package/src/store/builderStore.ts +1879 -0
- package/src/store/infraClient.ts +63 -0
- package/src/store/nodeMeta.test.ts +24 -0
- package/src/store/nodeMeta.ts +21 -0
- package/src/store/persistence.test.ts +15 -0
- package/src/store/persistence.ts +54 -0
- package/src/store/serverRunner.ts +381 -0
- package/src/store/setupClient.ts +57 -0
- package/studio-dist/assets/ibm-plex-mono-cyrillic-400-normal-BSMlKf0J.woff2 +0 -0
- package/studio-dist/assets/ibm-plex-mono-cyrillic-400-normal-CEL4l2ZJ.woff +0 -0
- package/studio-dist/assets/ibm-plex-mono-cyrillic-500-normal-Ael50iVv.woff +0 -0
- package/studio-dist/assets/ibm-plex-mono-cyrillic-500-normal-Bq9vWWag.woff2 +0 -0
- package/studio-dist/assets/ibm-plex-mono-cyrillic-ext-400-normal-DMdlQ8Kv.woff +0 -0
- package/studio-dist/assets/ibm-plex-mono-cyrillic-ext-400-normal-xuaO2J-f.woff2 +0 -0
- package/studio-dist/assets/ibm-plex-mono-cyrillic-ext-500-normal-BIfNGwUT.woff +0 -0
- package/studio-dist/assets/ibm-plex-mono-cyrillic-ext-500-normal-BqneJy0T.woff2 +0 -0
- package/studio-dist/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
- package/studio-dist/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
- package/studio-dist/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
- package/studio-dist/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
- package/studio-dist/assets/ibm-plex-mono-latin-ext-400-normal-BmRBH3aV.woff2 +0 -0
- package/studio-dist/assets/ibm-plex-mono-latin-ext-400-normal-D3D2R8hC.woff +0 -0
- package/studio-dist/assets/ibm-plex-mono-latin-ext-500-normal-CAhNIIs5.woff2 +0 -0
- package/studio-dist/assets/ibm-plex-mono-latin-ext-500-normal-CZ70TYgx.woff +0 -0
- package/studio-dist/assets/ibm-plex-mono-vietnamese-400-normal-BulugwFq.woff2 +0 -0
- package/studio-dist/assets/ibm-plex-mono-vietnamese-400-normal-DDuiU_S-.woff +0 -0
- package/studio-dist/assets/ibm-plex-mono-vietnamese-500-normal-C8zxqsMH.woff +0 -0
- package/studio-dist/assets/ibm-plex-mono-vietnamese-500-normal-DZ4AoWbu.woff2 +0 -0
- package/studio-dist/assets/index-DNJwW-hM.css +1 -0
- package/studio-dist/assets/index-O26dKRjW.js +65 -0
- package/studio-dist/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
- package/studio-dist/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
- package/studio-dist/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
- package/studio-dist/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
- package/studio-dist/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
- package/studio-dist/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
- package/studio-dist/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
- package/studio-dist/favicon.svg +1 -0
- package/studio-dist/icons.svg +24 -0
- package/studio-dist/index.html +14 -0
- package/templates/skills/emberflow-basics/SKILL.md +316 -0
- package/templates/skills/emberflow-model-process/SKILL.md +209 -0
- package/templates/skills/emberflow-new-workflow/SKILL.md +211 -0
- package/templates/skills/emberflow-review-workflow/SKILL.md +148 -0
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
export type FieldType = 'string' | 'number' | 'boolean' | 'enum' | 'datetime' | 'object' | 'array';
|
|
2
|
+
export interface FieldDefinition {
|
|
3
|
+
name: string;
|
|
4
|
+
type: FieldType;
|
|
5
|
+
required?: boolean;
|
|
6
|
+
enumValues?: string[];
|
|
7
|
+
description?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface SchemaDefinition {
|
|
10
|
+
fields: FieldDefinition[];
|
|
11
|
+
}
|
|
12
|
+
export interface NodeDefinition {
|
|
13
|
+
type: string;
|
|
14
|
+
label: string;
|
|
15
|
+
description?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Plain-language one-liner for non-developers, shown in the simple register
|
|
18
|
+
* (runbook idle text, simple Inspector description) instead of `description`,
|
|
19
|
+
* which may lead with mechanism (PORT NOTE, file paths, implementation
|
|
20
|
+
* detail). Falls back to the first sentence of `description` when absent.
|
|
21
|
+
*/
|
|
22
|
+
simpleDescription?: string;
|
|
23
|
+
category?: string;
|
|
24
|
+
inputSchema?: SchemaDefinition;
|
|
25
|
+
outputSchema?: SchemaDefinition;
|
|
26
|
+
configSchema?: SchemaDefinition;
|
|
27
|
+
icon?: string;
|
|
28
|
+
tags?: string[];
|
|
29
|
+
/**
|
|
30
|
+
* Side-effect class. 'mutation' nodes write state someone else can observe
|
|
31
|
+
* (DB writes, emails, external POSTs that create/change resources) and are
|
|
32
|
+
* dry-run under safe mode. Absent = 'read' (default).
|
|
33
|
+
*/
|
|
34
|
+
effects?: 'read' | 'mutation';
|
|
35
|
+
/**
|
|
36
|
+
* What kind of work this node does, for the runbook's technical register. Pure
|
|
37
|
+
* documentation — the engine ignores it. 'db' = queries Postgres, 'http' =
|
|
38
|
+
* calls an external service over HTTP, 'llm' = calls a language model,
|
|
39
|
+
* 'compute' = pure in-process logic. Absent when unknown (an absent badge is
|
|
40
|
+
* honest; a wrong one is not).
|
|
41
|
+
*/
|
|
42
|
+
traceKind?: 'db' | 'http' | 'llm' | 'compute';
|
|
43
|
+
/**
|
|
44
|
+
* A short, STATIC technical descriptor of the concrete call this node makes,
|
|
45
|
+
* for the runbook's technical register (the trace badge hover card). Pure
|
|
46
|
+
* documentation — the engine ignores it. For 'db' the query shape (e.g.
|
|
47
|
+
* 'SELECT … FROM projects JOIN users … WHERE p.id = $1'); for 'http' the
|
|
48
|
+
* method + endpoint (e.g. 'POST https://oauth2.googleapis.com/token'); for
|
|
49
|
+
* 'llm' the provider + model (e.g. 'openrouter.ai · x-ai/grok-4.3:online').
|
|
50
|
+
* Describes the mechanism, NOT runtime values. Absent on 'compute' nodes and
|
|
51
|
+
* whenever the target is dynamic enough that no honest static shape exists.
|
|
52
|
+
*/
|
|
53
|
+
traceDetail?: string;
|
|
54
|
+
}
|
|
55
|
+
export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
|
|
56
|
+
/** Outcome of running a child workflow synchronously via a Subflow node. */
|
|
57
|
+
export interface SubflowResult {
|
|
58
|
+
status: 'succeeded' | 'failed';
|
|
59
|
+
output?: unknown;
|
|
60
|
+
error?: string;
|
|
61
|
+
}
|
|
62
|
+
export interface NodeExecutionContext {
|
|
63
|
+
input: Record<string, unknown>;
|
|
64
|
+
config: Record<string, unknown>;
|
|
65
|
+
/** Runner-supplied secrets; empty in browser execution. */
|
|
66
|
+
secrets: Record<string, string>;
|
|
67
|
+
/** Non-secret environment values (API base URLs, seed ids, flags). Empty in browser execution. */
|
|
68
|
+
vars: Record<string, string>;
|
|
69
|
+
/** The environment name this run targets (e.g. 'prod'); undefined in browser/in-tab runs. */
|
|
70
|
+
environment?: string;
|
|
71
|
+
/** When true, mutation nodes must dry-run their side effects instead of executing them. */
|
|
72
|
+
safeMode: boolean;
|
|
73
|
+
/** The payload this run was invoked with (emberflow.run(name, input)). */
|
|
74
|
+
runInput: Record<string, unknown>;
|
|
75
|
+
log: (level: LogLevel, message: string) => void;
|
|
76
|
+
/**
|
|
77
|
+
* Runs a child workflow to completion and returns its collected output.
|
|
78
|
+
* Provided by the host (browser store / server run registry); absent when the
|
|
79
|
+
* run has no way to look up other workflows, in which case a Subflow node
|
|
80
|
+
* fails with a clear message.
|
|
81
|
+
*/
|
|
82
|
+
runSubflow?: (workflowId: string, input: Record<string, unknown>) => Promise<SubflowResult>;
|
|
83
|
+
}
|
|
84
|
+
export type NodeImplementation = (ctx: NodeExecutionContext) => Promise<unknown>;
|
|
85
|
+
export interface FieldMapping {
|
|
86
|
+
sourceNodeId: string;
|
|
87
|
+
/** Dot path into the source node's output. '$' means the entire output. */
|
|
88
|
+
sourceField: string;
|
|
89
|
+
transform?: string;
|
|
90
|
+
}
|
|
91
|
+
export interface WorkflowNode {
|
|
92
|
+
id: string;
|
|
93
|
+
type: string;
|
|
94
|
+
label: string;
|
|
95
|
+
position: {
|
|
96
|
+
x: number;
|
|
97
|
+
y: number;
|
|
98
|
+
};
|
|
99
|
+
config: Record<string, unknown>;
|
|
100
|
+
inputMap?: Record<string, FieldMapping>;
|
|
101
|
+
/**
|
|
102
|
+
* Fail-soft flag. When true, this node throwing marks it `failed` but does
|
|
103
|
+
* NOT abort the run: the cursor continues, so independent branches still run
|
|
104
|
+
* and the run can finish `succeeded`. The failed node produces no output, so
|
|
105
|
+
* its downstream consumers become unreachable (dead edges) and skip — exactly
|
|
106
|
+
* like a live "mirror" step whose failure must not break the deterministic
|
|
107
|
+
* path beside it. A non-optional node's failure still aborts the run.
|
|
108
|
+
*
|
|
109
|
+
* Applies to standard node execution. A ForEach region's own setup failure is
|
|
110
|
+
* NOT fail-soft (the flag is ignored there); mark ordinary/Subflow nodes.
|
|
111
|
+
*/
|
|
112
|
+
optional?: boolean;
|
|
113
|
+
/**
|
|
114
|
+
* Retry the implementation call on throw. `maxTries` is the total number of
|
|
115
|
+
* attempts including the first (so `maxTries: 3` means up to 2 retries).
|
|
116
|
+
* `waitMs` (default 0) is a fixed delay between attempts. Only the
|
|
117
|
+
* `implementation({...})` call is retried — config/input resolution runs
|
|
118
|
+
* once, and only the final attempt's outcome is recorded (state, trace,
|
|
119
|
+
* fail-soft). A non-object value or `maxTries < 1` is treated as absent.
|
|
120
|
+
*/
|
|
121
|
+
retry?: {
|
|
122
|
+
maxTries: number;
|
|
123
|
+
waitMs?: number;
|
|
124
|
+
};
|
|
125
|
+
/** Builder-owned extras (e.g. pinnedOutput). The engine never reads this. */
|
|
126
|
+
metadata?: Record<string, unknown>;
|
|
127
|
+
}
|
|
128
|
+
export interface WorkflowEdge {
|
|
129
|
+
id: string;
|
|
130
|
+
source: string;
|
|
131
|
+
target: string;
|
|
132
|
+
sourceHandle?: string;
|
|
133
|
+
targetHandle?: string;
|
|
134
|
+
label?: string;
|
|
135
|
+
}
|
|
136
|
+
export interface ScenarioDefinition {
|
|
137
|
+
id: string;
|
|
138
|
+
name: string;
|
|
139
|
+
description?: string;
|
|
140
|
+
/** Run payload, same shape as StartRunOptions.input. For an operation (a flow
|
|
141
|
+
* with `http`), this is the request shape `{ params?, query?, body?, headers? }`
|
|
142
|
+
* — an in-studio "Run" of a scenario passes this object as `input` through the
|
|
143
|
+
* same `RunRegistry.create` path a live HTTP request builds and passes, so the
|
|
144
|
+
* two are parity-equivalent (see server/httpOperations.test.ts). For a non-HTTP
|
|
145
|
+
* (internal) flow, `input` is used as-is — unchanged behavior. */
|
|
146
|
+
input: Record<string, unknown>;
|
|
147
|
+
/** Optional assertion the scenario's run must satisfy. Consumed by `emberflow
|
|
148
|
+
* test` (evaluateExpectation in server/scenarioTest.ts) and mock mode; a
|
|
149
|
+
* scenario without `expect` is a fixture only (not assertable). */
|
|
150
|
+
expect?: ScenarioExpectation;
|
|
151
|
+
/** nodeId -> canned output (verbatim, same shape the implementation would return).
|
|
152
|
+
* Consulted only when the run has `mockRun: true`; merges over the operation-level
|
|
153
|
+
* mocks map (scenario wins per nodeId). Plumbing (reading/merging these into a run)
|
|
154
|
+
* is a later increment — this is type-only for now. */
|
|
155
|
+
mocks?: Record<string, unknown>;
|
|
156
|
+
}
|
|
157
|
+
/** Assertion checked against a scenario's run + its flow's response mapping.
|
|
158
|
+
* `body` is a deep-subset match (see evaluateExpectation): every expected key
|
|
159
|
+
* must match recursively, arrays compare index-wise on the expected prefix,
|
|
160
|
+
* extra actual keys are fine, and `null` matches only `null`. */
|
|
161
|
+
export interface ScenarioExpectation {
|
|
162
|
+
/** Expected HTTP status of the extracted response (default response is 200). */
|
|
163
|
+
status?: number;
|
|
164
|
+
/** Expected response body, matched as a deep subset of the actual body. */
|
|
165
|
+
body?: unknown;
|
|
166
|
+
/** Node ids expected to have run to `succeeded`. */
|
|
167
|
+
executedNodes?: string[];
|
|
168
|
+
}
|
|
169
|
+
/** An inheritable auth policy: scheme + secret ref + optional named verifier.
|
|
170
|
+
* Set at the API/folder level via `_meta.json` (inherited by descendants) or
|
|
171
|
+
* overridden per-operation via `HttpTrigger.auth`. Lives here (not in
|
|
172
|
+
* server/auth/types.ts) because src/ must not import from server/; server/auth/types.ts
|
|
173
|
+
* re-exports this type. */
|
|
174
|
+
export interface AuthPolicy {
|
|
175
|
+
scheme: 'bearer' | 'apiKey';
|
|
176
|
+
/** Name of the secret (in the run environment's secrets) holding the expected token/key. */
|
|
177
|
+
secretRef: string;
|
|
178
|
+
/** Optional named custom verifier; when set, overrides the built-in scheme verifier. */
|
|
179
|
+
verify?: string;
|
|
180
|
+
/** Header to read for apiKey (default 'x-api-key'); bearer always reads Authorization. */
|
|
181
|
+
header?: string;
|
|
182
|
+
}
|
|
183
|
+
/** HTTP trigger metadata for an operation. Routing is a later increment; this
|
|
184
|
+
* increment only stores/round-trips it. */
|
|
185
|
+
export interface HttpTrigger {
|
|
186
|
+
method: string;
|
|
187
|
+
path: string;
|
|
188
|
+
inputSchema?: unknown;
|
|
189
|
+
/** Effective auth policy for this operation. 'none' makes it explicitly
|
|
190
|
+
* public (overriding any inherited _meta.json policy); 'inherit' (or
|
|
191
|
+
* absent) defers to the nearest ancestor _meta.json's policy, if any. */
|
|
192
|
+
auth?: AuthPolicy | 'none' | 'inherit';
|
|
193
|
+
}
|
|
194
|
+
export interface WorkflowDefinition {
|
|
195
|
+
id: string;
|
|
196
|
+
name: string;
|
|
197
|
+
version: number;
|
|
198
|
+
/** Optional sidebar folder path (single level, e.g. "Anomaly Detection"). */
|
|
199
|
+
folder?: string;
|
|
200
|
+
/** Present → this flow is (or will be) an HTTP endpoint. Absent → internal sub-flow. */
|
|
201
|
+
http?: HttpTrigger;
|
|
202
|
+
/**
|
|
203
|
+
* Preferred environment name. When set and the runner offers a matching
|
|
204
|
+
* environment, opening this flow auto-selects it — so a flow that targets
|
|
205
|
+
* prod infrastructure doesn't silently run against local.
|
|
206
|
+
*/
|
|
207
|
+
environment?: string;
|
|
208
|
+
nodes: WorkflowNode[];
|
|
209
|
+
edges: WorkflowEdge[];
|
|
210
|
+
/** Named test inputs ("storybook" scenarios). Dev fixtures — stripped on publish. */
|
|
211
|
+
scenarios?: ScenarioDefinition[];
|
|
212
|
+
/** Op-level nodeId -> canned output (verbatim), consulted only when a run
|
|
213
|
+
* has `mockRun: true`. Merges under any per-scenario `mocks` (scenario
|
|
214
|
+
* wins per nodeId — see `ScenarioDefinition.mocks`). Lives in the
|
|
215
|
+
* `<op>.scenarios.json` sidecar alongside `scenarios`, exposed here by
|
|
216
|
+
* the store that loads it (e.g. `ApiStore`). Dev fixture — stripped on
|
|
217
|
+
* publish, same as `scenarios`. */
|
|
218
|
+
mocks?: Record<string, unknown>;
|
|
219
|
+
viewport?: {
|
|
220
|
+
x: number;
|
|
221
|
+
y: number;
|
|
222
|
+
zoom: number;
|
|
223
|
+
};
|
|
224
|
+
createdAt: string;
|
|
225
|
+
updatedAt: string;
|
|
226
|
+
}
|
|
227
|
+
export type NodeStatus = 'idle' | 'queued' | 'running' | 'succeeded' | 'failed' | 'skipped';
|
|
228
|
+
export type RunStatus = 'running' | 'succeeded' | 'failed' | 'cancelled';
|
|
229
|
+
/** One execution of a node within a loop region — one entry per iteration. */
|
|
230
|
+
export interface ExecutionRecord {
|
|
231
|
+
iteration: {
|
|
232
|
+
index: number;
|
|
233
|
+
total: number;
|
|
234
|
+
};
|
|
235
|
+
input?: unknown;
|
|
236
|
+
output?: unknown;
|
|
237
|
+
error?: string;
|
|
238
|
+
status: 'succeeded' | 'failed';
|
|
239
|
+
}
|
|
240
|
+
export interface NodeRunState {
|
|
241
|
+
status: NodeStatus;
|
|
242
|
+
input?: unknown;
|
|
243
|
+
output?: unknown;
|
|
244
|
+
error?: string;
|
|
245
|
+
startedAt?: string;
|
|
246
|
+
completedAt?: string;
|
|
247
|
+
/** True when this node's output came from a pin instead of execution. */
|
|
248
|
+
pinned?: boolean;
|
|
249
|
+
/** True when a mutation-declared node completed under safe mode (its writes were dry-run). */
|
|
250
|
+
mutationBlocked?: boolean;
|
|
251
|
+
/**
|
|
252
|
+
* Present on a ForEach node and its loop-body nodes while a loop region is
|
|
253
|
+
* iterating (and left at its last value once the loop completes), giving
|
|
254
|
+
* the 0-based position and total iteration count.
|
|
255
|
+
*/
|
|
256
|
+
iteration?: {
|
|
257
|
+
index: number;
|
|
258
|
+
total: number;
|
|
259
|
+
};
|
|
260
|
+
/**
|
|
261
|
+
* Full per-iteration execution history, present on a ForEach node and its
|
|
262
|
+
* loop-body nodes, ordered by iteration. Absent on non-loop executions.
|
|
263
|
+
*/
|
|
264
|
+
executions?: ExecutionRecord[];
|
|
265
|
+
/**
|
|
266
|
+
* Total number of implementation-call attempts (including the first),
|
|
267
|
+
* present only when the node has a `retry` and used more than one attempt
|
|
268
|
+
* to reach its final outcome (success or exhausted failure). Absent
|
|
269
|
+
* whenever a single attempt sufficed — including nodes without `retry` at
|
|
270
|
+
* all — so existing single-try consumers see no shape change.
|
|
271
|
+
*/
|
|
272
|
+
attempts?: number;
|
|
273
|
+
/** True when this node's output is a canned mock value from a mock run, not the result of executing its implementation. */
|
|
274
|
+
mocked?: boolean;
|
|
275
|
+
}
|
|
276
|
+
export interface WorkflowRun {
|
|
277
|
+
id: string;
|
|
278
|
+
workflowId: string;
|
|
279
|
+
status: RunStatus;
|
|
280
|
+
startedAt: string;
|
|
281
|
+
completedAt?: string;
|
|
282
|
+
/** The environment this run pointed at (from StartRunOptions.environment). */
|
|
283
|
+
environment?: string;
|
|
284
|
+
/** Whether safe mode was active for this run. */
|
|
285
|
+
safeMode?: boolean;
|
|
286
|
+
nodeStates: Record<string, NodeRunState>;
|
|
287
|
+
}
|
|
288
|
+
export interface LogLine {
|
|
289
|
+
timestamp: string;
|
|
290
|
+
level: LogLevel;
|
|
291
|
+
runId: string;
|
|
292
|
+
nodeId?: string;
|
|
293
|
+
nodeLabel?: string;
|
|
294
|
+
message: string;
|
|
295
|
+
}
|
|
296
|
+
export interface NodeExecutionSample {
|
|
297
|
+
id: string;
|
|
298
|
+
workflowId: string;
|
|
299
|
+
runId: string;
|
|
300
|
+
nodeId: string;
|
|
301
|
+
nodeType: string;
|
|
302
|
+
nodeLabel: string;
|
|
303
|
+
input: unknown;
|
|
304
|
+
output?: unknown;
|
|
305
|
+
status: 'succeeded' | 'failed';
|
|
306
|
+
startedAt: string;
|
|
307
|
+
completedAt?: string;
|
|
308
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { WorkflowDefinition } from './types.js';
|
|
2
|
+
import type { NodeRegistry } from './registry.js';
|
|
3
|
+
export interface ValidationIssue {
|
|
4
|
+
severity: 'error' | 'warning';
|
|
5
|
+
nodeId?: string;
|
|
6
|
+
message: string;
|
|
7
|
+
}
|
|
8
|
+
export interface LoopRegion {
|
|
9
|
+
forEachId: string;
|
|
10
|
+
collectId: string;
|
|
11
|
+
/** Loop-body node ids: downstream(forEach) ∩ upstream(collect), excluding both. */
|
|
12
|
+
bodyIds: string[];
|
|
13
|
+
}
|
|
14
|
+
export declare function topoSort(flow: WorkflowDefinition): string[];
|
|
15
|
+
/** All nodes upstream of nodeId (direct parents and further ancestors). */
|
|
16
|
+
export declare function upstreamNodeIds(flow: WorkflowDefinition, nodeId: string): Set<string>;
|
|
17
|
+
/** All nodes downstream of nodeId (direct children and further descendants). */
|
|
18
|
+
export declare function downstreamNodeIds(flow: WorkflowDefinition, nodeId: string): Set<string>;
|
|
19
|
+
/**
|
|
20
|
+
* Computes ForEach/Collect loop regions for a flow. Only returns regions for
|
|
21
|
+
* valid pairings (1:1 ForEach↔Collect, no leaking edges, not nested) — an
|
|
22
|
+
* invalid flow fails validateFlow and startRun already throws before this
|
|
23
|
+
* would be consulted.
|
|
24
|
+
*/
|
|
25
|
+
export declare function computeLoopRegions(flow: WorkflowDefinition): LoopRegion[];
|
|
26
|
+
export declare function validateFlow(flow: WorkflowDefinition, registry: NodeRegistry): ValidationIssue[];
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
export function topoSort(flow) {
|
|
2
|
+
const indegree = new Map();
|
|
3
|
+
const adjacency = new Map();
|
|
4
|
+
for (const n of flow.nodes) {
|
|
5
|
+
indegree.set(n.id, 0);
|
|
6
|
+
adjacency.set(n.id, []);
|
|
7
|
+
}
|
|
8
|
+
for (const e of flow.edges) {
|
|
9
|
+
if (!adjacency.has(e.source) || !indegree.has(e.target))
|
|
10
|
+
continue;
|
|
11
|
+
adjacency.get(e.source).push(e.target);
|
|
12
|
+
indegree.set(e.target, indegree.get(e.target) + 1);
|
|
13
|
+
}
|
|
14
|
+
const queue = flow.nodes.filter((n) => indegree.get(n.id) === 0).map((n) => n.id);
|
|
15
|
+
const order = [];
|
|
16
|
+
while (queue.length > 0) {
|
|
17
|
+
const id = queue.shift();
|
|
18
|
+
order.push(id);
|
|
19
|
+
for (const next of adjacency.get(id)) {
|
|
20
|
+
indegree.set(next, indegree.get(next) - 1);
|
|
21
|
+
if (indegree.get(next) === 0)
|
|
22
|
+
queue.push(next);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (order.length !== flow.nodes.length)
|
|
26
|
+
throw new Error('Flow contains a cycle');
|
|
27
|
+
return order;
|
|
28
|
+
}
|
|
29
|
+
/** All nodes upstream of nodeId (direct parents and further ancestors). */
|
|
30
|
+
export function upstreamNodeIds(flow, nodeId) {
|
|
31
|
+
const incoming = new Map();
|
|
32
|
+
for (const e of flow.edges) {
|
|
33
|
+
if (!incoming.has(e.target))
|
|
34
|
+
incoming.set(e.target, []);
|
|
35
|
+
incoming.get(e.target).push(e.source);
|
|
36
|
+
}
|
|
37
|
+
const seen = new Set();
|
|
38
|
+
const queue = [...(incoming.get(nodeId) ?? [])];
|
|
39
|
+
while (queue.length > 0) {
|
|
40
|
+
const id = queue.shift();
|
|
41
|
+
if (seen.has(id))
|
|
42
|
+
continue;
|
|
43
|
+
seen.add(id);
|
|
44
|
+
queue.push(...(incoming.get(id) ?? []));
|
|
45
|
+
}
|
|
46
|
+
return seen;
|
|
47
|
+
}
|
|
48
|
+
/** All nodes downstream of nodeId (direct children and further descendants). */
|
|
49
|
+
export function downstreamNodeIds(flow, nodeId) {
|
|
50
|
+
const outgoing = new Map();
|
|
51
|
+
for (const e of flow.edges) {
|
|
52
|
+
if (!outgoing.has(e.source))
|
|
53
|
+
outgoing.set(e.source, []);
|
|
54
|
+
outgoing.get(e.source).push(e.target);
|
|
55
|
+
}
|
|
56
|
+
const seen = new Set();
|
|
57
|
+
const queue = [...(outgoing.get(nodeId) ?? [])];
|
|
58
|
+
while (queue.length > 0) {
|
|
59
|
+
const id = queue.shift();
|
|
60
|
+
if (seen.has(id))
|
|
61
|
+
continue;
|
|
62
|
+
seen.add(id);
|
|
63
|
+
queue.push(...(outgoing.get(id) ?? []));
|
|
64
|
+
}
|
|
65
|
+
return seen;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Matches ForEach nodes to a single downstream Collect (and vice versa) and
|
|
69
|
+
* computes each region's body + any edges that leak out of it. A ForEach/
|
|
70
|
+
* Collect that doesn't pair 1:1 is simply omitted here — validateFlow raises
|
|
71
|
+
* the specific "no Collect"/"multiple Collect"/etc. errors separately.
|
|
72
|
+
*/
|
|
73
|
+
function findLoopPairings(flow) {
|
|
74
|
+
const forEachNodes = flow.nodes.filter((n) => n.type === 'ForEach');
|
|
75
|
+
const collectNodes = flow.nodes.filter((n) => n.type === 'Collect');
|
|
76
|
+
const pairings = [];
|
|
77
|
+
for (const forEachNode of forEachNodes) {
|
|
78
|
+
const downstream = downstreamNodeIds(flow, forEachNode.id);
|
|
79
|
+
const collectsDownstream = collectNodes.filter((c) => downstream.has(c.id));
|
|
80
|
+
if (collectsDownstream.length !== 1)
|
|
81
|
+
continue;
|
|
82
|
+
const collectNode = collectsDownstream[0];
|
|
83
|
+
const upstream = upstreamNodeIds(flow, collectNode.id);
|
|
84
|
+
const forEachesUpstream = forEachNodes.filter((f) => upstream.has(f.id));
|
|
85
|
+
if (forEachesUpstream.length !== 1 || forEachesUpstream[0].id !== forEachNode.id)
|
|
86
|
+
continue;
|
|
87
|
+
const bodyIds = [...downstream].filter((id) => upstream.has(id) && id !== forEachNode.id && id !== collectNode.id);
|
|
88
|
+
const bodySet = new Set(bodyIds);
|
|
89
|
+
const leaks = flow.edges.filter((e) => bodySet.has(e.source) && !bodySet.has(e.target) && e.target !== collectNode.id);
|
|
90
|
+
pairings.push({ forEachNode, collectNode, bodyIds, leaks });
|
|
91
|
+
}
|
|
92
|
+
return pairings;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* ForEach node ids that sit downstream of another ForEach node — i.e. one
|
|
96
|
+
* region wraps the other. Deliberately independent of findLoopPairings: a
|
|
97
|
+
* genuinely nested pair almost always also fails the "exactly one Collect
|
|
98
|
+
* descendant" check for the outer ForEach (its downstream includes both the
|
|
99
|
+
* inner and outer Collect), which would otherwise prevent a pairing — and
|
|
100
|
+
* therefore prevent detecting the nesting — from ever being computed.
|
|
101
|
+
*/
|
|
102
|
+
function nestedForEachIds(flow) {
|
|
103
|
+
const forEachNodes = flow.nodes.filter((n) => n.type === 'ForEach');
|
|
104
|
+
const nested = new Set();
|
|
105
|
+
for (const outer of forEachNodes) {
|
|
106
|
+
const downstream = downstreamNodeIds(flow, outer.id);
|
|
107
|
+
for (const inner of forEachNodes) {
|
|
108
|
+
if (outer === inner)
|
|
109
|
+
continue;
|
|
110
|
+
if (downstream.has(inner.id)) {
|
|
111
|
+
nested.add(outer.id);
|
|
112
|
+
nested.add(inner.id);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return nested;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Computes ForEach/Collect loop regions for a flow. Only returns regions for
|
|
120
|
+
* valid pairings (1:1 ForEach↔Collect, no leaking edges, not nested) — an
|
|
121
|
+
* invalid flow fails validateFlow and startRun already throws before this
|
|
122
|
+
* would be consulted.
|
|
123
|
+
*/
|
|
124
|
+
export function computeLoopRegions(flow) {
|
|
125
|
+
const pairings = findLoopPairings(flow);
|
|
126
|
+
const nested = nestedForEachIds(flow);
|
|
127
|
+
return pairings
|
|
128
|
+
.filter((p) => p.leaks.length === 0 && !nested.has(p.forEachNode.id))
|
|
129
|
+
.map((p) => ({
|
|
130
|
+
forEachId: p.forEachNode.id,
|
|
131
|
+
collectId: p.collectNode.id,
|
|
132
|
+
bodyIds: p.bodyIds,
|
|
133
|
+
}));
|
|
134
|
+
}
|
|
135
|
+
export function validateFlow(flow, registry) {
|
|
136
|
+
const issues = [];
|
|
137
|
+
const nodeIds = new Set(flow.nodes.map((n) => n.id));
|
|
138
|
+
for (const node of flow.nodes) {
|
|
139
|
+
if (!registry.has(node.type)) {
|
|
140
|
+
issues.push({ severity: 'error', nodeId: node.id, message: `Unknown node type: ${node.type}` });
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
for (const edge of flow.edges) {
|
|
144
|
+
for (const end of [edge.source, edge.target]) {
|
|
145
|
+
if (!nodeIds.has(end)) {
|
|
146
|
+
issues.push({ severity: 'error', message: `Edge ${edge.id} references missing node: ${end}` });
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
try {
|
|
151
|
+
topoSort(flow);
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
issues.push({ severity: 'error', message: 'Flow contains a cycle' });
|
|
155
|
+
}
|
|
156
|
+
const ancestorsOf = (nodeId) => upstreamNodeIds(flow, nodeId);
|
|
157
|
+
for (const node of flow.nodes) {
|
|
158
|
+
for (const [field, mapping] of Object.entries(node.inputMap ?? {})) {
|
|
159
|
+
if (!nodeIds.has(mapping.sourceNodeId)) {
|
|
160
|
+
issues.push({
|
|
161
|
+
severity: 'error', nodeId: node.id,
|
|
162
|
+
message: `Input "${field}" maps to missing node: ${mapping.sourceNodeId}`,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
else if (!ancestorsOf(node.id).has(mapping.sourceNodeId)) {
|
|
166
|
+
issues.push({
|
|
167
|
+
severity: 'warning', nodeId: node.id,
|
|
168
|
+
message: `Input "${field}" maps to ${mapping.sourceNodeId}, which is not upstream of this node`,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
// A Subflow that names its own containing flow would recurse forever; the
|
|
173
|
+
// runtime guard catches cross-flow cycles, but a direct self-call is a
|
|
174
|
+
// static error.
|
|
175
|
+
if (node.type === 'Subflow' && node.config.workflowId === flow.id) {
|
|
176
|
+
issues.push({
|
|
177
|
+
severity: 'error', nodeId: node.id,
|
|
178
|
+
message: 'subflow cannot call its own flow',
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
if (!registry.has(node.type))
|
|
182
|
+
continue;
|
|
183
|
+
const definition = registry.get(node.type).definition;
|
|
184
|
+
const inputSchema = definition.inputSchema;
|
|
185
|
+
// Mutation nodes write observable state — warn so they can be reviewed
|
|
186
|
+
// before running against a protected environment; safe mode dry-runs them.
|
|
187
|
+
if (definition.effects === 'mutation') {
|
|
188
|
+
issues.push({
|
|
189
|
+
severity: 'warning',
|
|
190
|
+
nodeId: node.id,
|
|
191
|
+
message: `Mutation node "${node.label}" performs writes — dry-run under safe mode`,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
// Mappings to fields the node never declared are silently ignored by
|
|
195
|
+
// implementations — surface them.
|
|
196
|
+
const declared = new Set((inputSchema?.fields ?? []).map((f) => f.name));
|
|
197
|
+
for (const field of Object.keys(node.inputMap ?? {})) {
|
|
198
|
+
if (!declared.has(field)) {
|
|
199
|
+
issues.push({
|
|
200
|
+
severity: 'warning', nodeId: node.id,
|
|
201
|
+
message: `Input "${field}" is mapped but not declared in ${node.type}'s input schema`,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
for (const field of inputSchema?.fields ?? []) {
|
|
206
|
+
if (!field.required)
|
|
207
|
+
continue;
|
|
208
|
+
const mapped = node.inputMap?.[field.name] !== undefined;
|
|
209
|
+
const configured = node.config[field.name] !== undefined;
|
|
210
|
+
if (!mapped && !configured) {
|
|
211
|
+
issues.push({
|
|
212
|
+
severity: 'warning', nodeId: node.id,
|
|
213
|
+
message: `Required input "${field.name}" is not mapped or configured`,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
if (flow.scenarios && flow.scenarios.length > 0) {
|
|
219
|
+
const inputNode = flow.nodes.find((n) => n.type === 'Input');
|
|
220
|
+
if (!inputNode) {
|
|
221
|
+
issues.push({
|
|
222
|
+
severity: 'warning',
|
|
223
|
+
message: 'Flow has scenarios but no Input node — scenario input is never consumed',
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
const rawFields = inputNode.config?.fields;
|
|
228
|
+
const fields = Array.isArray(rawFields)
|
|
229
|
+
? rawFields.filter((f) => f !== null && typeof f === 'object' && typeof f.name === 'string')
|
|
230
|
+
: [];
|
|
231
|
+
const rawDefaults = inputNode.config?.defaults;
|
|
232
|
+
const defaults = rawDefaults !== null && typeof rawDefaults === 'object' && !Array.isArray(rawDefaults)
|
|
233
|
+
? rawDefaults
|
|
234
|
+
: {};
|
|
235
|
+
const declaredNames = new Set(fields.map((f) => f.name));
|
|
236
|
+
for (const scenario of flow.scenarios) {
|
|
237
|
+
for (const field of fields) {
|
|
238
|
+
if (!field.required)
|
|
239
|
+
continue;
|
|
240
|
+
if (Object.prototype.hasOwnProperty.call(defaults, field.name))
|
|
241
|
+
continue;
|
|
242
|
+
if (Object.prototype.hasOwnProperty.call(scenario.input, field.name))
|
|
243
|
+
continue;
|
|
244
|
+
issues.push({
|
|
245
|
+
severity: 'warning',
|
|
246
|
+
nodeId: inputNode.id,
|
|
247
|
+
message: `Scenario "${scenario.name}" omits required input field "${field.name}"`,
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
for (const key of Object.keys(scenario.input)) {
|
|
251
|
+
if (!declaredNames.has(key)) {
|
|
252
|
+
issues.push({
|
|
253
|
+
severity: 'warning',
|
|
254
|
+
nodeId: inputNode.id,
|
|
255
|
+
message: `Scenario "${scenario.name}" provides undeclared input field "${key}"`,
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
// ── ForEach/Collect loop regions ──────────────────────────────────
|
|
263
|
+
for (const n of flow.nodes) {
|
|
264
|
+
if (n.type !== 'ForEach')
|
|
265
|
+
continue;
|
|
266
|
+
const downstream = downstreamNodeIds(flow, n.id);
|
|
267
|
+
const collectsDownstream = flow.nodes.filter((c) => c.type === 'Collect' && downstream.has(c.id));
|
|
268
|
+
if (collectsDownstream.length === 0) {
|
|
269
|
+
issues.push({
|
|
270
|
+
severity: 'error', nodeId: n.id,
|
|
271
|
+
message: `ForEach "${n.id}" has no Collect node among its descendants`,
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
else if (collectsDownstream.length > 1) {
|
|
275
|
+
issues.push({
|
|
276
|
+
severity: 'error', nodeId: n.id,
|
|
277
|
+
message: `ForEach "${n.id}" has multiple Collect nodes among its descendants: ${collectsDownstream.map((c) => c.id).join(', ')}`,
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
for (const n of flow.nodes) {
|
|
282
|
+
if (n.type !== 'Collect')
|
|
283
|
+
continue;
|
|
284
|
+
const upstream = upstreamNodeIds(flow, n.id);
|
|
285
|
+
const forEachesUpstream = flow.nodes.filter((f) => f.type === 'ForEach' && upstream.has(f.id));
|
|
286
|
+
if (forEachesUpstream.length === 0) {
|
|
287
|
+
issues.push({
|
|
288
|
+
severity: 'error', nodeId: n.id,
|
|
289
|
+
message: `Collect "${n.id}" has no ForEach node among its ancestors`,
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
else if (forEachesUpstream.length > 1) {
|
|
293
|
+
issues.push({
|
|
294
|
+
severity: 'error', nodeId: n.id,
|
|
295
|
+
message: `Collect "${n.id}" has multiple ForEach nodes among its ancestors: ${forEachesUpstream.map((f) => f.id).join(', ')}`,
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
const pairings = findLoopPairings(flow);
|
|
300
|
+
for (const p of pairings) {
|
|
301
|
+
for (const edge of p.leaks) {
|
|
302
|
+
issues.push({
|
|
303
|
+
severity: 'error',
|
|
304
|
+
nodeId: edge.source,
|
|
305
|
+
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}"`,
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
const nested = nestedForEachIds(flow);
|
|
310
|
+
for (const forEachId of nested) {
|
|
311
|
+
issues.push({
|
|
312
|
+
severity: 'error',
|
|
313
|
+
nodeId: forEachId,
|
|
314
|
+
message: 'Nested ForEach regions are not supported',
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
return issues;
|
|
318
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { WorkflowDefinition } from '../engine/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Live call: whole-series detection, then branch on severity.
|
|
4
|
+
* build → detect → summarize → cond(anomalyCount > 0?)
|
|
5
|
+
* ├─ anomalous → composeIncident → incidentResult
|
|
6
|
+
* └─ clean → cleanResult
|
|
7
|
+
*
|
|
8
|
+
* Deviation from spec: the spec's Conditional has two rules (`gt 3 → P1`,
|
|
9
|
+
* `gt 0 → P2`) feeding three downstream paths, but this flow only ever had
|
|
10
|
+
* two downstream branches (anomalous/clean). Kept the original two-branch
|
|
11
|
+
* shape as a single rule `gt 0 → anomalous`, fallback `clean`, rather than
|
|
12
|
+
* inventing a third P1/P2 branch nothing here consumes.
|
|
13
|
+
*/
|
|
14
|
+
export declare function createDetectEntireFlow(): WorkflowDefinition;
|
|
15
|
+
/** Live call: streaming-style last-point verdict. */
|
|
16
|
+
export declare function createDetectLastFlow(): WorkflowDefinition;
|
|
17
|
+
/** Live call: detection + Adtributor root-cause analysis. */
|
|
18
|
+
export declare function createRootCauseFlow(): WorkflowDefinition;
|
|
19
|
+
/** Replica of today's quota/email business logic (Clerk-gated server-side). */
|
|
20
|
+
export declare function createQuotaFlow(): WorkflowDefinition;
|
|
21
|
+
/** Replica of today's key issuance + 5-key limit guard. */
|
|
22
|
+
export declare function createKeyLifecycleFlow(): WorkflowDefinition;
|
|
23
|
+
export declare function createAnomalyFlows(): WorkflowDefinition[];
|