From 2076f21ec9d6c84f1b58b0901bb62e7f1b20be4b Mon Sep 17 00:00:00 2001 From: Aryan Date: Thu, 27 Aug 2026 13:17:55 +0530 Subject: [PATCH] fix(ci): keep desktop main health current --- ...th-filter-masks-default-branch-health.json | 16 ++++++++++++++ .../scripts/test_desktop_swift_ci_contract.py | 14 ++++++++++++ .../scripts/test_pre_push_ci_prediction.py | 22 ++++++++++++++----- .github/workflows/desktop-swift-ci.yml | 8 ++++++- scripts/pre_push_ci_prediction.py | 19 ++++++++++++++++ 5 files changed, 72 insertions(+), 7 deletions(-) create mode 100644 .github/failure-classes/FC-path-filter-masks-default-branch-health.json diff --git a/.github/failure-classes/FC-path-filter-masks-default-branch-health.json b/.github/failure-classes/FC-path-filter-masks-default-branch-health.json new file mode 100644 index 00000000000..e1b763f1cb1 --- /dev/null +++ b/.github/failure-classes/FC-path-filter-masks-default-branch-health.json @@ -0,0 +1,16 @@ +{ + "schema_version": 1, + "id": "FC-path-filter-masks-default-branch-health", + "violated_contract": "A path-filtered workflow answers whether one diff requires a component check; it does not establish that the component still compiles at the current default-branch SHA. Treating a later unrelated commit's skipped component jobs or overall green workflow as refreshed health evidence lets an older compiler failure remain on main behind a newer green run (#12275).", + "canonical_prevention": "Keep ordinary push and pull-request path filtering for cost control, but give the component an authoritative recurring health event and an operator recovery event that force its real compile/test phases against the current SHA regardless of changed paths. Guard both sides: authoritative events must select the full phases, while unrelated ordinary pushes must remain filtered.", + "canonical_prevention_artifact": [ + ".github/scripts/test_pre_push_ci_prediction.py", + ".github/scripts/test_desktop_swift_ci_contract.py" + ], + "evidence_prs": [], + "scope_hints": [ + ".github/workflows/", + "scripts/pre_push_ci_prediction.py" + ], + "status": "open" +} diff --git a/.github/scripts/test_desktop_swift_ci_contract.py b/.github/scripts/test_desktop_swift_ci_contract.py index 353a753ec4c..4b170ef472d 100755 --- a/.github/scripts/test_desktop_swift_ci_contract.py +++ b/.github/scripts/test_desktop_swift_ci_contract.py @@ -154,6 +154,20 @@ def test_no_closed_pull_request_runs_exist(self): self.assertNotIn("closed", workflow) self.assertNotIn("pull_request.merged", workflow) + def test_current_main_health_is_independent_of_the_last_commit_paths(self): + """#12275: unrelated pushes must not keep the last Desktop Swift verdict alive.""" + triggers = _workflow_text().split("concurrency:", 1)[0] + + self.assertIn("schedule:", triggers) + self.assertRegex(triggers, r'cron:\s*["\']17 5 \* \* \*["\']') + self.assertIn("workflow_dispatch:", triggers) + for event in ("schedule", "workflow_dispatch"): + with self.subTest(event=event): + plan = resolve_impact(["backend/database/users.py"], event=event) + self.assertTrue(plan.includes("desktop-ci-only")) + self.assertTrue(plan.includes("desktop-swift-tests")) + self.assertTrue(plan.includes("desktop-swift-release-compile")) + def test_required_release_check_names_are_literals(self): """GitHub does not evaluate `name:` for a skipped job. diff --git a/.github/scripts/test_pre_push_ci_prediction.py b/.github/scripts/test_pre_push_ci_prediction.py index 947df01dc1d..e8a48867e72 100644 --- a/.github/scripts/test_pre_push_ci_prediction.py +++ b/.github/scripts/test_pre_push_ci_prediction.py @@ -313,13 +313,23 @@ def test_accepted_events_keep_the_local_hook_value(self) -> None: """`scripts/pre-push` relies on the default; dropping it would break the hook.""" self.assertIn("local", ACCEPTED_EVENTS) - def test_event_does_not_change_the_resolved_plan(self) -> None: - """Widening `--event` is safe precisely because no routing decision reads it.""" - paths = ["desktop/macos/Desktop/Package.swift", "backend/database/users.py", "app/lib/main.dart"] - baseline = self.plan(paths, event="push").ordered() - for event in ACCEPTED_EVENTS: + def test_path_filtered_events_keep_unrelated_changes_off_macos(self) -> None: + """Ordinary local, PR, and push routing must retain the hosted-macOS saving.""" + for event in ("local", "pull_request", "push"): with self.subTest(event=event): - self.assertEqual(self.plan(paths, event=event).ordered(), baseline) + plan = self.plan(["backend/database/users.py"], event=event) + self.assertFalse(plan.includes("desktop-ci-only")) + self.assertFalse(plan.includes("desktop-swift-tests")) + self.assertFalse(plan.includes("desktop-swift-release-compile")) + + def test_authoritative_main_health_events_ignore_changed_paths(self) -> None: + """#12275: recovery/health runs must compile the current main SHA itself.""" + for event in ("workflow_dispatch", "schedule"): + with self.subTest(event=event): + plan = self.plan(["backend/database/users.py"], event=event) + self.assertTrue(plan.includes("desktop-ci-only")) + self.assertTrue(plan.includes("desktop-swift-tests")) + self.assertTrue(plan.includes("desktop-swift-release-compile")) if __name__ == "__main__": diff --git a/.github/workflows/desktop-swift-ci.yml b/.github/workflows/desktop-swift-ci.yml index 22a47feb605..b7dc5864bde 100644 --- a/.github/workflows/desktop-swift-ci.yml +++ b/.github/workflows/desktop-swift-ci.yml @@ -5,12 +5,18 @@ on: branches: main pull_request: branches: main + # A later backend/docs commit must not make the last path-selected Desktop + # Swift verdict look current. Run against default-branch HEAD every day at an + # off-round UTC minute; the selector treats this as a full health event. + schedule: + - cron: "17 5 * * *" # Recovery hatch. `plan-desktop-release.py` requires this workflow's checks on the # exact source SHA, so any interruption — the workflow being disabled, a run cancelled # by concurrency, an infrastructure blip — leaves that SHA permanently unreleasable: # push events do not replay, and neither a force-push nor a PR close/reopen produces a # run for a commit already on main. Manual dispatch is the only way to re-mint the - # evidence without an unrelated commit. + # evidence without an unrelated commit. Manual dispatch is path-independent so + # it still compiles a HEAD whose final commit only touched another component. workflow_dispatch: concurrency: diff --git a/scripts/pre_push_ci_prediction.py b/scripts/pre_push_ci_prediction.py index e54cc9dad09..eae0fb1a926 100644 --- a/scripts/pre_push_ci_prediction.py +++ b/scripts/pre_push_ci_prediction.py @@ -84,9 +84,16 @@ "local", "pull_request", "push", + "schedule", "workflow_dispatch", ) +# These events ask whether the current default-branch SHA is healthy, not whether its +# final commit happened to touch a Desktop Swift path. A path-filtered main push can +# only establish evidence for its own diff; it cannot keep an older compiler verdict +# current after unrelated commits land (#12275). +FULL_DESKTOP_HEALTH_EVENTS = frozenset({"schedule", "workflow_dispatch"}) + ROUTING_INPUTS = { ".github/checks-manifest.yaml", ".github/scripts/run_checks.py", @@ -381,6 +388,18 @@ def resolve_impact( } ) + if event in FULL_DESKTOP_HEALTH_EVENTS: + # Manual dispatch is the exact-SHA recovery hatch and the scheduled run + # is the default-branch health pulse. Both must exercise debug tests and + # release compilation even when HEAD's final diff is backend/docs only. + selected.update( + { + "desktop-ci-only", + "desktop-swift-tests", + "desktop-swift-release-compile", + } + ) + releasable_desktop = any(_is_releasable_desktop_path(path) for path in normalized_paths) or selector_changed package_changed = any( path in {"desktop/macos/Desktop/Package.swift", "desktop/macos/Desktop/Package.resolved"}