Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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"
}
14 changes: 14 additions & 0 deletions .github/scripts/test_desktop_swift_ci_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
22 changes: 16 additions & 6 deletions .github/scripts/test_pre_push_ci_prediction.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__":
Expand Down
8 changes: 7 additions & 1 deletion .github/workflows/desktop-swift-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
19 changes: 19 additions & 0 deletions scripts/pre_push_ci_prediction.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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"}
Expand Down
Loading