Skip to content

release: v1.3.0-rc.1 — handler-type seam, DSOFT008 compilation-wide, dep bumps - #5

Merged
yandersr merged 11 commits into
mainfrom
1.3.0
Jun 21, 2026
Merged

release: v1.3.0-rc.1 — handler-type seam, DSOFT008 compilation-wide, dep bumps#5
yandersr merged 11 commits into
mainfrom
1.3.0

Conversation

@yandersr

Copy link
Copy Markdown
Contributor

Pre-release (rc.1) soaking ~2 weeks before promotion to the stable 1.3.0.

Package versions

Package New version Bump
DSoftStudio.Mediator · .Abstractions 1.3.0-rc.1 minor
DSoftStudio.Mediator.OpenTelemetry 1.1.0-rc.1 minor
DSoftStudio.Mediator.HybridCache · .FluentValidation 1.0.9-rc.1 patch

Added

  • IPipelineHandlerTypeAccessor (Abstractions) — exposes the concrete request/stream handler type at the tail of the pipeline chain to an outermost behavior, without resolving or instantiating it. A behavior is open-generic and may serve many handlers; the correct one is only knowable by walking the chain it was handed as next. Implemented by the internal BehaviorHandlerAdapter / StreamBehaviorHandlerAdapter.
  • OpenTelemetry: mediator.handler.type on request & stream spans (it already did for notification-handler spans) — an imported OTLP/Jaeger trace now maps each span to its handler source and renders HTTP/DB child spans as dependencies under it. Read through the new accessor; the handler is never resolved.

Changed

  • DSOFT008 is now compilation-wide (reported from a CompilationEndAction) — splitting AddMediator() and the handler registration across different methods is no longer a false positive.
  • Dependency bumpsMicrosoft.Extensions.DependencyInjection.Abstractions 10.0.9, Microsoft.Bcl.AsyncInterfaces 10.0.9, OpenTelemetry 1.16.0, Microsoft.Extensions.Caching.Hybrid 10.7.0. Microsoft.CodeAnalysis.CSharp intentionally kept at 4.12.0 — it's the minimum compiler host a consumer needs; raising it would break consumers on older SDK/VS.
  • Aligned stale test/sample/benchmark package pins (NU1605).

Security

Validation

  • Full solution build: 0 errors / 0 warnings
  • Tests green: core 347 · OpenTelemetry 54 · HybridCache 13 · FluentValidation 21
  • dotnet list --vulnerable (solution-wide): 0 vulnerabilities
  • dotnet pack: all 5 .nupkg produced; companion → core dependency resolves to 1.3.0-rc.1

Promotion to stable

When the rc soaks without issues: drop the -rc.1 suffix, move DSOFT008 from AnalyzerReleases.Unshipped.mdShipped.md (under "Release 1.3.0"), and date the CHANGELOG section.

yandersr added 3 commits June 13, 2026 22:32
Anchor the PreferCqrsInterface (DSOFT006) suggestion to the whole type header —
identifier through base list — instead of just the type name, so the IDE offers
the ConvertToCqrs lightbulb when hovering the offending IRequest<T> base type too.
DSOFT006 stays Info severity (suggestion dots, no squiggle); this only widens the
lightbulb reach. Mirrors CqrsSemanticAnalyzerEnterprise (DiagnosticLocations.TypeHeader).

Analyzer-only change: emits no source and does not alter compiled output, so there
is no runtime impact.
…nversion

The registration-API analyzer was an [Generator], which cannot see other
generators' output. So it could not resolve the generated RegisterMediatorHandlers()
or the AddMediator(builder) overload — DSOFT007 was effectively dead in real
projects (it only fired against hand-written stubs in tests).

Convert it to a DiagnosticAnalyzer: it runs after all source generators on the
final compilation, so the semantic model resolves the generated members. Analysis
is now per method body (operation-block) instead of compilation-wide.

- Fixes DSOFT007: now fires on real builds, not just stubs.
- Adds DSOFT008: parameterless AddMediator() that never registers handlers in the
  scope (no builder overload, no RegisterMediatorHandlers(), no manual
  AddTransient<IRequestHandler<,>,...>()) while handlers exist -> the runtime
  'No service for IRequestHandler<...>' failure, caught at compile time.
