omnigraph/.github/workflows/codeowners.yml
Andrew Altshuler c7365bf8ef
ci(codeowners): un-trap required checks, auto-render, generate owner tables (#142)
The CODEOWNERS required checks blocked every PR — the real root cause was a
name mismatch, compounded by a path filter:

- branch-protection.json required the contexts `CODEOWNERS / drift` and
  `CODEOWNERS / noedit` (the GitHub UI "workflow / job-id" display form), but
  the jobs report check-run names from their `name:` fields — "CODEOWNERS
  matches source" / "CODEOWNERS not hand-edited". The required contexts
  therefore never matched any reported check and sat permanently pending.
- The workflow was also path-filtered to CODEOWNERS files, so it didn't even
  run for most PRs.

Net effect: with both required checks unsatisfiable, every PR could only land
via admin override (e.g. #140).

Fixes:
- A: drop the `paths:` filter so the workflow runs on every PR and both
  required contexts always report.
- name fix: point branch-protection.json at the actual job names verbatim, and
  add a doc note that the contexts must equal the job `name:` values.
- B: the `drift` job now re-renders and, on same-repo PRs, auto-commits the
  regenerated artifacts back to the branch (mirrors the openapi.json job in
  ci.yml); forks / manual runs strict-check instead. Contributors no longer
  run the script by hand.
- D: render-codeowners.py also generates a "who owns what" path->owners +
  roles table spliced into docs/dev/codeowners.md between markers, so the
  human-readable view never drifts. Idempotent; CODEOWNERS output unchanged.
- docs: correct the stale `enforce_admins: true` line (JSON and live are
  false).

NOTE: the branch-protection.json change only takes effect after an admin runs
`./scripts/apply-branch-protection.sh` (deliberate manual step, per
docs/dev/branch-protection.md). Until then `main` still requires the old
mismatched contexts, so this PR itself needs an admin-override merge — the last
one that should be necessary.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 18:09:47 +03:00

110 lines
4.7 KiB
YAML

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)."