Skip to content

Validate UseWorkItemFilters names against registered tasks at worker build time#719

Merged
YunchuWang merged 6 commits into
microsoft:mainfrom
YunchuWang:validate-workitem-filters-against-registry
May 6, 2026
Merged

Validate UseWorkItemFilters names against registered tasks at worker build time#719
YunchuWang merged 6 commits into
microsoft:mainfrom
YunchuWang:validate-workitem-filters-against-registry

Conversation

@YunchuWang
Copy link
Copy Markdown
Member

Summary

What changed?

  • Added DurableTaskWorkerWorkItemFiltersValidator (IValidateOptions<DurableTaskWorkerWorkItemFilters>) that cross-checks every orchestration / activity / entity name in the configured filters against the worker's DurableTaskRegistry.
  • UseWorkItemFilters(builder, filters) now auto-registers this validator (only when explicit, non-empty filters are supplied) so customers do not have to invoke validation themselves. The validator runs once after every PostConfigure callback, so any later overwrites are still validated against the final state.
  • Filter-name lookups use the registry's dictionary keys (TaskName, OrdinalIgnoreCase) so case differences and entity-name normalization match the runtime behavior.
  • Failure message lists every unknown name grouped by Orchestrations / Activities / Entities so customers can fix all mismatches in one shot.
  • CHANGELOG entry + XML doc updated to call out the new behavior.
  • Two pre-existing tests that previously configured filters without registering the matching tasks were updated to register valid tasks; the behaviors they verify (filter propagation and explicit-overrides-auto-gen) are preserved.

Why is this change needed?

When a customer calls UseWorkItemFilters(...) with a name that is not registered with the worker (typo, removed task, copy/paste mistake, etc.), the worker would previously start successfully and then silently wait for work items it can never handle — orchestrations / activities / entities targeting those names would simply never run, with no error surfaced. Failing fast at worker startup with a precise, actionable OptionsValidationException makes this misconfiguration immediately diagnosable.

Issues / work items

  • Resolves #
  • Related #

Project checklist

  • Release notes are not required for the next release
    • Otherwise: Notes added to release_notes.md (entry added to CHANGELOG.md under Unreleased)
  • 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?
    • Behavior change only: previously-silent misconfiguration in UseWorkItemFilters(filters) now throws OptionsValidationException at worker startup. Existing callers whose filter names match registered tasks are unaffected.
    • Impact: Workers that were silently mis-configured (filtering for unregistered names) will now fail to start.
    • Migration guidance: Either register the missing task with the worker or remove the unknown name from the filters; the exception message lists every unknown name grouped by category.

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 (Claude)
  • AI-assisted areas/files:
    • src/Worker/Core/DependencyInjection/DurableTaskWorkerWorkItemFiltersValidator.cs
    • src/Worker/Core/DependencyInjection/DurableTaskWorkerBuilderExtensions.cs
    • test/Worker/Core.Tests/DependencyInjection/UseWorkItemFiltersTests.cs
    • test/Worker/Grpc.Tests/DurableTaskWorkerWorkItemFiltersExtensionTests.cs
    • CHANGELOG.md
  • What you changed after AI output: Reviewed the validator wiring (post-configure ordering, single-registration, opt-in only when explicit non-empty filters are supplied), confirmed registry-key comparison semantics match runtime behavior, tightened the error-message grouping, and updated two existing tests so they exercise valid task registrations.

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 — added coverage in UseWorkItemFiltersTests for: unknown orchestration / activity / entity names producing OptionsValidationException at startup, multi-category error grouping in the message, case-insensitive matching against registry keys, validator only registering for explicit non-empty filters, validator re-running after later PostConfigure overrides, and entity-name normalization matching the runtime path.

Manual validation (only if runtime/behavior changed)

  • N/A — behavior is exercised by automated tests.

Notes for reviewers

  • The validator is registered only when explicit, non-empty filters are supplied via UseWorkItemFilters(builder, filters) — auto-generated filters (the no-args overload) are intentionally not validated, since they are derived directly from the registry and cannot be inconsistent with it.
  • The validator key/comparer matches DurableTaskRegistry's dictionary semantics so the validation is exactly aligned with what the worker would dispatch.

…build time

When customers call `UseWorkItemFilters(filters)` with explicit filters, any filter
that references an orchestration, activity, or entity name not registered with the
worker now throws `OptionsValidationException` at worker startup instead of silently
waiting for work items the worker can never handle. This is wired up automatically
through `IValidateOptions<DurableTaskWorkerWorkItemFilters>` so customers do not
need to invoke validation themselves.

Changes:
- Add `DurableTaskWorkerWorkItemFiltersValidator` (`IValidateOptions<>`).
- Register it from `UseWorkItemFilters(builder, filters)` when explicit, non-empty
  filters are supplied. Validator runs once after every PostConfigure callback so
  later overwrites are validated against the final state.
- Filter-name lookups use `DurableTaskRegistry` dictionary keys (`TaskName`,
  `OrdinalIgnoreCase`) so case differences and entity-name normalization match
  the runtime behavior.
- Failure message lists every unknown name grouped by Orchestrations / Activities /
  Entities for one-shot fix-up.
