From 2043f3dd767d3f0970e4008a4d9067c5f7327c32 Mon Sep 17 00:00:00 2001 From: "pulse-triage[bot]" <249995291+pulse-triage[bot]@users.noreply.github.com> Date: Wed, 30 Sep 2026 23:39:47 +0100 Subject: [PATCH] Keep rehearsal policy checks aligned with watchdog admission Replace two stale schema assertions with explicit watchdog omission and ordinary candidate rollback requirements. The actual workflow admission/source-selection shell tests preserve the fail-closed distinction. Contract-Neutral: Tests and explanatory comments reflect the already recorded watchdog admission contract; no further runtime contract change. Change-source: pulse-maintainer --- .github/workflows/release-dry-run.yml | 4 ++-- .../release_promotion_policy_test.py | 19 +++++++++++++------ 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/.github/workflows/release-dry-run.yml b/.github/workflows/release-dry-run.yml index 31c0db00e7..3f46a4b58e 100644 --- a/.github/workflows/release-dry-run.yml +++ b/.github/workflows/release-dry-run.yml @@ -7,8 +7,8 @@ on: # week at a time instead of piling up until an RC publish. Scheduled runs # carry no workflow_dispatch inputs (GitHub does not apply input defaults to # schedule events), so the rehearsal step derives the rollback target as the - # latest stable tag preceding VERSION. Manual dispatches must still supply - # rollback_version explicitly. + # latest stable tag preceding VERSION. The fixed on-demand watchdog uses the + # same envelope; ordinary manual rehearsals still supply rollback explicitly. schedule: - cron: '0 7 * * 2' workflow_dispatch: diff --git a/scripts/release_control/release_promotion_policy_test.py b/scripts/release_control/release_promotion_policy_test.py index 64b2b843e8..066788c0ae 100644 --- a/scripts/release_control/release_promotion_policy_test.py +++ b/scripts/release_control/release_promotion_policy_test.py @@ -1713,7 +1713,7 @@ class ReleasePromotionPolicyTest(unittest.TestCase): self.assertIn(promotion_metadata_envelope(), normalize_ws(template)) self.assertIn("rc-to-ga-rehearsal-summary", workflow) self.assertIn("build_release_candidate:", workflow) - self.assertIn("if: ${{ inputs.version != '' }}", workflow) + self.assertIn("if: ${{ inputs.watchdog != true && inputs.version != '' }}", workflow) self.assertIn("require_macos_signing: true", workflow) self.assertIn( "require_windows_signing: false", @@ -2200,12 +2200,19 @@ class ReleasePromotionPolicyTest(unittest.TestCase): dry_run_workflow = read(".github/workflows/release-dry-run.yml") self.assertIn("Required rollback stable version to rehearse", dry_run_workflow) self.assertIn("rollback_version:\n description: 'Required rollback stable version to rehearse", dry_run_workflow) - self.assertIn("required: true", dry_run_workflow) - # Scheduled watchdog runs carry no dispatch inputs, so the rehearsal - # step must derive the rollback target; the derive flag stays gated on - # the schedule event so manual dispatches keep explicit rollback. + # The fixed watchdog has no rollback override, so its dispatch schema + # must permit omission. Ordinary manual rehearsals still require an + # explicit rollback in the pre-checkout admission shell. + dry_run = yaml.safe_load(dry_run_workflow) + inputs = dry_run.get("on", dry_run.get(True))["workflow_dispatch"]["inputs"] + self.assertIs(inputs["rollback_version"]["required"], False) + self.assertIn('workflow_dispatch:false)', dry_run_workflow) + self.assertIn('[ -z "${ROLLBACK_VERSION_INPUT:-}" ]', dry_run_workflow) + self.assertIn('Candidate rehearsal requires explicit rollback and no watchdog SHA.', dry_run_workflow) + # Both admitted watchdog modes derive rollback; a candidate rehearsal + # can never reach that derivation, including when the input is absent. self.assertIn( - 'if [ "${EVENT_NAME}" = "schedule" ] && [ -z "${ROLLBACK_VERSION_INPUT:-}" ]; then', + 'if [ "${WATCHDOG_MODE}" = "true" ] && [ -z "${ROLLBACK_VERSION_INPUT:-}" ]; then', dry_run_workflow, ) self.assertIn("--derive-rollback-latest-stable", dry_run_workflow)