Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .github/tools/tag-selector/src/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,8 @@ export type ComputeOutput = {
export function stripPrefix(tag: string, prefix?: string): string {
if (!prefix)
return tag;

return tag.startsWith(prefix) ? tag.slice(prefix.length) : tag;
}

/** Compute outputs from a set of tag names */
export function computeFromTags(input: ComputeInput): ComputeOutput {
const tagPrefix = input.tagPrefix ?? "";
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/sdk_build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,8 @@ jobs:
INPUT_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
INPUT_TAG_PREFIX: "v"
INPUT_STABLE_COUNT: "2"
INPUT_RC_IDENTIFIER: "rc"
INPUT_RC_COUNT: "2"
INPUT_RC_IDENTIFIER: "rc"
run: |
node .github/tools/tag-selector/dist/index.js
- name: Show outputs
Expand Down Expand Up @@ -375,4 +375,4 @@ jobs:
- name: Publish ${{ matrix.package }} to NuGet
run: |
dotnet nuget push "${{ matrix.package }}" --skip-duplicate --api-key ${{ steps.login.outputs.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json


35 changes: 35 additions & 0 deletions all.sln
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,16 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.Testcontainers.Xunit",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.IntegrationTest.AI", "test\Dapr.IntegrationTest.AI\Dapr.IntegrationTest.AI.csproj", "{AD9F25C7-7BBD-459A-B3EF-1BE75C25E80A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.Workflow.Versioning.Abstractions", "src\Dapr.Workflow.Versioning.Abstractions\Dapr.Workflow.Versioning.Abstractions.csproj", "{FB92C5BC-BEAB-465F-8976-CF2866A2D699}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.Workflow.Versioning.Runtime", "src\Dapr.Workflow.Versioning.Runtime\Dapr.Workflow.Versioning.Runtime.csproj", "{B46E7964-03F5-450E-BF16-C3518EBF36CB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.Workflow.Versioning.Generators", "src\Dapr.Workflow.Versioning.Generators\Dapr.Workflow.Versioning.Generators.csproj", "{BD1FA767-AC6D-429D-8BC0-3C0B52AA11FF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.Workflow.Versioning", "src\Dapr.Workflow.Versioning\Dapr.Workflow.Versioning.csproj", "{CB619F1E-B90C-4BCB-9DDA-A5A4F5967661}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.IntegrationTest.Workflow.Versioning", "test\Dapr.IntegrationTest.Workflow.Versioning\Dapr.IntegrationTest.Workflow.Versioning.csproj", "{1AD32297-630E-4DFB-B3E4-CAFCE993F27F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -609,6 +619,26 @@ Global
{AD9F25C7-7BBD-459A-B3EF-1BE75C25E80A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AD9F25C7-7BBD-459A-B3EF-1BE75C25E80A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AD9F25C7-7BBD-459A-B3EF-1BE75C25E80A}.Release|Any CPU.Build.0 = Release|Any CPU
{FB92C5BC-BEAB-465F-8976-CF2866A2D699}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FB92C5BC-BEAB-465F-8976-CF2866A2D699}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FB92C5BC-BEAB-465F-8976-CF2866A2D699}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FB92C5BC-BEAB-465F-8976-CF2866A2D699}.Release|Any CPU.Build.0 = Release|Any CPU
{B46E7964-03F5-450E-BF16-C3518EBF36CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B46E7964-03F5-450E-BF16-C3518EBF36CB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B46E7964-03F5-450E-BF16-C3518EBF36CB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B46E7964-03F5-450E-BF16-C3518EBF36CB}.Release|Any CPU.Build.0 = Release|Any CPU
{BD1FA767-AC6D-429D-8BC0-3C0B52AA11FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BD1FA767-AC6D-429D-8BC0-3C0B52AA11FF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BD1FA767-AC6D-429D-8BC0-3C0B52AA11FF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BD1FA767-AC6D-429D-8BC0-3C0B52AA11FF}.Release|Any CPU.Build.0 = Release|Any CPU
{CB619F1E-B90C-4BCB-9DDA-A5A4F5967661}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CB619F1E-B90C-4BCB-9DDA-A5A4F5967661}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CB619F1E-B90C-4BCB-9DDA-A5A4F5967661}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CB619F1E-B90C-4BCB-9DDA-A5A4F5967661}.Release|Any CPU.Build.0 = Release|Any CPU
{1AD32297-630E-4DFB-B3E4-CAFCE993F27F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1AD32297-630E-4DFB-B3E4-CAFCE993F27F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1AD32297-630E-4DFB-B3E4-CAFCE993F27F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1AD32297-630E-4DFB-B3E4-CAFCE993F27F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -721,6 +751,11 @@ Global
{38AAD849-B59C-4011-B309-3E9F291E9B9F} = {0AF0FE8D-C234-4F04-8514-32206ACE01BD}
{2D6EB9E0-C5BF-4BA4-B69F-0D2B5A0E36D5} = {27C5D71D-0721-4221-9286-B94AB07B58CF}
{AD9F25C7-7BBD-459A-B3EF-1BE75C25E80A} = {8462B106-175A-423A-BA94-BE0D39D0BD8E}
{FB92C5BC-BEAB-465F-8976-CF2866A2D699} = {27C5D71D-0721-4221-9286-B94AB07B58CF}
{B46E7964-03F5-450E-BF16-C3518EBF36CB} = {27C5D71D-0721-4221-9286-B94AB07B58CF}
{BD1FA767-AC6D-429D-8BC0-3C0B52AA11FF} = {27C5D71D-0721-4221-9286-B94AB07B58CF}
{CB619F1E-B90C-4BCB-9DDA-A5A4F5967661} = {27C5D71D-0721-4221-9286-B94AB07B58CF}
{1AD32297-630E-4DFB-B3E4-CAFCE993F27F} = {8462B106-175A-423A-BA94-BE0D39D0BD8E}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {65220BF2-EAE1-4CB2-AA58-EBE80768CB40}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
<PackageReference Include="Microsoft.Extensions.Options" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// ------------------------------------------------------------------------
// Copyright 2026 The Dapr Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------------

namespace Dapr.Workflow.Versioning;

/// <summary>
/// Provides shared, localized-friendly diagnostic titles and messages used by both the source generator and the
/// runtime.
/// </summary>
/// <remarks>
/// Implements can integrate with localization frameworks. The message content should be deterministic and
/// safe to surface to developers.
/// </remarks>
public interface IWorkflowVersionDiagnostics
{
/// <summary>
/// Gets the title used when a strategy type cannot be constructed or is invalid.
/// </summary>
string UnknownStrategyTitle { get; }

/// <summary>
/// Formats the message shown when a strategy type could not be created or does not implement
/// <see cref="IWorkflowVersionStrategy"/>.
/// </summary>
/// <param name="typeName">The CLR type name of the workflow.</param>
/// <param name="strategyType">The provided strategy type.</param>
/// <returns>A human-readable message string.</returns>
string UnknownStrategyMessage(string typeName, Type strategyType);

/// <summary>
/// Gets the title used when the version information cannot be parsed from a type name.
/// </summary>
string CouldNotParseTitle { get; }

/// <summary>
/// Formats the message shown when no available strategy can derive a canonical name and version.
/// </summary>
/// <param name="typeName">The CLR type name of the workflow.</param>
/// <returns>A human-readable message string.</returns>
string CouldNotParseMessage(string typeName);

/// <summary>
/// Gets the title used when a canonical family contains no versions.
/// </summary>
string EmptyFamilyTitle { get; }

/// <summary>
/// Formats the message shown when a canonical family has no versions.
/// </summary>
/// <param name="canonicalName">The canonical name of the workflow family.</param>
/// <returns>A human-readable message string.</returns>
string EmptyFamilyMessage(string canonicalName);

/// <summary>
/// Gets the title used when latest version selection is ambiguous.
/// </summary>
string AmbiguousLatestTitle { get; }

/// <summary>
/// Formats the message shown when the version selector cannot determine a unique latest version.
/// </summary>
/// <param name="canonicalName">The canonical name of the workflow family.</param>
/// <param name="versions">The set of tied version strings.</param>
/// <returns>A human-readable message string.</returns>
string AmbiguousLatestMessage(string canonicalName, IReadOnlyList<string>? versions);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// ------------------------------------------------------------------------
// Copyright 2026 The Dapr Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------------

namespace Dapr.Workflow.Versioning;

/// <summary>
/// Defines the policy that selects the "latest" workflow version from a set of candidates that share the same
/// canonical name.
/// </summary>
/// <remarks>
/// The selector may apply arbitrary rules (e.g., exclude pre-release tags, prefer a specific branch, or implement
/// canary behaviors) on top of the comparison semantics provided by the active strategy.
/// </remarks>
public interface IWorkflowVersionSelector
{
/// <summary>
/// Selects the "latest" version identity from a non-empty set of candidates.
/// </summary>
/// <param name="canonicalName">The canonical name shared by all <paramref name="candidates"/>.</param>
/// <param name="candidates">The collection of workflow version identities to select from.</param>
/// <param name="strategy">The active versioning strategy, used to order version strings or resolve tiebreakers.</param>
/// <returns>The chosen latest <see cref="WorkflowVersionIdentity"/>.</returns>
WorkflowVersionIdentity SelectLatest(string canonicalName, IReadOnlyCollection<WorkflowVersionIdentity> candidates,
IWorkflowVersionStrategy strategy);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// ------------------------------------------------------------------------
// Copyright 2026 The Dapr Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------------

namespace Dapr.Workflow.Versioning;

/// <summary>
/// Defines how workflow type names are parsed into canonical names and versions, and how
/// two version strings are compared for ordering.
/// </summary>
/// <remarks>
/// Implementations may encode numeric suffix rules, prefix rules, SemVer, date-based versions, etc.
/// This interface also implements <see cref="IComparer{T}"/> for comparing version strings directly.
/// </remarks>
public interface IWorkflowVersionStrategy : IComparer<string>
{
/// <summary>
/// Attempts to derive a canonical name and version from a workflow type name.
/// Returns true on success; false if this strategy cannot parse the name.
/// </summary>
/// <param name="typeName">The workflow type name to parse.</param>
/// <param name="canonicalName">When successful, receives the canonical name for the workflow family.</param>
/// <param name="version">When successful, receives the parsed version string.</param>
/// <returns>
/// <see langword="true"/> if the strategy could derive a canonical name and version;
/// otherwise <see langword="false"/>.
/// </returns>
bool TryParse(string typeName, out string canonicalName, out string version);

/// <summary>
/// Compares two version strings and returns a value indicating their relative order.
/// </summary>
/// <param name="v1">The first version string.</param>
/// <param name="v2">The second version string.</param>
/// <returns>
/// A signed integer that indicates the relative values of <paramref name="v1"/> and <paramref name="v2"/>:
/// - Less than zero if <paramref name="v1"/> is older than <paramref name="v2"/>;
/// - Zero if they are equal;
/// - Greater than zero if <paramref name="v1"/> is newer than <paramref name="v2"/>.
/// </returns>
new int Compare(string v1, string v2);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// ------------------------------------------------------------------------
// Copyright 2026 The Dapr Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------------

namespace Dapr.Workflow.Versioning;

/// <summary>
/// Standard diagnostic IDs provides by the workflow versioning generator and runtime.
/// </summary>
/// <remarks>
/// These IDs are intentionally stable and can be used for filtering or documentation.
/// </remarks>
public static class IWorkflowVersioningDiagnosticIds
{
/// <summary>
/// Diagnostic with a <see cref="WorkflowVersionAttribute.StrategyType"/> cannot be instantiated or does not
/// implement <see cref="IWorkflowVersionStrategy"/>.
/// </summary>
public const string UnknownStrategy = "DWV001";

/// <summary>
/// Diagnostic with no strategy can parse the workflow type name into a canonical name and version.
/// </summary>
public const string CouldNotParse = "DWV002";

/// <summary>
/// Diagnostic when a canonical family has no versions (e.g., all were filtered out).
/// </summary>
public const string EmptyFamily = "DWV003";

/// <summary>
/// Diagnostic when selection policy cannot determine a unique latest version (ambiguous winners).
/// </summary>
public const string AmbiguousLatest = "DWV004";
}
22 changes: 22 additions & 0 deletions src/Dapr.Workflow.Versioning.Abstractions/VersionParseResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// ------------------------------------------------------------------------
// Copyright 2026 The Dapr Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------------

namespace Dapr.Workflow.Versioning;

/// <summary>
/// The result of deriving a canonical name and version from a workflow type.
/// </summary>
/// <param name="CanonicalName">The canonical name for the workflow family.</param>
/// <param name="Version">The derived or explicit version string.</param>
/// <param name="IsExplicit"><see langword="true"/> if provided explicitly (e.g. by <see cref="WorkflowVersionAttribute"/>); otherwise <see langword="false"/>.</param>
public readonly record struct VersionParseResult(string CanonicalName, string Version, bool IsExplicit);
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// ------------------------------------------------------------------------
// Copyright 2026 The Dapr Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------------

namespace Dapr.Workflow.Versioning;

/// <summary>
/// Well-known names and constants related to workflow versioning.
/// </summary>
public static class WellKnownVersioning
{
/// <summary>
/// The fully qualified name of <see cref="WorkflowVersionAttribute"/>.
/// </summary>
public const string WorkflowVersionAttributeFullName = "Dapr.Workflow.Versioning.WorkflowVersionAttribute";
}
21 changes: 21 additions & 0 deletions src/Dapr.Workflow.Versioning.Abstractions/WorkflowFamily.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// ------------------------------------------------------------------------
// Copyright 2026 The Dapr Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------------

namespace Dapr.Workflow.Versioning;

/// <summary>
/// Represents all discovered versions of a workflow family that share the same canonical name.
/// </summary>
/// <param name="CanonicalName">Gets the canonical name shared by the versions in this family.</param>
/// <param name="Versions">Gets the unordered collection of versions discovered for this family.</param>
public sealed record WorkflowFamily(string CanonicalName, IReadOnlyCollection<WorkflowVersionIdentity> Versions);
Loading
Loading