Skip to content

[One Workflow][Custom steps] Async conditional registration#264788

Merged
semd merged 7 commits into
elastic:mainfrom
semd:workflows/async_conditional_step_registration
Apr 22, 2026
Merged

[One Workflow][Custom steps] Async conditional registration#264788
semd merged 7 commits into
elastic:mainfrom
semd:workflows/async_conditional_step_registration

Conversation

@semd
Copy link
Copy Markdown
Contributor

@semd semd commented Apr 21, 2026

Summary

From: #259159

Enables conditional async registration of workflow custom steps on both the public and server side of the workflows_extensions plugin.

  • registerStepDefinition now accepts a sync definition or an async loader (() => Promise<Definition | undefined>). A loader resolving to undefined is a no-op, so plugins can gate registration behind async checks (e.g. feature flags, license, capabilities) without an explicit branch around the call.
  • Adds isReady() to the shared WorkflowsExtensionsStartContract (already present on the public start; now also on the server start). It resolves once every pending async loader has settled.
  • Loader rejections — and any error thrown while inserting the resolved definition into the registry — are caught and logged via the plugin logger as Failed to register step definition with the original error attached as meta. They do not propagate, so a single broken loader cannot break other steps or workflow execution as a whole. Consequently, isReady() always resolves; consumers can await it without try/catch.
  • The execution engine setupDependencies awaits workflowsExtensions.isReady() before reading the workflow execution, so step handlers registered asynchronously are guaranteed to be available when the engine runs.
  • Both PublicStepRegistry and ServerStepRegistry now take a Logger in their constructor, wired from the corresponding plugin's initializerContext.logger.get().

Changes

  • workflows_extensions/common/types.ts — add isReady(): Promise<void> to WorkflowsExtensionsStartContract.
  • workflows_extensions/public/step_registry/step_registry.ts — accept loader, skip on undefined, log loader/registration errors via the injected Logger.
  • workflows_extensions/server/step_registry/step_registry.ts — same async/loader behavior + whenReady(), takes a Logger in the constructor.
  • workflows_extensions/server/plugin.ts — expose isReady() from start; accept ServerStepDefinitionOrLoader in registerStepDefinition; pass the plugin logger to ServerStepRegistry.
  • workflows_extensions/public/plugin.ts — pass the plugin logger to PublicStepRegistry.
  • workflows_extensions/public/types.ts & server/types.ts — types for the new loader signature; move isReady() into the shared start contract.
  • workflows_extensions/server/mocks.ts — add isReady to the start mock.
  • workflows_extensions/dev_docs/STEPS.md — document async + conditional registration on both sides, the log-don't-throw error model, and the server-side isReady().
  • workflows_execution_engine/server/execution_functions/setup_dependencies.tsawait workflowsExtensions.isReady() before fetching the workflow execution.

Test plan

  • Unit tests updated/added for both registries (sync, async loader, undefined-skip, mixed sync/async, duplicate handling via async loader, loader rejection — all assert that whenReady() resolves and that logger.error is called with the original error).
  • Unit tests added for setupDependencies covering: it awaits isReady() before reading the execution, and propagates errors thrown by isReady().
  • All affected Jest suites pass locally.

Allow registering workflow step definitions via an async loader
(both public and server side). The loader can resolve with
`undefined` to skip registration, enabling conditional registration
based on async checks (e.g. feature flags).

Adds `isReady()` to the workflows_extensions start contract so
consumers can wait for all pending async registrations to settle.
The execution engine's `setupDependencies` awaits it before reading
the workflow execution.

Made-with: Cursor
@semd semd requested a review from a team as a code owner April 21, 2026 17:07
@semd semd added release_note:skip Skip the PR/issue when compiling release notes backport:version Backport to applied version labels Team:One Workflow Team label for One Workflow (Workflow automation) v9.5.0 labels Apr 21, 2026
@semd semd self-assigned this Apr 21, 2026
Copy link
Copy Markdown
Contributor

@yngrdyn yngrdyn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 🚀

@elasticmachine
Copy link
Copy Markdown
Contributor

⏳ Build in-progress, with failures

Failed CI Steps

History

cc @semd