- Recognizes manual handler registration to avoid false positives.
- Analyzer-only change: the runtime assembly and generated code are untouched
  (zero runtime/allocation impact).

Tests: stub logic tests rerun via WithAnalyzers; new high-fidelity integration
tests run the real DependencyInjectionGenerator + analyzer to lock the regression.
Deliberate 'no handler' self-tests suppressed with #pragma. Full suite green.
…dep bumps

Pre-release (rc.1) soaking before promotion to the stable 1.3.0.

Added
- IPipelineHandlerTypeAccessor (Abstractions): exposes the concrete request/
  stream handler type at the tail of the pipeline chain to an outermost
  behavior, without resolving or instantiating it. Implemented by the internal
  BehaviorHandlerAdapter / StreamBehaviorHandlerAdapter (walk next -> handler).
- OpenTelemetry: tag mediator.handler.type on request and stream spans (it
  already did for notification-handler spans). Read through the new accessor;
  the handler is never resolved or instantiated.

Changed
- DSOFT008 detection is now compilation-wide (reported from a
  CompilationEndAction), so splitting AddMediator() and the handler
  registration across different methods is no longer a false positive.
- Dependency bumps: Microsoft.Extensions.DependencyInjection.Abstractions 10.0.9,
  Microsoft.Bcl.AsyncInterfaces 10.0.9, OpenTelemetry 1.16.0,
  Microsoft.Extensions.Caching.Hybrid 10.7.0. Microsoft.CodeAnalysis.CSharp is
  intentionally kept at 4.12.0 — that version is the minimum compiler host a
  consumer needs, so raising it would break consumers on older SDK/VS.
- Align stale test/sample/benchmark package pins (NU1605 downgrade fixes).

Security
- Scriban 7.0.3 -> 7.2.4 (benchmarks, dev-only, not shipped) — GHSA-24c8-4792-22hx.

Versions: Mediator/Abstractions 1.3.0-rc.1, OpenTelemetry 1.1.0-rc.1,
HybridCache/FluentValidation 1.0.9-rc.1.
@github-actions

github-actions Bot commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Summary

Summary
Generated on: 06/21/2026 - 23:01:20
Coverage date: 06/21/2026 - 23:00:57 - 06/21/2026 - 23:01:15
Parser: MultiReport (8x Cobertura)
Assemblies: 6
Classes: 64
Files: 58
Line coverage: 93.9% (3236 of 3445)
Covered lines: 3236
Uncovered lines: 209
Coverable lines: 3445
Total lines: 8081
Branch coverage: 81% (1172 of 1446)
Covered branches: 1172
Total branches: 1446
Method coverage: Feature is only available for sponsors
Tag: 131_27920220301

Coverage

