Repository navigation
feat: add versioned task registration and dispatch - #368
Conversation
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: e0af01a5-0dfa-4e71-a660-c4186e65d7e0
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
The child rewind path loses its recorded version, and activity mismatch messages use orchestration wording.
Get a fresh assessment by requesting another Copilot review.
Review effort: Lite
Findings: 1
What changed in this PR
Adds version-aware task registration and dispatch across workers, scheduling, replay, retries, filters, and integrations.
Changes:
- Supports multiple same-name task implementations by version.
- Preserves version inheritance and fallback behavior.
- Adds tests, documentation, and changelog updates.
- Two findings remain: child rewind version preservation and activity mismatch wording.
| File | Summary |
|---|---|
README.md |
Documents versioning and migration behavior. |
packages/durabletask-js/test/work-item-filters.spec.ts |
Updates filter coverage. |
packages/durabletask-js/test/versioned-in-memory.spec.ts |
Tests in-memory versioning. |
packages/durabletask-js/test/versioned-dispatch.spec.ts |
Tests versioned dispatch. |
packages/durabletask-js/test/versioned-dispatch-grpc.spec.ts |
Tests gRPC dispatch and acceptance. |
packages/durabletask-js/test/orchestration-id-reuse-policy.spec.ts |
Updates versioned scheduling options. |
packages/durabletask-js/src/worker/work-item-filters.ts |
Implements version-aware filters. |
packages/durabletask-js/src/worker/versioning-options.ts |
Updates versioning options. |
packages/durabletask-js/src/worker/task-hub-grpc-worker.ts |
Registers, dispatches, and validates versions. |
packages/durabletask-js/src/worker/runtime-orchestration-context.ts |
Applies activity and child defaults. |
packages/durabletask-js/src/worker/registry.ts |
Stores and resolves versioned registrations. |
packages/durabletask-js/src/worker/orchestration-executor.ts |
Dispatches orchestrations by version. |
packages/durabletask-js/src/worker/exception/orchestrator-not-registered-error.ts |
Reports missing orchestrator versions. |
packages/durabletask-js/src/worker/exception/activity-not-registered-error.ts |
Reports missing activity versions. |
packages/durabletask-js/src/worker/activity-executor.ts |
Dispatches activities by version. |
packages/durabletask-js/src/utils/pb-helper.util.ts |
Marks registration failures non-retriable. |
packages/durabletask-js/src/testing/test-worker.ts |
Adds versioned test-worker support. |
packages/durabletask-js/src/testing/test-client.ts |
Preserves scheduled versions. |
packages/durabletask-js/src/testing/in-memory-backend.ts |
Preserves versions in in-memory execution. |
packages/durabletask-js/src/task/options/task-options.ts |
Documents version defaults. |
packages/durabletask-js-azuremanaged/test/unit/worker-builder.spec.ts |
Tests builder version transfer. |
packages/durabletask-js-azuremanaged/src/worker-builder.ts |
Transfers versioned registrations. |
packages/durabletask-js-azuremanaged/README.md |
Documents managed-worker versioning. |
packages/durabletask-js-azuremanaged/CHANGELOG.md |
Records managed-worker changes. |
packages/azure-functions-durable/test/unit/worker.spec.ts |
Tests Functions integration behavior. |
packages/azure-functions-durable/README.md |
Documents Functions boundaries. |
packages/azure-functions-durable/CHANGELOG.md |
Records Functions changes. |
CHANGELOG.md |
Records core versioning changes. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Real Azure DTS validation: FAILED — not ready for reviewTested unchanged head Reproduced blockerWith { "versions": ["", "1.0.0", "2.0.0"] }A versioned parent explicitly scheduling that child with This was not merely an idle worker or a connectivity timeout:
The failed filtered and successful unfiltered cases both used the published SDK's unchanged child serialization (empty version wrapper omitted). This establishes a production compatibility/routing boundary around the mixed-version filter, not its internal root cause. It does not prove that the wrapper omission alone is the cause, nor that a backend fix rather than SDK handling is necessarily required. No workaround was used to declare a pass. What did work before the blockerBoth same-name parent versions were genuinely delivered/replayed in one worker. Each received correct persisted activity results for inherited parent version, explicit The six-scenario suite stopped at its first failing scenario; remaining fallback/unknown-version, Strict Fail/Reject recovery, and retry scenarios were not run. The fresh filtered reproduction independently confirmed the blocker after an unfiltered positive control. Additional service constraintThe initial fixture used symbolic Readiness and cleanupThe requested pass condition is not met; Local evidence: |
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: e0af01a5-0dfa-4e71-a660-c4186e65d7e0
Fixed and revalidated on real Azure DTS — PASSThe empty-version failure reported above is fixed in
Real production-service verificationRan against a fresh, isolated Azure DTS Consumption task hub in Private Test Sub WANGBILL, West US 2, with Azure CLI authentication over TLS. Production code remained byte-identical to the committed head. The test-only overlay was archived and removed afterward. The original controlled reproduction now passes: with the same concrete child filter All six named scenarios passed in the real-Azure matrix (one sequential Jest test, 31.332 seconds total):
Observed 70 real work-item deliveries, 15 activity responses and one acknowledged abandonment/recovery. All 21 created instance IDs plus one absence probe were read back absent after cleanup. Both focused controls also completed and were purged. No emulator, fabricated work-item responses or service fault injection was used. One intermediate run passed the first two scenarios but exposed test isolation between an earlier filtered worker and a later unfiltered worker using the same names. Independent scenarios now use distinct names; their assertions were retained, including the unfiltered case's own positive exact-version control. That run and the original failures remain archived; this is not a skipped assertion or production workaround. Additional validation and limits
Evidence includes exact-head metadata, actual delivery and persisted history JSON, Jest output, cleanup reads and archived reproduction scripts. The latest full-run overlay SHA-256 is |