semd added 2 commits April 22, 2026 16:10
…ditional_step_registration' into workflows/async_conditional_step_registration
@semd semd enabled auto-merge (squash) April 22, 2026 15:19
@semd semd merged commit 9d1a9a4 into elastic:main Apr 22, 2026
18 checks passed
@kibanamachine kibanamachine added backport:skip This PR does not require backporting and removed backport:version Backport to applied version labels labels Apr 22, 2026
tiansivive pushed a commit to tiansivive/kibana that referenced this pull request Apr 23, 2026
…264788)

## Summary

From: elastic#259159

Enables conditional async registration of workflow custom steps on both
the public and server side of the `workflows_extensions` plugin.

- `registerStepDefinition` now accepts a sync definition **or** an async
loader (`() => Promise<Definition | undefined>`). A loader resolving to
`undefined` is a no-op, so plugins can gate registration behind async
checks (e.g. feature flags, license, capabilities) without an explicit
branch around the call.
- Adds `isReady()` to the shared `WorkflowsExtensionsStartContract`
(already present on the public start; now also on the server start). It
resolves once every pending async loader has settled.
- Loader rejections — and any error thrown while inserting the resolved
definition into the registry — are caught and **logged via the plugin
logger** as `Failed to register step definition` with the original error
attached as meta. They do **not** propagate, so a single broken loader
cannot break other steps or workflow execution as a whole. Consequently,
`isReady()` always resolves; consumers can `await` it without try/catch.
- The execution engine `setupDependencies` awaits
`workflowsExtensions.isReady()` before reading the workflow execution,
so step handlers registered asynchronously are guaranteed to be
available when the engine runs.
- Both `PublicStepRegistry` and `ServerStepRegistry` now take a `Logger`
in their constructor, wired from the corresponding plugin's
`initializerContext.logger.get()`.

## Changes

- `workflows_extensions/common/types.ts` — add `isReady():
Promise<void>` to `WorkflowsExtensionsStartContract`.
- `workflows_extensions/public/step_registry/step_registry.ts` — accept
loader, skip on `undefined`, log loader/registration errors via the
injected `Logger`.
- `workflows_extensions/server/step_registry/step_registry.ts` — same
async/loader behavior + `whenReady()`, takes a `Logger` in the
constructor.
- `workflows_extensions/server/plugin.ts` — expose `isReady()` from
start; accept `ServerStepDefinitionOrLoader` in
`registerStepDefinition`; pass the plugin logger to
`ServerStepRegistry`.
- `workflows_extensions/public/plugin.ts` — pass the plugin logger to
`PublicStepRegistry`.
- `workflows_extensions/public/types.ts` & `server/types.ts` — types for
the new loader signature; move `isReady()` into the shared start
contract.
- `workflows_extensions/server/mocks.ts` — add `isReady` to the start
mock.
- `workflows_extensions/dev_docs/STEPS.md` — document async +
conditional registration on both sides, the log-don't-throw error model,
and the server-side `isReady()`.
-
`workflows_execution_engine/server/execution_functions/setup_dependencies.ts`
— `await workflowsExtensions.isReady()` before fetching the workflow
execution.

## Test plan

