name: CODEOWNERS # Runs on EVERY pull request (no paths filter). The two jobs below are # required status checks on `main`; a path-filtered required check never # reports for PRs outside the filter and leaves them permanently "pending" # (the trap that forced admin-override merges). Always-run + cheap # short-circuit is what keeps them honest. on: pull_request: workflow_dispatch: # `drift` auto-commits the regenerated artifacts back to same-repo PR # branches, so it needs write access. permissions: contents: write jobs: # NOTE: the job `name:` values below ("CODEOWNERS matches source" / # "CODEOWNERS not hand-edited") ARE the status-check contexts that # .github/branch-protection.json must list verbatim. Renaming a job here # is a branch-protection change — update the JSON and re-apply. drift: name: CODEOWNERS matches source runs-on: ubuntu-latest steps: - uses: actions/checkout@v5.0.1 - name: Set up Python uses: actions/setup-python@v5.4.0 with: python-version: '3.13' - name: Install PyYAML run: pip install pyyaml - name: Re-render CODEOWNERS + ownership docs run: python3 .github/scripts/render-codeowners.py # Same-repo PR: push the regenerated artifacts back so contributors # never have to run the script locally. Mirrors the openapi.json # auto-commit in ci.yml (separate shallow clone of the head branch so # the pushed commit carries only the regenerated files). - name: Commit regenerated artifacts to PR branch if: | github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | if git diff --quiet -- .github/CODEOWNERS docs/dev/codeowners.md; then echo "CODEOWNERS and ownership docs already in sync." exit 0 fi tmp=$(mktemp -d) git clone --depth 1 --branch "${{ github.head_ref }}" \ "https://x-access-token:${GITHUB_TOKEN}@github.com/${{ github.repository }}.git" \ "$tmp" cp .github/CODEOWNERS "$tmp/.github/CODEOWNERS" cp docs/dev/codeowners.md "$tmp/docs/dev/codeowners.md" cd "$tmp" if git diff --quiet -- .github/CODEOWNERS docs/dev/codeowners.md; then echo "Head branch already matches; nothing to push." exit 0 fi git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" git add .github/CODEOWNERS docs/dev/codeowners.md git commit -m "chore: regenerate CODEOWNERS + ownership docs" git push # Fork PR / workflow_dispatch: cannot push back, so enforce drift # strictly. The contributor runs the script and commits the result. - name: Verify in sync (forks / manual runs) if: | !(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) run: | if ! git diff --quiet -- .github/CODEOWNERS docs/dev/codeowners.md; then echo "::error::Generated CODEOWNERS / ownership docs are out of sync with .github/codeowners-roles.yml." echo "::error::Run \`python3 .github/scripts/render-codeowners.py\` and commit the result." echo "--- diff ---" git --no-pager diff -- .github/CODEOWNERS docs/dev/codeowners.md exit 1 fi echo "Generated artifacts are in sync with their source." noedit: name: CODEOWNERS not hand-edited runs-on: ubuntu-latest steps: - uses: actions/checkout@v5.0.1 with: # Need history so we can diff against the PR base. fetch-depth: 0 - name: Reject hand-edits to generated file # Only meaningful for PRs (needs a base to diff against). if: github.event_name == 'pull_request' run: | base="origin/${{ github.base_ref }}" git fetch origin "${{ github.base_ref }}" --quiet changed=$(git diff --name-only "$base" HEAD) edited_generated=$(echo "$changed" | grep -E '^\.github/CODEOWNERS$' || true) edited_source=$(echo "$changed" | grep -E '^\.github/codeowners-roles\.yml$' || true) if [ -n "$edited_generated" ] && [ -z "$edited_source" ]; then echo "::error::This PR edits .github/CODEOWNERS but not its source .github/codeowners-roles.yml." echo "::error::Edit the yml and regenerate via \`python3 .github/scripts/render-codeowners.py\`." exit 1 fi echo "CODEOWNERS edits accompany source edits (or no CODEOWNERS edits in this PR)."