Summary
What changed?
Latest follow-up: 27c167b preserves explicitly empty activity/child version fields on the protobuf wire and the empty child default. Only three production expressions changed; concrete automatic filters remain unchanged. Both the former failing case and the full real Azure DTS feature matrix now pass.
One worker can now host multiple implementations of the same orchestrator or activity name, selected by the recorded/request version rather than by name alone.
The optional final registration argument is also available on
TestOrchestrationWorkerandDurableTaskAzureManagedWorkerBuilder.ExecutionStarted.version; activity dispatch usesActivityRequest.version. Exact matches win. An unversioned registration accepts other request versions only when that name has no versioned registrations, following .NET's fallback rule. Missing registrations retain the JS error types, include the requested version, and are non-retriable.versioning.defaultVersion, not the parent's version. Unspecified activities use the orchestration instance's version, not the worker default. Explicit versions override defaults, including""to select unversioned. In-memory scheduling, history, activity work items, and child starts now preserve versions. Retries retain the selected version;continueAsNewbehavior is unchanged.No new dependencies, protobuf changes, routing framework, or configuration flags.
Why is this change needed?
Previously, versions could travel on the wire and constrain worker selection, but registrations collided by name, activities ignored their request version, and children ignored the worker default. Worker filtering alone cannot select between same-name implementations inside one process. This change connects registration, dispatch, and scheduling end to end.
Immutable .NET reference
Behavior was checked against
microsoft/durabletask-dotnetat92474e9e35c66d64de36cabd0a17652376d37873:Compatibility decisions
Versions are case-insensitive opaque strings, not semantic registration identities (
"1"and"1.0"remain distinct). Missing/null/empty registration versions are unversioned; whitespace-only registration versions are rejected, while whitespace-only incoming wire versions resolve as unversioned. Nonempty versions are not trimmed.Existing JS task names remain case-sensitive, unlike .NET. Native function names are unchanged. Existing unversioned call sites stay type-compatible and retain the reference fallback until the name opts into versioned registrations. Entities are unchanged.
Acceptance and local identity are independent. Omitted
matchStrategymeansNone;Strictwith an empty worker version no longer silently disables acceptance checks. Activity mismatches useReject/abandon or an explicit non-retriableFailresponse. The pinned .NET activity processor has an unconditional early return on the mismatch branch, even forFail; this PR deliberately does not reproduce that dropped-response bug and instead honors the documented Fail contract and the existing JS orchestration completion pattern.Issues / work items
Project checklist
CHANGELOG.mdand the affected package changelogsversion: ""to retain unversioned calls; drain affected instances before changing replay-sensitive defaults or mixing worker versions. See the README versioning section.AI-assisted code disclosure (required)
Was an AI tool used? (select one)
If AI was used:
AI verification (required if AI was used):
Testing
Automated tests
27c167b: 107 suites, 1,968 tests across core, Azure-managed unit tests, and Functions provider unit tests, including real loopback gRPC fixtures.npx --no-install jest --runInBand --silent --testPathPattern 'packages.(durabletask-js.test|durabletask-js-azuremanaged.test.unit|azure-functions-durable.test.unit).'npm run build:core,npm run build:azuremanaged,npm run build -w durable-functions; scoped ESLint on all changed TypeScript; scoped Prettier checks;git diff --check. Normal Husky/lint-staged hooks also passed.Manual validation (only if runtime/behavior changed)
25fa0c4was fixed in27c167bby preserving explicit empty protobuf wrappers and the unversioned child default. The original filtered reproduction and the no-default control now pass; all six named scenarios in the real production Azure matrix passed (one sequential Jest test). This covers same-name dispatch/defaults, actual filters/fallback, non-retriable unknown versions, Strict Fail, acknowledged Reject/recovery, and activity/child retries. Exact-head evidence, initial/intermediate failures, test isolation, and cleanup status. No live Functions host deployment is claimed.Notes for reviewers
app.orchestration/app.activitystill require unique Functions host names. EmbeddedDurableFunctionsWorkerversion dispatch is not a new host registration capability.mainatecddf16e5eda45b47cf03074919f922892790770.