From cb907b1883e2d5f372c6a8900f883520a57d59a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Gro=C3=9F?= Date: Sun, 16 Aug 2026 14:05:44 +0200 Subject: [PATCH] docs: state the support horizon and what happens if maintenance stops MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SECURITY.md said which line receives fixes and that older majors are dropped, but not for how long the current line lives or how its end would be signalled — the two things a downstream consumer doing CRA-era supplier due diligence asks for after "do you have a disclosure channel". The horizon is tied to what the packages target: .NET 10 (and EF Core 10 for the EF Core packages), maintained while Microsoft supports that .NET release (LTS, November 2028) or until a new major supersedes it — with the note that majors here are cheap and a new one has never meant the old one lost fixes before its successor shipped. No fixed date, no SLA — an intention, phrased as one, with the promise that a change is recorded there first. A second paragraph names the single-maintainer risk plainly, says what "unmaintained" would look like, and that forking is the intended continuity mechanism. A third says how advisories reach consumers' tooling: GitHub Security Advisories → GitHub Advisory Database → NuGetAudit on restore. The .NET major in the horizon sentence is anchored to the core project's TargetFramework by SecurityPolicyConventionTests, so moving to net11.0 without moving the sentence fails the build. Verified: the new test passes as written, fails with the sentence saying .NET 11, fails with the anchor phrase reworded, passes restored. Co-Authored-By: Claude Fable 5 --- SECURITY.md | 21 ++++++++++++ .../SecurityPolicyConventionTests.cs | 33 +++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/SECURITY.md b/SECURITY.md index 3f07ea9..9247bf2 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -54,6 +54,27 @@ remedy for those is to upgrade. | 6.2.x | ✅ | | < 6.2 | ❌ | +### For how long + +There is no fixed end-of-support date. The current line targets **.NET 10** (`net10.0`, and EF Core +10 for the EF Core packages), and the intention is to keep it maintained for as long as Microsoft +supports that .NET release (.NET 10 is LTS, supported until November 2028), or until a new major of +this package supersedes it, whichever comes first — majors here are cheap, and a new one has never +meant the old one lost fixes before its successor shipped. If that intention changes, it is recorded +here before anywhere else. + +### If this project stops being maintained + +This is a single-maintainer project, and that is the honest continuity risk. The signal would be +unambiguous: the repository archived, the packages marked deprecated on nuget.org, and a note here. +Published versions stay on nuget.org regardless (a package can be unlisted, not removed), the code +is MIT-licensed, and the release path needs nothing but this repository — forking is the intended +continuity mechanism, not a fallback. + +Advisories are published as GitHub Security Advisories, which reach the GitHub Advisory Database +and from there `dotnet restore` (NuGetAudit): a consumer on an affected version sees a build +warning without subscribing to anything. + ## Where this package sits Useful context for judging impact, and for anyone doing supply-chain due diligence. diff --git a/test/CodoMetis.ValueRanges.Conventions.Tests/SecurityPolicyConventionTests.cs b/test/CodoMetis.ValueRanges.Conventions.Tests/SecurityPolicyConventionTests.cs index 4507445..d603472 100644 --- a/test/CodoMetis.ValueRanges.Conventions.Tests/SecurityPolicyConventionTests.cs +++ b/test/CodoMetis.ValueRanges.Conventions.Tests/SecurityPolicyConventionTests.cs @@ -22,12 +22,45 @@ public sealed class SecurityPolicyConventionTests private static readonly Regex UnsupportedRow = new(@"^\|\s*<\s*(?\d+)\.(?\d+)\s*\|\s*❌\s*\|", RegexOptions.Multiline); + /// "the current line targets **.NET 10**" — the number the horizon statement is anchored to. + private static readonly Regex TargetsDotNet = + new(@"targets \*\*\.NET (?\d+)\*\*", RegexOptions.None); + [TestMethod] public void SecurityPolicy_Exists() { Assert.IsTrue(SecurityPolicy.Exists, $"No SECURITY.md at the repository root ({SecurityPolicy.FullName})."); } + /// + /// The support horizon is phrased against the .NET major the packages target, and that number + /// lives in the core project's TargetFramework. When the TFM moves to net11.0 the sentence has + /// to move with it, or the policy promises support against a release the package no longer + /// targets. + /// + [TestMethod] + public void TheSupportHorizon_NamesTheTargetedDotNetMajor() + { + var text = File.ReadAllText(SecurityPolicy.FullName); + var match = TargetsDotNet.Match(text); + + Assert.IsTrue( + match.Success, + "SECURITY.md has no 'targets **.NET N**' sentence in its support horizon — a consumer doing " + + "due diligence needs to know which platform line the support intention is tied to."); + + var core = RepoLayout.PackableProjects.Single(project => project.PackageId == "CodoMetis.ValueRanges"); + var tfm = core.Property("TargetFramework") ?? ""; + var tfmMajor = Regex.Match(tfm, @"^net(?\d+)\.").Groups["major"].Value; + + Assert.IsFalse(string.IsNullOrEmpty(tfmMajor), $"Could not read a net. TargetFramework from {core.File.Name} (got '{tfm}')."); + + Assert.AreEqual( + tfmMajor, match.Groups["major"].Value, + $"SECURITY.md says the current line targets .NET {match.Groups["major"].Value}, but " + + $"{core.File.Name} targets {tfm}. Move the horizon statement with the TargetFramework."); + } + [TestMethod] public void TheSupportedVersionsTable_NamesTheShippedMinor() {