diff --git a/CLAUDE.md b/CLAUDE.md index 5115dc0..f11227b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -21,7 +21,7 @@ so moving a project cannot silently retarget a test. - `test/CodoMetis.ValueRanges.EFCore.PostgreSQL.Tests/` — EF Core SQL translation tests (no database) - `test/CodoMetis.ValueRanges.EFCore.PostgreSQL.NodaTime.Tests/` — NodaTime EF translation tests (no database) - `test/CodoMetis.ValueRanges.EFCore.PostgreSQL.IntegrationTests/` — Live PostgreSQL via Testcontainers (needs Docker; Inconclusive without, but hard failure under CI=true). Covers BCL and NodaTime types. Authority on PostgreSQL semantics — run when changing translations or range algebra -- `test/CodoMetis.ValueRanges.Conventions.Tests/` — Repo-level conventions: changelog consistency, packaging metadata, value set contract compliance, EF mapping parity. Everything is discovered (projects by globbing `src/`, types by reflection), so adding a package or a type needs no edit here +- `test/CodoMetis.ValueRanges.Conventions.Tests/` — Repo-level conventions: changelog consistency, packaging metadata, release wiring, the SECURITY.md supported-versions table, value set contract compliance, EF mapping parity. Everything is discovered (projects by globbing `src/`, types by reflection), so adding a package or a type needs no edit here - `docs/` — Agent docs (read relevant doc before starting work) ## Commands diff --git a/SECURITY.md b/SECURITY.md index ea36ca5..54a38a5 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -51,8 +51,8 @@ remedy for those is to upgrade. | Version | Supported | |---|---| -| 6.1.x | ✅ | -| < 6.1 | ❌ | +| 6.2.x | ✅ | +| < 6.2 | ❌ | ## Where this package sits diff --git a/test/CodoMetis.ValueRanges.Conventions.Tests/SecurityPolicyConventionTests.cs b/test/CodoMetis.ValueRanges.Conventions.Tests/SecurityPolicyConventionTests.cs new file mode 100644 index 0000000..4507445 --- /dev/null +++ b/test/CodoMetis.ValueRanges.Conventions.Tests/SecurityPolicyConventionTests.cs @@ -0,0 +1,68 @@ +using System.Text.RegularExpressions; + +namespace CodoMetis.ValueRanges.Conventions.Tests; + +/// +/// SECURITY.md promises fixes for "the latest released minor version" and tabulates which line that +/// is. The table is prose: nothing ties it to Directory.Build.props, so a release that bumps +/// the version and forgets the table leaves the policy pointing a reporter at a line that no longer +/// receives fixes. That is not hypothetical — 6.2.0 shipped with the table still saying 6.1.x. A +/// supported-versions table that is wrong is worse than none: it reads as a considered statement. +/// +[TestClass] +public sealed class SecurityPolicyConventionTests +{ + private static FileInfo SecurityPolicy => new(Path.Combine(RepoLayout.Root.FullName, "SECURITY.md")); + + /// "| 6.2.x | ✅ |" — the one line that receives fixes. + private static readonly Regex SupportedRow = + new(@"^\|\s*(?\d+)\.(?\d+)\.x\s*\|\s*✅\s*\|", RegexOptions.Multiline); + + /// "| < 6.2 | ❌ |" — everything before it. + private static readonly Regex UnsupportedRow = + new(@"^\|\s*<\s*(?\d+)\.(?\d+)\s*\|\s*❌\s*\|", RegexOptions.Multiline); + + [TestMethod] + public void SecurityPolicy_Exists() + { + Assert.IsTrue(SecurityPolicy.Exists, $"No SECURITY.md at the repository root ({SecurityPolicy.FullName})."); + } + + [TestMethod] + public void TheSupportedVersionsTable_NamesTheShippedMinor() + { + var text = File.ReadAllText(SecurityPolicy.FullName); + var shipped = Version.Parse(RepoLayout.ShippedVersion); + var expected = $"{shipped.Major}.{shipped.Minor}"; + + var supported = SupportedRow.Matches(text); + + Assert.AreEqual( + 1, supported.Count, + "SECURITY.md should have exactly one '| x.y.x | ✅ |' row — the policy is that fixes land " + + "on the latest released minor only, so there is one supported line to name."); + + var supportedLine = $"{supported[0].Groups["major"].Value}.{supported[0].Groups["minor"].Value}"; + + Assert.AreEqual( + expected, supportedLine, + $"SECURITY.md lists {supportedLine}.x as the supported line, but Directory.Build.props ships " + + $"{shipped}. The table is part of the version bump: a reporter reads it to decide whether " + + "their version still receives fixes."); + + var unsupported = UnsupportedRow.Match(text); + + Assert.IsTrue( + unsupported.Success, + "SECURITY.md should have a '| < x.y | ❌ |' row saying that everything before the supported " + + "line is unmaintained."); + + var unsupportedBelow = $"{unsupported.Groups["major"].Value}.{unsupported.Groups["minor"].Value}"; + + Assert.AreEqual( + expected, unsupportedBelow, + $"SECURITY.md says versions below {unsupportedBelow} are unsupported, but the supported line " + + $"is {expected}.x — the two rows should meet at the same minor, or a range is left " + + "described by neither."); + } +}