From 7981892838b4e8ece254da359d1c2597f55efc71 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Fri, 19 Jun 2026 21:03:10 +0200 Subject: [PATCH 1/5] Add opt-out for publish-on-validation-error failure (3.8) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This forward-ports the FailOnValidationErrors publish toggle to the 3.8 mainline. It is purely additive and changes no default behavior: publishing a workflow definition still fails when validation errors are present (FailOnValidationErrors defaults to true, i.e. strict = opt-out). Setting FailOnValidationErrors to false — via WorkflowManagementFeature.UseFailOnValidationErrors(false) or the ManagementOptions.FailOnValidationErrors option — allows publication to succeed while surfacing the validation errors as warnings on the publish result. This enables publishing workflows that intentionally leave required properties blank (for example, an empty Cron expression used to disable a trigger). This is the 3.8 counterpart to #7740, which introduces the same toggle as opt-in (default false) on the 3.6.x line. References #7738. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Features/WorkflowManagementFeature.cs | 13 +++++++ .../Options/ManagementOptions.cs | 10 +++++ .../Services/WorkflowDefinitionPublisher.cs | 7 +++- .../ImportAndPublishCronTests.cs | 30 +++++++++++++- .../ImportAndPublishHttpEndpointsTests.cs | 39 +++++++++++++++++++ 5 files changed, 96 insertions(+), 3 deletions(-) diff --git a/src/modules/Elsa.Workflows.Management/Features/WorkflowManagementFeature.cs b/src/modules/Elsa.Workflows.Management/Features/WorkflowManagementFeature.cs index 0a26da083..c544636ee 100644 --- a/src/modules/Elsa.Workflows.Management/Features/WorkflowManagementFeature.cs +++ b/src/modules/Elsa.Workflows.Management/Features/WorkflowManagementFeature.cs @@ -61,6 +61,7 @@ public class WorkflowManagementFeature(IModule module) : FeatureBase(module) private string CompressionAlgorithm { get; set; } = nameof(None); private LogPersistenceMode LogPersistenceMode { get; set; } = LogPersistenceMode.Include; private bool IsReadOnlyMode { get; set; } + private bool FailOnValidationErrors { get; set; } = true; /// /// A set of activity types to make available to the system. @@ -219,6 +220,17 @@ public class WorkflowManagementFeature(IModule module) : FeatureBase(module) return this; } + /// + /// Enables or disables failing workflow publication when the workflow has validation errors. + /// Defaults to true as of 3.8.0 (publication fails when validation errors are present). + /// Set to false to allow publication to succeed, returning validation errors as warnings. + /// + public WorkflowManagementFeature UseFailOnValidationErrors(bool enabled = true) + { + FailOnValidationErrors = enabled; + return this; + } + public WorkflowManagementFeature UseWorkflowDefinitionPublisher(Func workflowDefinitionPublisher) { _workflowDefinitionPublisher = workflowDefinitionPublisher; @@ -313,6 +325,7 @@ public class WorkflowManagementFeature(IModule module) : FeatureBase(module) options.CompressionAlgorithm = CompressionAlgorithm; options.LogPersistenceMode = LogPersistenceMode; options.IsReadOnlyMode = IsReadOnlyMode; + options.FailOnValidationErrors = FailOnValidationErrors; }); Services.Configure(options => options.RegisterTypeAlias(typeof(ClrWorkflowMaterializerContext), nameof(ClrWorkflowMaterializerContext))); diff --git a/src/modules/Elsa.Workflows.Management/Options/ManagementOptions.cs b/src/modules/Elsa.Workflows.Management/Options/ManagementOptions.cs index aca77eb19..53e2f4751 100644 --- a/src/modules/Elsa.Workflows.Management/Options/ManagementOptions.cs +++ b/src/modules/Elsa.Workflows.Management/Options/ManagementOptions.cs @@ -32,4 +32,14 @@ public class ManagementOptions /// A mode that does not allow editing workflows. /// public bool IsReadOnlyMode { get; set; } + + /// + /// Determines whether publishing a workflow definition fails when the workflow has validation errors. + /// When true (the default as of 3.8.0), publishing fails if any validation errors are present. + /// When false, publishing is allowed to succeed and any validation errors are returned as warnings + /// on the publish result. This allows publishing workflows that intentionally leave required properties + /// blank (for example, an empty Cron expression used to disable a trigger). In 3.6.x and 3.7.x this + /// defaulted to false (opt-in); as of 3.8.0 it defaults to true (opt-out). + /// + public bool FailOnValidationErrors { get; set; } = true; } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Management/Services/WorkflowDefinitionPublisher.cs b/src/modules/Elsa.Workflows.Management/Services/WorkflowDefinitionPublisher.cs index 007fc855c..30afc6d7a 100644 --- a/src/modules/Elsa.Workflows.Management/Services/WorkflowDefinitionPublisher.cs +++ b/src/modules/Elsa.Workflows.Management/Services/WorkflowDefinitionPublisher.cs @@ -8,7 +8,9 @@ using Elsa.Workflows.Management.Filters; using Elsa.Workflows.Management.Materializers; using Elsa.Workflows.Management.Models; using Elsa.Workflows.Management.Notifications; +using Elsa.Workflows.Management.Options; using Elsa.Workflows.Models; +using Microsoft.Extensions.Options; namespace Elsa.Workflows.Management.Services; @@ -20,7 +22,8 @@ public class WorkflowDefinitionPublisher( IIdentityGenerator identityGenerator, IActivitySerializer activitySerializer, IMediator mediator, - ISystemClock systemClock) + ISystemClock systemClock, + IOptions options) : IWorkflowDefinitionPublisher { /// @@ -87,7 +90,7 @@ public class WorkflowDefinitionPublisher( var workflowGraph = await workflowDefinitionService.MaterializeWorkflowAsync(definition, cancellationToken); var validationErrors = (await workflowValidator.ValidateAsync(workflowGraph.Workflow, cancellationToken)).ToList(); - if (validationErrors.Any()) + if (validationErrors.Any() && options.Value.FailOnValidationErrors) return new(false, validationErrors, new([])); await mediator.SendAsync(new WorkflowDefinitionPublishing(definition), cancellationToken); diff --git a/test/integration/Elsa.Workflows.IntegrationTests/Scenarios/ImportAndPublish/ImportAndPublishCronTests.cs b/test/integration/Elsa.Workflows.IntegrationTests/Scenarios/ImportAndPublish/ImportAndPublishCronTests.cs index e61fa08b1..2d41f788e 100644 --- a/test/integration/Elsa.Workflows.IntegrationTests/Scenarios/ImportAndPublish/ImportAndPublishCronTests.cs +++ b/test/integration/Elsa.Workflows.IntegrationTests/Scenarios/ImportAndPublish/ImportAndPublishCronTests.cs @@ -1,4 +1,5 @@ -using Elsa.Testing.Shared; +using Elsa.Extensions; +using Elsa.Testing.Shared; using Elsa.Workflows.Management; using Microsoft.Extensions.DependencyInjection; using Xunit.Abstractions; @@ -8,10 +9,12 @@ namespace Elsa.Workflows.IntegrationTests.Scenarios.ImportAndPublish; public class ImportAndPublishCronTests { private readonly CapturingTextWriter _capturingTextWriter = new(); + private readonly ITestOutputHelper _testOutputHelper; private readonly IServiceProvider _services; public ImportAndPublishCronTests(ITestOutputHelper testOutputHelper) { + _testOutputHelper = testOutputHelper; _services = new TestApplicationBuilder(testOutputHelper) .WithCapturingTextWriter(_capturingTextWriter) .Build(); @@ -53,4 +56,29 @@ public class ImportAndPublishCronTests Assert.Single(result.ValidationErrors); Assert.Equal("Error when parsing cron expression: The given cron expression has an invalid format. Seconds: Value must be a number between 0 and 59 (all inclusive).", result.ValidationErrors.Single().Message); } + + [Fact(DisplayName = "Cron workflow with bad cron expression should publish successfully when FailOnValidationErrors is disabled.")] + public async Task ImportAndPublish_ShouldSucceed_WithBadCronExpression_WhenFailOnValidationErrorsDisabled() + { + // Opt out of strict publishing. + var services = new TestApplicationBuilder(_testOutputHelper) + .WithCapturingTextWriter(_capturingTextWriter) + .ConfigureElsa(elsa => elsa.UseWorkflowManagement(management => management.UseFailOnValidationErrors(false))) + .Build(); + + // Populate registries. + await services.PopulateRegistriesAsync(); + + // Import workflow. + var workflowDefinition = await services.ImportWorkflowDefinitionAsync($"Scenarios/ImportAndPublish/Workflows/bad-cron-expression.json"); + + // Publish. + IWorkflowDefinitionPublisher workflowDefinitionPublisher = services.GetRequiredService(); + var result = await workflowDefinitionPublisher.PublishAsync(workflowDefinition); + + // Assert: publishing succeeds while the validation error is surfaced as a warning. + Assert.True(result.Succeeded); + Assert.Single(result.ValidationErrors); + Assert.Equal("Error when parsing cron expression: The given cron expression has an invalid format. Seconds: Value must be a number between 0 and 59 (all inclusive).", result.ValidationErrors.Single().Message); + } } \ No newline at end of file diff --git a/test/integration/Elsa.Workflows.IntegrationTests/Scenarios/ImportAndPublish/ImportAndPublishHttpEndpointsTests.cs b/test/integration/Elsa.Workflows.IntegrationTests/Scenarios/ImportAndPublish/ImportAndPublishHttpEndpointsTests.cs index d1b5a322e..29d127f92 100644 --- a/test/integration/Elsa.Workflows.IntegrationTests/Scenarios/ImportAndPublish/ImportAndPublishHttpEndpointsTests.cs +++ b/test/integration/Elsa.Workflows.IntegrationTests/Scenarios/ImportAndPublish/ImportAndPublishHttpEndpointsTests.cs @@ -9,10 +9,12 @@ namespace Elsa.Workflows.IntegrationTests.Scenarios.ImportAndPublish; public class ImportAndPublishHttpEndpointsTests { private readonly CapturingTextWriter _capturingTextWriter = new(); + private readonly ITestOutputHelper _testOutputHelper; private readonly IServiceProvider _services; public ImportAndPublishHttpEndpointsTests(ITestOutputHelper testOutputHelper) { + _testOutputHelper = testOutputHelper; _services = new TestApplicationBuilder(testOutputHelper) .WithCapturingTextWriter(_capturingTextWriter) .ConfigureElsa(configure => configure.UseHttp()) @@ -65,4 +67,41 @@ public class ImportAndPublishHttpEndpointsTests Assert.Single(result.ValidationErrors); Assert.Equal("The /test path and get method are already in use by another workflow!", result.ValidationErrors.Single().Message); } + + [Fact(DisplayName = "Http endpoint workflow with duplicate path and method should publish successfully when FailOnValidationErrors is disabled.")] + public async Task ImportAndPublish_ShouldSucceed_WithTwoHttpEndpointSamePathMethod_WhenFailOnValidationErrorsDisabled() + { + // Opt out of strict publishing. + var services = new TestApplicationBuilder(_testOutputHelper) + .WithCapturingTextWriter(_capturingTextWriter) + .ConfigureElsa(configure => configure + .UseHttp() + .UseWorkflowManagement(management => management.UseFailOnValidationErrors(false))) + .Build(); + + // Populate registries. + await services.PopulateRegistriesAsync(); + + // Import first workflow. + var workflowDefinition = await services.ImportWorkflowDefinitionAsync($"Scenarios/ImportAndPublish/Workflows/http-workflow.json"); + + // Publish first workflow. + IWorkflowDefinitionPublisher workflowDefinitionPublisher = services.GetRequiredService(); + var result = await workflowDefinitionPublisher.PublishAsync(workflowDefinition); + + // Assert first workflow. + Assert.True(result.Succeeded); + Assert.Empty(result.ValidationErrors); + + // Import second workflow. + workflowDefinition = await services.ImportWorkflowDefinitionAsync($"Scenarios/ImportAndPublish/Workflows/http-workflow.json"); + + // Publish second workflow. + result = await workflowDefinitionPublisher.PublishAsync(workflowDefinition); + + // Assert: publishing succeeds while the validation error is surfaced as a warning. + Assert.True(result.Succeeded); + Assert.Single(result.ValidationErrors); + Assert.Equal("The /test path and get method are already in use by another workflow!", result.ValidationErrors.Single().Message); + } } \ No newline at end of file From b266b32ef9d5adb29a8f265a4ade393a60b89ee2 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Fri, 19 Jun 2026 21:13:01 +0200 Subject: [PATCH 2/5] Restore trailing newline in ManagementOptions.cs Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Elsa.Workflows.Management/Options/ManagementOptions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/Elsa.Workflows.Management/Options/ManagementOptions.cs b/src/modules/Elsa.Workflows.Management/Options/ManagementOptions.cs index 53e2f4751..47db863e5 100644 --- a/src/modules/Elsa.Workflows.Management/Options/ManagementOptions.cs +++ b/src/modules/Elsa.Workflows.Management/Options/ManagementOptions.cs @@ -42,4 +42,4 @@ public class ManagementOptions /// defaulted to false (opt-in); as of 3.8.0 it defaults to true (opt-out). /// public bool FailOnValidationErrors { get; set; } = true; -} \ No newline at end of file +} From 354d59942c2e8827a2335fb911b365331d8efc29 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Fri, 19 Jun 2026 21:13:01 +0200 Subject: [PATCH 3/5] Honor publish result in Publish/BulkPublish endpoints The Publish and BulkPublish API endpoints invoked PublishAsync but ignored the returned result.Succeeded, so publications that failed due to validation errors were silently reported as successful. On main, strict publishing is the default, making this a high-impact correctness issue. - Publish endpoint: when the publish result indicates failure, surface the validation errors via AddError and return a 400, mirroring the existing save-and-publish (Post) endpoint pattern. - BulkPublish endpoint: add a Failed bucket; definitions whose publication fails are recorded in Failed instead of Published, and the new bucket is returned on the response. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../WorkflowDefinitions/BulkPublish/Endpoint.cs | 10 +++++++++- .../WorkflowDefinitions/BulkPublish/Models.cs | 3 ++- .../Endpoints/WorkflowDefinitions/Publish/Endpoint.cs | 10 ++++++++++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/BulkPublish/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/BulkPublish/Endpoint.cs index da23172a1..dbff37959 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/BulkPublish/Endpoint.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/BulkPublish/Endpoint.cs @@ -41,6 +41,7 @@ internal class BulkPublish( var alreadyPublished = new List(); var skipped = new List(); var updatedConsumers = new List(); + var failed = new List(); var publishableDefinitions = new List<(string DefinitionId, WorkflowDefinition Definition)>(); var definitions = (await store.FindManyAsync(new WorkflowDefinitionFilter @@ -88,13 +89,20 @@ internal class BulkPublish( foreach (var (definitionId, definition) in publishableDefinitions) { var result = await workflowDefinitionPublisher.PublishAsync(definition, cancellationToken); + + if (!result.Succeeded) + { + failed.Add(definitionId); + continue; + } + published.Add(definitionId); if (result.AffectedWorkflows.WorkflowDefinitions.Count > 0) updatedConsumers.AddRange(result.AffectedWorkflows.WorkflowDefinitions.Select(x => x.DefinitionId)); } - return new(published, alreadyPublished, notFound, skipped, updatedConsumers); + return new(published, alreadyPublished, notFound, skipped, updatedConsumers, failed); } } diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/BulkPublish/Models.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/BulkPublish/Models.cs index 03a4f9f4c..af74688c0 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/BulkPublish/Models.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/BulkPublish/Models.cs @@ -5,11 +5,12 @@ internal class Request public ICollection DefinitionIds { get; set; } = default!; } -internal class Response(ICollection published, ICollection alreadyPublished, ICollection notFound, ICollection skipped, ICollection updatedConsumers) +internal class Response(ICollection published, ICollection alreadyPublished, ICollection notFound, ICollection skipped, ICollection updatedConsumers, ICollection failed) { public ICollection Published { get; } = published; public ICollection AlreadyPublished { get; } = alreadyPublished; public ICollection NotFound { get; } = notFound; public ICollection Skipped { get; } = skipped; public ICollection UpdatedConsumers { get; } = updatedConsumers; + public ICollection Failed { get; } = failed; } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Publish/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Publish/Endpoint.cs index 299534025..54c4f6949 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Publish/Endpoint.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Publish/Endpoint.cs @@ -60,6 +60,16 @@ internal class Publish( var isPublished = definition.IsPublished; var result = !isPublished ? await workflowDefinitionPublisher.PublishAsync(definition, cancellationToken) : null; + + if (result is { Succeeded: false }) + { + foreach (var validationError in result.ValidationErrors) + AddError(validationError.Message); + + await Send.ErrorsAsync(400, cancellationToken); + return; + } + var mappedDefinition = await linker.MapAsync(definition, cancellationToken); var response = new Response(mappedDefinition, isPublished, result?.AffectedWorkflows.WorkflowDefinitions.Count ?? 0); await Send.OkAsync(response, cancellationToken); From 2863c980f32c561d06fcfbce32d5f91ad17059bb Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Fri, 19 Jun 2026 21:29:00 +0200 Subject: [PATCH 4/5] Surface publish validation warnings on API responses In lenient mode (FailOnValidationErrors = false), PublishAsync returns Succeeded = true while ValidationErrors may still be non-empty. Previously the publish API endpoints discarded those warnings, so lenient-mode callers got a 200 OK with no indication of the validation issues. - Publish and Post (save-and-publish) responses now include a ValidationErrors collection populated with the publish result's validation messages. - BulkPublish response now includes a Warnings dictionary mapping each successfully-published definition id to its validation warning messages. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Endpoints/WorkflowDefinitions/BulkPublish/Endpoint.cs | 6 +++++- .../Endpoints/WorkflowDefinitions/BulkPublish/Models.cs | 3 ++- .../Endpoints/WorkflowDefinitions/Post/Endpoint.cs | 3 ++- .../Endpoints/WorkflowDefinitions/Post/Models.cs | 2 +- .../Endpoints/WorkflowDefinitions/Publish/Endpoint.cs | 3 ++- .../Endpoints/WorkflowDefinitions/Publish/Models.cs | 2 +- 6 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/BulkPublish/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/BulkPublish/Endpoint.cs index dbff37959..053491b2e 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/BulkPublish/Endpoint.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/BulkPublish/Endpoint.cs @@ -42,6 +42,7 @@ internal class BulkPublish( var skipped = new List(); var updatedConsumers = new List(); var failed = new List(); + var warnings = new Dictionary>(); var publishableDefinitions = new List<(string DefinitionId, WorkflowDefinition Definition)>(); var definitions = (await store.FindManyAsync(new WorkflowDefinitionFilter @@ -97,12 +98,15 @@ internal class BulkPublish( } published.Add(definitionId); + + if (result.ValidationErrors.Count > 0) + warnings[definitionId] = result.ValidationErrors.Select(x => x.Message).ToList(); if (result.AffectedWorkflows.WorkflowDefinitions.Count > 0) updatedConsumers.AddRange(result.AffectedWorkflows.WorkflowDefinitions.Select(x => x.DefinitionId)); } - return new(published, alreadyPublished, notFound, skipped, updatedConsumers, failed); + return new(published, alreadyPublished, notFound, skipped, updatedConsumers, failed, warnings); } } diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/BulkPublish/Models.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/BulkPublish/Models.cs index af74688c0..879a86321 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/BulkPublish/Models.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/BulkPublish/Models.cs @@ -5,7 +5,7 @@ internal class Request public ICollection DefinitionIds { get; set; } = default!; } -internal class Response(ICollection published, ICollection alreadyPublished, ICollection notFound, ICollection skipped, ICollection updatedConsumers, ICollection failed) +internal class Response(ICollection published, ICollection alreadyPublished, ICollection notFound, ICollection skipped, ICollection updatedConsumers, ICollection failed, IDictionary> warnings) { public ICollection Published { get; } = published; public ICollection AlreadyPublished { get; } = alreadyPublished; @@ -13,4 +13,5 @@ internal class Response(ICollection published, ICollection alrea public ICollection Skipped { get; } = skipped; public ICollection UpdatedConsumers { get; } = updatedConsumers; public ICollection Failed { get; } = failed; + public IDictionary> Warnings { get; } = warnings; } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Post/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Post/Endpoint.cs index a0895762e..1dfd307c7 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Post/Endpoint.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Post/Endpoint.cs @@ -116,7 +116,8 @@ internal class Post( var mappedDefinition = await linker.MapAsync(draft, cancellationToken); var affectedWorkflows = result?.AffectedWorkflows?.WorkflowDefinitions ?? []; - var response = new Response(mappedDefinition, false, affectedWorkflows.Count); + var validationErrors = result?.ValidationErrors.Select(e => e.Message).ToList() ?? []; + var response = new Response(mappedDefinition, false, affectedWorkflows.Count, validationErrors); await HttpContext.Response.WriteAsJsonAsync(response, serializerOptions, cancellationToken); } diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Post/Models.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Post/Models.cs index baceba1df..847f83179 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Post/Models.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Post/Models.cs @@ -2,4 +2,4 @@ using Elsa.Workflows.Api.Models; namespace Elsa.Workflows.Api.Endpoints.WorkflowDefinitions.Post; -internal record Response(LinkedWorkflowDefinitionModel WorkflowDefinition, bool AlreadyPublished, int ConsumingWorkflowCount); \ No newline at end of file +internal record Response(LinkedWorkflowDefinitionModel WorkflowDefinition, bool AlreadyPublished, int ConsumingWorkflowCount, ICollection ValidationErrors); \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Publish/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Publish/Endpoint.cs index 54c4f6949..c5f0116b1 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Publish/Endpoint.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Publish/Endpoint.cs @@ -70,8 +70,9 @@ internal class Publish( return; } + var validationErrors = result?.ValidationErrors.Select(e => e.Message).ToList() ?? []; var mappedDefinition = await linker.MapAsync(definition, cancellationToken); - var response = new Response(mappedDefinition, isPublished, result?.AffectedWorkflows.WorkflowDefinitions.Count ?? 0); + var response = new Response(mappedDefinition, isPublished, result?.AffectedWorkflows.WorkflowDefinitions.Count ?? 0, validationErrors); await Send.OkAsync(response, cancellationToken); } } diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Publish/Models.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Publish/Models.cs index a4ed95916..2554d668a 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Publish/Models.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Publish/Models.cs @@ -7,4 +7,4 @@ internal class Request public string DefinitionId { get; set; } = default!; } -internal record Response(LinkedWorkflowDefinitionModel WorkflowDefinition, bool AlreadyPublished, int ConsumingWorkflowCount); \ No newline at end of file +internal record Response(LinkedWorkflowDefinitionModel WorkflowDefinition, bool AlreadyPublished, int ConsumingWorkflowCount, ICollection ValidationErrors); \ No newline at end of file From 6b08988edd8630de8e30859c8c4e7720017bfd86 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Fri, 19 Jun 2026 22:39:56 +0200 Subject: [PATCH 5/5] Fix CShells package restore in CI CI failed during restore because the centrally pinned CShells preview version was no longer available from the mapped cshells Feedz source. Local builds did not reproduce while the preview packages existed in the developer NuGet cache. Pin the CShells packages to the public 0.0.28 release and allow CShells package IDs to resolve from NuGet.org in package source mapping, so clean CI restores no longer depend on unavailable private/feed preview packages. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- Directory.Packages.props | 12 ++++++------ NuGet.Config | 2 ++ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index f008be8d6..49db077d8 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -106,12 +106,12 @@ - - - - - - + + + + + + diff --git a/NuGet.Config b/NuGet.Config index 3a1e84216..590436810 100644 --- a/NuGet.Config +++ b/NuGet.Config @@ -12,6 +12,8 @@ + +