@xyne/workflow-sdk 3.2.37 → 3.2.39

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 (253) hide show
  1. package/dist/agents/agent-step.d.ts +2 -2
  2. package/dist/agents/host-agent-step.d.ts +1 -1
  3. package/dist/builder/index.d.ts +2 -2
  4. package/dist/builder/index.d.ts.map +1 -1
  5. package/dist/builder/index.js +1 -1
  6. package/dist/builder/index.js.map +1 -1
  7. package/dist/client/types.d.ts +7 -5
  8. package/dist/client/types.d.ts.map +1 -1
  9. package/dist/client/workflow-client.d.ts.map +1 -1
  10. package/dist/client/workflow-client.js +7 -0
  11. package/dist/client/workflow-client.js.map +1 -1
  12. package/dist/common/citation-ref.d.ts +4 -4
  13. package/dist/common/credentials.d.ts +107 -0
  14. package/dist/common/credentials.d.ts.map +1 -1
  15. package/dist/common/credentials.js +67 -0
  16. package/dist/common/credentials.js.map +1 -1
  17. package/dist/common/index.d.ts +2 -2
  18. package/dist/common/index.d.ts.map +1 -1
  19. package/dist/common/index.js +2 -2
  20. package/dist/common/index.js.map +1 -1
  21. package/dist/connectors/builtin/builtin-connector.d.ts +33 -0
  22. package/dist/connectors/builtin/builtin-connector.d.ts.map +1 -0
  23. package/dist/connectors/builtin/builtin-connector.js +63 -0
  24. package/dist/connectors/builtin/builtin-connector.js.map +1 -0
  25. package/dist/connectors/builtin/credentials.d.ts +18 -0
  26. package/dist/connectors/builtin/credentials.d.ts.map +1 -0
  27. package/dist/connectors/builtin/credentials.js +50 -0
  28. package/dist/connectors/builtin/credentials.js.map +1 -0
  29. package/dist/{steps/builtin → connectors/builtin/steps}/code.step.d.ts +4 -4
  30. package/dist/connectors/builtin/steps/code.step.d.ts.map +1 -0
  31. package/dist/{steps/builtin → connectors/builtin/steps}/code.step.js +3 -3
  32. package/dist/connectors/builtin/steps/code.step.js.map +1 -0
  33. package/dist/{steps/builtin → connectors/builtin/steps}/conditional.step.d.ts +10 -10
  34. package/dist/connectors/builtin/steps/conditional.step.d.ts.map +1 -0
  35. package/dist/{steps/builtin → connectors/builtin/steps}/conditional.step.js +4 -4
  36. package/dist/connectors/builtin/steps/conditional.step.js.map +1 -0
  37. package/dist/{steps/builtin → connectors/builtin/steps}/dedup.step.d.ts +4 -4
  38. package/dist/connectors/builtin/steps/dedup.step.d.ts.map +1 -0
  39. package/dist/{steps/builtin → connectors/builtin/steps}/dedup.step.js +3 -3
  40. package/dist/connectors/builtin/steps/dedup.step.js.map +1 -0
  41. package/dist/{steps/builtin → connectors/builtin/steps}/http-request.step.d.ts +29 -22
  42. package/dist/connectors/builtin/steps/http-request.step.d.ts.map +1 -0
  43. package/dist/{steps/builtin → connectors/builtin/steps}/http-request.step.js +15 -9
  44. package/dist/connectors/builtin/steps/http-request.step.js.map +1 -0
  45. package/dist/{steps/builtin → connectors/builtin/steps}/loop.step.d.ts +4 -4
  46. package/dist/connectors/builtin/steps/loop.step.d.ts.map +1 -0
  47. package/dist/{steps/builtin → connectors/builtin/steps}/loop.step.js +3 -3
  48. package/dist/connectors/builtin/steps/loop.step.js.map +1 -0
  49. package/dist/{steps/builtin → connectors/builtin/steps}/map.step.d.ts +4 -4
  50. package/dist/connectors/builtin/steps/map.step.d.ts.map +1 -0
  51. package/dist/{steps/builtin → connectors/builtin/steps}/map.step.js +4 -4
  52. package/dist/connectors/builtin/steps/map.step.js.map +1 -0
  53. package/dist/{steps/builtin → connectors/builtin/steps}/parallel.step.d.ts +4 -4
  54. package/dist/connectors/builtin/steps/parallel.step.d.ts.map +1 -0
  55. package/dist/{steps/builtin → connectors/builtin/steps}/parallel.step.js +3 -3
  56. package/dist/connectors/builtin/steps/parallel.step.js.map +1 -0
  57. package/dist/connectors/builtin/steps/ssrf-guard.d.ts.map +1 -0
  58. package/dist/connectors/builtin/steps/ssrf-guard.js.map +1 -0
  59. package/dist/{steps/builtin → connectors/builtin/steps}/switch.step.d.ts +14 -14
  60. package/dist/connectors/builtin/steps/switch.step.d.ts.map +1 -0
  61. package/dist/{steps/builtin → connectors/builtin/steps}/switch.step.js +4 -4
  62. package/dist/connectors/builtin/steps/switch.step.js.map +1 -0
  63. package/dist/{steps/builtin → connectors/builtin/steps}/wait.step.d.ts +4 -4
  64. package/dist/connectors/builtin/steps/wait.step.d.ts.map +1 -0
  65. package/dist/{steps/builtin → connectors/builtin/steps}/wait.step.js +4 -4
  66. package/dist/connectors/builtin/steps/wait.step.js.map +1 -0
  67. package/dist/{triggers/builtin → connectors/builtin/triggers}/default-cron-trigger.d.ts +4 -4
  68. package/dist/connectors/builtin/triggers/default-cron-trigger.d.ts.map +1 -0
  69. package/dist/{triggers/builtin → connectors/builtin/triggers}/default-cron-trigger.js +3 -3
  70. package/dist/connectors/builtin/triggers/default-cron-trigger.js.map +1 -0
  71. package/dist/{triggers/builtin → connectors/builtin/triggers}/default-event-trigger.d.ts +3 -3
  72. package/dist/connectors/builtin/triggers/default-event-trigger.d.ts.map +1 -0
  73. package/dist/{triggers/builtin → connectors/builtin/triggers}/default-event-trigger.js +3 -3
  74. package/dist/connectors/builtin/triggers/default-event-trigger.js.map +1 -0
  75. package/dist/{triggers/builtin → connectors/builtin/triggers}/default-manual-trigger.d.ts +3 -3
  76. package/dist/connectors/builtin/triggers/default-manual-trigger.d.ts.map +1 -0
  77. package/dist/{triggers/builtin → connectors/builtin/triggers}/default-manual-trigger.js +3 -3
  78. package/dist/connectors/builtin/triggers/default-manual-trigger.js.map +1 -0
  79. package/dist/{triggers/builtin → connectors/builtin/triggers}/default-webhook-v2-trigger.d.ts +10 -8
  80. package/dist/connectors/builtin/triggers/default-webhook-v2-trigger.d.ts.map +1 -0
  81. package/dist/{triggers/builtin → connectors/builtin/triggers}/default-webhook-v2-trigger.js +10 -8
  82. package/dist/connectors/builtin/triggers/default-webhook-v2-trigger.js.map +1 -0
  83. package/dist/connectors/core/base-connector.d.ts +50 -0
  84. package/dist/connectors/core/base-connector.d.ts.map +1 -0
  85. package/dist/connectors/core/base-connector.js +8 -0
  86. package/dist/connectors/core/base-connector.js.map +1 -0
  87. package/dist/connectors/core/connector-registry.d.ts +130 -0
  88. package/dist/connectors/core/connector-registry.d.ts.map +1 -0
  89. package/dist/connectors/core/connector-registry.js +353 -0
  90. package/dist/connectors/core/connector-registry.js.map +1 -0
  91. package/dist/connectors/core/credentials.d.ts +104 -0
  92. package/dist/connectors/core/credentials.d.ts.map +1 -0
  93. package/dist/connectors/core/credentials.js +67 -0
  94. package/dist/connectors/core/credentials.js.map +1 -0
  95. package/dist/connectors/core/index.d.ts +7 -0
  96. package/dist/connectors/core/index.d.ts.map +1 -0
  97. package/dist/connectors/core/index.js +5 -0
  98. package/dist/connectors/core/index.js.map +1 -0
  99. package/dist/engine/available-context.d.ts +3 -4
  100. package/dist/engine/available-context.d.ts.map +1 -1
  101. package/dist/engine/available-context.js +17 -13
  102. package/dist/engine/available-context.js.map +1 -1
  103. package/dist/engine/config-validator.d.ts +13 -5
  104. package/dist/engine/config-validator.d.ts.map +1 -1
  105. package/dist/engine/config-validator.js +89 -11
  106. package/dist/engine/config-validator.js.map +1 -1
  107. package/dist/engine/credential-resolver.d.ts +42 -0
  108. package/dist/engine/credential-resolver.d.ts.map +1 -0
  109. package/dist/engine/credential-resolver.js +63 -0
  110. package/dist/engine/credential-resolver.js.map +1 -0
  111. package/dist/engine/workflow-executor.d.ts +4 -5
  112. package/dist/engine/workflow-executor.d.ts.map +1 -1
  113. package/dist/engine/workflow-executor.js +16 -38
  114. package/dist/engine/workflow-executor.js.map +1 -1
  115. package/dist/index.d.ts +43 -28
  116. package/dist/index.d.ts.map +1 -1
  117. package/dist/index.js +30 -15
  118. package/dist/index.js.map +1 -1
  119. package/dist/ingest/body.d.ts +18 -0
  120. package/dist/ingest/body.d.ts.map +1 -0
  121. package/dist/ingest/body.js +47 -0
  122. package/dist/ingest/body.js.map +1 -0
  123. package/dist/ingest/filter.d.ts +11 -0
  124. package/dist/ingest/filter.d.ts.map +1 -0
  125. package/dist/ingest/filter.js +23 -0
  126. package/dist/ingest/filter.js.map +1 -0
  127. package/dist/ingest/headers.d.ts +3 -0
  128. package/dist/ingest/headers.d.ts.map +1 -0
  129. package/dist/ingest/headers.js +11 -0
  130. package/dist/ingest/headers.js.map +1 -0
  131. package/dist/ingest/types.d.ts +50 -0
  132. package/dist/ingest/types.d.ts.map +1 -0
  133. package/dist/ingest/types.js +9 -0
  134. package/dist/ingest/types.js.map +1 -0
  135. package/dist/persistence/in-memory-adapter.d.ts +4 -8
  136. package/dist/persistence/in-memory-adapter.d.ts.map +1 -1
  137. package/dist/persistence/in-memory-adapter.js +8 -25
  138. package/dist/persistence/in-memory-adapter.js.map +1 -1
  139. package/dist/persistence/types.d.ts +5 -19
  140. package/dist/persistence/types.d.ts.map +1 -1
  141. package/dist/router/types.d.ts +31 -3
  142. package/dist/router/types.d.ts.map +1 -1
  143. package/dist/router/workflow-router.d.ts +4 -3
  144. package/dist/router/workflow-router.d.ts.map +1 -1
  145. package/dist/router/workflow-router.js +84 -41
  146. package/dist/router/workflow-router.js.map +1 -1
  147. package/dist/runtime/types.d.ts +6 -4
  148. package/dist/runtime/types.d.ts.map +1 -1
  149. package/dist/runtime/workflow-runtime.d.ts +46 -37
  150. package/dist/runtime/workflow-runtime.d.ts.map +1 -1
  151. package/dist/runtime/workflow-runtime.js +173 -133
  152. package/dist/runtime/workflow-runtime.js.map +1 -1
  153. package/dist/steps/base-step.d.ts +24 -0
  154. package/dist/steps/base-step.d.ts.map +1 -1
  155. package/dist/steps/base-step.js +23 -0
  156. package/dist/steps/base-step.js.map +1 -1
  157. package/dist/steps/{step-registry.d.ts → step-descriptor.d.ts} +24 -32
  158. package/dist/steps/step-descriptor.d.ts.map +1 -0
  159. package/dist/steps/step-descriptor.js +33 -0
  160. package/dist/steps/step-descriptor.js.map +1 -0
  161. package/dist/testing/authz-conformance.d.ts.map +1 -1
  162. package/dist/testing/authz-conformance.js +6 -7
  163. package/dist/testing/authz-conformance.js.map +1 -1
  164. package/dist/testing/index.d.ts +1 -0
  165. package/dist/testing/index.d.ts.map +1 -1
  166. package/dist/testing/index.js +1 -0
  167. package/dist/testing/index.js.map +1 -1
  168. package/dist/testing/mock-step-context.d.ts +1 -0
  169. package/dist/testing/mock-step-context.d.ts.map +1 -1
  170. package/dist/testing/mock-step-context.js +1 -0
  171. package/dist/testing/mock-step-context.js.map +1 -1
  172. package/dist/testing/test-connector.d.ts +40 -0
  173. package/dist/testing/test-connector.d.ts.map +1 -0
  174. package/dist/testing/test-connector.js +39 -0
  175. package/dist/testing/test-connector.js.map +1 -0
  176. package/dist/triggers/api-trigger.d.ts +15 -0
  177. package/dist/triggers/api-trigger.d.ts.map +1 -0
  178. package/dist/triggers/api-trigger.js +14 -0
  179. package/dist/triggers/api-trigger.js.map +1 -0
  180. package/dist/triggers/base-trigger.d.ts +30 -0
  181. package/dist/triggers/base-trigger.d.ts.map +1 -1
  182. package/dist/triggers/base-trigger.js +17 -0
  183. package/dist/triggers/base-trigger.js.map +1 -1
  184. package/dist/triggers/trigger-descriptor.d.ts +44 -0
  185. package/dist/triggers/trigger-descriptor.d.ts.map +1 -0
  186. package/dist/triggers/trigger-descriptor.js +30 -0
  187. package/dist/triggers/trigger-descriptor.js.map +1 -0
  188. package/dist/triggers/webhook-trigger.d.ts +48 -36
  189. package/dist/triggers/webhook-trigger.d.ts.map +1 -1
  190. package/dist/triggers/webhook-trigger.js +40 -6
  191. package/dist/triggers/webhook-trigger.js.map +1 -1
  192. package/dist/types/known-types.d.ts +0 -1
  193. package/dist/types/known-types.d.ts.map +1 -1
  194. package/dist/types/known-types.js +0 -1
  195. package/dist/types/known-types.js.map +1 -1
  196. package/dist/types/validation.d.ts +3 -1
  197. package/dist/types/validation.d.ts.map +1 -1
  198. package/dist/types/validation.js +2 -0
  199. package/dist/types/validation.js.map +1 -1
  200. package/dist/types/workflow-config.d.ts +23 -0
  201. package/dist/types/workflow-config.d.ts.map +1 -1
  202. package/dist/types/workflow-config.js +8 -0
  203. package/dist/types/workflow-config.js.map +1 -1
  204. package/dist/util/credential-slot-schema.d.ts +20 -0
  205. package/dist/util/credential-slot-schema.d.ts.map +1 -0
  206. package/dist/util/credential-slot-schema.js +30 -0
  207. package/dist/util/credential-slot-schema.js.map +1 -0
  208. package/package.json +1 -1
  209. package/dist/runtime/execution-event-bus.d.ts +0 -38
  210. package/dist/runtime/execution-event-bus.d.ts.map +0 -1
  211. package/dist/runtime/execution-event-bus.js +0 -87
  212. package/dist/runtime/execution-event-bus.js.map +0 -1
  213. package/dist/steps/builtin/code.step.d.ts.map +0 -1
  214. package/dist/steps/builtin/code.step.js.map +0 -1
  215. package/dist/steps/builtin/conditional.step.d.ts.map +0 -1
  216. package/dist/steps/builtin/conditional.step.js.map +0 -1
  217. package/dist/steps/builtin/dedup.step.d.ts.map +0 -1
  218. package/dist/steps/builtin/dedup.step.js.map +0 -1
  219. package/dist/steps/builtin/http-request.step.d.ts.map +0 -1
  220. package/dist/steps/builtin/http-request.step.js.map +0 -1
  221. package/dist/steps/builtin/loop.step.d.ts.map +0 -1
  222. package/dist/steps/builtin/loop.step.js.map +0 -1
  223. package/dist/steps/builtin/map.step.d.ts.map +0 -1
  224. package/dist/steps/builtin/map.step.js.map +0 -1
  225. package/dist/steps/builtin/parallel.step.d.ts.map +0 -1
  226. package/dist/steps/builtin/parallel.step.js.map +0 -1
  227. package/dist/steps/builtin/ssrf-guard.d.ts.map +0 -1
  228. package/dist/steps/builtin/ssrf-guard.js.map +0 -1
  229. package/dist/steps/builtin/switch.step.d.ts.map +0 -1
  230. package/dist/steps/builtin/switch.step.js.map +0 -1
  231. package/dist/steps/builtin/wait.step.d.ts.map +0 -1
  232. package/dist/steps/builtin/wait.step.js.map +0 -1
  233. package/dist/steps/step-registry.d.ts.map +0 -1
  234. package/dist/steps/step-registry.js +0 -103
  235. package/dist/steps/step-registry.js.map +0 -1
  236. package/dist/triggers/builtin/default-cron-trigger.d.ts.map +0 -1
  237. package/dist/triggers/builtin/default-cron-trigger.js.map +0 -1
  238. package/dist/triggers/builtin/default-event-trigger.d.ts.map +0 -1
  239. package/dist/triggers/builtin/default-event-trigger.js.map +0 -1
  240. package/dist/triggers/builtin/default-manual-trigger.d.ts.map +0 -1
  241. package/dist/triggers/builtin/default-manual-trigger.js.map +0 -1
  242. package/dist/triggers/builtin/default-webhook-trigger.d.ts +0 -72
  243. package/dist/triggers/builtin/default-webhook-trigger.d.ts.map +0 -1
  244. package/dist/triggers/builtin/default-webhook-trigger.js +0 -91
  245. package/dist/triggers/builtin/default-webhook-trigger.js.map +0 -1
  246. package/dist/triggers/builtin/default-webhook-v2-trigger.d.ts.map +0 -1
  247. package/dist/triggers/builtin/default-webhook-v2-trigger.js.map +0 -1
  248. package/dist/triggers/trigger-registry.d.ts +0 -48
  249. package/dist/triggers/trigger-registry.d.ts.map +0 -1
  250. package/dist/triggers/trigger-registry.js +0 -83
  251. package/dist/triggers/trigger-registry.js.map +0 -1
  252. /package/dist/{steps/builtin → connectors/builtin/steps}/ssrf-guard.d.ts +0 -0
  253. /package/dist/{steps/builtin → connectors/builtin/steps}/ssrf-guard.js +0 -0
