Commit 4d1aeff
Pipeline Validation At Startup (#4033)
* docs: add ADR 0053 and spec 0023 for pipeline validation at startup
Requirements and design for opt-in pipeline validation and diagnostic
reporting across all three configuration paths (AddBrighter, AddProducers,
AddConsumers). ADR 0053 accepted after six revisions covering dry-run
PipelineBuilder.Describe(), Specification<T> move, ValidationRule<T>,
SubscriberRegistry handler type tracking, and sync/async mapper distinction.
Closes #2176
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: clarify why Describe(Type) stays on PipelineBuilder<TRequest>
Address reviewer feedback about the non-generic method on a generic class.
Document the two reasons: drift prevention (shared code with Build) and
no runtime instance (validator works with Type objects at registration time).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: make ResolveMapperInfo accept Type instead of generic TRequest
The underlying dictionaries are keyed by Type, so the generic constraint
adds nothing and would force the validator to use MakeGenericMethod()
reflection. Non-generic overloads match the validator's runtime Type
iteration pattern.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: document thread safety assumption for _allHandlerTypes
Plain Dictionary matches existing _observers field — both are written
during single-threaded DI registration and read-only after. Contrasts
with PipelineBuilder's ConcurrentDictionary caches which are lazily
populated during concurrent message dispatch.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: specify hosted service coordination via optional DI resolution
Replace underspecified IAmAValidationState flag with concrete design:
BrighterValidationHostedService resolves IDispatcher? (no-op if present),
ServiceActivatorHostedService resolves IAmAPipelineValidator? (runs if
present). No shared mutable state — decisions based on immutable DI
registrations. Includes scenario matrix for all three configurations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: fix AttributeAsyncConsistency rule to check AfterSteps too
The detailed example only checked BeforeSteps while the summary section
correctly used BeforeSteps.Concat(AfterSteps). After-steps can also be
misconfigured, so the broader form is correct. Both instances now match.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: define IAmABackstopHandler/IAmAResilienceHandler marker interfaces
Replace undefined IsBackstopAttribute()/IsResilienceAttribute() helpers
with marker interfaces following the existing IAmA* naming convention.
Built-in handlers implement the appropriate interface; third-party
handlers can opt in. Both code examples now use the interfaces consistently.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: document PumpHandlerMatch/HandlerRegistered rule interaction
All() returns true on empty collections, so PumpHandlerMatch vacuously
passes when no handlers are registered. This is intentional — the
HandlerRegistered rule catches that case with a more specific message.
Added comment to make the dependency explicit for future maintainers.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: document why BackstopAttributeOrdering is Warning not Error
A resilience pipeline before a backstop could intentionally catch and
act on exceptions before passing them on. Unusual and probably not
intended, but not provably wrong — so we warn rather than block startup.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: add trailing newline to design_principles.md
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* first version of roslyn analyzer extensions for validation
* docs: address review feedback on ADR 0054 Roslyn analyzer extensions
- Add implementation ordering: ADR 0053 marker interfaces must be
implemented before analyzer diagnostics in this ADR
- BRT008: suppress when getRequestType/MapRequestType is provided to
avoid false positives from dynamic dispatch overriding static type
- BRT008: support non-generic Subscription with requestType: typeof(X)
in addition to generic Subscription<T>
- BRT006: specify parameter-name-based step extraction via
AttributeData.ConstructorArguments matched against IMethodSymbol.Parameters
- Add 4 new BRT008 test cases for non-generic and dynamic dispatch scenarios
- Replace "Non-generic subscriptions" limitation with "Dynamic request
type dispatch" limitation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: spell out how PipelineValidator enumerates all handler types
Address review feedback asking how the validator gets descriptions for
all registered request types given that PipelineBuilder<TRequest> is
generic.
- Add parameterless Describe() overload that iterates all registered
request types via SubscriberRegistry.GetRegisteredRequestTypes()
- Add GetRegisteredRequestTypes() to IAmASubscriberRegistry alongside
the existing GetHandlerTypes(Type)
- Make describe-only constructor public (not internal) for testability
without [InternalsVisibleTo]
- Add sequence diagram showing end-to-end flow from validator through
PipelineBuilder to SubscriberRegistry
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: guard against handler type implementing neither sync nor async interface
AttributeAsyncConsistency rule now explicitly checks both IHandleRequests
and IHandleRequestsAsync rather than only checking async and implicitly
treating unknown types as sync. Throws InvalidOperationException if a
pipeline step handler implements neither — this is a programming error
invariant, not a validation finding.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: replace ValidationRule<T> with visitor pattern on Specification<T>
Eliminate ValidationRule<T> coordinator by enriching Specification<T> to
carry its own validation metadata. Each specification now encapsulates
both the predicate and the error information, using the visitor pattern
to collect detailed ValidationResults from the specification graph on
failure.
Key changes to ADR 0053:
- Rename PipelineValidationError → ValidationError (generic)
- Rename PipelineValidationSeverity → ValidationSeverity
- Add ValidationResult (bool Success + ValidationError)
- Add ISpecificationVisitor<TData, TResult> and ValidationResultCollector
- Specification<T> gains two new constructors: simple (predicate + error
factory) and collapsed (Func<T, IEnumerable<ValidationResult>>)
- Calling code invokes visitor after IsSatisfiedBy returns false
- Not() gains overload with error factory for validation use
- Update all rule examples, project layout table, and consequences
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: extract IsSatisfiedBy into EvaluateCollapsed and EvaluateSimple
Reduce nesting in Specification<T>.IsSatisfiedBy by delegating to two
private methods based on the construction mode, making each path flat
and self-contained.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: extract Validate() into per-path methods with shared EvaluateSpecs helper
Split PipelineValidator.Validate() into ValidateHandlerPipelines,
ValidateProducers, and ValidateConsumers. Extract the repeated
evaluate-and-collect loop into a generic EvaluateSpecs<T> helper.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: add HandlerTypeVisibility specification for non-public handlers
Brighter only supports public handler types. Add a validation
specification that checks Type.IsVisible and reports an error when a
handler is internal or nested inside a non-public parent, guiding the
developer to make the class public.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: mark Roslyn analyzer extensions as out of scope for spec 0023
Focus spec 0023 on runtime startup validation and diagnostic reporting
(Layers 1-2). Roslyn analyzer extensions (FR-14, FR-15, NFR-4, AC-13,
AC-14) moved to out-of-scope with reference to ADR 0054 for future work.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: fix Specification<T> duplicate constructor and AndSpecification short-circuit
Replace primary constructor with regular class declaration to eliminate
duplicate Func<T, bool> constructor signature (compile error). Specify
that AndSpecification evaluates both children unconditionally to prevent
stale visitor results from short-circuit evaluation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: acknowledge ISpecification<T> new members as breaking change
Note that Accept<TResult> and Not(Func<T,ValidationError>) additions
to ISpecification<T> are breaking for direct implementors. Acceptable
because Mediator is alpha with no known external implementations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: make PumpHandlerMatch error message directional per FR-11
Branch error message on MessagePumpType so it names the exact mismatch
direction and suggested fix, matching the FR-11 example format.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: align FR-10 with PipelineValidationException, clarify caching and preconditions
- Update FR-10 to specify ConfigurationException subclass instead of
AggregateException, matching ADR 0053's PipelineValidationException
- Document ValidatePipelines() precondition requiring
IAmASubscriberRegistryInspector for custom registries
- Clarify that _lastResults caching prevents double evaluation in
collapsed specifications
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: replace implicit DI-presence coordination with explicit options flag
Introduce BrighterPipelineValidationOptions with ConsumerOwnsValidation
flag so hosted service coordination is explicit rather than probing for
IDispatcher?. Also return defensive snapshots (.ToArray()) from
GetHandlerTypes() and GetRegisteredRequestTypes().
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: distinguish misconfigured default mapper from missing mapper
ResolveMapperInfo now returns (null, true) when the default mapper
exists but is not an open generic type, enabling a distinct validation
error instead of silently reporting 'no mapper found'.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add a tasks file for pipeline validation
* refactor: move Specification types from Mediator to Brighter namespace
Move ISpecification<TData> and Specification<T> from
Paramore.Brighter.Mediator to Paramore.Brighter namespace.
Steps.cs and workflow tests resolve types via enclosing namespace
and need no changes. Specification test updated to remove
unnecessary Mediator using.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: extract HandlerMethodDiscovery static utility
Extract FindHandlerMethod(Type, Type) from RequestHandler<T> and
RequestHandlerAsync<T> into a shared internal static class.
Determines sync vs async from the handler type hierarchy.
Both instance methods now delegate to the static utility.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: extract MapperMethodDiscovery static utility
Extract FindMapToMessage, FindMapToRequest, FindMapToMessageAsync,
FindMapToRequestAsync from TransformPipelineBuilder and
TransformPipelineBuilderAsync into a shared internal static class.
Methods accept Type parameters for later use in Describe(Type).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: remove duplicate AddGlobalInboxAttributesAsync call in BuildAsyncPipeline
BuildAsyncPipeline called AddGlobalInboxAttributesAsync twice: once
inside the cache-miss block (correct) and once unconditionally outside
it (bug). Removed the duplicate outer call.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: add IAmASubscriberRegistryInspector to SubscriberRegistry
Add IAmASubscriberRegistryInspector interface with GetHandlerTypes(Type)
and GetRegisteredRequestTypes() for startup validation introspection.
SubscriberRegistry tracks all handler types in a parallel
Dictionary<Type, HashSet<Type>>, populated during registration.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: add ResolveMapperInfo and ResolveAsyncMapperInfo to MessageMapperRegistry
Add non-generic Type-keyed methods for startup validation introspection.
Four return states: explicit mapper, default resolved, no mapper, and
misconfigured default (non-generic default type). No factory calls.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: add IAmABackstopHandler and IAmAResilienceHandler marker interfaces
Apply IAmABackstopHandler to all 6 backstop error handlers
(Reject, Defer, DontAck — sync and async). Apply IAmAResilienceHandler
to ResilienceExceptionPolicyHandler and its async counterpart.
Marker interfaces add no behavior — used for pipeline validation classification.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: enhance Specification<T> with validation support and visitor pattern
Three constructors: pure predicate (backward compatible), simple rule
(predicate + error factory), and collapsed rule (result evaluator).
IsSatisfiedBy catches exceptions and produces Error-severity results.
Accept<TResult>(visitor) enables traversal for result collection.
New types: ValidationResult, ValidationError, ValidationSeverity,
ISpecificationVisitor<TData,TResult>, ValidationResultCollector<TData>.
- Test: 6 cases covering all constructors, exception handling, caching
- Existing Specification and Workflow tests pass unchanged
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add AndSpecification<T> with non-short-circuit evaluation
AndSpecification evaluates both children unconditionally so the visitor
can collect errors from both sides. Specification<T>.And() now returns
AndSpecification<T> instead of a composed lambda.
- Test: 3 cases (both fail, left only fails, both pass)
- ISpecificationVisitor gains Visit(AndSpecification<T>)
- ValidationResultCollector traverses both children
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add OrSpecification, NotSpecification, and collector graph traversal
OrSpecification evaluates both sides unconditionally (like And).
NotSpecification stores its own error via Not(errorFactory) overload.
ValidationResultCollector traverses And/Or/Not nodes collecting all
leaf results. ISpecification gains Not(Func<T, ValidationError>).
- Test: 5 cases (Ok/Fail factories, Or graph, Not with error, nested graph)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: move specification tests, to specification directory
* feat: add PipelineValidationResult and PipelineValidationException
PipelineValidationResult aggregates errors and warnings, with IsValid,
ThrowIfInvalid(), and Combine(). PipelineValidationException extends
ConfigurationException with formatted error messages and source context.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add PipelineBuilder.Describe() for dry-run pipeline description
Adds describe-only constructor, Describe(Type), and parameterless
Describe() to PipelineBuilder. Uses reflection to produce
HandlerPipelineDescription and PipelineStepDescription without
instantiating handlers. Shares the same code path as Build().
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add TransformPipelineBuilder.DescribeTransforms for dry-run transform description
- Static method using reflection only (no mapper/transform instantiation)
- Returns TransformPipelineDescription with MapperType, IsDefaultMapper, WrapTransforms, UnwrapTransforms
- Uses ResolveMapperInfo + MapperMethodDiscovery to inspect attributes
- Tests: 5 cases covering custom/default mappers, wrap/unwrap/empty transforms
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: tidy validation types — move to Paramore.Brighter, modernize to records and sealed primary constructors
- Move ISpecificationVisitor, ValidationError, ValidationResult, ValidationResultCollector,
ValidationSeverity from Paramore.Brighter.Validation to Paramore.Brighter namespace
- Convert ValidationError to positional record
- Convert ValidationResult to record with private ctor and static factories
- Convert PipelineStepDescription to positional record
- Seal HandlerPipelineDescription, PipelineValidationResult, TransformPipelineDescription,
TransformStepDescription and use primary constructors
- Add standard exception constructors to PipelineValidationException
- Add explicit values to ValidationSeverity enum (Error=0, Warning=1)
- Replace inline mapper factories in test with actual Brighter implementations
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add HandlerPipelineValidationRules with visibility and backstop ordering specs
- HandlerTypeVisibility: reports Error for non-public handler types
- BackstopAttributeOrdering: reports Warning when backstop step > resilience step
- Tests: InternalHandlerValidationTests, BackstopResilienceValidationTests
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: add happy path test for BackstopAttributeOrdering rule
- Verifies backstop at step 0, resilience at step 1 passes with no warnings
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add AttributeAsyncConsistency rule to HandlerPipelineValidationRules
- Reports Error when async handler has sync attribute step (or vice versa)
- Reports Error when step implements neither IHandleRequests nor IHandleRequestsAsync
- Test: AsyncHandlerSyncAttributeValidationTests
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: add reverse direction test for AttributeAsyncConsistency rule
- Verifies sync handler with async attribute step reports Error
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: add neither-interface test for AttributeAsyncConsistency rule
- Verifies step implementing neither IHandleRequests nor IHandleRequestsAsync reports Error
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add happy path test and fix open generic type detection in AttributeAsyncConsistency
- Make MyCommandHandler public — it's a general-purpose pipeline test double
- Fix IsAssignableFrom not working with open generic types by walking base type chain
- Test: CorrectlyConfiguredHandlerValidationTests verifies all three rules pass
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: mark HandlerPipelineValidationRules task as complete in tasks.md
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add ProducerValidationRules with PublicationRequestTypeSet spec
- Reports Error when Publication.RequestType is null
- Test: PublicationMissingRequestTypeValidationTests
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add PublicationRequestTypeImplementsIRequest spec to ProducerValidationRules
- Reports Error when Publication.RequestType does not implement IRequest
- Vacuously passes when RequestType is null (caught by PublicationRequestTypeSet)
- Test: PublicationRequestTypeNotIRequestValidationTests
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: add happy path test for ProducerValidationRules
- Verifies valid publication with IRequest RequestType passes both rules
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: mark ProducerValidationRules task as complete in tasks.md
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add ConsumerValidationRules with PumpHandlerMatch spec
- Test: When_reactor_subscription_has_async_handler_should_report_error
- Implementation: ConsumerValidationRules.PumpHandlerMatch checks that handler
sync/async nature matches the subscription's MessagePumpType
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: add Proactor+sync handler mismatch test for PumpHandlerMatch
- Test: When_proactor_subscription_has_sync_handler_should_report_error
- Already passes — symmetric case covered by existing PumpHandlerMatch implementation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: add vacuous pass test for PumpHandlerMatch with no handlers
- Test: When_subscription_has_no_handlers_pump_match_should_pass
- Already passes — PumpHandlerMatch vacuously passes when no handlers registered
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add HandlerRegistered spec to ConsumerValidationRules
- Test: When_subscription_has_no_handler_registered_should_report_error
- Implementation: ConsumerValidationRules.HandlerRegistered checks that at least
one handler is registered for the subscription's RequestType
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add RequestTypeSubtype spec to ConsumerValidationRules
- Test: When_subscription_request_type_not_command_or_event_should_report_warning
- Implementation: ConsumerValidationRules.RequestTypeSubtype checks that
RequestType implements ICommand or IEvent, warns if neither
- Added MyBareRequest test double (implements IRequest only)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: add happy path tests for ConsumerValidationRules
- Test: When_subscription_request_type_is_command_should_pass
- Test: When_subscription_correctly_configured_should_report_no_findings
- Verifies all three specs pass for correctly configured subscriptions
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: mark ConsumerValidationRules task as complete in tasks.md
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add PipelineValidator that aggregates errors across all validation paths
- Add IAmAPipelineValidator interface and PipelineValidator implementation
- Validator evaluates handler pipelines, producers, and consumers
- Scales to path: only configured paths are validated
- Clarify test class naming convention in testing.md and test-first.md
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: add PipelineValidator behavior tests for warning separation, IsValid semantics, and path scaling
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add PipelineDiagnosticWriter with Information-level summary logging
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add Debug-level handler pipeline chain logging to PipelineDiagnosticWriter
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add Debug-level publication detail logging to PipelineDiagnosticWriter
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add Debug-level subscription detail logging to PipelineDiagnosticWriter
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: add no-output test for PipelineDiagnosticWriter with empty configuration
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: mark Phase 6 tasks as complete in tasks.md
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: move Brighter assembly scan from builder constructor to factory method
The ServiceCollectionBrighterBuilder constructor was scanning all
Paramore.Brighter* assemblies to register built-in handlers, mappers,
and transforms. This forced assembly scanning on every builder
construction, including in tests, causing mapper conflicts with test
doubles. Moving the scan to BrighterHandlerBuilder preserves the
runtime behavior while allowing direct builder construction in tests
without side effects.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add ValidatePipelines() extension method on IBrighterBuilder
Registers IAmAPipelineValidator in DI when called, returning the
builder for fluent chaining. The validator is created via a factory
that resolves the subscriber registry and builds a PipelineValidator.
- Test: When_validate_pipelines_called_should_register_validator_in_di
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add DescribePipelines() extension method on IBrighterBuilder
Registers IAmAPipelineDiagnosticWriter in DI when called, returning the
builder for fluent chaining. The diagnostic writer is created via a
factory that resolves the subscriber registry, logger, and builds a
PipelineDiagnosticWriter.
- Test: When_describe_pipelines_called_should_register_diagnostic_writer_in_di
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: register hosted service and options in ValidatePipelines()
ValidatePipelines() now also registers BrighterValidationHostedService
as IHostedService and configures BrighterPipelineValidationOptions with
ConsumerOwnsValidation = false. The hosted service is a skeleton that
will be fleshed out in Task 2.
- Test: When_validate_pipelines_called_should_register_hosted_service
- Test: When_validate_pipelines_called_should_register_options_with_consumer_owns_false
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: mark Phase 7 Task 1 as complete in tasks.md
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: implement BrighterValidationHostedService startup validation
Adds constructor and StartAsync logic to run pipeline validation and
diagnostics at host startup when ConsumerOwnsValidation is false.
Errors throw PipelineValidationException; warnings do not prevent startup.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: mark Phase 7 Task 2 as complete in tasks.md
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: ServiceActivatorHostedService runs validation before Receive when opted in
Adds optional IAmAPipelineValidator and IAmAPipelineDiagnosticWriter
constructor parameters. When present, Describe() and Validate() run
before Receive(). Validation errors throw PipelineValidationException,
preventing the dispatcher from starting. Backward compatible via
existing 2-arg constructor.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: mark Phase 7 Task 3 as complete in tasks.md
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: AddConsumers sets ConsumerOwnsValidation flag when validation is opted in
Both AddConsumers overloads now call Configure<BrighterPipelineValidationOptions>
to set ConsumerOwnsValidation=true. This works regardless of call order with
ValidatePipelines(), ensuring BrighterValidationHostedService becomes a no-op
when consumers are present.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: mark Phase 7 Task 4 as complete in tasks.md
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs: add ValidatePipelines and DescribePipelines to WebAPI samples
Demonstrates pipeline validation and diagnostic description at startup
in all WebAPI sample projects: GreetingsWeb (Dapper, EFCore, Dynamo),
SalutationAnalytics (Dapper, EFCore, Dynamo), and mTLS TodoApi.
For non-consumer WebAPI apps, BrighterValidationHostedService runs
validation. For consumer apps, AddConsumers sets ConsumerOwnsValidation
so ServiceActivatorHostedService handles it before Receive.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: ServiceCollectionSubscriberRegistry implements IAmASubscriberRegistryInspector
Adds IAmASubscriberRegistryInspector to ServiceCollectionSubscriberRegistry,
delegating GetRegisteredRequestTypes() and GetHandlerTypes() to the inner
SubscriberRegistry. This fixes the ConfigurationException thrown when
ValidatePipelines() resolves the validator through the full DI path.
- Test: When_cast_to_inspector_should_return_registered_request_types
- Test: When_cast_to_inspector_should_return_handler_types_for_request
- Test: When_cast_to_inspector_should_return_empty_for_unregistered_request
- Test: When_validator_resolved_from_di_should_validate_without_configuration_exception
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: hosted services log validation warnings at Warning level
Both BrighterValidationHostedService and ServiceActivatorHostedService now
iterate result.Warnings after ThrowIfInvalid() and log each at LogLevel.Warning
with Source and Message, matching the documented behavior.
- Test: When_validation_has_warnings_should_log_them_at_warning_level
- Test: When_validation_has_no_warnings_should_not_log_warnings
- Test: When_service_activator_has_warnings_should_log_them_at_warning_level
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: collapse ServiceActivatorHostedService to single constructor
Replaces two constructors with a single one taking IServiceProvider and
IOptions<BrighterPipelineValidationOptions>. Optional validation and
diagnostic dependencies are resolved from the service provider in StartAsync
when ConsumerOwnsValidation is true, eliminating DI ambiguity.
- Test: When_consumer_owns_validation_and_validator_registered_should_validate_before_receive
- Test: When_consumer_owns_validation_and_validator_not_registered_should_go_to_receive
- Test: When_consumer_does_not_own_validation_should_go_straight_to_receive
- Updated existing tests to use new constructor signature
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: DescribePipelines registers BrighterDiagnosticHostedService
DescribePipelines() now registers a BrighterDiagnosticHostedService as
IHostedService so that pipeline descriptions are logged at startup even
without ValidatePipelines(). Respects ConsumerOwnsValidation (no-op when
ServiceActivatorHostedService handles diagnostics).
- Test: When_describe_pipelines_called_should_register_diagnostic_hosted_service
- Test: When_describe_pipelines_standalone_should_produce_log_output_at_startup
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: PumpHandlerMatch error names the specific mismatched handler
Replaces handlerTypes.First() with a filtered First() that selects the
handler whose sync/async nature conflicts with the pump type. Reactor
errors now name the async handler, Proactor errors name the sync handler.
- Test: When_reactor_has_both_sync_and_async_handlers_should_name_async_handler
- Test: When_proactor_has_both_async_and_sync_handlers_should_name_sync_handler
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: exclude open generic handler types from assembly scanning
Adds IsGenericTypeDefinition: false to the handler scanning filter,
preventing open generic types like DeferMessageOnErrorHandler<TRequest>
from being registered with their type parameter as the request type key.
- Test: When_scanning_assemblies_should_not_register_open_generic_type_parameters
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update copyright year to 2026 and fix ConsumerOwnsValidation doc
Updates Copyright © 2024 → Copyright © 2026 in all files new to this
branch. Fixes ConsumerOwnsValidation XML doc to reflect that AddConsumers()
unconditionally sets the flag (not only alongside ValidatePipelines).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: register open generic handlers in DI without adding to subscriber registry
The Phase 6 fix excluded open generic handler types (e.g.
ExceptionPolicyHandler<>) from assembly scanning entirely, which
prevented both DI registration and subscriber registry addition.
Open generics must be in DI so ServiceProviderHandlerFactory can
resolve closed types at runtime, but must not appear in the subscriber
registry where their generic type parameters pollute
GetRegisteredRequestTypes().
Split the registration: EnsureHandlerIsRegistered() adds to DI only,
Add() continues to do both. RegisterHandlersFromAssembly now routes
open generics to the DI-only path.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update documents
* chore: add code review and commit code plugins
* fix: resolve optional IAmAPipelineDiagnosticWriter from IServiceProvider at startup
Microsoft.Extensions.DependencyInjection does not support optional constructor
injection. BrighterValidationHostedService now injects IServiceProvider and
resolves IAmAPipelineDiagnosticWriter in StartAsync, matching the pattern used
by ServiceActivatorHostedService. This prevents a DI exception when
ValidatePipelines() is called without DescribePipelines().
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: remove double Describe() call when both ValidatePipelines and DescribePipelines are used
BrighterValidationHostedService no longer calls Describe() — that
responsibility belongs exclusively to BrighterDiagnosticHostedService.
This prevents the diagnostic report being logged twice when both
extension methods are registered.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address PR #4033 review feedback items 3-7
- #7: Change IAmASubscriberRegistryInspector return types from Type[] to
IReadOnlyCollection<Type> for idiomatic interface design
- #3: Resolve IAmASubscriberRegistryInspector via interface in
ValidatePipelines/DescribePipelines with fallback to concrete type;
add PipelineBuilder describe-only constructor accepting the interface
- #5: Remove duplicated XML doc sentence in BrighterPipelineValidationOptions
- #4: Make MapperMethodDiscovery return MethodInfo? (nullable), remove
null-forgiving operators, add descriptive ConfigurationException on
build-path null
- #6: Use IHandleRequests/IHandleRequestsAsync interface checks in
AttributeAsyncConsistency instead of base class chain walking
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: wire publications, subscriptions, and consumer specs into validator and diagnostic writer
ValidatePipelines() and DescribePipelines() were creating PipelineValidator
and PipelineDiagnosticWriter without passing publications, subscriptions,
mapper registry, or consumer validation specs — so producer validation,
consumer validation, and publication/subscription diagnostic logging were
all silently no-ops.
- Resolve IAmAProducerRegistry and IAmConsumerOptions from DI to extract
publications and subscriptions at service resolution time
- Resolve ISpecification<Subscription> services for consumer validation
- Build MessageMapperRegistry for mapper info in diagnostic output
- Register consumer validation specs (PumpHandlerMatch, HandlerRegistered,
RequestTypeSubtype) in AddConsumers()
- Add regression tests for producer and consumer validation wiring
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add validation to samples
* chore: merge
* fix: correct async mismatch validation message — pipeline throws, not silently ignores
Mismatched sync/async attributes cause a ConfigurationException at pipeline
build time, not silent ignoring. Updated error messages and XML doc to
accurately reflect this behavior.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add enabled and throwOnError flags to ValidatePipelines/DescribePipelines
Allow developers to control whether pipeline validation and diagnostics
run at startup (FR-14) and whether validation errors terminate the host
or are logged as warnings (FR-15). Both flags use simple bool parameters
so the caller can source the decision from IHostEnvironment, IConfiguration,
or any other mechanism.
- ValidatePipelines(bool enabled = true, bool throwOnError = true)
- DescribePipelines(bool enabled = true)
- BrighterPipelineValidationOptions gains ThrowOnError property
- BrighterValidationHostedService respects ThrowOnError
- ServiceActivatorHostedService respects ThrowOnError
- Updated requirements (FR-14, FR-15, AC-13, AC-14), ADR 0053, and tasks
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>1 parent c3e1a45 commit 4d1aeff
172 files changed
Lines changed: 10465 additions & 203 deletions
File tree
- .agent_instructions
- .claude/commands/tdd
- docs/adr
- samples/WebAPI
- WebAPI_Common
- DbMaker
- Greetings_Migrations
- Salutations_Migrations
- TransportMaker
- WebAPI_Dapper
- GreetingsApp
- Handlers
- Responses
- GreetingsWeb
- Greetings_Sweeper
- SalutationAnalytics
- SalutationApp
- Salutation_Sweeper
- WebAPI_Dynamo
- GreetingsApp
- GreetingsWeb
- Greetings_Sweeper
- SalutationAnalytics
- SalutationApp
- WebAPI_EFCore
- GreetingsApp
- Responses
- GreetingsWeb
- Greetings_Sweeper
- SalutationAnalytics
- SalutationApp
- WebAPI_mTLS_TestHarness/TodoApi
- specs
- 0023-Pipeline-Validation-At-Startup
- src
- Paramore.Brighter.Extensions.DependencyInjection
- Paramore.Brighter.Mediator
- Paramore.Brighter.ServiceActivator.Extensions.DependencyInjection
- Paramore.Brighter.ServiceActivator.Extensions.Hosting
- Paramore.Brighter.ServiceActivator/Validation
- Paramore.Brighter
- Defer/Handlers
- DontAck/Handlers
- Policies/Handlers
- Reject/Handlers
- Validation
- tests
- Paramore.Brighter.Core.Tests
- CommandProcessors/TestDoubles
- Specifications
- Validation
- TestDoubles
- Paramore.Brighter.Extensions.Tests
- TestDoubles
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 319 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | 3 | | |
4 | | - | |
| 4 | + | |
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
| |||
Lines changed: 1 addition & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | 3 | | |
4 | | - | |
| 4 | + | |
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
| |||
Lines changed: 1 addition & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | 3 | | |
4 | | - | |
| 4 | + | |
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
| |||
Lines changed: 1 addition & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | 3 | | |
4 | | - | |
| 4 | + | |
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
| |||
Lines changed: 1 addition & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | 3 | | |
4 | | - | |
| 4 | + | |
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
| |||
0 commit comments