- CHANGELOG note + XML doc updated to call out the new behavior.
- Update two pre-existing tests that relied on configuring filters without
  registering the matching tasks; behavior they verify (filter propagation and
  explicit-overrides-auto-gen) is preserved with valid task registrations.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings May 4, 2026 18:17
@YunchuWang YunchuWang marked this pull request as draft May 4, 2026 18:22
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

Note

Copilot was unable to run its full agentic suite in this review.

Adds startup-time validation for explicitly supplied UseWorkItemFilters(filters) so mis-typed or unregistered orchestration/activity/entity names fail fast with an OptionsValidationException instead of silently preventing work from being processed.

Changes:

  • Introduces DurableTaskWorkerWorkItemFiltersValidator (IValidateOptions<DurableTaskWorkerWorkItemFilters>) to cross-check filter names against the worker’s DurableTaskRegistry.
  • Automatically wires the validator from UseWorkItemFilters(builder, filters) when explicit, non-empty filters are provided, and updates XML docs + changelog.
  • Updates and adds unit tests to cover validation failures, message grouping, case-insensitive matching, and post-configure override scenarios.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
test/Worker/Grpc.Tests/DurableTaskWorkerWorkItemFiltersExtensionTests.cs Updates test filter names to point at registered tasks.
test/Worker/Core.Tests/DependencyInjection/UseWorkItemFiltersTests.cs Registers tasks where needed and adds validation-focused tests.
src/Worker/Core/DependencyInjection/DurableTaskWorkerWorkItemFiltersValidator.cs New options validator that checks filter names against the task registry and formats failures.
src/Worker/Core/DependencyInjection/DurableTaskWorkerBuilderExtensions.cs Auto-registers the validator (explicit non-empty filters) and updates XML docs.
CHANGELOG.md Notes the new fail-fast behavior for invalid explicit work-item filters.

Comment thread src/Worker/Core/DependencyInjection/DurableTaskWorkerBuilderExtensions.cs Outdated
@YunchuWang YunchuWang marked this pull request as ready for review May 4, 2026 18:56
Copilot AI added 2 commits May 4, 2026 12:05
…me conversion, stronger DI registration test

- DurableTaskWorkerBuilderExtensions: Make UseWorkItemFilters(filters) validator registration idempotent per builder name using a private marker class. Repeat calls with explicit non-empty filters no longer register multiple validator singletons (which would otherwise produce duplicated/order-dependent validation failures).

- DurableTaskWorkerWorkItemFiltersValidator: Construct TaskName explicitly when probing the registry instead of relying on the implicit string->TaskName conversion at the call site.

- UseWorkItemFiltersTests: Strengthen WorkItemFilters_NullExplicitFilters_DoNotRegisterValidator to actually inspect the IServiceCollection for the absence of IValidateOptions<DurableTaskWorkerWorkItemFilters> registrations. Add WorkItemFilters_RepeatedExplicitCalls_RegisterSingleValidatorPerWorker and WorkItemFilters_RepeatedExplicitInvalidCalls_ReportFailureExactlyOnce to lock in the idempotent-registration contract.
…arameter

Replace the per-builder marker-based registration with TryAddEnumerable<IValidateOptions<...>, DurableTaskWorkerWorkItemFiltersValidator>(). The framework already passes the named-options name to IValidateOptions.Validate, so a single validator instance can dispatch by that parameter instead of being bound to a captured builderName. TryAddEnumerable de-duplicates by implementation type, eliminating the need for a separately tracked WorkItemFiltersValidatorRegistrationMarker class and the O(N) IServiceCollection scan that went with it.

Removes ~100 lines of indirection (validator field, marker class, helper scan, conditional registration block) while preserving every observable behavior. The two now-redundant tests (NullExplicitFilters_DoNotRegisterValidator, RepeatedExplicitCalls_RegisterSingleValidatorPerWorker) are removed because they locked down internal registration mechanics that no longer apply; the user-observable invariants they encoded are still covered by RepeatedExplicitInvalidCalls_ReportFailureExactlyOnce and ValidationIsScopedToEachNamedWorker.
Copilot AI review requested due to automatic review settings May 4, 2026 21:44
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

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Comment thread src/Worker/Core/DependencyInjection/DurableTaskWorkerBuilderExtensions.cs Outdated
…me display

1. Restore the registration gate in UseWorkItemFilters(builder, filters): only call TryAddEnumerable when filters is non-null AND has at least one orchestration/activity/entity entry. Passing null (opt-out) or an empty filter set has nothing to validate, so registering the validator would unnecessarily pull IOptionsMonitor<DurableTaskRegistry> into the resolution chain and contradict the documented intent.

2. In DurableTaskWorkerWorkItemFiltersValidator, normalize the worker name shown in the failure message: when 'name' is null or string.Empty (the default-name worker), display '<default>' instead of an empty quoted name. The actual 'name' is still used to resolve the registry.

Adds three lock-in tests: NullExplicitFilters_DoNotRegisterValidator, EmptyExplicitFilters_DoNotRegisterValidator, and DefaultNamedWorkerInvalidFilter_FailureMessageUsesDefaultPlaceholder.
Comment thread CHANGELOG.md Outdated
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings May 6, 2026 17:16
@YunchuWang YunchuWang requested a review from bachuv May 6, 2026 17:35
…FiltersValidator.Validate (review feedback).
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.

4 participants