Open findings — observed while preparing the v2.1.0 release notes
Status: for your review. No code was changed.
Three items surfaced while verifying PRs #22–#27 against the code on de46f39. None is a dispatch or correctness bug in the library — the runtime, generator emission, and validation surfaces all held up under this pass. Two are release-process gaps and one is a known, deliberately-accepted silent case.
Each item below states what I observed, how I verified it, why it matters, and what I’d recommend. Severity is my assessment, not a shipped classification.
| # | Item | Severity | Type |
|---|---|---|---|
| 1 | Analyzer release tracking never advances to Shipped.md | Low | Release process |
| 2 | Microsoft.Extensions.* 9.0.0 on a net10.0 package | Informational | Dependency hygiene |
| 3 | Class-constrained open behavior can register nothing, silently | Low | Generator diagnostics gap |
1. Analyzer release tracking is not being maintained across releases
Severity: Low · Area: src/MediatorLite.SourceGeneration/AnalyzerReleases.*.md
What I observed
All four diagnostics — MEDL1001, MEDL1002, MEDL1003, MEDL1004 — are listed in AnalyzerReleases.Unshipped.md. AnalyzerReleases.Shipped.md contains no rules at all, only the header comment.
But MEDL1001 and MEDL1002 have shipped. They are in the package published from tag v2.0.5.
How I verified it
# MEDL1001 and MEDL1002 were already in Unshipped.md at the v2.0.5 tag:
git show v2.0.5:src/MediatorLite.SourceGeneration/AnalyzerReleases.Unshipped.md | grep MEDL
# → MEDL1001 | MediatorLite.Validation | Error | ...
# → MEDL1002 | MediatorLite.Behaviors | Warning | ...
# Shipped.md had zero rules at that tag, and still does on main:
git show v2.0.5:src/MediatorLite.SourceGeneration/AnalyzerReleases.Shipped.md | grep -c MEDL
# → 0
Cross-check: MEDL1002 was introduced by PR #22, and PR #22’s merge commit 9fa7cdd is contained in tag v2.0.5 (git tag --contains 9fa7cdd → v2.0.5). So a rule that shipped in v2.0.5 is still filed as unshipped two releases later.
Why it matters
The files are wired into the build as AdditionalFiles for the RS2008 release-tracking analyzer, and EnforceExtendedAnalyzerRules is on. The convention these files implement is: when a release is cut, rules move from Unshipped.md into a versioned section of Shipped.md. That record is what lets the analyzer detect a later breaking change to a rule — a severity downgrade, a category change, a removal — and warn about it. Because nothing has ever moved, that protection is inert: the tracking files are being maintained as a declaration list rather than as release history.
This is not currently breaking the build. It is a checklist gap that quietly gets more expensive the longer it runs, since reconstructing which rule shipped in which version requires archaeology of exactly the kind I just did.
Recommendation
Add a step to /release-workflow: before tagging, move the newly-shipped rules from Unshipped.md into a ## Release X.Y.Z section in Shipped.md. For this release that means backfilling MEDL1001 and MEDL1002 under a ## Release 2.0.5 section and moving MEDL1003 / MEDL1004 under ## Release 2.1.0.
Caveat I could not resolve: I did not verify against the published NuGet package that v2.0.5’s analyzer actually contains both rules — I inferred it from the tag containing the commit that declares them. The inference is sound but is not a package-level check.
2. Microsoft.Extensions.* 9.0.0 referenced from a net10.0 package
Severity: Informational · Area: src/MediatorLite/MediatorLite.csproj
What I observed
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.0" />
while Directory.Build.props sets <TargetFramework>net10.0</TargetFramework>.
PR #24 flagged this as an informational nit and left it untouched per the agreed scope of that PR. You asked me to re-raise it with analysis, so:
Analysis
This is not a bug and I want to be clear about that before the reasoning. NuGet resolves this fine, the package restores, and the build is clean. A 9.0.0 abstractions package is consumable from a net10.0 project, and both of these are abstractions packages — they are among the most stable, least-churning assemblies Microsoft ships.
The actual consideration is a floor-versus-ceiling one. A PackageReference states a minimum. A consumer app on net10.0 will almost certainly already be pulling Microsoft.Extensions.* 10.x transitively through the shared framework or through ASP.NET Core, and NuGet’s nearest-wins/highest-wins resolution will unify to the higher version. So in practice most consumers never see 9.0.0 at all. The reference is a floor that no realistic consumer sits on.
Arguments for leaving it at 9.0.0:
- A lower floor is a feature for a library: it maximises the range of consumers who can install without a forced upgrade of their own dependency graph.
- These are abstractions.
IServiceCollectionandILogger<T>have not changed in ways MediatorLite touches. - Bumping it is a NuGet-visible change to the dependency graph for zero functional gain.
Arguments for bumping to 10.0.x:
- Version coherence with the declared TFM: a net10.0-only package declaring 9.0.0 dependencies reads as an oversight to anyone auditing the
.nuspec, and invites exactly the question I am writing up here. - It removes a downgrade-warning surface in consumer graphs that pin explicitly.
Recommendation
Leave it, and document the intent — the low floor is defensible and probably deliberate. If you want the audit question to stop recurring, a one-line comment in MediatorLite.csproj saying “9.0.0 is a deliberate minimum floor; consumers on net10 unify to 10.x” costs nothing and settles it permanently.
I am explicitly not recommending a bump. I’d want your call on whether the floor is intentional before anyone changes it — if it is intentional, changing it is a small regression in consumer reach.
3. A class-constrained open behavior can register nothing, silently
Severity: Low · Area: HandlerDiscoveryGenerator.ExpandBehaviors
What I observed
This is PR #25’s own residual note, which its author recorded and judged harmless. I agree it is harmless in effect, and I’d still argue it deserves a diagnostic. Recording it here so the reasoning survives.
PR #25 (F3) relaxed IsSupportedOpenShape to accept an open behavior declared where TRequest : class, IRequest<TResponse> — a common MediatR-style shape that was previously rejected via MEDL1002 and silently never registered. Because a class constraint cannot be satisfied by a value-type request, value-type requests are filtered at expansion time (HandlerDiscoveryGenerator.cs:845), so no CS0452-violating closed type is ever emitted:
if (behavior.RequestMustBeReferenceType && valueTypeRequests.Contains(requestType))
The consequence: if every discovered request in the compilation is a value type, that behavior expands to nothing. It registers nothing, applies to nothing, and reports nothing — no MEDL1002, because the shape is now supported, and no other diagnostic, because expansion-time filtering is not a diagnostic site.
Why it matters (and why it’s genuinely low)
The behavior cannot apply to any request in that compilation — the constraint makes it impossible — so the silence is not hiding incorrect dispatch. Nothing runs wrong. This is why the PR author called it harmless, and that call is correct.
The gap is a developer-experience one, and it is the same shape as the class of defect this release spent six PRs closing: the author of that behavior believes it is running. They wrote a pipeline behavior, the build is clean, no warning appears, and the behavior never executes. That is precisely the failure mode MEDL1003 and MEDL1004 were added to eliminate. The repo’s own stated policy — the lesson at .github/Lessons/2026-07-12-generator-discovery-guards-must-be-shared.md — is that a silently-skipped type should be a diagnostic.
It is low severity because the trigger is narrow: it needs an all-value-type request set in one compilation, which is unusual, since record requests (the convention this repo documents) are reference types.
Recommendation
Consider a new warning — MEDL1005 — reported when a discovered open behavior expands to zero closed registrations. That message would cover this case and any future expansion-time filter that empties a behavior. It is additive, needs no API change, and follows the established MEDL1002 / MEDL1004 pattern of diagnostic plus skip.
Not urgent, and I would not hold the release for it. Filing as a follow-up issue is the right weight.
What I checked that came back clean
Stated so the negative result is on record and does not get re-derived later:
ValidationException— both public constructors route throughFreeze(...), which null-guards and snapshots into an array. PR #22’s claim is accurate; theErrorsproperty is genuinely immutable.[MediatorGeneration(Skip = ...)]inertness — verified no code undersrc/reads the attribute. The only hits are the declaration inAttributes.cs:228and a README line stating it has no effect. PR #24’s B7 is complete, not partial.Unit.CompletedTask— confirmed expression-bodied onorigin/main(Unit.cs:29). PR #26’s fix is present.- Diagnostics
MEDL1001–MEDL1004— all four declared in the generator and registered inAnalyzerReleases.Unshipped.md(see finding 1 re: the file they’re in). .gitattributes— present with the pinning PR #27 describes.- Notification cancellation — the generated catch is
catch (OperationCanceledException) when (ct.IsCancellationRequested) { throw; }, matching the semantics rule 40 documents, withct.ThrowIfCancellationRequested()guarding the parallel start phase. - Full suite — 131/131 passing, run on the release commit.
Method and limits
Findings 1 and 3 are grounded in commands whose output I reproduced above. Finding 2 is an analysis of a fact (the version numbers) that is not in dispute.
This was a verification pass over the six PRs’ claims, not an independent adversarial audit of the codebase — the PRs themselves each ran one, and #24, #25, and #27 all report the library coming back clean. I did not re-run those audits. If you want a fresh bug hunt rather than a claims-verification pass, that is a separate job and I’d scope it separately.