Multi-model review of #2827 caught: the script as-shipped would have silently weakened branch protection on EVERY non-checks dimension the moment anyone ran it. Live staging had enforce_admins=true, dismiss_stale_reviews=false, strict=true, allow_fork_syncing=false, bypass_pull_request_allowances={ HongmingWang-Rabbit + molecule-ai app } Script wrote the opposite for all five. Per memory feedback_dismiss_stale_reviews_blocks_promote.md, the dismiss_stale_reviews flip alone is the load-bearing one — would silently re-block every auto-promote PR (cost user 2.5h once). This PR: 1. apply.sh: per-branch payloads (build_staging_payload / build_main_payload) that codify the deliberate per-branch policy already on the repo, with the script's net contribution being ONLY the new check names (Canvas tabs E2E + E2E API Smoke on staging, Canvas tabs E2E on main). 2. apply.sh: R3 preflight that hits /commits/{sha}/check-runs and asserts every desired check name has at least one historical run on the branch tip. Catches typos like "Canvas Tabs E2E" vs "Canvas tabs E2E" — pre-fix a typo would silently block every PR forever waiting for a context that never emits. Skip via --skip-preflight for genuinely-new workflows whose first run hasn't fired. 3. drift_check.sh: compares the FULL normalised payload (admin, review, lock, conversation, fork-syncing, deletion, force-push) not just the checks list. Pre-fix the drift gate would have missed a UI click that flipped enforce_admins or dismiss_stale_reviews. Drops app_id from the comparison since GH auto-resolves -1 to a specific app id post-write. 4. branch-protection-drift.yml: per memory feedback_schedule_vs_dispatch_secrets_hardening.md — schedule + pull_request triggers HARD-FAIL when GH_TOKEN_FOR_ADMIN_API is missing (silent skip masks the gate disappearing). workflow_dispatch keeps soft-skip for one-off operator runs. Verified by running drift_check against live state: pre-fix would have shown 5 destructive drifts on staging + 5 on main. Post-fix shows ONLY the 2 intended additions on staging + 1 on main, which go away after `apply.sh` runs.
65 lines
2.6 KiB
YAML
65 lines
2.6 KiB
YAML
name: branch-protection drift check
|
|
|
|
# Catches out-of-band edits to branch protection (UI clicks, manual gh
|
|
# api PATCH from a one-off ops session) by comparing live state against
|
|
# tools/branch-protection/apply.sh's desired state every day. Fails the
|
|
# workflow when they drift; the failure is the signal.
|
|
#
|
|
# When it fails: re-run apply.sh to put the live state back to the
|
|
# script's intent, OR update apply.sh to encode the new intent and
|
|
# commit. Either way the script is the source of truth.
|
|
|
|
on:
|
|
schedule:
|
|
# 14:00 UTC daily. Off-hours for most teams; gives a fresh signal
|
|
# at the start of every working day.
|
|
- cron: '0 14 * * *'
|
|
workflow_dispatch:
|
|
pull_request:
|
|
branches: [staging, main]
|
|
paths:
|
|
- 'tools/branch-protection/**'
|
|
- '.github/workflows/branch-protection-drift.yml'
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
drift:
|
|
name: Branch protection drift
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 5
|
|
steps:
|
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
|
|
# Per memory feedback_schedule_vs_dispatch_secrets_hardening.md:
|
|
# schedule + pull_request triggers MUST hard-fail when the admin
|
|
# token is missing — silent soft-skip masks the gate disappearing.
|
|
# workflow_dispatch keeps soft-skip so an operator can run a
|
|
# diagnostic one-off without configuring the secret first.
|
|
- name: Verify admin token present (hard-fail on schedule/PR)
|
|
if: github.event_name != 'workflow_dispatch'
|
|
env:
|
|
GH_TOKEN_FOR_ADMIN_API: ${{ secrets.GH_TOKEN_FOR_ADMIN_API }}
|
|
run: |
|
|
if [[ -z "$GH_TOKEN_FOR_ADMIN_API" ]]; then
|
|
echo "::error::GH_TOKEN_FOR_ADMIN_API secret missing." >&2
|
|
echo "" >&2
|
|
echo "drift_check requires repo-admin scope to read /branches/:b/protection." >&2
|
|
echo "GITHUB_TOKEN does not have that scope." >&2
|
|
echo "Set GH_TOKEN_FOR_ADMIN_API at Settings → Secrets and variables → Actions." >&2
|
|
echo "" >&2
|
|
echo "On workflow_dispatch this step soft-skips for one-off operator runs." >&2
|
|
exit 1
|
|
fi
|
|
|
|
- name: Run drift check
|
|
env:
|
|
# GH_TOKEN_FOR_ADMIN_API — repo-admin scope, needed for the
|
|
# /branches/:b/protection endpoint. Falls back to GITHUB_TOKEN
|
|
# only on workflow_dispatch (operator override); the verify
|
|
# step above hard-fails any other trigger when the secret is
|
|
# missing.
|
|
GH_TOKEN: ${{ secrets.GH_TOKEN_FOR_ADMIN_API || secrets.GITHUB_TOKEN }}
|
|
run: bash tools/branch-protection/drift_check.sh
|