Skip to content

v0.4.0 — the corpus starts proposing rules

Latest

Choose a tag to compare

@jzabroski jzabroski released this 01 Aug 02:08

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 instead

📖 Documentation

New 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 --online
Npgsql.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.