Skip to content

Implement per-orchestration and per-activity versioning#695

Open
torosent wants to merge 44 commits into
mainfrom
torosent/orchestration-versioning
Open

Implement per-orchestration and per-activity versioning#695
torosent wants to merge 44 commits into
mainfrom
torosent/orchestration-versioning

Conversation

@torosent
Copy link
Copy Markdown
Member

@torosent torosent commented Apr 2, 2026

Summary

What changed?

  • Implemented per-orchestration and per-activity versioning across the abstractions, worker runtime, gRPC worker, in-proc sidecar, and source generator surfaces.
  • Added version-aware routing and migration support for orchestrators and activities, including explicit ActivityOptions.Version overrides, inherited version fallback behavior, ContinueAsNew version migration, and strict worker-version filter handling.
  • Added and updated focused samples and tests, including the activity versioning sample and unit/integration coverage for explicit version selection, fallback behavior, tag propagation, and review follow-ups.
  • Removed the local-only docs/superpowers planning/spec artifacts from the branch diff.

Why is this change needed?

  • The SDK needs task-level version selection so orchestrations and activities can evolve independently without forcing users onto worker-level versioning semantics.
  • Explicit activity version selection must be strict for correctness, while inherited orchestration-version activity routing still needs compatibility fallback to existing unversioned registrations.
  • This PR moves the implementation from the design/proposal stage in #692 to a working end-to-end implementation with generator, runtime, sample, and test coverage.

Issues / work items


Project checklist

  • Release notes are not required for the next release
    • Otherwise: Notes added to release_notes.md
  • Backport is not required
    • Otherwise: Backport tracked by issue/PR #issue_or_pr
  • All required tests have been added/updated (unit tests, E2E tests)
  • Breaking change?
    • If yes:
      • Impact:
      • Migration guidance:

AI-assisted code disclosure (required)

Was an AI tool used? (select one)

  • No
  • Yes, AI helped write parts of this PR (e.g., GitHub Copilot)
  • Yes, an AI agent generated most of this PR

