name: PR Gate # The constitutional pipeline for pull requests: # 1. classify — label the PR with its governance-semver level # (governance:major / governance:minor / governance:patch) # 2. vote-gate — if the PR touches an amendment proposal, run the full # vote gate. The PR cannot merge until the gate passes. # # Configure branch protection on `main` to REQUIRE the "Vote gate" check. on: pull_request: branches: [main] permissions: contents: read pull-requests: write concurrency: group: pr-gate-${{ github.event.pull_request.number }} cancel-in-progress: true jobs: classify: name: Classify governance impact runs-on: ubuntu-latest outputs: level: ${{ steps.classify.outputs.level }} steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - uses: actions/setup-python@v5 with: python-version: "3.11" - name: Install govtool run: | python -m pip install --upgrade pip pip install -e . - name: Collect changed files run: | git fetch origin "${{ github.base_ref }}" git diff --name-only "origin/${{ github.base_ref }}...HEAD" > changed.txt echo "changed files:" cat changed.txt - name: Classify change set id: classify run: | python -m govtool classify --paths-file changed.txt --json | tee classification.json level="$(python -m govtool classify --paths-file changed.txt --level-only)" echo "level=${level}" >> "$GITHUB_OUTPUT" - name: Label pull request if: github.event.pull_request.head.repo.full_name == github.repository env: GH_TOKEN: ${{ github.token }} run: | for lvl in major minor patch; do case "$lvl" in major) color="b60205"; desc="Breaking kernel change - supermajority required";; minor) color="fbca04"; desc="Backwards-compatible governance change";; patch) color="0e8a16"; desc="Editorial / userland parameter change";; esac gh label create "governance:${lvl}" --color "$color" --description "$desc" 2>/dev/null || true done gh pr edit "${{ github.event.pull_request.number }}" \ --remove-label "governance:major" \ --remove-label "governance:minor" \ --remove-label "governance:patch" 2>/dev/null || true gh pr edit "${{ github.event.pull_request.number }}" \ --add-label "governance:${{ steps.classify.outputs.level }}" vote-gate: name: Vote gate runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - uses: actions/setup-python@v5 with: python-version: "3.11" - name: Install govtool run: | python -m pip install --upgrade pip pip install -e . - name: Detect amendment proposals in this PR run: | git fetch origin "${{ github.base_ref }}" git diff --name-only "origin/${{ github.base_ref }}...HEAD" > changed.txt python -m govtool proposal detect --paths-file changed.txt > pids.txt echo "proposals touched by this PR:" cat pids.txt || true - name: Run the vote gate run: | if [ ! -s pids.txt ]; then echo "No amendment proposals touched by this PR; the vote gate is not applicable." echo "(Code and documentation changes are gated by the CI workflow instead.)" exit 0 fi rc=0 while IFS= read -r pid; do [ -z "$pid" ] && continue echo "::group::vote gate for proposal ${pid}" python -m govtool gate --proposal "$pid" || rc=1 echo "::endgroup::" done < pids.txt exit "$rc"