- Unit tests updated/added for both registries (sync, async loader,
`undefined`-skip, mixed sync/async, duplicate handling via async loader,
loader rejection — all assert that `whenReady()` resolves and that
`logger.error` is called with the original error).
- Unit tests added for `setupDependencies` covering: it awaits
`isReady()` before reading the execution, and propagates errors thrown
by `isReady()`.
- All affected Jest suites pass locally.

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
KDKHD added a commit that referenced this pull request Apr 23, 2026
…nc loaders (#265230)

## Summary

Follows up on #264788 (merged by @semd) which added
async-loader support to the `ServerStepRegistry` and
`PublicStepRegistry`.

Previously, `registerStepDefinition` was called inside a deferred
`getStartServices().then()` callback — meaning registration happened
*after* the `setup()` lifecycle completed. semd flagged this as
problematic in review comments on #259159 ([comment
1](#259159 (comment)),
[comment
2](#259159 (comment))).

This PR applies the fix on the security_solution side:
`registerStepDefinition` is now called **synchronously** during
`setup()`. Each step is registered with an async loader that checks the
feature flag at resolution time and returns `undefined` to skip
registration when the flag is disabled.

Changes on both server and public sides:
- `registerWorkflowSteps` is now a synchronous function accepting
`CoreSetup` instead of `CoreStart`
- Each step is wrapped in an async loader that defers the feature-flag
check to resolution time
- `plugin.ts` / `plugin.tsx` call `registerWorkflowSteps` directly in
`setup()` (no deferred `.then()`)
- New unit tests for both server and public `registerWorkflowSteps`

## Test plan

- [ ] `node scripts/jest
x-pack/solutions/security/plugins/security_solution/server/workflows/step_types/register_workflow_steps.test.ts`
- [ ] `node scripts/jest
x-pack/solutions/security/plugins/security_solution/public/workflows/step_types/register_workflow_steps.test.ts`
- [ ] Confirm no regression in existing workflow step tests
smith pushed a commit to smith/kibana that referenced this pull request Apr 23, 2026
…nc loaders (elastic#265230)

## Summary

Follows up on elastic#264788 (merged by @semd) which added
async-loader support to the `ServerStepRegistry` and
`PublicStepRegistry`.

Previously, `registerStepDefinition` was called inside a deferred
`getStartServices().then()` callback — meaning registration happened
*after* the `setup()` lifecycle completed. semd flagged this as
problematic in review comments on elastic#259159 ([comment
1](elastic#259159 (comment)),
[comment
2](elastic#259159 (comment))).

This PR applies the fix on the security_solution side:
`registerStepDefinition` is now called **synchronously** during
`setup()`. Each step is registered with an async loader that checks the
feature flag at resolution time and returns `undefined` to skip
registration when the flag is disabled.

Changes on both server and public sides:
- `registerWorkflowSteps` is now a synchronous function accepting
`CoreSetup` instead of `CoreStart`
- Each step is wrapped in an async loader that defers the feature-flag
check to resolution time
- `plugin.ts` / `plugin.tsx` call `registerWorkflowSteps` directly in
`setup()` (no deferred `.then()`)
- New unit tests for both server and public `registerWorkflowSteps`

## Test plan

- [ ] `node scripts/jest
x-pack/solutions/security/plugins/security_solution/server/workflows/step_types/register_workflow_steps.test.ts`
- [ ] `node scripts/jest
x-pack/solutions/security/plugins/security_solution/public/workflows/step_types/register_workflow_steps.test.ts`
- [ ] Confirm no regression in existing workflow step tests
rbrtj pushed a commit to walterra/kibana that referenced this pull request Apr 27, 2026
…nc loaders (elastic#265230)

## Summary

Follows up on elastic#264788 (merged by @semd) which added
async-loader support to the `ServerStepRegistry` and
`PublicStepRegistry`.

Previously, `registerStepDefinition` was called inside a deferred
`getStartServices().then()` callback — meaning registration happened
*after* the `setup()` lifecycle completed. semd flagged this as
problematic in review comments on elastic#259159 ([comment
1](elastic#259159 (comment)),
[comment
2](elastic#259159 (comment))).

This PR applies the fix on the security_solution side:
`registerStepDefinition` is now called **synchronously** during
`setup()`. Each step is registered with an async loader that checks the
feature flag at resolution time and returns `undefined` to skip
registration when the flag is disabled.

Changes on both server and public sides:
- `registerWorkflowSteps` is now a synchronous function accepting
`CoreSetup` instead of `CoreStart`
- Each step is wrapped in an async loader that defers the feature-flag
check to resolution time
- `plugin.ts` / `plugin.tsx` call `registerWorkflowSteps` directly in
`setup()` (no deferred `.then()`)
- New unit tests for both server and public `registerWorkflowSteps`

## Test plan

- [ ] `node scripts/jest
x-pack/solutions/security/plugins/security_solution/server/workflows/step_types/register_workflow_steps.test.ts`
- [ ] `node scripts/jest
x-pack/solutions/security/plugins/security_solution/public/workflows/step_types/register_workflow_steps.test.ts`
- [ ] Confirm no regression in existing workflow step tests
SoniaSanzV pushed a commit to SoniaSanzV/kibana that referenced this pull request Apr 27, 2026
…264788)

## Summary

From: elastic#259159

Enables conditional async registration of workflow custom steps on both
the public and server side of the `workflows_extensions` plugin.

- `registerStepDefinition` now accepts a sync definition **or** an async
loader (`() => Promise<Definition | undefined>`). A loader resolving to
`undefined` is a no-op, so plugins can gate registration behind async
checks (e.g. feature flags, license, capabilities) without an explicit
branch around the call.
- Adds `isReady()` to the shared `WorkflowsExtensionsStartContract`
(already present on the public start; now also on the server start). It
resolves once every pending async loader has settled.
- Loader rejections — and any error thrown while inserting the resolved
definition into the registry — are caught and **logged via the plugin
logger** as `Failed to register step definition` with the original error
attached as meta. They do **not** propagate, so a single broken loader
cannot break other steps or workflow execution as a whole. Consequently,
`isReady()` always resolves; consumers can `await` it without try/catch.
- The execution engine `setupDependencies` awaits
`workflowsExtensions.isReady()` before reading the workflow execution,
so step handlers registered asynchronously are guaranteed to be
available when the engine runs.
- Both `PublicStepRegistry` and `ServerStepRegistry` now take a `Logger`
in their constructor, wired from the corresponding plugin's
`initializerContext.logger.get()`.

## Changes

- `workflows_extensions/common/types.ts` — add `isReady():
Promise<void>` to `WorkflowsExtensionsStartContract`.
- `workflows_extensions/public/step_registry/step_registry.ts` — accept
loader, skip on `undefined`, log loader/registration errors via the
injected `Logger`.
- `workflows_extensions/server/step_registry/step_registry.ts` — same
async/loader behavior + `whenReady()`, takes a `Logger` in the
constructor.
- `workflows_extensions/server/plugin.ts` — expose `isReady()` from
start; accept `ServerStepDefinitionOrLoader` in
`registerStepDefinition`; pass the plugin logger to
`ServerStepRegistry`.
- `workflows_extensions/public/plugin.ts` — pass the plugin logger to
`PublicStepRegistry`.
- `workflows_extensions/public/types.ts` & `server/types.ts` — types for
the new loader signature; move `isReady()` into the shared start
contract.
- `workflows_extensions/server/mocks.ts` — add `isReady` to the start
mock.
- `workflows_extensions/dev_docs/STEPS.md` — document async +
conditional registration on both sides, the log-don't-throw error model,
and the server-side `isReady()`.
-
`workflows_execution_engine/server/execution_functions/setup_dependencies.ts`
— `await workflowsExtensions.isReady()` before fetching the workflow
execution.

## Test plan

- Unit tests updated/added for both registries (sync, async loader,
`undefined`-skip, mixed sync/async, duplicate handling via async loader,
loader rejection — all assert that `whenReady()` resolves and that
`logger.error` is called with the original error).
- Unit tests added for `setupDependencies` covering: it awaits
`isReady()` before reading the execution, and propagates errors thrown
by `isReady()`.
- All affected Jest suites pass locally.

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
SoniaSanzV pushed a commit to SoniaSanzV/kibana that referenced this pull request Apr 27, 2026
…nc loaders (elastic#265230)

## Summary

Follows up on elastic#264788 (merged by @semd) which added
async-loader support to the `ServerStepRegistry` and
`PublicStepRegistry`.

Previously, `registerStepDefinition` was called inside a deferred
`getStartServices().then()` callback — meaning registration happened
*after* the `setup()` lifecycle completed. semd flagged this as
problematic in review comments on elastic#259159 ([comment
1](elastic#259159 (comment)),
[comment
2](elastic#259159 (comment))).

This PR applies the fix on the security_solution side:
`registerStepDefinition` is now called **synchronously** during
`setup()`. Each step is registered with an async loader that checks the
feature flag at resolution time and returns `undefined` to skip
registration when the flag is disabled.

Changes on both server and public sides:
- `registerWorkflowSteps` is now a synchronous function accepting
`CoreSetup` instead of `CoreStart`
- Each step is wrapped in an async loader that defers the feature-flag
check to resolution time
- `plugin.ts` / `plugin.tsx` call `registerWorkflowSteps` directly in
`setup()` (no deferred `.then()`)
- New unit tests for both server and public `registerWorkflowSteps`

## Test plan

- [ ] `node scripts/jest
x-pack/solutions/security/plugins/security_solution/server/workflows/step_types/register_workflow_steps.test.ts`
- [ ] `node scripts/jest
x-pack/solutions/security/plugins/security_solution/public/workflows/step_types/register_workflow_steps.test.ts`
- [ ] Confirm no regression in existing workflow step tests
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport:skip This PR does not require backporting release_note:skip Skip the PR/issue when compiling release notes Team:One Workflow Team label for One Workflow (Workflow automation) v9.5.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants