diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d57331..d6c0cde 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,23 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/), and this project adheres to [Semantic Versioning](https://semver.org/). -## [Unreleased] +## [0.19.0] — 2026-09-22 + +Migration: **none required.** The one new surface is an opt-in flag, and nothing existing changes +shape. The change a CI author should expect is **fewer findings**: four false-positive sources are +fixed here, so a `--fail-on` gate or a baseline diff that previously tripped on a +`timing-unsafe-compare` against a length, a `weak-password-hash` over an identifier, a +browser-extension handler whose sender check sat behind an alias, or a PII projection that actually +removed the field will now come back clean. That is a precision gain, not a coverage loss — each fix +ships with the paired true-positive control proving the real case still fires. + +**Precision work, with the controls attached.** The theme of this release is that a detector fix is +only accepted alongside a test proving it did not go quiet. Every false-positive fix below names the +near misses that must still report, and one piece of #143 is deliberately left out because it never +fired end-to-end and carried no control. The claimspec `ignore` writer applies the same standard to +an export format: entries whose reason cannot be established honestly are omitted **and counted**, +so a partial policy can never read as a whole one. + ### Added — the claimspec `ignore` writer diff --git a/pyproject.toml b/pyproject.toml index ff9ec8f..6be2300 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "websec-validator" -version = "0.18.0" +version = "0.19.0" description = "Defensive, local-first security recon that briefs your AI coding agent on your own codebase — read-only by default (code in, artifacts out): facts + tailored probe scripts, no LLM, no server, no running app." readme = "README.md" requires-python = ">=3.11" diff --git a/tests/test_explained_brief.py b/tests/test_explained_brief.py index 6689b1f..763ab30 100644 --- a/tests/test_explained_brief.py +++ b/tests/test_explained_brief.py @@ -89,7 +89,15 @@ def test_stated_version_shipped_and_is_current_in_masthead(self): changelog = (REPO / "CHANGELOG.md").read_text(encoding="utf-8") self.assertIn(f"## [{STATED_VERSION}]", changelog) self.assertTrue(_has(f"Technical Brief · v{STATED_VERSION}")) - releases = len(re.findall(r"^## \[\d+\.\d+", changelog, flags=re.M)) + # The brief is a SNAPSHOT of STATED_VERSION and says so ("asserted ... at the version in + # the masthead"), so it must count the releases that existed at that version -- not the + # running total. Binding it to the total made every future release fail this assertion + # while proving nothing extra: a brief cannot overstate a release that shipped after it. + # Same counting basis as before (a grouped heading like "[0.2.x]" counts once), + # newest first, so slicing from STATED_VERSION keeps only what had shipped by then. + shipped = re.findall(r"^## \[(\d+\.\d+[^\]]*)\]", changelog, flags=re.M) + self.assertIn(STATED_VERSION, shipped) + releases = len(shipped[shipped.index(STATED_VERSION):]) self.assertTrue(_has("twenty-eight releases"), "release count wording drifted") self.assertEqual(releases, 28) breaking = changelog.count("### Changed — BREAKING")