diff --git a/_project/scripts/ruleset_review_enforcement.py b/_project/scripts/ruleset_review_enforcement.py index e9e8d10c0..a47bd2307 100644 --- a/_project/scripts/ruleset_review_enforcement.py +++ b/_project/scripts/ruleset_review_enforcement.py @@ -119,16 +119,15 @@ def is_review_enforced(rules: list[dict[str, Any]]) -> bool: # v* tag-creation protection (tag-and-pypi-environment-admin-hardening w3) # --------------------------------------------------------------------------- -# WARN-until-applied flag, mirroring ruleset_drift_check.DEVELOP_REVIEW_RULE_ENFORCED. -# The v* tag-creation ruleset is a pending admin action (see -# docs/operations/repo-admin-settings.md, "Tag creation restricted to release -# flow"): the develop-PR GITHUB_TOKEN has no ``administration`` scope to POST a -# ruleset. While this is False, ``tag_protection_findings`` are surfaced as -# non-blocking warnings so the check can land BEFORE the admin acts without -# turning the (eventual) canary red. Flip to True once the runbook's live-state -# note records the applied ruleset id + conditions, exactly as -# DEVELOP_REVIEW_RULE_ENFORCED is flipped for the develop review rule. -TAG_RULESET_ENFORCED = False +# Enforcement flag, mirroring ruleset_drift_check.DEVELOP_REVIEW_RULE_ENFORCED. +# The v* tag-creation ruleset was applied by admin on 2026-07-10 (ruleset id +# 18774756 ``v-tag-restricted``; see docs/operations/repo-admin-settings.md, +# "Tag creation restricted to release flow"), so this is flipped to True: +# ``tag_protection_findings`` for a missing/incomplete tag ruleset are now a +# BLOCKING drift finding rather than a non-blocking warning. The bypass list +# was confirmed to be the release-finalize identity only (User:57046) before +# flipping, per the runbook's "CONFIRM before enforcing" gate. +TAG_RULESET_ENFORCED = True # The ref pattern the release flow tags with (make release-finalize pushes v*). TAG_REF_PATTERN = "refs/tags/v*" diff --git a/docs/operations/repo-admin-settings.md b/docs/operations/repo-admin-settings.md index 16a0e5dd6..649809a6c 100644 --- a/docs/operations/repo-admin-settings.md +++ b/docs/operations/repo-admin-settings.md @@ -385,11 +385,21 @@ Live-state note (fill in after applying — do not leave blank): ```text # Tag-creation ruleset live state -# checked: by: -# ruleset id: enforcement: conditions.ref_name.include: <...> -# rules: bypass_actors: <...> +# checked: 2026-07-10 by: joeharris76 (admin) +# ruleset id: 18774756 enforcement: active conditions.ref_name.include: [refs/tags/v*] +# rules: [creation] bypass_actors: [User:57046 (always)] ``` +Applied 2026-07-10: `v-tag-restricted` (id 18774756) is live and active. The +bypass list is a single `User` actor (57046, the release-finalize identity) — +confirmed to be the release identity only, not a broad Write/Admin role, which +is what `release-finalize`'s `git push origin v$(VERSION)` needs to still +succeed. With this confirmed, `TAG_RULESET_ENFORCED` in +`_project/scripts/ruleset_review_enforcement.py` is flipped to `True`, so a +future regression (ruleset deleted, made inactive, ref narrowed, creation rule +dropped, or bypass emptied) becomes a blocking drift finding instead of a +warning. + ### `pypi` environment required-reviewers gate (verify/pending admin action) `release.yml`'s `publish` job already scopes the real-PyPI publish to the diff --git a/tests/unit/release/test_ruleset_drift_review_coverage.py b/tests/unit/release/test_ruleset_drift_review_coverage.py index 51dfef065..e6ae726be 100644 --- a/tests/unit/release/test_ruleset_drift_review_coverage.py +++ b/tests/unit/release/test_ruleset_drift_review_coverage.py @@ -224,20 +224,35 @@ def test_tag_protection_summary_only_payload_does_not_pass(): assert findings, "summary-only ruleset lacks a creation rule and must be flagged" -def test_tag_check_is_warn_until_applied_by_default(): - # Mirror DEVELOP_REVIEW_RULE_ENFORCED: the flag is False until the admin - # POST lands, so the check lands non-blocking. - assert _rre.TAG_RULESET_ENFORCED is False +def test_tag_check_is_enforced_now_that_ruleset_is_applied(): + # The v* tag ruleset was applied 2026-07-10 (see repo-admin-settings.md + # live-state note), so the flag is flipped: a regression is now blocking. + assert _rre.TAG_RULESET_ENFORCED is True -def test_tag_check_main_warns_non_blocking_when_missing(tmp_path, capsys): +def test_tag_check_main_fails_when_missing_now_that_enforced(tmp_path, capsys): import json as _json payload = tmp_path / "rulesets.json" payload.write_text(_json.dumps([]), encoding="utf-8") rc = _rre.main(["--rulesets-file", str(payload)]) out = capsys.readouterr().out - assert rc == 0, "missing tag ruleset must be non-blocking while WARN-until-applied" + assert rc == 1, "missing tag ruleset must block now that TAG_RULESET_ENFORCED is True" + assert "Tag-creation ruleset - FAILED" in out + assert "target='tag'" in out + + +def test_tag_check_main_warns_non_blocking_when_flag_off(tmp_path, capsys, monkeypatch): + # Preserve coverage of the WARN-until-applied path the flag used to give by + # default: with the flag forced off, a missing ruleset is non-blocking. + import json as _json + + monkeypatch.setattr(_rre, "TAG_RULESET_ENFORCED", False) + payload = tmp_path / "rulesets.json" + payload.write_text(_json.dumps([]), encoding="utf-8") + rc = _rre.main(["--rulesets-file", str(payload)]) + out = capsys.readouterr().out + assert rc == 0 assert "WARNING (non-blocking):" in out assert "target='tag'" in out @@ -358,15 +373,26 @@ def test_tag_protection_narrower_exclude_does_not_negate_coverage(): # --------------------------------------------------------------------------- -def test_tag_creation_findings_warns_non_blocking_when_no_tag_ruleset_exists(): - # Live state: only develop/main-release rulesets exist, no target='tag' - # ruleset yet -- must surface as a non-blocking warning by default - # (TAG_RULESET_ENFORCED is False), same WARN-until-applied pattern as the - # develop review rule. +def test_tag_creation_findings_blocks_by_default_when_no_tag_ruleset_exists(): + # Live state after 2026-07-10: the v* tag ruleset is applied and + # TAG_RULESET_ENFORCED is True, so a missing target='tag' ruleset is a + # BLOCKING drift finding by default (no longer WARN-until-applied). all_live = [ _live_develop_ruleset(review_count=0, code_owner_review=False), ] findings = tag_creation_findings(all_live) + assert findings and blocking_findings(findings) == findings + assert not any(f.startswith(WARNING_PREFIX) for f in findings) + assert any("target='tag'" in f for f in findings) + + +def test_tag_creation_findings_warns_when_enforcement_forced_off(): + # Preserve the pre-2026-07-10 WARN-until-applied coverage via an explicit + # enforce_tag_rule=False override. + all_live = [ + _live_develop_ruleset(review_count=0, code_owner_review=False), + ] + findings = tag_creation_findings(all_live, enforce_tag_rule=False) assert findings and all(f.startswith(WARNING_PREFIX) for f in findings) assert blocking_findings(findings) == [] assert any("target='tag'" in f for f in findings)