Elsa 3.6.0 — Release Notes
Compare: 3.5.3...3.6.0
⚠️ Breaking changes / upgrade notes
-
Database migrations (EF Core — all providers):
ActivityNodeIdcolumns inActivityExecutionRecordsandWorkflowExecutionLogRecordshave 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;nullnow means tenant-agnostic (visible to all tenants). EF Core query filters and theActivityRegistryhave been updated accordingly. If your database contains rows with anullTenantIdthat were intended to represent the default tenant, migrate those rows to""before upgrading. The newNormalizeTenantId()extension method onstringhandles 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) -
DefaultRegistriesPopulatorconstructor change: The constructor now requires an additionalINotificationSenderdependency. If you manually instantiate or overrideDefaultRegistriesPopulatorin tests or custom DI registrations, add the new parameter. Standard DI consumers are unaffected. (#7293) -
Elsa.Commondependency trimmed: TheDistributedLockmeta-package (which pulled in all provider implementations) has been replaced withDistributedLock.Core. Applications that relied on the transitive provider packages (e.g.DistributedLock.SqlServer) being pulled in viaElsa.Commonmust add explicit package references. (53245cafbd) (#7169)
✨ New features
Activity host registration
HostMethodActivity/ activity hosts: Introduces support for registering CLR types as activity hosts. Publicasyncmethods 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:
BackgroundWorkflowDispatchernow emits four new notifications at dispatch time, allowing subscribers to react to durable dispatch scenarios without a custom dispatcher:WorkflowDefinitionDispatching/WorkflowDefinitionDispatchedWorkflowInstanceDispatching/WorkflowInstanceDispatched
(411ca0a332) (#7157)
-
WorkflowDefinitionsReloadednotification:DefaultRegistriesPopulatornow dispatches aWorkflowDefinitionsReloadednotification 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 newIWorkflowReferenceGraphBuilder/WorkflowReferenceGraphmodel that replaces the previous ad-hoc recursive query approach. (0a2e99ad42) (#7309) -
Export with transitive consumers: The export endpoint and
BulkExportWorkflowDefinitionsRequestnow accept anIncludeConsumingWorkflowsflag (defaultfalse). 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()andWithDefaultActivityCommitStrategy()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
TenantIdfor tenant-agnostic entities: Entities with anullTenantIdare now treated as visible across all tenants. EF Core query filters andActivityRegistryquery methods includenull-tenanted records in every tenant's scope. NewActivityDescriptor.TenantId(nullable) property added. (7bc9035f5e) (#7226) -
TenantsOptions.IsEnabledflag: A newIsEnabledproperty onTenantsOptionsallows tenant-specific logic to be conditionally applied. Tenant filters andActivityRegistryqueries now respect this flag. (1b8083a5c6) (#7281) -
Distributed locks for
Reload/Refresh:IWorkflowDefinitionsReloaderandIWorkflowDefinitionsRefresherare 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 newITransientExceptionDetectorservice 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 andTopologicalTaskSorterensure tasks execute in the correct dependency order. (b577279321) (#7174) -
Resultclass improvements:Result<T>now supports strongly-typed operations and async handlers;IsSuccessis exposed for use in storage drivers and tests. (1b8083a5c6) (#7281) -
HTTP activity —
Content-Dispositionfilename parsing:GetDownloadedFileNameOrDefaultnow parses theContent-Dispositionheader (including quoted and extended filenames) before falling back to the URI. (25575e65af) -
HTTP endpoint — graceful
JsonExceptionhandling:HttpEndpoint.csnow catchesJsonExceptionwithout 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)
-
WorkflowTriggerEqualityComparerserialization 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) -
WorkflowDefinitionActivityProvidertenant isolation: Tenant ID is now included in the activity type cache key, preventing cross-tenant activity descriptor collisions. (558902bb77) -
GetSectionAsJsonnull guard: Returnsnullwhen the requested configuration section is absent, preventingNullReferenceExceptionduring configuration lookups. (2250e25e5c)
🐛 Fixes
-
Elsa.Workflows.Core—WaitAnyjoin ordering: Inbound connection handling was refactored to schedule the outbound activity before canceling remaining branches, fixing a race condition whereWaitAnycould cancel the winning branch prematurely. (d4b69be44b) (#7340) -
EF Core —
ActivityNodeIdcolumn truncation on deeply nested workflows: FixedString or binary data would be truncatederrors 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.TryGet—Literalhandling restored: Restores theLiteralspecial-case inActivityExecutionContext.TryGet(MemoryBlockReference)that was inadvertently removed in 3.5.2. Activities that programmatically createInput<T>withLiteralvalues no longer throwInvalidOperationException. (37921b5ee0) (#7075) -
Workflow reload logic: Fixed two issues in the reload pipeline — a
NullReferenceExceptioncaused byTriggerSchedulerreturning 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
metadatapayload (layout, annotations, etc.), preventing loss of designer context during round-trips. (7e2829ddcd) (#7323) -
Multi-tenancy — cross-tenant cache collision in
CachingWorkflowDefinitionService: Cache keys inWorkflowDefinitionCacheManagerwere 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
DefaultWorkflowDefinitionStorePopulatorwas assigningDefaultTenantIdto code-first (CLR) workflow graphs instead ofTenant.AgnosticTenantId, causingWorkflowGraphlookups 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.DefaultWorkflowDefinitionStorePopulatornow filters definitions by current tenant. (b09a564812) (#7217) -
Multi-tenancy — label endpoint permissions: Label API endpoints now use
ElsaEndpointbase withConfigurePermissions, matching the pattern used by other endpoints. Unused permission constants removed. (72569702fe) (#7205) -
Multi-tenancy —
BackgroundWorkflowCancellationDispatchertenant 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'stypeNameis missing (e.g. when a workflow input name clashes with a reserved property likemetadataorcustomProperties) no longer blocks publishing of other workflows. Exceptions are now collected and logged rather than propagated. (b8228351ae) (#7199) -
Memory leak — Zstd codec (
IronCompress):IronCompressResultobjects returned byIron.Compress()andIron.Decompress()inZstd.cswere not being disposed, causingArrayPoolbuffer leaks on every compression/decompression call. BothCompressAsyncandDecompressAsyncnow dispose the result correctly. (05d40e3a2a) (#7193) -
ResumeBulkDispatchWorkflowActivity— missingParentInstanceId: Graceful handling added forParentInstanceIdbeing absent, preventingNullReferenceExceptionduring bulk workflow dispatch resumption. (641dd664d6)
🧩 Developer-facing changes
-
IWorkflowReferenceGraphBuilder/WorkflowReferenceGraph: New graph abstraction for querying workflow consumer relationships. Registered automatically viaWorkflowManagementFeature.WorkflowReferenceGraphOptionsexposesMaxDepthandMaxDefinitionsguards. (#7309) -
TaskDependencyAttribute+TopologicalTaskSorter: Decorate tenant task implementations with[TaskDependency(typeof(OtherTask))]to declare ordering requirements. The newTopologicalTaskSortersorts 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.TenantIdis now nullable (string?). Providers must handlenullto mean "visible to all tenants". (#7226) -
IWorkflowDefinitionsApi.GetConsumersAsync: New client method added toElsa.Api.Client. (#7309)
🧪 Tests
- Added idempotency component tests for trigger indexing (
TriggerIndexingIdempotencyTests) and unit tests forWorkflowTriggerEqualityComparer. (#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 inExpressionExecutionContextExtensions. (#7290) - Added unit tests documenting and verifying reserved activity input keyword behavior (e.g.
metadata,customProperties). (#7180) - Added unit tests for
WorkflowReferenceGraphBuilderand 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
WaitAnyjoin schedule-before-cancel ordering. (#7340)
🔁 CI / Build
- Replaced
DistributedLockmeta-package withDistributedLock.CoreinElsa.Common.csprojto remove unused transitive provider references. (53245cafbd) (#7169) - Refined GitHub Actions
packages.ymlbranch matching to include allrelease/*branches and removed theprereleasedcondition from the package handling trigger. (34ecf02c32, 394f22b6e0) - Removed
--depth=1fromgit fetchcommands inpackages.ymlto ensure full history is available. (34ecf02c32)
📦 Full changelog
- Trigger serialization fix (
WorkflowTriggerEqualityComparer): avoid type discriminator injection during equality comparison. (6f53c26f24) WorkflowExecutionContext: removed unusedClearCompletionCallbacksmethod;correlationIdparameter made non-optional in the constructor. (fa798b0a47, 79a64e90fd)AttributeUsagetargets updated forInputAttribute,OutputAttribute, andActivityAttributeto reflect correct usage scenarios. (3778a14e54)ClrWorkflowsProviderrefactored to remove tenant prefix logic, relying solely on theTenantIdproperty. (41409b156d)- Removed
ConvertNullTenantIdToEmptyStringmigration and its designer file. (ccd8268413) - Tenant isolation enforced in
WorkflowDefinitionActivityProvider; tenant ID included in activity type cache key. (558902bb77)