DSoftStudio.Mediator - 98.1%
Name Line Branch
DSoftStudio.Mediator 98.1% 94.1%
DSoftStudio.Mediator.BehaviorHandlerAdapter`2 100% 100%
DSoftStudio.Mediator.HandlerCache`2 100% 100%
DSoftStudio.Mediator.Mediator 100% 91.6%
DSoftStudio.Mediator.MediatorBuilder 100% 100%
DSoftStudio.Mediator.NotificationCachedDispatcher 100% 100%
DSoftStudio.Mediator.NotificationDispatch`1 100%
DSoftStudio.Mediator.NotificationHandlerCache`1 100% 100%
DSoftStudio.Mediator.NotificationObjectDispatch 90.4% 75%
DSoftStudio.Mediator.NotificationPublisherFlag 100% 100%
DSoftStudio.Mediator.ParallelNotificationPublisher 100% 100%
DSoftStudio.Mediator.PipelineChainCache`2 100% 100%
DSoftStudio.Mediator.PipelineChainHandler`2 98% 93%
DSoftStudio.Mediator.RequestDispatch`2 100%
DSoftStudio.Mediator.RequestObjectDispatch 90.9% 50%
DSoftStudio.Mediator.SequentialNotificationPublisher 100% 100%
DSoftStudio.Mediator.ServiceCollectionExtensions 100%
DSoftStudio.Mediator.StreamBehaviorHandlerAdapter`2 100% 100%
DSoftStudio.Mediator.StreamDispatch`2 100%
DSoftStudio.Mediator.StreamHandlerCache`2 100% 100%
DSoftStudio.Mediator.StreamPipelineChainCache`2 100% 100%
DSoftStudio.Mediator.StreamPipelineChainHandler`2 92.8% 75%
DSoftStudio.Mediator.StreamPipelineInvoker 100% 100%
DSoftStudio.Mediator.Abstractions - 100%
Name Line Branch
DSoftStudio.Mediator.Abstractions 100% ****
DSoftStudio.Mediator.Abstractions.MediatorHandlerRegistrationAttribute 100%
DSoftStudio.Mediator.Abstractions.RequestExceptionHandlerState`1 100%
DSoftStudio.Mediator.Abstractions.Unit 100%
DSoftStudio.Mediator.FluentValidation - 100%
Name Line Branch
DSoftStudio.Mediator.FluentValidation 100% 87.5%
DSoftStudio.Mediator.FluentValidation.FluentValidationServiceCollectionExte
nsions
100%
DSoftStudio.Mediator.FluentValidation.MediatorValidationException 100% 83.3%
DSoftStudio.Mediator.FluentValidation.ValidationBehavior`2 100% 90%
DSoftStudio.Mediator.Generators - 92.9%
Name Line Branch
DSoftStudio.Mediator.Generators 92.9% 78.4%
DSoftStudio.Mediator.Generators.BehaviorTypeInfo 100% 100%
DSoftStudio.Mediator.Generators.CqrsSemanticAnalyzer 86.3% 61.5%
DSoftStudio.Mediator.Generators.DependencyInjectionGenerator 96.8% 88.5%
DSoftStudio.Mediator.Generators.DiagnosticDescriptors 100%
DSoftStudio.Mediator.Generators.EquatableArray`1 100% 92.8%
DSoftStudio.Mediator.Generators.HandlerDiscovery 97.1% 84%
DSoftStudio.Mediator.Generators.InterceptorHelpers 84.2% 55.8%
DSoftStudio.Mediator.Generators.MediatorExtensionsGenerator 93.4% 69.3%
DSoftStudio.Mediator.Generators.MediatorPipelineGenerator 92.9% 78.2%
DSoftStudio.Mediator.Generators.MixedRegistrationApiAnalyzer 91.1% 74.5%
DSoftStudio.Mediator.Generators.MockDetectionAnalyzer 100% 100%
DSoftStudio.Mediator.Generators.NotificationGenerator 92.5% 77.7%
DSoftStudio.Mediator.Generators.PublishInterceptorGenerator 90.4% 67.2%
DSoftStudio.Mediator.Generators.ReferencedAssemblyScanner 85.8% 81.6%
DSoftStudio.Mediator.Generators.SelfHandlerDetail 100% 83.3%
DSoftStudio.Mediator.Generators.SelfHandlerParam 100% 83.3%
DSoftStudio.Mediator.Generators.SendInterceptorGenerator 91.6% 75%
DSoftStudio.Mediator.Generators.StreamGenerator 94.9% 81%
DSoftStudio.Mediator.Generators.StreamInterceptorGenerator 90.9% 73.3%
DSoftStudio.Mediator.HybridCache - 100%
Name Line Branch
DSoftStudio.Mediator.HybridCache 100% 100%
DSoftStudio.Mediator.HybridCache.CachingBehavior`2 100% 100%
DSoftStudio.Mediator.HybridCache.HybridCacheServiceCollectionExtensions 100%
DSoftStudio.Mediator.HybridCache.ICachedRequest 100%
DSoftStudio.Mediator.OpenTelemetry - 97.1%
Name Line Branch
DSoftStudio.Mediator.OpenTelemetry 97.1% 86.8%
DSoftStudio.Mediator.OpenTelemetry.ActivityHelper 100% 100%
DSoftStudio.Mediator.OpenTelemetry.InstrumentedNotificationPublisher 97.3% 90.9%
DSoftStudio.Mediator.OpenTelemetry.MediatorInstrumentation 100% 50%
DSoftStudio.Mediator.OpenTelemetry.MediatorInstrumentationOptions 100%
DSoftStudio.Mediator.OpenTelemetry.MediatorMetricsBehavior`2 100% 100%
DSoftStudio.Mediator.OpenTelemetry.MediatorNotificationMetadata`1 100%
DSoftStudio.Mediator.OpenTelemetry.MediatorStreamMetadata`2 100%
DSoftStudio.Mediator.OpenTelemetry.MediatorStreamMetricsBehavior`2 100% 85.7%
DSoftStudio.Mediator.OpenTelemetry.MediatorStreamTracingBehavior`2 100% 84.6%
DSoftStudio.Mediator.OpenTelemetry.MediatorTelemetryMetadata`2 100% 100%
DSoftStudio.Mediator.OpenTelemetry.MediatorTracingBehavior`2 100% 90%
DSoftStudio.Mediator.OpenTelemetry.OpenTelemetryServiceCollectionExtensions 84.3% 77.2%
OpenTelemetry.Metrics.MediatorMeterProviderBuilderExtensions 100%
OpenTelemetry.Trace.MediatorTracerProviderBuilderExtensions 100%

yandersr added 8 commits June 21, 2026 17:35
- Test the whole solution (dotnet test DSoftStudio.Mediator.slnx) instead of a
  hand-listed set of projects. The old steps silently skipped
  ModularMonolith.Tests (and the cross-project-mocking sample tests); the
  solution target discovers every test project, so new ones are covered with no
  workflow edit. 453 tests run (was a subset).
- Merge per-project coverage with ReportGenerator before posting. Each project
  emits its own coverage.cobertura.xml covering the same assemblies only in the
  slice it exercises; the previous summary listed each assembly N times with
  different numbers (the misleading 34%). The merged report shows each assembly
  once with its real combined coverage (e.g. core ~92%).
The stream/notification/publish/pipeline source generators had ZERO test
coverage. Add a shared in-memory harness (GeneratorTestHarness) that drives the
real IIncrementalGenerators against a small user compilation and inspects the
generated source, then cover each:

- StreamGenerator                0% -> 96.7%  (registry, AOT behavior closure, empty)
- StreamInterceptorGenerator     0% -> 79.0%  (CreateStream<,> interception, no-call-site)
- NotificationGenerator          0% -> 90.6%  (dispatch table, Publish(object) switch, empty)
- PublishInterceptorGenerator    0% -> 88.1%  (Publish<T> interception, Publish(object) excluded)
- MediatorPipelineGenerator      0% -> 73.1%  (MediatorRegistry entry points, empty skeleton)

Harness note: the netstandard2.0 Abstractions exposes IAsyncEnumerable from
Microsoft.Bcl.AsyncInterfaces, so stream call sites only bind once that assembly
is referenced — the harness adds it. 11 tests, all green (project: 358).
Follow-up to the 0%->coverage pass. Add a shared in-memory harness RunChain
(runs two generators in sequence, so the second sees the first's output) plus a
Release/optimization toggle, and cover the deeper branches:

- MediatorPipelineGenerator   -> 94.1%  (AOT open-generic behavior closure, self-handling request)
- StreamGenerator             -> 96.7%
- NotificationGenerator       -> 93.4%  (multi-handler grouping, abstract/generic/file-local rejection)
- PublishInterceptorGenerator -> 91.2%  (explicit + inferred Publish, Release, non-publisher exclusion)
- StreamInterceptorGenerator  -> 92.0%  (inferred CreateStream via the generated typed extension)

Note: the inferred CreateStream path is NOT dead code (an earlier guess) — it is
reachable once MediatorExtensionsGenerator emits the typed CreateStream(this
IMediator, T) extension that makes the inferred call bind; RunChain exercises
exactly that two-generator scenario. The remaining uncovered lines are defensive
early-exits that only fire when the mediator interface is absent (impossible in a
real project), config-flag (SuppressInterceptors) branches, and external-assembly
scans — covering those needs dedicated harness machinery for marginal value.
… discovery

Close the remaining low-coverage generator helpers (the merged CI report showed
these well below the rest):

- EquatableArray<T>      35% -> 100%  (direct unit tests: Equals/GetHashCode/enumeration/default)
- SelfHandlerDetail      24% -> 100%  (direct struct equality + properties)
- SelfHandlerParam       29% -> 100%
- HandlerDiscovery       75% -> 95%   (self-handler discovery across every Execute return
                                       shape — sync/Task<T>/ValueTask<T>/void/Task — and param
                                       kind — request/service/cancellation)
- SendInterceptorGenerator 79% -> 93% (inferred Send via the generated typed extension using
                                       RunChain, Release path, non-sender + expression-tree exclusions)

Adds [InternalsVisibleTo("DSoftStudio.Mediator.Tests")] on the generators project
(matching the strong-name key) so the internal helper structs can be tested directly.
The pull_request:synchronize event for a6065c1 (EquatableArray / SendInterceptor /
self-handler coverage) was skipped by GitHub Actions, so the CI workflow never ran
and the merged coverage comment is stale. Empty commit to fire a fresh run.
- BehaviorTypeInfo            73%/20%br -> 100%/100%br  (direct struct tests:
  properties, structural equality across all fields, null-safe GetHashCode)
- StreamBehaviorHandlerAdapter  0% branch -> 100% branch  (the stream half of the
  ADR-0049 IPipelineHandlerTypeAccessor seam; the request adapter was already
  tested, the stream chain-walk was not)
An interceptor cannot represent an open-generic call site: a single
[InterceptsLocation] is instantiated for every TRequest/TResponse the
enclosing generic method is called with, so no one concrete interceptor
can stand in for it. The three interceptor generators emitted a method
referencing the unbound type parameters anyway -> CS0246, breaking the
build of any consumer with a generic dispatch wrapper, e.g.:

    ValueTask<TResp> Dispatch<TReq, TResp>(TReq req)
        where TReq : IRequest<TResp> => _sender.Send<TReq, TResp>(req);

Guard with InterceptorHelpers.ContainsTypeParameter: such call sites are
skipped and dispatch through the real Mediator.Send/Publish/CreateStream
at runtime (which is exactly what those methods exist for).

This also covers the previously-untested non-intercepted Mediator dispatch
path (line 30% -> 100%, branch 17% -> 92%): an open-generic helper reaches
the real Mediator.Send/Publish/CreateStream, exercising every branch
(pipeline vs handler-cache, custom-publisher vs sequential, precompiled
stream vs invoker fallback).

Generator output for concrete call sites is byte-identical (the guard
returns false for closed types), so allocation/perf are unchanged:
Send 6.7ns/72B (ratio 1.00), Publish 4.4ns/0B, Stream 46ns/232B (ratio 1.00).

+12 tests (9 runtime dispatch, 3 generator regression), 395 green.
… stale docs

Core-runtime audit cleanup -- no behavior change, hot paths byte-identical.

- Remove RequestDispatch<>.Pipeline / TryInitialize and the PipelineBuilder class.
  The generator built and stored a per-request-type dispatch delegate that nothing
  ever invoked: Send / the interceptor / RequestObjectDispatch all dispatch via
  HasPipelineChain + the ThreadStatic PipelineChainCache/HandlerCache. (The stream
  side still uses StreamDispatch.Pipeline; the request side left it vestigial.)
  Saves one delegate allocation per request type at startup. The 3 tests that drove
  it are migrated to the live PipelineChainHandler path.

- MediatorBuilder: collapse 4 identical interface-matching registration loops into a
  shared RegisterByOpenInterface helper.

- Fix XML docs describing the removed mutable-index / reentrancy / PipelineBuilder
  design (PipelineChainHandler, BehaviorHandlerAdapter, ParallelNotificationPublisher)
  + broken brace indentation.

Send 6.6ns/72B, Publish 4.4ns/0B, Stream 46ns/232B (ratio 1.00). All suites green.
@yandersr
yandersr merged commit a991e47 into main Jun 21, 2026
6 checks passed
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.

1 participant