If AI was used:

  • Tool(s): GitHub Copilot CLI (GPT-5.4)
  • AI-assisted areas/files:
    • src/Abstractions/*
    • src/Worker/Core/*
    • src/Worker/Grpc/*
    • src/InProcessTestHost/*
    • src/Grpc/orchestrator_service.proto
    • test/Worker/Core.Tests/*
    • test/Grpc.IntegrationTests/*
    • samples/*versioning*
  • What you changed after AI output:
    • Drove the implementation through multiple review/debug cycles, tightened explicit activity-version semantics, fixed tag propagation gaps in the in-proc sidecar/protobuf path, added stricter regression coverage, and removed local-only planning docs from the PR.

AI verification (required if AI was used):

  • I understand the code and can explain it
  • I verified referenced APIs/types exist and are correct
  • I reviewed edge cases/failure paths (timeouts, retries, cancellation, exceptions)
  • I reviewed concurrency/async behavior
  • I checked for unintended breaking or behavior changes

Testing

Automated tests

  • Result: Passed
  • dotnet build Microsoft.DurableTask.sln --nologo --verbosity minimal
  • dotnet test test/Worker/Core.Tests/Worker.Tests.csproj --nologo --verbosity minimal
  • dotnet test test/Worker/Grpc.Tests/Worker.Grpc.Tests.csproj --nologo --verbosity minimal
  • dotnet test test/Grpc.IntegrationTests/Grpc.IntegrationTests.csproj --filter "FullyQualifiedName~VersionedClassSyntaxIntegrationTests" --nologo --verbosity minimal

Manual validation (only if runtime/behavior changed)

  • Environment (OS, .NET version, components): macOS, .NET SDK 10.0.5, gRPC worker, in-proc sidecar/integration host
  • Steps + observed results:
    1. Scheduled versioned orchestrations with explicit versions and observed routing to the matching class-based implementation.
    2. Executed explicit activity-version override scenarios and observed exact-match routing with no fallback to unversioned registrations.
    3. Executed inherited activity-version scenarios and observed compatibility fallback to unversioned activity registrations when no explicit activity version was requested.
  • Evidence (optional):
    • Integration coverage in VersionedClassSyntaxIntegrationTests

Notes for reviewers

  • src/Grpc/orchestrator_service.proto is sourced from microsoft/durabletask-protobuf; after microsoft/durabletask-protobuf#68 merged, src/Grpc/versions.txt was refreshed to the current main commit.
  • Refreshing from upstream main also pulls in the additive RewindOrchestrationAction contract already present in the protobuf source of truth.
  • Explicit activity-version strictness relies on ActivityRequest.tags; sidecars outside this repo need matching support to preserve the same semantics end-to-end.
  • The PR includes the review follow-ups that protect the internal explicit-version tag from user spoofing and keep TaskScheduledEvent.Tags in the in-proc protobuf path.

torosent and others added 30 commits March 31, 2026 15:28
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Split into two focused samples:
- WorkerVersioningSample: deployment-based versioning with UseDefaultVersion()
- PerOrchestratorVersioningSample: multi-version routing with [DurableTaskVersion]

Both tested against the DTS emulator.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The v1 orchestrator uses an AlreadyMigrated flag in the input to prevent
infinite ContinueAsNew loops if the backend does not propagate NewVersion.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
torosent and others added 5 commits April 2, 2026 09:17
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment thread src/Generators/DurableTaskSourceGenerator.cs Fixed
torosent and others added 5 commits April 2, 2026 09:35
Imported src/Grpc/orchestrator_service.proto from microsoft/durabletask-protobuf branch torosent/activity-request-tags (PR #68) instead of carrying the contract change as a local hand edit.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Refresh src/Grpc/orchestrator_service.proto from durabletask-protobuf main after PR #68 merged and update src/Grpc/versions.txt to the mainline source commit.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Resolves conflicts in:
- src/Grpc/versions.txt: take main's newer protobuf import
- test/Worker/Core.Tests/Shims/TaskOrchestrationContextWrapperTests.cs:
  union activity-versioning tests with the new preserveUnprocessedEvents test
- src/Worker/Core/DependencyInjection/DurableTaskWorkerWorkItemFiltersValidator.cs:
  semantic conflict — main's validator referenced TaskName-keyed registry dicts
  while this branch keys them by OrchestratorVersionKey/ActivityVersionKey.
  Updated FindUnknown to use a HashSet of registered logical names so the
  validator works regardless of how many versions are registered for a name.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…combined

Addresses gap (4) in proposal #692: per-task [DurableTaskVersion] routing and
worker-level UseVersioning() both consume the orchestration instance version
field. When combined with MatchStrategy != None the worker-level filter runs
before per-task dispatch and silently rejects orchestrations whose instance
version does not match the worker version, masking per-task routing.

This change emits a single Warning-level log entry (EventId 78) at worker
construction when both features are configured, pointing the operator at
the docs and clarifying that the two features are not designed to be
combined.

Implementation notes:
- Added DurableTaskRegistry.HasAnyVersionedRegistration() extension in
  Worker.csproj (which already has IVT to Abstractions internals).
- Threaded an optional IOptionsMonitor<DurableTaskRegistry> through the
  GrpcDurableTaskWorker constructor so the check can run at startup
  without coupling it to the work-item filter path.
- Added three unit tests covering: combined-strict (warns), per-task only
  (no warn), worker-level only (no warn).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment on lines +31 to +37
foreach (OrchestratorVersionKey key in registry.Orchestrators.Keys)
{
if (!string.IsNullOrWhiteSpace(key.Version))
{
return true;
}
}
Comment on lines +39 to +45
foreach (ActivityVersionKey key in registry.Activities.Keys)
{
if (!string.IsNullOrWhiteSpace(key.Version))
{
return true;
}
}
torosent and others added 2 commits May 8, 2026 09:25
…validation, helper-conflict throw, line endings

Closes review findings #1, #5, #6, #8 from PR #695:

- ToVersionSuffix collision (#1): the previous encoder left underscore unescaped, so '1.0' and '1_x002E_0' both produced '_1_x002E_0'. Now every non-alphanumeric character (including '_') is escaped as '_xHHHH_', making the encoding injective and avoiding silent generated-helper collisions.

- Whitespace version validation (#5): DurableTaskVersionAttribute and the versioned AddOrchestrator/AddActivity overloads now reject whitespace-only version strings (null/empty still means 'unversioned'). The source generator emits a new error diagnostic DURABLE3005 so the misconfiguration is caught at compile time rather than first instance construction.

- Helper-conflict throw (#8): generated helpers ApplyGeneratedVersion / ApplyGeneratedActivityVersion now throw InvalidOperationException at runtime when a caller-supplied options.Version disagrees with the version baked into the generator-emitted helper name. Previously the caller's version silently won, defeating the purpose of the version-suffixed method. Updated the generator-output snapshot tests accordingly.

- TaskOptions.cs CRLF/LF normalization (#6): the file was the only one in the repo with mixed/CRLF line endings, polluting the PR diff. Normalized to LF to match the rest of the codebase.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…it-unversioned activity, fail-closed tag, throw on combined versioning

Closes review findings #2, #3, #4, #7 from PR #695:

- Orchestrator fallback strictness (#2): DurableTaskFactory.TryCreateOrchestrator no longer falls back to the unversioned registration when at least one versioned registration exists for the same logical name. Previously, scheduling 'PaymentWorkflow' v3 against a registry that had v1, v2, and an unversioned default would silently route the call to the unversioned default. Now this returns 'not found' so the caller is told what actually happened. The unversioned-fallback path is preserved when no versioned registration exists for the name (the migration scenario where pre-versioning instances scheduled with a specific version still need to dispatch). Same gating applied to TryCreateActivity for symmetry on the inherited-fallback path.

- Explicit-unversioned activity selection (#3): the API now distinguishes 'inherit' from 'explicit unversioned'. ActivityOptions.Version = null still means inherit; ActivityOptions.Version = TaskVersion.Unversioned (or any non-null TaskVersion, including default) is treated as an explicit selection — even when the version string is empty. This lets a v2 orchestration call the unversioned activity by passing TaskVersion.Unversioned. Added the public TaskVersion.Unversioned static for clarity.

- Tag fail-closed (#4): the dispatch-routing tag is renamed from 'microsoft.durabletask.activity.explicit-version' (boolean) to 'microsoft.durabletask.activity.version-source' with values 'explicit' or 'inherited'. The SDK now stamps the tag for both cases (not just explicit), and the worker treats a missing tag on a versioned request as 'explicit' — i.e., strict, no fallback. This means an older sidecar that drops tags can no longer silently degrade strict-explicit semantics to inherited-with-fallback; instead, those calls fail closed with 'activity not found'.

- Combined-versioning guard escalated and centralized (#7): the warning-only check on combined UseVersioning() + [DurableTaskVersion] is now a fail-fast InvalidOperationException at worker construction. Moved into a shared WorkerVersioningPolicy.EnsureNotCombined helper in Worker.csproj so any worker subclass (gRPC and any future transport) gets the same check. EventId 78 warning log entry removed.

Tests updated to reflect new semantics:
- TryCreateOrchestrator_WithMixedRegistrations_DoesNotFallBackForUnknownVersion (replaces old 'UsesUnversionedFallbackForUnknownVersion').
- TryCreateOrchestrator_WithOnlyUnversionedRegistration_FallsBackForVersionedRequest (preserves the migration path).
- CallActivityAsync_ExplicitUnversionedActivityOption_BypassesInherit (new b2 coverage).
- CallActivityAsync tag assertions updated to the new VersionSourceTagName / ExplicitSource / InheritedSource constants, including a new test that an unversioned-orchestration unversioned-activity stamps no tag at all.
- Constructor_PerTaskVersioningCombinedWithStrictWorkerVersioning_Throws (replaces old 'LogsWarning').

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment on lines +261 to +282
foreach (AttributeData attributeData in classType.GetAttributes())
{
if (attributeData.AttributeClass?.ToDisplayString() == "Microsoft.DurableTask.DurableTaskVersionAttribute"
&& attributeData.ConstructorArguments.Length > 0
&& attributeData.ConstructorArguments[0].Value is string version)
{
if (version.Length > 0 && string.IsNullOrWhiteSpace(version))
{
hasWhitespaceVersion = true;
taskVersionLocation = attributeData.ApplicationSyntaxReference?.GetSyntax().GetLocation();
// Treat as unversioned for downstream emission so we don't generate code referencing
// a whitespace literal; the diagnostic below will fail the build.
taskVersion = string.Empty;
}
else
{
taskVersion = version;
}

break;
}
}
Comment on lines +409 to +419
foreach (DurableTaskTypeInfo task in allTasks)
{
if (task.HasWhitespaceVersion)
{
Location location = task.TaskVersionLocation ?? task.TaskNameLocation ?? Location.None;
context.ReportDiagnostic(Diagnostic.Create(
WhitespaceTaskVersionRule,
location,
task.TaskName));
}
}
Comment on lines +164 to +171
foreach (string reserved in ActivityVersioning.ReservedTagKeys)
{
if (string.Equals(key, reserved, StringComparison.Ordinal))
{
isReserved = true;
break;
}
}
torosent and others added 2 commits May 8, 2026 10:17
…flict, TaskVersion null-safety, integration test refresh, migration recipe

Closes the second-pass review findings from PR #695:

#1 — Stale tag references in integration tests: VersionedClassSyntaxTestOrchestration.cs and VersionedClassSyntaxIntegrationTests.cs were still using the pre-rename 'microsoft.durabletask.activity.explicit-version' constant. The spoof-protection test in particular was passing for the wrong reason since the SDK no longer recognizes that key. Updated both files to use ActivityVersioning.VersionSourceTagName / ExplicitSource / InheritedSource and added IVT for Worker.Grpc.IntegrationTests in Worker.csproj so the integration tests can reference the SDK constants directly.

#2 — Generator helper conflict check missed explicit-unversioned: ApplyGeneratedVersion (StartOrchestrationOptions / SubOrchestrationOptions) and ApplyGeneratedActivityVersion all used patterns that required '.Version.Length > 0' or '!IsNullOrWhiteSpace', which excluded TaskVersion.Unversioned. A user calling CallProcessPayment_2Async(..., new ActivityOptions { Version = TaskVersion.Unversioned }) silently got v2 instead of the contradicting throw the helper was supposed to enforce. Now the patterns match any non-null Version (including the empty/unversioned case) and the diagnostic message distinguishes 'explicit-unversioned' from a specific version. Generator-output snapshot tests updated.

#3 — TaskVersion null-storage caused null/empty mismatch and GetHashCode crash: the constructor stored 'null' verbatim when given null. Effects: TaskVersion.Unversioned.Equals(new TaskVersion('')) was false, and TaskVersion.Unversioned.GetHashCode() called StringComparer.OrdinalIgnoreCase.GetHashCode(null) which throws — making any user who put TaskVersion in a dictionary key crash at runtime. Constructor now normalizes null/empty to string.Empty; Equals and GetHashCode are null-safe and treat null and empty as identical. (default(TaskVersion) still has Version=null at the field level — that's a struct-default constraint — but Equals/GetHashCode handle it.)

#4 — Whitespace TaskVersion ctor validation: the registry rejected whitespace-only TaskVersion, but new TaskVersion('  ') itself accepted the value, so it could leak into ActivityOptions / StartOrchestrationOptions / SubOrchestrationOptions and route silently to 'no exact match'. The constructor now throws ArgumentException for non-empty whitespace. The redundant ValidateRegistrationVersion method and the explicit whitespace check in DurableTaskVersionAttribute were removed because TaskVersion's constructor handles them centrally.

#7 — Migration recipe: added a new section to the proposal documenting the 'add [DurableTaskVersion] to an existing class' migration path. Recommended recipe is keep the unversioned class registered until in-flight unversioned instances drain or ContinueAsNew to the new version. Reverse migration (removing [DurableTaskVersion]) is documented as unsupported.

#8 — Tag rename pre-release note: added a one-paragraph note in the proposal acknowledging the in-flight rename from 'explicit-version' (boolean) to 'version-source' ('explicit'/'inherited') and recommending pre-release deployments drain before pointing at the new contract since the worker now fail-closes on missing tag for versioned activity requests.

Tests: 84 generator + 127 worker.tests + 135 worker.grpc.tests + 159 abstractions.tests + 8 integration = 513 total, all passing.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@torosent torosent marked this pull request as ready for review May 12, 2026 19:55
Copilot AI review requested due to automatic review settings May 12, 2026 19:55
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Implements per-orchestration and per-activity versioning across the Durable Task abstractions, worker runtime, gRPC worker, in-proc sidecar, and source generator to enable version-aware routing and migration scenarios.

Changes:

  • Adds version-aware registration keys, a [DurableTaskVersion] attribute, and updated task options to express per-task version selection.
  • Updates worker dispatch (gRPC + in-proc) and work-item filtering to route by (name, version) with explicit vs inherited activity-version semantics.
  • Adds new samples plus unit/integration/source-generator tests covering routing, fallback, tag propagation, and versioning policy behavior.

Reviewed changes

Copilot reviewed 52 out of 53 changed files in this pull request and generated no comments.

Show a summary per file
File Description
test/Worker/Grpc.Tests/GrpcDurableTaskWorkerTests.cs Adds gRPC worker construction tests around mixed worker/per-task versioning.
test/Worker/Core.Tests/Shims/TaskOrchestrationContextWrapperTests.cs Adds shim tests for activity version override/inheritance and reserved-tag stripping.
test/Worker/Core.Tests/DurableTaskFactoryVersioningTests.cs Adds factory tests for orchestrator version resolution and fallback rules.
test/Worker/Core.Tests/DurableTaskFactoryActivityVersioningTests.cs Adds factory tests for activity version resolution, with/without fallback.
test/Worker/Core.Tests/DependencyInjection/UseWorkItemFiltersTests.cs Expands filter tests for grouping and strict version handling.
test/Grpc.IntegrationTests/VersionedClassSyntaxTestOrchestration.cs Adds versioned orchestrator/activity types used by integration tests.
test/Grpc.IntegrationTests/VersionedClassSyntaxIntegrationTests.cs Adds end-to-end routing/migration/tag-propagation integration coverage.
test/Generators.Tests/VersionedOrchestratorTests.cs Adds generator tests for versioned orchestrator helper emission and diagnostics.
test/Generators.Tests/VersionedActivityTests.cs Adds generator tests for versioned activity helper emission and diagnostics.
test/Generators.Tests/AzureFunctionsTests.cs Adds Azure Functions diagnostics for unsupported multi-version class-based tasks.
test/Abstractions.Tests/TaskOptionsTests.cs Extends options tests for new ActivityOptions.Version behavior.
test/Abstractions.Tests/DurableTaskVersionAttributeTests.cs Adds tests for [DurableTaskVersion] attribute and reflection helper.
test/Abstractions.Tests/DurableTaskRegistryVersioningTests.cs Adds registry tests for versioned registrations and duplicate detection.
src/Worker/Grpc/GrpcDurableTaskWorker.Processor.cs Enables version-aware orchestrator/activity dispatch and versioned “not found” errors.
src/Worker/Grpc/GrpcDurableTaskWorker.cs Adds per-task vs worker-versioning fail-fast policy check.
src/Worker/Core/WorkerVersioningPolicy.cs Centralizes “do not combine” enforcement for worker-level and per-task versioning.
src/Worker/Core/Worker.csproj Exposes internals to gRPC worker and integration tests.
src/Worker/Core/Shims/TaskOrchestrationContextWrapper.cs Stamps/strips version-source tags and applies activity-version selection rules.
src/Worker/Core/IVersionedOrchestratorFactory.cs Introduces internal interface for versioned orchestrator instantiation.
src/Worker/Core/IVersionedActivityFactory.cs Introduces internal interface for versioned activity instantiation with fallback flag.
src/Worker/Core/DurableTaskWorkerWorkItemFilters.cs Updates filter generation to group by logical name and emit version lists when safe.
src/Worker/Core/DurableTaskRegistryExtensions.cs Adds registry inspection helper for “any versioned registration” detection.
src/Worker/Core/DurableTaskFactory.cs Implements version-aware creation logic and guarded fallback behavior.
src/Worker/Core/DependencyInjection/DurableTaskWorkerWorkItemFiltersValidator.cs Updates validation to account for versioned keys and entity naming.
src/Worker/Core/ActivityVersioning.cs Adds internal constants for version-source tag semantics and reserved keys.
src/InProcessTestHost/Sidecar/Grpc/TaskHubGrpcServer.cs Propagates activity tags into gRPC work items in the in-proc host.
src/InProcessTestHost/Sidecar/Grpc/ProtobufUtils.cs Preserves scheduled-task tags in proto conversions.
src/InProcessTestHost/Sidecar/Dispatcher/TaskOrchestrationDispatcher.cs Rehydrates scheduled-event tags back into runtime history events.
src/Generators/DurableTaskSourceGenerator.cs Adds [DurableTaskVersion] handling, helper suffix encoding, and new diagnostics.
src/Generators/AzureFunctions/SyntaxNodeUtility.cs Fixes orchestration trigger attribute name detection.
src/Generators/AnalyzerReleases.Unshipped.md Documents new generator diagnostic IDs (DURABLE3003-3005).
src/Abstractions/TypeExtensions.cs Adds reflection helper for reading [DurableTaskVersion].
src/Abstractions/TaskVersion.cs Extends TaskVersion semantics (unversioned sentinel, validation, equality/hash updates).
src/Abstractions/TaskOptions.cs Adds ActivityOptions (including Version) and updates copy-constructor behavior.
src/Abstractions/OrchestratorVersionKey.cs Introduces composite key for orchestrator registration by (name, version).
src/Abstractions/DurableTaskVersionAttribute.cs Adds new attribute to declare class-based task versions.
src/Abstractions/DurableTaskRegistry.Orchestrators.cs Adds version-aware orchestrator registration APIs and attribute-based version capture.
src/Abstractions/DurableTaskRegistry.cs Migrates internal registries to versioned keys and adjusts activity/entity registration.
src/Abstractions/DurableTaskRegistry.Activities.cs Adds version-aware activity registration APIs and attribute-based version capture.
src/Abstractions/ActivityVersionKey.cs Introduces composite key for activity registration by (name, version).
src/Abstractions/Abstractions.csproj Adds InternalsVisibleTo for the abstractions test assembly.
samples/WorkerVersioningSample/WorkerVersioningSample.csproj Adds new runnable sample project for worker-level versioning.
samples/WorkerVersioningSample/README.md Documents worker-level versioning sample usage and guidance.
samples/WorkerVersioningSample/Program.cs Implements worker-level versioning sample with default client version stamping.
samples/PerOrchestratorVersioningSample/README.md Documents per-orchestrator versioning sample and “don’t combine” guidance.
samples/PerOrchestratorVersioningSample/Program.cs Implements per-orchestrator versioning + ContinueAsNew migration sample.
samples/PerOrchestratorVersioningSample/PerOrchestratorVersioningSample.csproj Adds new runnable sample project for per-orchestrator versioning.
samples/ActivityVersioningSample/README.md Documents activity versioning sample, including explicit override semantics.
samples/ActivityVersioningSample/Program.cs Implements activity versioning sample with inherited defaults and explicit override.
samples/ActivityVersioningSample/ActivityVersioningSample.csproj Adds new runnable sample project for activity versioning.
README.md Adds documentation section for versioned class-based orchestrators and links to samples.
Microsoft.DurableTask.sln Adds new sample projects to the solution.
.gitignore Ignores .worktrees/ working folders.
Comments suppressed due to low confidence (1)

src/Abstractions/TaskVersion.cs:65

  • TaskVersion.Version is documented as never returning null, but as an auto-property on a struct it will be null for default(TaskVersion) (and TaskVersion.Unversioned, which is currently default). This can leak a null string to callers (and via the implicit TaskVersion -> string conversion), potentially causing NullReferenceExceptions and violating the API contract. Consider storing a private backing field and making the getter return string.Empty when the backing field is null, and/or initialize Unversioned using new TaskVersion(string.Empty) so the sentinel itself is non-null.
    /// <summary>
    /// Gets the version of a task. Returns <see cref="string.Empty"/> for an unversioned task; never
    /// returns <c>null</c>.
    /// </summary>
    public string Version { get; }

    /// <summary>
    /// Implicitly converts a <see cref="TaskVersion"/> into a <see cref="string"/> of the <see cref="Version"/> property value.
    /// </summary>
    /// <param name="value">The <see cref="TaskVersion"/> to be converted into a <see cref="string"/>.</param>
    public static implicit operator string(TaskVersion value) => value.Version;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Per-Task Versioning: Architecture Proposal

2 participants