diff --git a/api/tests/test_pr_status_bridge.py b/api/tests/test_pr_status_bridge.py index 65f8d09..eaeb8e9 100644 --- a/api/tests/test_pr_status_bridge.py +++ b/api/tests/test_pr_status_bridge.py @@ -147,3 +147,27 @@ def test_image_build_jobs_are_not_posted(fake): ) bridge.post_statuses("r", SHA) assert f.posted == [] + + +def test_non_blocking_jobs_are_not_posted_for_that_repo_only(fake, monkeypatch): + """Grant ruled windy-pro's desktop/installer jobs non-blocking: they must not + reach GitHub for windy-pro, and the rule must not leak to other repos.""" + monkeypatch.setattr(bridge, "NON_BLOCKING", {"windy-pro": {"ci/build-desktop"}}) + runs = [_run(1, "ci.yml", "build-desktop", "failure"), _run(2, "ci.yml", "test", "success")] + f = fake(runs=runs) + bridge.post_statuses("windy-pro", SHA) + assert [p["context"] for p in f.posted] == ["windy-git/ci/test"] + f2 = fake(runs=runs) + bridge.post_statuses("windy-chat", SHA) + assert sorted(p["context"] for p in f2.posted) == [ + "windy-git/ci/build-desktop", + "windy-git/ci/test", + ] + + +def test_default_non_blocking_is_grants_ruling(): + assert bridge.NON_BLOCKING.get("windy-pro") == { + "ci/build-desktop", + "ci/test-installer", + "ci/reality-check", + } diff --git a/scripts/pr_status_bridge.py b/scripts/pr_status_bridge.py index 1bdd6e5..70c517c 100755 --- a/scripts/pr_status_bridge.py +++ b/scripts/pr_status_bridge.py @@ -71,6 +71,19 @@ MIRROR_TAG = "[GH#" # exists; that is a decision, recorded in docs/CUTOVER.md, not a failure. NO_DAEMON_JOB = re.compile(r"docker", re.IGNORECASE) +# Jobs Grant ruled NON-BLOCKING (GRANT_DECISIONS_2026-09-23): still run on +# Windy Git and visible there, but not posted to GitHub, so they cannot turn a +# commit's combined status red. Format: "repo:workflow/job,workflow/job;repo2:..." +# windy-pro's desktop/installer jobs belong to Grant's desktop side (fixed from +# his Mac mini), not to any lane's merge gate. +NON_BLOCKING: dict[str, set[str]] = {} +for _entry in os.environ.get( + "BRIDGE_NON_BLOCKING", "windy-pro:ci/build-desktop,ci/test-installer,ci/reality-check" +).split(";"): + if ":" in _entry: + _repo, _jobs = _entry.split(":", 1) + NON_BLOCKING[_repo.strip()] = {j.strip() for j in _jobs.split(",") if j.strip()} + def _call(base: str, token_header: str, method: str, path: str, body=None): req = urllib.request.Request( @@ -155,6 +168,8 @@ def post_statuses(repo: str, sha: str) -> None: for r in runs: if r["head_sha"] != sha or NO_DAEMON_JOB.search(r["name"]): continue + if f"{r['workflow_id'].removesuffix('.yml')}/{r['name']}" in NON_BLOCKING.get(repo, ()): + continue ctx = f"windy-git/{r['workflow_id'].removesuffix('.yml')}/{r['name']}" if ctx not in latest or r["id"] > latest[ctx]["id"]: latest[ctx] = r