github elsa-workflows/elsa-core 3.6.0

6 hours ago

Elsa 3.6.0 — Release Notes

Compare: 3.5.3...3.6.0


⚠️ Breaking changes / upgrade notes

  • Database migrations (EF Core — all providers): ActivityNodeId columns in ActivityExecutionRecords and WorkflowExecutionLogRecords have been widened to unlimited types (nvarchar(max) / longtext / NCLOB) to support deeply nested workflows. The corresponding B-tree indexes (IX_ActivityExecutionRecord_ActivityNodeId, IX_WorkflowExecutionLogRecord_ActivityNodeId) are dropped as part of the V3_6 migrations. You must apply the EF Core migrations before upgrading to 3.6.0 in any SQL Server, MySQL, or Oracle deployment. (d4b69be44b) (#7338)

  • Multitenancy — tenant ID convention change: An empty string ("") is now the canonical default tenant ID for all tenant-aware entities; null now means tenant-agnostic (visible to all tenants). EF Core query filters and the ActivityRegistry have been updated accordingly. If your database contains rows with a null TenantId that were intended to represent the default tenant, migrate those rows to "" before upgrading. The new NormalizeTenantId() extension method on string handles the conversion in code. (#7217, #7226)

  • Multitenancy — stored-trigger index updated: Database indexes on stored triggers now include TenantId. The V3_6 migration adds this index; no manual action is required beyond running migrations. (#7217)

  • DefaultRegistriesPopulator constructor change: The constructor now requires an additional INotificationSender dependency. If you manually instantiate or override DefaultRegistriesPopulator in tests or custom DI registrations, add the new parameter. Standard DI consumers are unaffected. (#7293)

  • Elsa.Common dependency trimmed: The DistributedLock meta-package (which pulled in all provider implementations) has been replaced with DistributedLock.Core. Applications that relied on the transitive provider packages (e.g. DistributedLock.SqlServer) being pulled in via Elsa.Common must add explicit package references. (53245cafbd) (#7169)


✨ New features

Activity host registration

  • HostMethodActivity / activity hosts: Introduces support for registering CLR types as activity hosts. Public async methods on a registered type are automatically discovered and exposed as individual activities in the workflow designer, enabling code-first activity generation without manual activity boilerplate. New types: HostMethodActivity, HostMethodActivityProvider, IHostMethodActivityDescriber, FromServicesAttribute. (fa04e1ebcd) (#7172)

Workflow dispatch notifications

  • Dispatch lifecycle notifications: BackgroundWorkflowDispatcher now emits four new notifications at dispatch time, allowing subscribers to react to durable dispatch scenarios without a custom dispatcher:

    • WorkflowDefinitionDispatching / WorkflowDefinitionDispatched
    • WorkflowInstanceDispatching / WorkflowInstanceDispatched

    (411ca0a332) (#7157)

  • WorkflowDefinitionsReloaded notification: DefaultRegistriesPopulator now dispatches a WorkflowDefinitionsReloaded notification after repopulating the workflow definition store, enabling subscriber nodes to synchronize their registries. (f5505d66c9) (#7293)

Consumers API & recursive export

  • GET /workflow-definitions/{definitionId}/consumers: New endpoint returns all recursive consuming workflow definitions for a given definition. Built on a new IWorkflowReferenceGraphBuilder / WorkflowReferenceGraph model that replaces the previous ad-hoc recursive query approach. (0a2e99ad42) (#7309)

  • Export with transitive consumers: The export endpoint and BulkExportWorkflowDefinitionsRequest now accept an IncludeConsumingWorkflows flag (default false). When enabled, the ZIP export recursively resolves and includes all consuming workflows at their latest version. Exported filenames are now deterministic ({Name}-{DefinitionId}.json). (0a2e99ad42) (#7309)

Default commit strategies

  • Global default commit strategies: New WithDefaultWorkflowCommitStrategy() and WithDefaultActivityCommitStrategy() extension methods on the workflows builder let you set application-wide fallback commit strategies. The resolution order for activities is: activity-specific → default activity → workflow-specific → default workflow → no commit. Default strategies are stored separately from the registry so they don't appear in the UI/tooling. (31eff52d24) (#7148)

Multitenancy enhancements

  • Null TenantId for tenant-agnostic entities: Entities with a null TenantId are now treated as visible across all tenants. EF Core query filters and ActivityRegistry query methods include null-tenanted records in every tenant's scope. New ActivityDescriptor.TenantId (nullable) property added. (7bc9035f5e) (#7226)

  • TenantsOptions.IsEnabled flag: A new IsEnabled property on TenantsOptions allows tenant-specific logic to be conditionally applied. Tenant filters and ActivityRegistry queries now respect this flag. (1b8083a5c6) (#7281)

  • Distributed locks for Reload / Refresh: IWorkflowDefinitionsReloader and IWorkflowDefinitionsRefresher are now wrapped with distributed lock decorators to prevent multiple pods from concurrently executing reload or refresh operations. The refresher key incorporates definition IDs to allow concurrent refreshes of different definitions. (689f19f2f2) (#7311)


🔧 Improvements

  • Elsa.Common — distributed lock resilience: A retry pipeline now wraps distributed lock acquisition and release to survive transient errors (network glitches, database timeouts). A new ITransientExceptionDetector service identifies retryable exceptions. (ca268c16ad) (#7161)

  • Tenant task manager with dependency ordering: Tenant startup, background, and recurring task execution is now consolidated into a single TenantTaskManager. A new [TaskDependency] attribute and TopologicalTaskSorter ensure tasks execute in the correct dependency order. (b577279321) (#7174)

  • Result class improvements: Result<T> now supports strongly-typed operations and async handlers; IsSuccess is exposed for use in storage drivers and tests. (1b8083a5c6) (#7281)

  • HTTP activity — Content-Disposition filename parsing: GetDownloadedFileNameOrDefault now parses the Content-Disposition header (including quoted and extended filenames) before falling back to the URI. (25575e65af)

  • HTTP endpoint — graceful JsonException handling: HttpEndpoint.cs now catches JsonException without rethrowing, preventing unhandled exceptions from breaking workflow execution on malformed request bodies. (d5efd38dcc)

  • Deterministic ZIP exports: A fixed timestamp is applied to all ZIP archive entries, ensuring byte-for-byte reproducible exports across runs. (87122082fd)

  • WorkflowTriggerEqualityComparer serialization fix: Type discriminator injection was incorrectly mutating the serialized payload used for trigger equality comparisons, causing spurious re-indexing. The comparer now uses a clean serialization path. (a7e064abb0) (#7320)

  • WorkflowDefinitionActivityProvider tenant isolation: Tenant ID is now included in the activity type cache key, preventing cross-tenant activity descriptor collisions. (558902bb77)

  • GetSectionAsJson null guard: Returns null when the requested configuration section is absent, preventing NullReferenceException during configuration lookups. (2250e25e5c)


🐛 Fixes

  • Elsa.Workflows.CoreWaitAny join ordering: Inbound connection handling was refactored to schedule the outbound activity before canceling remaining branches, fixing a race condition where WaitAny could cancel the winning branch prematurely. (d4b69be44b) (#7340)

  • EF Core — ActivityNodeId column truncation on deeply nested workflows: Fixed String or binary data would be truncated errors on SQL Server, MySQL, and Oracle for workflows with deep nesting (4+ levels). Columns widened; affected indexes dropped. Requires running V3_6 migrations. (71438596f3) (#7338)

  • ActivityExecutionContext.TryGetLiteral handling restored: Restores the Literal special-case in ActivityExecutionContext.TryGet(MemoryBlockReference) that was inadvertently removed in 3.5.2. Activities that programmatically create Input<T> with Literal values no longer throw InvalidOperationException. (37921b5ee0) (#7075)

  • Workflow reload logic: Fixed two issues in the reload pipeline — a NullReferenceException caused by TriggerScheduler returning a null payload (blocking other workflows from publishing), and the reloader only executing partial loading logic (causing workflow-as-activity versions to be unresolved). (5816f6e3be) (#7324)

  • Unknown activity metadata preservation: When deserializing a workflow that contains an unresolvable activity type, the placeholder activity now retains the original metadata payload (layout, annotations, etc.), preventing loss of designer context during round-trips. (7e2829ddcd) (#7323)

  • Multi-tenancy — cross-tenant cache collision in CachingWorkflowDefinitionService: Cache keys in WorkflowDefinitionCacheManager were not scoped by tenant, causing one tenant's workflow definitions to be served to another in multi-tenant deployments. All cache key methods now include a tenant prefix. Single-tenant setups are unaffected. (856f7b40b4) (#7318)

  • Multi-tenancy — code-first workflow tenant assignment: The DefaultWorkflowDefinitionStorePopulator was assigning DefaultTenantId to code-first (CLR) workflow graphs instead of Tenant.AgnosticTenantId, causing WorkflowGraph lookups to fail after upgrading to 3.6.0-rc3. (09d768791a) (#7252)

  • Multi-tenancy — normalization and workflow definition loading: Tenant ID normalization (null"") applied consistently across the stack. DefaultWorkflowDefinitionStorePopulator now filters definitions by current tenant. (b09a564812) (#7217)

  • Multi-tenancy — label endpoint permissions: Label API endpoints now use ElsaEndpoint base with ConfigurePermissions, matching the pattern used by other endpoints. Unused permission constants removed. (72569702fe) (#7205)

  • Multi-tenancy — BackgroundWorkflowCancellationDispatcher tenant headers: Tenant headers are now forwarded when dispatching workflow cancellation in the background, fixing cancellation of suspended multi-tenant workflows. (bc70beff12) (#7040)

  • ReadSyntheticInputs — resilient error handling: An unhandled exception thrown when a synthetic input's typeName is missing (e.g. when a workflow input name clashes with a reserved property like metadata or customProperties) no longer blocks publishing of other workflows. Exceptions are now collected and logged rather than propagated. (b8228351ae) (#7199)

  • Memory leak — Zstd codec (IronCompress): IronCompressResult objects returned by Iron.Compress() and Iron.Decompress() in Zstd.cs were not being disposed, causing ArrayPool buffer leaks on every compression/decompression call. Both CompressAsync and DecompressAsync now dispose the result correctly. (05d40e3a2a) (#7193)

  • ResumeBulkDispatchWorkflowActivity — missing ParentInstanceId: Graceful handling added for ParentInstanceId being absent, preventing NullReferenceException during bulk workflow dispatch resumption. (641dd664d6)


🧩 Developer-facing changes

  • IWorkflowReferenceGraphBuilder / WorkflowReferenceGraph: New graph abstraction for querying workflow consumer relationships. Registered automatically via WorkflowManagementFeature. WorkflowReferenceGraphOptions exposes MaxDepth and MaxDefinitions guards. (#7309)

  • TaskDependencyAttribute + TopologicalTaskSorter: Decorate tenant task implementations with [TaskDependency(typeof(OtherTask))] to declare ordering requirements. The new TopologicalTaskSorter sorts tasks before execution. (#7174)

  • IHostMethodActivityDescriber / FromServicesAttribute: New contracts for the activity host feature. Decorate host method parameters with [FromServices] to resolve them from the DI container at execution time. (#7172)

  • ActivityDescriptor.TenantId is now nullable (string?). Providers must handle null to mean "visible to all tenants". (#7226)

  • IWorkflowDefinitionsApi.GetConsumersAsync: New client method added to Elsa.Api.Client. (#7309)


🧪 Tests

  • Added idempotency component tests for trigger indexing (TriggerIndexingIdempotencyTests) and unit tests for WorkflowTriggerEqualityComparer. (#7320)
  • Added 11 integration tests for default workflow and activity commit strategies with exact commit-count assertions. (#7148)
  • Added integration tests for IEnumerable-to-array projection via JavaScript expression execution and refined type inference in ExpressionExecutionContextExtensions. (#7290)
  • Added unit tests documenting and verifying reserved activity input keyword behavior (e.g. metadata, customProperties). (#7180)
  • Added unit tests for WorkflowReferenceGraphBuilder and component tests for Consumers API and export with consumers. (#7309)
  • Added resilience unit and component tests for distributed lock retry pipeline. (#7161)
  • Added regression test for WaitAny join schedule-before-cancel ordering. (#7340)

🔁 CI / Build

  • Replaced DistributedLock meta-package with DistributedLock.Core in Elsa.Common.csproj to remove unused transitive provider references. (53245cafbd) (#7169)
  • Refined GitHub Actions packages.yml branch matching to include all release/* branches and removed the prereleased condition from the package handling trigger. (34ecf02c32, 394f22b6e0)
  • Removed --depth=1 from git fetch commands in packages.yml to ensure full history is available. (34ecf02c32)

📦 Full changelog

  • Trigger serialization fix (WorkflowTriggerEqualityComparer): avoid type discriminator injection during equality comparison. (6f53c26f24)
  • WorkflowExecutionContext: removed unused ClearCompletionCallbacks method; correlationId parameter made non-optional in the constructor. (fa798b0a47, 79a64e90fd)
  • AttributeUsage targets updated for InputAttribute, OutputAttribute, and ActivityAttribute to reflect correct usage scenarios. (3778a14e54)
  • ClrWorkflowsProvider refactored to remove tenant prefix logic, relying solely on the TenantId property. (41409b156d)
  • Removed ConvertNullTenantIdToEmptyString migration and its designer file. (ccd8268413)
  • Tenant isolation enforced in WorkflowDefinitionActivityProvider; tenant ID included in activity type cache key. (558902bb77)

Don't miss a new elsa-core release

NewReleases is sending notifications on new releases.