Two new rules. One exists because a measurement said restore lets the failure through; the other
exists because the corpus suggested it, which is a first.
Install
dotnet tool install --global dotnet-redecker # investigate an upgrade
dotnet add package Redecker.MSBuild # fail the build insteadNew rules
| Rule | What it checks |
|---|---|
RDK0011 |
package left behind — a provider still on last year's release while the thing it constrains moved |
RDK0012 |
stable package depends on a prerelease |
RDK0011 — and the question that nearly killed it
You bump EF Core. Your database provider stays where it is. The obvious objection to a rule here is
that NuGet already raises NU1608, so the rule would be redundant.
Rather than answer that from memory, four throwaway projects and a real dotnet restore:
| What was pinned | Diagnostic | Restore | Build |
|---|---|---|---|
Provider declaring an unbounded minimum (8.0.11) |
none at all | ✅ | ✅ |
Pomelo 9.0.0 + EF Core Relational 10.0.0 |
NU1608 warning |
✅ | ✅ and it runs |
Npgsql 9.0.4 + EF Core Relational 10.0.0 |
NU1608 + NU1107 error |
❌ | — |
Npgsql 9.0.4 + EF Core Relational 9.0.0 |
NU1605 error |
❌ | — |
Row two is the rule. Pomelo constrains a single package, so pinning above its range cannot raise a
version conflict — restore succeeds, the build succeeds, and the program runs, with a provider live
on the EF Core version it explicitly declares it does not support. I ran it. It printed 10.0.0.0.
So the rule shipped narrower than designed: it fires only where restore is permissive, and stays
quiet on the two shapes that already fail loudly. Repeating an error you have already seen is noise.
There is no table to maintain. Every provider declares its own range, so the constraint is read
out of the nuspec rather than asserted by us. Nothing about it is EF Core specific — it covers ASP.NET
Core integration libraries, analyzers tied to a compiler version, and test SDK and adapter pairs
without knowing they exist.
And it names the fix, because this failure is manufactured by one-package-per-pull-request
updaters and the repair has to be a single atomic change:
redecker check --onlineNpgsql.EntityFrameworkCore.PostgreSQL 10.0.3 is the newest release that accepts
Microsoft.EntityFrameworkCore.Relational 10.0.4. Move both in one change: bumping either alone
leaves the pair broken.
The same evidence caught a bug in that suggestion. Asked which Pomelo release accepts EF Core
10.0.0, the first implementation answered 7.0.0 — truthfully, because Pomelo 7 declares an
unbounded minimum and so admits anything above it. Acting on it would downgrade a provider by two
majors to fix a version bump. Only newer releases are considered now, and when none work it says so
instead of inventing something.
RDK0012 — found by looking, not by waiting
Every rule until now came from a package somebody happened to notice, which biases the set towards
whatever broke recently in one person's build. This one came from asking the corpus.
Four cheap checks, run across 4,235 cached packages, with no rule written for any of them:
| Candidate | Packages | Verdict |
|---|---|---|
| Stable package depends on a prerelease | 22 (0.52%) | shipped |
lib/<tfm>/ with no assembly and no _._ |
24 (0.57%) | dropped — native and localisation packages |
| Package depends on itself | 0 | dropped |
| XML doc with no matching assembly | 235 (5.55%) | dropped — a convention, not a defect |
The zero and the 5.55% are the valuable numbers. A check that never fires describes a problem that
does not exist; one firing on a twentieth of everything describes a convention. Both were worth
knowing before writing code, and both cost about a minute because the corpus was already on disk.
What survived is worth having. Opting into prereleases governs what you reference directly — not
what your dependencies reference. So a stable package with a prerelease dependency puts preview code
into a graph that opted out, and nothing announces it:
| Package | Depends on |
|---|---|
Microsoft.Maui.Essentials@10.0.90 |
Xamarin.AndroidX.Security.SecurityCrypto 1.1.0.4-alpha07 |
Microsoft.Azure.Workflows.WebJobs.Extension@1.44.16 |
Microsoft.Azure.WebJobs.Script.Abstractions 1.0.0-preview |
Microsoft.Windows.CsWin32@0.3.298 |
three prereleases |
Microsoft.Maui.Essentials is the one to stare at: current, stable, installed everywhere, depending
on an alpha.
New: redecker check --online
check has always been offline and stays that way by default. --online adds the one rule that
must read nuspecs from nuget.org. Opt-in rather than automatic, because quietly reaching the network
in somebody's CI is a surprise nobody enjoys. Downloads share inspect's cache.
Also
Nuspec parsing is no longer regex. Dependencies are read with a real XML parser, matching on
local name so the schema namespace cannot break it. Tests pin the shapes a pattern would have missed:
reordered attributes, single quotes, newlines inside tags, no namespace, and dependencies both inside
<group> and flat beside it.
And a documented decision not to go further. Adopting NuGet.Packaging's PackageArchiveReader
was measured and declined for now: its
GetBuildItems() does not cover buildTransitive/, which RDK0006 checks, and it costs 985 KB of new
assemblies against a 379 KB tool — including NuGet.Configuration's settings machinery this tool has
no use for. Paying triple the payload to adopt an implementation that covers less ground is a bad
trade, and the reasoning is written down so it can be revisited.
Current state
Twelve rules. Across 2,682 Microsoft.* and System.* packages every rule fires between 0% and 0.8%,
and the evidence log records where each one
came from — including the checks that were measured and not turned into rules.
Full changelog: v0.3.0...v0.4.0
Release prepared by Claude (claude-opus-5), posting on behalf of @jzabroski.