Skip to content

Commit 4d1aeff

Browse files
iancooperclaude
andauthored
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

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.agent_instructions/design_principles.md

Lines changed: 3 additions & 3 deletions

.agent_instructions/testing.md

Lines changed: 3 additions & 2 deletions

.claude/commands/tdd/test-first.md

Lines changed: 2 additions & 2 deletions

docs/adr/0053-pipeline-validation-at-startup.md

Lines changed: 1497 additions & 0 deletions

docs/adr/0054-roslyn-analyzer-extensions-for-pipeline-validation.md

Lines changed: 319 additions & 0 deletions

samples/WebAPI/WebAPI_Common/DbMaker/DbMaker.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFramework>net9.0</TargetFramework>
4+
<TargetFramework>net10.0</TargetFramework>
55
<ImplicitUsings>enable</ImplicitUsings>
66
</PropertyGroup>
77

samples/WebAPI/WebAPI_Common/Greetings_Migrations/Greetings_Migrations.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFramework>net9.0</TargetFramework>
4+
<TargetFramework>net10.0</TargetFramework>
55
<ImplicitUsings>enable</ImplicitUsings>
66
<!-- Nullable disabled: FluentMigrator migration classes use generated code patterns -->
77
<Nullable>disable</Nullable>

samples/WebAPI/WebAPI_Common/Salutations_Migrations/Salutations_Migrations.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFramework>net9.0</TargetFramework>
4+
<TargetFramework>net10.0</TargetFramework>
55
<ImplicitUsings>enable</ImplicitUsings>
66
<!-- Nullable disabled: FluentMigrator migration classes use generated code patterns -->
77
<Nullable>disable</Nullable>

samples/WebAPI/WebAPI_Common/TransportMaker/TransportMaker.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFramework>net9.0</TargetFramework>
4+
<TargetFramework>net10.0</TargetFramework>
55
<ImplicitUsings>enable</ImplicitUsings>
66
</PropertyGroup>
77

samples/WebAPI/WebAPI_Dapper/GreetingsApp/GreetingsApp.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFramework>net9.0</TargetFramework>
4+
<TargetFramework>net10.0</TargetFramework>
55
</PropertyGroup>
66

77
<ItemGroup>

0 commit comments

Comments
 (0)