From 3161740824d7efde1e3541ddc4d70412818c4298 Mon Sep 17 00:00:00 2001 From: Shohan RAHMAN Date: Wed, 24 Jun 2026 15:18:36 +0200 Subject: [PATCH 1/4] feat(workflow-executor): propagate run triggerType for observability Add a triggerType ("manual" | "webhook") field to the orchestrator run envelope and carry it through to step execution. Log it when a run starts executing for correlation/debugging. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../adapters/run-to-available-step-mapper.ts | 2 ++ .../src/adapters/server-types.ts | 7 +++++++ packages/workflow-executor/src/runner.ts | 7 +++++++ .../src/types/validated/execution.ts | 4 ++++ .../run-to-available-step-mapper.test.ts | 19 +++++++++++++++++++ .../integration/workflow-execution.test.ts | 2 ++ .../workflow-executor/test/runner.test.ts | 1 + 7 files changed, 42 insertions(+) diff --git a/packages/workflow-executor/src/adapters/run-to-available-step-mapper.ts b/packages/workflow-executor/src/adapters/run-to-available-step-mapper.ts index ba90c1a671..ffb9b43e9a 100644 --- a/packages/workflow-executor/src/adapters/run-to-available-step-mapper.ts +++ b/packages/workflow-executor/src/adapters/run-to-available-step-mapper.ts @@ -13,6 +13,7 @@ import type { import { z } from 'zod'; +import { ServerWorkflowTriggerType } from './server-types'; import { deserializeRecordId } from './record-id-serializer'; import toStepDefinition from './step-definition-mapper'; import { @@ -157,6 +158,7 @@ export default function toAvailableStepExecution( stepId: pending.stepName, stepIndex: pending.stepIndex, collectionId: run.collectionId, + triggerType: run.triggerType ?? ServerWorkflowTriggerType.manual, baseRecordRef: { collectionName: run.collectionName, recordId: deserializeRecordId(run.selectedRecordId), diff --git a/packages/workflow-executor/src/adapters/server-types.ts b/packages/workflow-executor/src/adapters/server-types.ts index f0d56a802a..54bbdeb5cf 100644 --- a/packages/workflow-executor/src/adapters/server-types.ts +++ b/packages/workflow-executor/src/adapters/server-types.ts @@ -179,6 +179,12 @@ export interface ServerStepHistory { /** Mirror of the server's `WorkflowRunState` enum (workflow-run-model.ts). */ export type ServerWorkflowRunState = 'started' | 'pending' | 'loading' | 'aborted' | 'finished'; +/** Mirror of the server's workflow run `triggerType` enum. Optional: older orchestrators omit it. */ +export enum ServerWorkflowTriggerType { + manual = 'manual', + webhook = 'webhook', +} + export interface ServerHydratedWorkflowRun { id: number; workflowId: string; @@ -187,6 +193,7 @@ export interface ServerHydratedWorkflowRun { selectedRecordId: string; bpmnVersion: string; runState: ServerWorkflowRunState; + triggerType?: ServerWorkflowTriggerType; workflowHistory: ServerStepHistory[]; /** Server types declare `Date`; Express serializes to ISO 8601 string on the wire. */ createdAt: string; diff --git a/packages/workflow-executor/src/runner.ts b/packages/workflow-executor/src/runner.ts index ae07bb9e04..1b4a6a867d 100644 --- a/packages/workflow-executor/src/runner.ts +++ b/packages/workflow-executor/src/runner.ts @@ -302,6 +302,13 @@ export default class Runner { let chainedCount = 0; // additional steps chained after the initial one const maxDepth = this.config.maxChainDepth ?? DEFAULT_MAX_CHAIN_DEPTH; + this.logger('Debug', 'Run started executing', { + runId: step.runId, + stepId: step.stepId, + stepIndex: step.stepIndex, + triggerType: step.triggerType, + }); + // Sequential by design: each step's outcome drives the next dispatch; steps within one run // cannot overlap. The no-await-in-loop rule doesn't apply here. /* eslint-disable no-await-in-loop, no-constant-condition */ diff --git a/packages/workflow-executor/src/types/validated/execution.ts b/packages/workflow-executor/src/types/validated/execution.ts index cca43350bb..b1d6e4bd03 100644 --- a/packages/workflow-executor/src/types/validated/execution.ts +++ b/packages/workflow-executor/src/types/validated/execution.ts @@ -31,12 +31,16 @@ export const StepSchema = z .strict(); export type Step = z.infer; +export const TriggerTypeSchema = z.enum(['manual', 'webhook']); +export type TriggerType = z.infer; + export const AvailableStepExecutionSchema = z .object({ runId: z.string().min(1), stepId: z.string().min(1), stepIndex: z.number().int().nonnegative(), collectionId: z.string().min(1), + triggerType: TriggerTypeSchema, baseRecordRef: RecordRefSchema, stepDefinition: StepDefinitionSchema, previousSteps: z.array(StepSchema), diff --git a/packages/workflow-executor/test/adapters/run-to-available-step-mapper.test.ts b/packages/workflow-executor/test/adapters/run-to-available-step-mapper.test.ts index 4b8cd06ff9..d36ca83b77 100644 --- a/packages/workflow-executor/test/adapters/run-to-available-step-mapper.test.ts +++ b/packages/workflow-executor/test/adapters/run-to-available-step-mapper.test.ts @@ -13,6 +13,7 @@ import { ServerStepExecutionTypeEnum, ServerStepTypeEnum, ServerTaskTypeEnum, + ServerWorkflowTriggerType, } from '../../src/adapters/server-types'; import { DomainValidationError, InvalidStepDefinitionError } from '../../src/errors'; import { StepType } from '../../src/types/validated/step-definition'; @@ -108,6 +109,7 @@ describe('toAvailableStepExecution', () => { stepId: 'step-a', stepIndex: 0, collectionId: '11', + triggerType: 'manual', baseRecordRef: { collectionName: 'customers', recordId: ['123'], @@ -124,6 +126,23 @@ describe('toAvailableStepExecution', () => { }); }); + it('should forward the run triggerType', () => { + const run = makeRun({ triggerType: ServerWorkflowTriggerType.webhook }); + + const result = toAvailableStepExecution(run); + + expect(result?.triggerType).toBe('webhook'); + }); + + it('should default triggerType to manual when the orchestrator omits it', () => { + const run = makeRun(); + delete run.triggerType; + + const result = toAvailableStepExecution(run); + + expect(result?.triggerType).toBe('manual'); + }); + it('should stringify the numeric run id', () => { const run = makeRun({ id: 999 }); diff --git a/packages/workflow-executor/test/integration/workflow-execution.test.ts b/packages/workflow-executor/test/integration/workflow-execution.test.ts index f4c38b460e..2e0bad4fe3 100644 --- a/packages/workflow-executor/test/integration/workflow-execution.test.ts +++ b/packages/workflow-executor/test/integration/workflow-execution.test.ts @@ -227,6 +227,7 @@ function buildPendingStep( stepId: 'step-1', stepIndex: 0, collectionId: 'col-1', + triggerType: 'manual', baseRecordRef: BASE_RECORD_REF, previousSteps: [], user: STEP_USER, @@ -248,6 +249,7 @@ describe('workflow execution (integration)', () => { stepId: 'step-1', stepIndex: 0, collectionId: 'col-1', + triggerType: 'manual', baseRecordRef: { collectionName: 'customers', recordId: [42], stepIndex: 0 }, stepDefinition: { type: StepType.ReadRecord, prompt: 'Read the customer email' }, previousSteps: [], diff --git a/packages/workflow-executor/test/runner.test.ts b/packages/workflow-executor/test/runner.test.ts index 29f24abce5..4fc30d0325 100644 --- a/packages/workflow-executor/test/runner.test.ts +++ b/packages/workflow-executor/test/runner.test.ts @@ -161,6 +161,7 @@ function makePendingStep( stepId: 'step-1', stepIndex: 0, collectionId: 'col-1', + triggerType: 'manual', baseRecordRef: { collectionName: 'customers', recordId: ['1'], stepIndex: 0 }, stepDefinition: makeStepDefinition(stepType), previousSteps: [], From 9348acef319c3b2b06c429396b59e301176b2b24 Mon Sep 17 00:00:00 2001 From: Shohan RAHMAN Date: Wed, 24 Jun 2026 15:33:18 +0200 Subject: [PATCH 2/4] fix(workflow-executor): order imports in run-to-available-step-mapper Co-Authored-By: Claude Opus 4.8 (1M context) --- .../src/adapters/run-to-available-step-mapper.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/workflow-executor/src/adapters/run-to-available-step-mapper.ts b/packages/workflow-executor/src/adapters/run-to-available-step-mapper.ts index ffb9b43e9a..36d0d0e4ae 100644 --- a/packages/workflow-executor/src/adapters/run-to-available-step-mapper.ts +++ b/packages/workflow-executor/src/adapters/run-to-available-step-mapper.ts @@ -13,8 +13,8 @@ import type { import { z } from 'zod'; -import { ServerWorkflowTriggerType } from './server-types'; import { deserializeRecordId } from './record-id-serializer'; +import { ServerWorkflowTriggerType } from './server-types'; import toStepDefinition from './step-definition-mapper'; import { DomainValidationError, From c71c96d0330fd60eaf368259d24f3f33275ccc03 Mon Sep 17 00:00:00 2001 From: Shohan RAHMAN Date: Wed, 24 Jun 2026 16:40:52 +0200 Subject: [PATCH 3/4] refactor(workflow-executor): use TriggerType enum instead of raw strings Introduce a domain TriggerType enum (decoupled from the wire ServerWorkflowTriggerType, mirroring the StepType pattern) and reference enum members everywhere instead of repeating the 'manual'/'webhook' literals. Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/workflow-executor/src/index.ts | 2 ++ .../workflow-executor/src/types/execution-context.ts | 1 + .../workflow-executor/src/types/validated/execution.ts | 9 +++++++-- .../test/adapters/run-to-available-step-mapper.test.ts | 7 ++++--- .../test/integration/workflow-execution.test.ts | 5 +++-- packages/workflow-executor/test/runner.test.ts | 3 ++- 6 files changed, 19 insertions(+), 8 deletions(-) diff --git a/packages/workflow-executor/src/index.ts b/packages/workflow-executor/src/index.ts index 7fa349a083..1ca27c0c83 100644 --- a/packages/workflow-executor/src/index.ts +++ b/packages/workflow-executor/src/index.ts @@ -58,6 +58,8 @@ export type { ExecutionContext, } from './types/execution-context'; +export { TriggerType } from './types/execution-context'; + export type { AgentPort, ExecuteActionQuery, diff --git a/packages/workflow-executor/src/types/execution-context.ts b/packages/workflow-executor/src/types/execution-context.ts index 70d965a09f..b2caff74b9 100644 --- a/packages/workflow-executor/src/types/execution-context.ts +++ b/packages/workflow-executor/src/types/execution-context.ts @@ -11,6 +11,7 @@ import type { BaseChatModel } from '@forestadmin/ai-proxy'; // Re-export the runtime result types alongside the context they flow with. export type { AvailableStepExecution, Step, StepUser }; +export { TriggerType } from './validated/execution'; export interface StepExecutionResult { stepOutcome: StepOutcome; diff --git a/packages/workflow-executor/src/types/validated/execution.ts b/packages/workflow-executor/src/types/validated/execution.ts index b1d6e4bd03..aff4edaa8e 100644 --- a/packages/workflow-executor/src/types/validated/execution.ts +++ b/packages/workflow-executor/src/types/validated/execution.ts @@ -31,8 +31,13 @@ export const StepSchema = z .strict(); export type Step = z.infer; -export const TriggerTypeSchema = z.enum(['manual', 'webhook']); -export type TriggerType = z.infer; +// Domain enum for what triggered the run. Decoupled from the server contract +// (`ServerWorkflowTriggerType`) — `run-to-available-step-mapper.ts` is the single translation point. +export enum TriggerType { + Manual = 'manual', + Webhook = 'webhook', +} +export const TriggerTypeSchema = z.nativeEnum(TriggerType); export const AvailableStepExecutionSchema = z .object({ diff --git a/packages/workflow-executor/test/adapters/run-to-available-step-mapper.test.ts b/packages/workflow-executor/test/adapters/run-to-available-step-mapper.test.ts index d36ca83b77..4397c00e91 100644 --- a/packages/workflow-executor/test/adapters/run-to-available-step-mapper.test.ts +++ b/packages/workflow-executor/test/adapters/run-to-available-step-mapper.test.ts @@ -16,6 +16,7 @@ import { ServerWorkflowTriggerType, } from '../../src/adapters/server-types'; import { DomainValidationError, InvalidStepDefinitionError } from '../../src/errors'; +import { TriggerType } from '../../src/types/validated/execution'; import { StepType } from '../../src/types/validated/step-definition'; const logger = jest.fn(); @@ -109,7 +110,7 @@ describe('toAvailableStepExecution', () => { stepId: 'step-a', stepIndex: 0, collectionId: '11', - triggerType: 'manual', + triggerType: TriggerType.Manual, baseRecordRef: { collectionName: 'customers', recordId: ['123'], @@ -131,7 +132,7 @@ describe('toAvailableStepExecution', () => { const result = toAvailableStepExecution(run); - expect(result?.triggerType).toBe('webhook'); + expect(result?.triggerType).toBe(TriggerType.Webhook); }); it('should default triggerType to manual when the orchestrator omits it', () => { @@ -140,7 +141,7 @@ describe('toAvailableStepExecution', () => { const result = toAvailableStepExecution(run); - expect(result?.triggerType).toBe('manual'); + expect(result?.triggerType).toBe(TriggerType.Manual); }); it('should stringify the numeric run id', () => { diff --git a/packages/workflow-executor/test/integration/workflow-execution.test.ts b/packages/workflow-executor/test/integration/workflow-execution.test.ts index 2e0bad4fe3..8109602b5e 100644 --- a/packages/workflow-executor/test/integration/workflow-execution.test.ts +++ b/packages/workflow-executor/test/integration/workflow-execution.test.ts @@ -13,6 +13,7 @@ import ExecutorHttpServer from '../../src/http/executor-http-server'; import Runner from '../../src/runner'; import SchemaCache from '../../src/schema-cache'; import InMemoryStore from '../../src/stores/in-memory-store'; +import { TriggerType } from '../../src/types/execution-context'; import { StepExecutionMode, StepType } from '../../src/types/validated/step-definition'; // --------------------------------------------------------------------------- @@ -227,7 +228,7 @@ function buildPendingStep( stepId: 'step-1', stepIndex: 0, collectionId: 'col-1', - triggerType: 'manual', + triggerType: TriggerType.Manual, baseRecordRef: BASE_RECORD_REF, previousSteps: [], user: STEP_USER, @@ -249,7 +250,7 @@ describe('workflow execution (integration)', () => { stepId: 'step-1', stepIndex: 0, collectionId: 'col-1', - triggerType: 'manual', + triggerType: TriggerType.Manual, baseRecordRef: { collectionName: 'customers', recordId: [42], stepIndex: 0 }, stepDefinition: { type: StepType.ReadRecord, prompt: 'Read the customer email' }, previousSteps: [], diff --git a/packages/workflow-executor/test/runner.test.ts b/packages/workflow-executor/test/runner.test.ts index 4fc30d0325..c50627b2f8 100644 --- a/packages/workflow-executor/test/runner.test.ts +++ b/packages/workflow-executor/test/runner.test.ts @@ -27,6 +27,7 @@ import TriggerRecordActionStepExecutor from '../src/executors/trigger-record-act import UpdateRecordStepExecutor from '../src/executors/update-record-step-executor'; import Runner from '../src/runner'; import SchemaCache from '../src/schema-cache'; +import { TriggerType } from '../src/types/execution-context'; import { StepExecutionMode, StepType } from '../src/types/validated/step-definition'; // --------------------------------------------------------------------------- @@ -161,7 +162,7 @@ function makePendingStep( stepId: 'step-1', stepIndex: 0, collectionId: 'col-1', - triggerType: 'manual', + triggerType: TriggerType.Manual, baseRecordRef: { collectionName: 'customers', recordId: ['1'], stepIndex: 0 }, stepDefinition: makeStepDefinition(stepType), previousSteps: [], From 26c8333c2a71387832a77ca4f236c7edc018f7d6 Mon Sep 17 00:00:00 2001 From: Shohan RAHMAN Date: Wed, 24 Jun 2026 22:46:21 +0200 Subject: [PATCH 4/4] refactor(workflow-executor): export TriggerType once from index, drop comments Export TriggerType directly from its home (validated/execution) in the barrel instead of re-exporting through execution-context, and remove the explanatory comments on the two trigger-type enums. Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/workflow-executor/src/adapters/server-types.ts | 1 - packages/workflow-executor/src/index.ts | 2 +- packages/workflow-executor/src/types/execution-context.ts | 1 - packages/workflow-executor/src/types/validated/execution.ts | 2 -- .../test/integration/workflow-execution.test.ts | 2 +- packages/workflow-executor/test/runner.test.ts | 2 +- 6 files changed, 3 insertions(+), 7 deletions(-) diff --git a/packages/workflow-executor/src/adapters/server-types.ts b/packages/workflow-executor/src/adapters/server-types.ts index 54bbdeb5cf..883984099e 100644 --- a/packages/workflow-executor/src/adapters/server-types.ts +++ b/packages/workflow-executor/src/adapters/server-types.ts @@ -179,7 +179,6 @@ export interface ServerStepHistory { /** Mirror of the server's `WorkflowRunState` enum (workflow-run-model.ts). */ export type ServerWorkflowRunState = 'started' | 'pending' | 'loading' | 'aborted' | 'finished'; -/** Mirror of the server's workflow run `triggerType` enum. Optional: older orchestrators omit it. */ export enum ServerWorkflowTriggerType { manual = 'manual', webhook = 'webhook', diff --git a/packages/workflow-executor/src/index.ts b/packages/workflow-executor/src/index.ts index 1ca27c0c83..77298f8948 100644 --- a/packages/workflow-executor/src/index.ts +++ b/packages/workflow-executor/src/index.ts @@ -58,7 +58,7 @@ export type { ExecutionContext, } from './types/execution-context'; -export { TriggerType } from './types/execution-context'; +export { TriggerType } from './types/validated/execution'; export type { AgentPort, diff --git a/packages/workflow-executor/src/types/execution-context.ts b/packages/workflow-executor/src/types/execution-context.ts index b2caff74b9..70d965a09f 100644 --- a/packages/workflow-executor/src/types/execution-context.ts +++ b/packages/workflow-executor/src/types/execution-context.ts @@ -11,7 +11,6 @@ import type { BaseChatModel } from '@forestadmin/ai-proxy'; // Re-export the runtime result types alongside the context they flow with. export type { AvailableStepExecution, Step, StepUser }; -export { TriggerType } from './validated/execution'; export interface StepExecutionResult { stepOutcome: StepOutcome; diff --git a/packages/workflow-executor/src/types/validated/execution.ts b/packages/workflow-executor/src/types/validated/execution.ts index aff4edaa8e..fa4ca7c265 100644 --- a/packages/workflow-executor/src/types/validated/execution.ts +++ b/packages/workflow-executor/src/types/validated/execution.ts @@ -31,8 +31,6 @@ export const StepSchema = z .strict(); export type Step = z.infer; -// Domain enum for what triggered the run. Decoupled from the server contract -// (`ServerWorkflowTriggerType`) — `run-to-available-step-mapper.ts` is the single translation point. export enum TriggerType { Manual = 'manual', Webhook = 'webhook', diff --git a/packages/workflow-executor/test/integration/workflow-execution.test.ts b/packages/workflow-executor/test/integration/workflow-execution.test.ts index 8109602b5e..3302c68403 100644 --- a/packages/workflow-executor/test/integration/workflow-execution.test.ts +++ b/packages/workflow-executor/test/integration/workflow-execution.test.ts @@ -13,7 +13,7 @@ import ExecutorHttpServer from '../../src/http/executor-http-server'; import Runner from '../../src/runner'; import SchemaCache from '../../src/schema-cache'; import InMemoryStore from '../../src/stores/in-memory-store'; -import { TriggerType } from '../../src/types/execution-context'; +import { TriggerType } from '../../src/types/validated/execution'; import { StepExecutionMode, StepType } from '../../src/types/validated/step-definition'; // --------------------------------------------------------------------------- diff --git a/packages/workflow-executor/test/runner.test.ts b/packages/workflow-executor/test/runner.test.ts index c50627b2f8..04efbecdb3 100644 --- a/packages/workflow-executor/test/runner.test.ts +++ b/packages/workflow-executor/test/runner.test.ts @@ -27,7 +27,7 @@ import TriggerRecordActionStepExecutor from '../src/executors/trigger-record-act import UpdateRecordStepExecutor from '../src/executors/update-record-step-executor'; import Runner from '../src/runner'; import SchemaCache from '../src/schema-cache'; -import { TriggerType } from '../src/types/execution-context'; +import { TriggerType } from '../src/types/validated/execution'; import { StepExecutionMode, StepType } from '../src/types/validated/step-definition'; // ---------------------------------------------------------------------------