@@ -18,10 +18,18 @@ import { validateTriggerPayload } from '../common/validate-payload.js';
18
18
  import { AccessDeniedError, ResourceNotFoundError } from '../authz/default.js';
19
19
  import { EventTrigger } from '../triggers/event-trigger.js';
20
20
  import { WebhookTrigger } from '../triggers/webhook-trigger.js';
21
+ import { ApiTrigger } from '../triggers/api-trigger.js';
22
+ import { CredentialResolver } from '../engine/credential-resolver.js';
23
+ import { headerValue } from '../ingest/headers.js';
24
+ import { parseInboundBody } from '../ingest/body.js';
25
+ import { matchesTriggerFilter } from '../ingest/filter.js';
21
26
  import { CronTrigger } from '../triggers/cron-trigger.js';
22
27
  import { PauseStep } from '../engine/pause-step.js';
23
28
  import { leafStepId } from '../engine/node-path.js';
24
29
  // ─── Helpers ───
30
+ function messageOf(err) {
31
+ return err instanceof Error ? err.message : String(err);
32
+ }
25
33
  const noopLogger = {
26
34
  info: () => { },
27
35
  warn: () => { },
@@ -32,6 +40,13 @@ function generateSecret() {
32
40
  crypto.getRandomValues(bytes);
33
41
  return Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');
34
42
  }
43
+ /** The trigger's author-supplied input schema, when it has one. */
44
+ function inputSchemaOf(config) {
45
+ const inputSchema = (config.trigger.config ?? {})['inputSchema'];
46
+ return inputSchema && typeof inputSchema === 'object' && !Array.isArray(inputSchema)
47
+ ? inputSchema
48
+ : undefined;
49
+ }
35
50
  function parseWorkflowConfig(raw) {
36
51
  return JSON.parse(raw ?? '{}');
37
52
  }
@@ -69,9 +84,9 @@ export class WorkflowRuntime {
69
84
  queue;
70
85
  scheduler;
71
86
  services;
72
- steps;
73
- triggers;
87
+ connectors;
74
88
  executor;
89
+ credentialResolver;
75
90
  log;
76
91
  baseUrl;
77
92
  /** Pub/sub transport for live execution events. Exposed for the router's SSE stream. */
@@ -95,9 +110,9 @@ export class WorkflowRuntime {
95
110
  this.queue = opts.queue;
96
111
  this.scheduler = opts.scheduler;
97
112
  this.services = opts.services;
98
- this.steps = opts.steps;
99
- this.triggers = opts.triggers;
113
+ this.connectors = opts.connectors;
100
114
  this.executor = opts.executor;
115
+ this.credentialResolver = new CredentialResolver(opts.persistence);
101
116
  this.log = opts.logger ?? noopLogger;
102
117
  this.eventBus = opts.eventBus;
103
118
  this.storage = opts.storage;
@@ -247,23 +262,26 @@ export class WorkflowRuntime {
247
262
  * can render inline in the workflow builder.
248
263
  */
249
264
  validateConfig(config) {
250
- const validator = new ConfigValidator(this.triggers, this.steps);
265
+ const validator = new ConfigValidator(this.connectors);
251
266
  return validator.validate(config);
252
267
  }
253
268
  // ─── Workflow Lifecycle ───
254
269
  /**
255
270
  * Activate a workflow's trigger.
256
271
  *
257
- * - WebhookTrigger: generates a unique path, stores it, calls onActivate()
272
+ * - Every trigger: its credential slots must resolve to accepted credentials
258
273
  * - CronTrigger: registers with the scheduler
259
- * - EventTrigger / ManualTrigger: no activation needed (status change only)
274
+ *
275
+ * Only then is the workflow marked ACTIVE. Webhook ingest and event dispatch
276
+ * both key off that status, so it is what actually turns a workflow on.
260
277
  */
261
278
  async activateWorkflow(ctx, workflowId, opts) {
262
279
  // Guard up front, before any external side-effects (webhook/scheduler).
263
280
  const { record } = await this.authorizedWorkflow(ctx, workflowId, 'workflow:activate');
264
281
  await this.activateInternal(record);
265
- // After the side-effects, never before: a webhook path that failed to store
266
- // is not a change worth telling anyone about.
282
+ await this.persistence.updateWorkflow(workflowId, { status: 'ACTIVE' });
283
+ // After the side-effects, never before: an activation that failed is not a
284
+ // change worth telling anyone about.
267
285
  this.publishWorkflowChange(workflowId, 'activated', opts?.origin);
268
286
  }
269
287
  /**
@@ -274,20 +292,16 @@ export class WorkflowRuntime {
274
292
  async activateInternal(workflow) {
275
293
  const workflowId = workflow.id;
276
294
  const config = parseWorkflowConfig(workflow.config);
277
- if (!this.triggers.has(config.trigger.type)) {
295
+ if (!this.connectors.hasTrigger(config.trigger.type)) {
278
296
  throw new Error(`Trigger type "${config.trigger.type}" is not registered`);
279
297
  }
280
- const trigger = this.triggers.get(config.trigger.type);
298
+ const trigger = this.connectors.getTrigger(config.trigger.type);
299
+ // A missing, revoked or wrong-type credential fails here, naming the slot,
300
+ // rather than on the first event.
301
+ if (trigger.credentialSlots?.length) {
302
+ await this.credentialResolver.resolve(workflow.attributes, trigger, config.trigger.config);
303
+ }
281
304
  if (trigger instanceof WebhookTrigger) {
282
- const secret = generateSecret();
283
- const path = `/webhooks/${workflowId}/${secret}`;
284
- await this.persistence.storeWebhookPath(workflowId, path, secret);
285
- if (trigger.onActivate) {
286
- await trigger.onActivate(config.trigger.config, {
287
- webhookUrl: this.baseUrl + path,
288
- credentials: {},
289
- });
290
- }
291
305
  this.log.info(`activated webhook trigger for workflow ${workflowId}`);
292
306
  }
293
307
  else if (trigger instanceof CronTrigger) {
@@ -310,24 +324,16 @@ export class WorkflowRuntime {
310
324
  // Falls through rather than returning early on an unregistered trigger type:
311
325
  // the call still succeeded, and every successful call must leave by the same
312
326
  // door so that none of them can quietly skip the notification below.
313
- const trigger = this.triggers.has(config.trigger.type)
314
- ? this.triggers.get(config.trigger.type)
327
+ const trigger = this.connectors.hasTrigger(config.trigger.type)
328
+ ? this.connectors.getTrigger(config.trigger.type)
315
329
  : null;
316
- if (trigger instanceof WebhookTrigger) {
317
- if (trigger.onDeactivate) {
318
- await trigger.onDeactivate(config.trigger.config, {
319
- webhookUrl: '',
320
- credentials: {},
321
- });
322
- }
323
- await this.persistence.removeWebhookPath(workflowId);
324
- this.log.info(`deactivated webhook trigger for workflow ${workflowId}`);
325
- }
326
- else if (trigger instanceof CronTrigger) {
330
+ if (trigger instanceof CronTrigger) {
327
331
  await this.scheduler.unschedule(workflowId);
328
332
  this.log.info(`deactivated cron trigger for workflow ${workflowId}`);
329
333
  }
330
- // EventTrigger, ManualTrigger and unregistered types need no teardown.
334
+ // Webhook, event and manual triggers need no teardown: webhook ingest and
335
+ // event dispatch only reach ACTIVE workflows, so the status below stops them.
336
+ await this.persistence.updateWorkflow(workflowId, { status: 'DISABLED' });
331
337
  this.publishWorkflowChange(workflowId, 'deactivated', opts?.origin);
332
338
  }
333
339
  // ─── Workflow callback secret ───
@@ -379,9 +385,9 @@ export class WorkflowRuntime {
379
385
  const executionIds = [];
380
386
  for (const workflow of workflows) {
381
387
  const config = parseWorkflowConfig(workflow.config);
382
- if (!this.triggers.has(config.trigger.type))
388
+ if (!this.connectors.hasTrigger(config.trigger.type))
383
389
  continue;
384
- const trigger = this.triggers.get(config.trigger.type);
390
+ const trigger = this.connectors.getTrigger(config.trigger.type);
385
391
  if (!(trigger instanceof EventTrigger))
386
392
  continue;
387
393
  // Hydrate lightweight payload → full context
@@ -399,6 +405,10 @@ export class WorkflowRuntime {
399
405
  if (!trigger.matchFilters(config.trigger.config, payload)) {
400
406
  continue;
401
407
  }
408
+ // And the author's filter, before a run exists.
409
+ if (!matchesTriggerFilter(config.trigger.filter, payload, config.variables)) {
410
+ continue;
411
+ }
402
412
  const execId = await this.createAndEnqueue(workflow, {
403
413
  type: event.type,
404
414
  ...payload,
@@ -408,43 +418,95 @@ export class WorkflowRuntime {
408
418
  return executionIds;
409
419
  }
410
420
  /**
411
- * Handle an incoming webhook request.
421
+ * Ingest a request to a workflow's webhook URL, `<baseUrl>/webhooks/<workflowId>`.
422
+ *
423
+ * The order is fixed, and the trigger's own code runs only after the sender is
424
+ * authenticated:
412
425
  *
413
- * Looks up the workflow by webhook path, delegates to the trigger's
414
- * `handleRequest()`, and enqueues an execution.
426
+ * 1. The workflow exists, is ACTIVE and uses a webhook trigger else 404, the
427
+ * same answer for each so workflow ids cannot be probed.
428
+ * 2. The trigger's credentials resolve — else 401, before any connector code runs.
429
+ * 3. The trigger's `handshake` may answer the request outright. It is the one
430
+ * connector hook that sees an unauthenticated request, so it can only reply,
431
+ * never start a run. A GET it does not answer gets 404: GET reaches nothing else.
432
+ * 4. The trigger's own `verify` accepts the request — else 401.
433
+ * 5. The body is parsed from the raw bytes — else 400.
434
+ * 6. The trigger turns the request into events and a reply.
435
+ * 7. Each event must pass the workflow's trigger filter, or it is dropped.
436
+ * 8. Survivors are enqueued before the reply is returned.
437
+ *
438
+ * Reasons for a rejection go to the log; responses stay generic.
415
439
  */
416
- async handleWebhook(path, request) {
417
- const webhookRecord = await this.persistence.getWebhookByPath(path);
418
- if (!webhookRecord) {
419
- return {
420
- executionId: '',
421
- httpResponse: { status: 404, body: { error: 'Webhook not found' } },
422
- };
440
+ async handleWebhook(workflowId, request) {
441
+ const notFound = {
442
+ reply: { status: 404, body: { error: 'Webhook not found' } },
443
+ executionIds: [],
444
+ };
445
+ const unauthorized = {
446
+ reply: { status: 401, body: { error: 'Unauthorized' } },
447
+ executionIds: [],
448
+ };
449
+ const workflow = await this.persistence.getWorkflow(workflowId);
450
+ if (!workflow || workflow.status !== 'ACTIVE')
451
+ return notFound;
452
+ const config = parseWorkflowConfig(workflow.config);
453
+ const trigger = this.connectors.findTrigger(config.trigger.type)?.trigger;
454
+ if (!(trigger instanceof WebhookTrigger))
455
+ return notFound;
456
+ const attributes = workflow.attributes;
457
+ const triggerConfig = config.trigger.config;
458
+ let ctx;
459
+ try {
460
+ const credentials = await this.credentialResolver.resolve(attributes, trigger, triggerConfig);
461
+ ctx = { credentials, attributes, baseUrl: this.baseUrl };
423
462
  }
424
- const workflow = await this.persistence.getWorkflow(webhookRecord.workflowId);
425
- if (!workflow || workflow.status !== 'ACTIVE') {
426
- return {
427
- executionId: '',
428
- httpResponse: { status: 404, body: { error: 'Workflow not active' } },
429
- };
463
+ catch (err) {
464
+ // Fail closed: verification never runs without the credentials it needs.
465
+ this.log.warn(`webhook ${workflowId}: rejected, ${messageOf(err)}`);
466
+ return unauthorized;
430
467
  }
431
- const config = parseWorkflowConfig(workflow.config);
432
- const trigger = this.triggers.get(config.trigger.type);
433
- if (!(trigger instanceof WebhookTrigger)) {
434
- return {
435
- executionId: '',
436
- httpResponse: {
437
- status: 400,
438
- body: { error: 'Workflow does not use a webhook trigger' },
439
- },
440
- };
468
+ let handshakeReply;
469
+ try {
470
+ handshakeReply = await trigger.handshake(request, triggerConfig, ctx);
441
471
  }
442
- const { triggerData, httpResponse } = await trigger.handleRequest(request, config.trigger.config);
443
- const execId = await this.createAndEnqueue(workflow, {
444
- type: config.trigger.type,
445
- ...triggerData,
446
- }, { publicTrigger: true });
447
- return { executionId: execId, httpResponse };
472
+ catch (err) {
473
+ // A broken handshake must not block signed deliveries: carry on to verification.
474
+ this.log.warn(`webhook ${workflowId}: handshake threw: ${messageOf(err)}`);
475
+ }
476
+ if (handshakeReply)
477
+ return { reply: handshakeReply, executionIds: [] };
478
+ if (request.method !== 'POST')
479
+ return notFound;
480
+ let verified = false;
481
+ try {
482
+ verified = await trigger.verify(request, triggerConfig, ctx);
483
+ }
484
+ catch (err) {
485
+ this.log.warn(`webhook ${workflowId}: verification threw: ${messageOf(err)}`);
486
+ }
487
+ if (!verified) {
488
+ this.log.warn(`webhook ${workflowId}: rejected by ${trigger.type} verification`);
489
+ return unauthorized;
490
+ }
491
+ let body;
492
+ try {
493
+ body = parseInboundBody(request.rawBody, headerValue(request.headers, 'content-type'));
494
+ }
495
+ catch (err) {
496
+ this.log.warn(`webhook ${workflowId}: rejected, ${messageOf(err)}`);
497
+ return { reply: { status: 400, body: { error: 'Malformed request body' } }, executionIds: [] };
498
+ }
499
+ const translation = await trigger.handleRequest({ ...request, body }, triggerConfig, ctx);
500
+ const executionIds = [];
501
+ for (const event of translation.events) {
502
+ if (!matchesTriggerFilter(config.trigger.filter, event.payload, config.variables))
503
+ continue;
504
+ executionIds.push(await this.createAndEnqueue(workflow, { type: event.type, ...event.payload }, { publicTrigger: true }));
505
+ }
506
+ return {
507
+ reply: translation.reply ?? { status: 200, body: { ok: true } },
508
+ executionIds,
509
+ };
448
510
  }
449
511
  /**
450
512
  * Handle an incoming wait-callback at `POST /wait/callback/<secret>/<executionId>`.
@@ -524,91 +586,67 @@ export class WorkflowRuntime {
524
586
  return this.createAndEnqueue(workflow, triggerPayload);
525
587
  }
526
588
  /**
527
- * Fire a workflow through the unauthenticated Webhook V2 API.
589
+ * Start a workflow from the API trigger route.
590
+ *
591
+ * No caller reaches the SDK here: the route has no session, and the SDK does
592
+ * not authorize whoever calls it. A host that exposes the route guards it
593
+ * itself, for example with an API key. The workflow must use an API trigger,
594
+ * and the payload must satisfy its input schema.
528
595
  *
529
- * This intentionally bypasses caller authorization because the host exposes
530
- * the route publicly. The workflow itself must opt in by using WEBHOOK_V2,
531
- * and the execution is marked as public so privileged steps can enforce their
532
- * own public-trigger restrictions.
596
+ * Runs are marked as public triggers, so privileged steps refuse them.
533
597
  */
534
- async triggerWebhookV2Public(workflowId, payload) {
535
- const workflow = await this.persistence.getWorkflow(workflowId);
536
- if (!workflow) {
537
- throw new Error(`Workflow ${workflowId} not found`);
538
- }
539
- const config = parseWorkflowConfig(workflow.config);
540
- if (config.trigger.type !== 'WEBHOOK_V2') {
541
- throw new Error(`Workflow ${workflowId} does not use a WEBHOOK_V2 trigger`);
542
- }
543
- const triggerCfg = (config.trigger.config ?? {});
544
- const inputSchema = triggerCfg['inputSchema'];
545
- const webhookV2InputSchema = inputSchema && Array.isArray(inputSchema['required'])
598
+ async triggerViaApi(workflowId, payload) {
599
+ const { workflow, config } = await this.apiTriggerWorkflow(workflowId);
600
+ const inputSchema = inputSchemaOf(config);
601
+ // Uploaded documents are optional on this route even when the schema lists
602
+ // them as required.
603
+ const apiInputSchema = inputSchema && Array.isArray(inputSchema['required'])
546
604
  ? {
547
605
  ...inputSchema,
548
606
  required: inputSchema['required'].filter((field) => field !== 'documents'),
549
607
  }
550
608
  : inputSchema;
551
- const errors = validateTriggerPayload(webhookV2InputSchema, payload);
609
+ const errors = validateTriggerPayload(apiInputSchema, payload);
552
610
  if (errors.length > 0) {
553
611
  throw new Error(`Invalid trigger payload: ${errors.map((e) => e.message).join('; ')}`);
554
612
  }
555
- return this.createAndEnqueue(workflow, {
556
- type: config.trigger.type,
557
- ...payload,
558
- }, { publicTrigger: true });
613
+ return this.createAndEnqueue(workflow, { type: config.trigger.type, ...payload }, { publicTrigger: true });
559
614
  }
560
615
  /**
561
- * Return the configured trigger input schema for routes that need to translate
562
- * transport-level payloads before calling `triggerManual()`.
616
+ * What the API trigger route needs before it builds a payload: the trigger's
617
+ * input schema, and the workflow's scope for storing uploaded files. Checked
618
+ * the same way as {@link triggerViaApi}.
563
619
  */
564
- async getWorkflowTriggerInputSchema(ctx, workflowId) {
565
- const workflow = await this.persistence.getWorkflow(workflowId);
566
- if (!workflow) {
567
- throw new Error(`Workflow ${workflowId} not found`);
568
- }
569
- await this.require(ctx, 'workflow:run', this.workflowRef(workflow));
570
- const config = parseWorkflowConfig(workflow.config);
571
- const triggerCfg = (config.trigger.config ?? {});
572
- const inputSchema = triggerCfg['inputSchema'];
573
- return inputSchema && typeof inputSchema === 'object' && !Array.isArray(inputSchema)
574
- ? inputSchema
575
- : undefined;
620
+ async getApiTriggerTarget(workflowId) {
621
+ const { workflow, config } = await this.apiTriggerWorkflow(workflowId);
622
+ return {
623
+ inputSchema: inputSchemaOf(config),
624
+ storageScope: workflow.attributes,
625
+ };
576
626
  }
577
- /**
578
- * Return trigger input schema for public transport routes.
579
- *
580
- * Unlike getWorkflowTriggerInputSchema(), this does not authorize a caller;
581
- * it only verifies the workflow exists and opted into the expected public
582
- * trigger type.
583
- */
584
- async getWorkflowTriggerInputSchemaPublic(workflowId, expectedTriggerType) {
627
+ /** The workflow exists and uses an API trigger. */
628
+ async apiTriggerWorkflow(workflowId) {
585
629
  const workflow = await this.persistence.getWorkflow(workflowId);
586
- if (!workflow) {
587
- throw new Error(`Workflow ${workflowId} not found`);
588
- }
630
+ if (!workflow)
631
+ throw new ResourceNotFoundError('Workflow', workflowId);
589
632
  const config = parseWorkflowConfig(workflow.config);
590
- if (config.trigger.type !== expectedTriggerType) {
591
- throw new Error(`Workflow ${workflowId} does not use a ${expectedTriggerType} trigger`);
633
+ const trigger = this.connectors.findTrigger(config.trigger.type)?.trigger;
634
+ if (!(trigger instanceof ApiTrigger)) {
635
+ throw new Error(`Workflow ${workflowId} does not use an API trigger`);
592
636
  }
593
- const triggerCfg = (config.trigger.config ?? {});
594
- const inputSchema = triggerCfg['inputSchema'];
595
- return inputSchema && typeof inputSchema === 'object' && !Array.isArray(inputSchema)
596
- ? inputSchema
597
- : undefined;
637
+ return { workflow, config };
598
638
  }
599
639
  /**
600
- * Return the workflow-owned storage scope for public transport routes.
640
+ * Return the configured trigger input schema for routes that need to translate
641
+ * transport-level payloads before calling `triggerManual()`.
601
642
  */
602
- async getWorkflowStorageScopePublic(workflowId, expectedTriggerType) {
643
+ async getWorkflowTriggerInputSchema(ctx, workflowId) {
603
644
  const workflow = await this.persistence.getWorkflow(workflowId);
604
645
  if (!workflow) {
605
646
  throw new Error(`Workflow ${workflowId} not found`);
606
647
  }
607
- const config = parseWorkflowConfig(workflow.config);
608
- if (config.trigger.type !== expectedTriggerType) {
609
- throw new Error(`Workflow ${workflowId} does not use a ${expectedTriggerType} trigger`);
610
- }
611
- return workflow.attributes;
648
+ await this.require(ctx, 'workflow:run', this.workflowRef(workflow));
649
+ return inputSchemaOf(parseWorkflowConfig(workflow.config));
612
650
  }
613
651
  /**
614
652
  * Resume a paused execution. Guards `execution:approve`, then does the work.
@@ -766,8 +804,10 @@ export class WorkflowRuntime {
766
804
  // Internal post-create activation — already authorized by the create above.
767
805
  await this.activateInternal(record);
768
806
  }
769
- catch {
770
- // Non-fatal — workflow is created but trigger may need manual activation
807
+ catch (err) {
808
+ // Non-fatal — the workflow is created, and its trigger can be activated
809
+ // once whatever failed is fixed.
810
+ this.log.warn(`created workflow ${id} but could not activate its trigger: ${messageOf(err)}`);
771
811
  }
772
812
  }
773
813
  this.log.info(`created workflow ${id} (${input.name})`);
@@ -1208,9 +1248,9 @@ export class WorkflowRuntime {
1208
1248
  if (!workflow || workflow.status !== 'ACTIVE')
1209
1249
  return null;
1210
1250
  const config = parseWorkflowConfig(workflow.config);
1211
- if (!this.triggers.has(config.trigger.type))
1251
+ if (!this.connectors.hasTrigger(config.trigger.type))
1212
1252
  return null;
1213
- const trigger = this.triggers.get(config.trigger.type);
1253
+ const trigger = this.connectors.getTrigger(config.trigger.type);
1214
1254
  if (!(trigger instanceof CronTrigger))
1215
1255
  return null;
1216
1256
  const metadata = JSON.parse(workflow.metadata ?? '{}');