culture(release): promote heritage engine + wow anchor to main #908
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: pr-gate | |
| # Server-side mirror of the local pre-commit hook's enforcement. | |
| # Closes Web UI bypass paths for branch-scope and base-branch rules. | |
| # | |
| # Triggers on every pull_request event (no branch filter) so a PR with | |
| # the wrong base — e.g. culture/germany -> main bypassing culture/release — | |
| # is also caught here. The base-branch-check job decides if the head/base | |
| # pair is even allowed. | |
| # | |
| # Adding a check: define a new job below and run a tests/validate_pr_*.py | |
| # helper. Keep CI logic in Python (testable) and yaml as orchestration only. | |
| # | |
| # The report job turns a rejection into instruction: it posts the | |
| # validators' prescriptive remedy as a PR comment, writes it to the job | |
| # summary, and fails its own check when routing is broken. The remedy | |
| # then reaches an agent that opened the PR via the GitHub API whether it | |
| # reads the check conclusion, the run log, the job summary, or the PR | |
| # comment -- four channels, because the failure has to be self-fixable. | |
| # | |
| # Note on the validation stamp: this workflow used to include a third job | |
| # (stamp-tree-hash-check) that verified .validation-stamp equalled the | |
| # tree hash of HEAD. That gate was dropped because API-driven PR pushes | |
| # (via the GitHub MCP / web UI) cannot run the local pre-commit hook to | |
| # refresh the stamp, which created persistent friction. The remaining | |
| # protection is the cultures - Validation stamp gate in validate.yml | |
| # (file existence check, regions-only) plus every actual validator job in | |
| # validate.yml. Branch protection still requires those checks before merge. | |
| on: | |
| pull_request: | |
| permissions: | |
| contents: read | |
| jobs: | |
| base-branch-check: | |
| name: pr-gate - Allowed base for head | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.11' | |
| - name: Verify PR base matches head's allowed target | |
| run: | | |
| python3 tests/validate_pr_base.py \ | |
| "${{ github.head_ref }}" \ | |
| "${{ github.base_ref }}" | |
| branch-scope-check: | |
| name: pr-gate - Diff within branch scope | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| # Need history back to the merge base so `base...HEAD` works. | |
| fetch-depth: 0 | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.11' | |
| - name: Verify diff stays inside head branch's allowed paths | |
| run: | | |
| python3 tests/validate_pr_scope.py \ | |
| "${{ github.head_ref }}" \ | |
| "origin/${{ github.base_ref }}" | |
| brownfield-check: | |
| name: pr-gate - Brownfield migration ("if you touch, you migrate") | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| # Need history back to the merge base so `base...HEAD` works, | |
| # same reason as branch-scope-check. | |
| fetch-depth: 0 | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.11' | |
| - name: Install dependencies | |
| run: pip install pyyaml | |
| - name: Verify touched files are on the khai-lexicon schema | |
| run: | | |
| python3 tests/validate_pr_brownfield.py \ | |
| "origin/${{ github.base_ref }}" | |
| report: | |
| name: pr-gate - Routing remedy | |
| needs: [base-branch-check, branch-scope-check] | |
| # Always runs for same-repo PRs: on failure it posts the prescriptive fix | |
| # as a PR comment (so an agent that opened the PR via the API still sees | |
| # it); on success it marks any earlier remedy comment resolved. Forked PRs | |
| # get a read-only token, so they are skipped -- they still get the check | |
| # failures and the step summary. | |
| if: always() && github.event.pull_request.head.repo.full_name == github.repository | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.11' | |
| - name: Build routing remedy | |
| id: build | |
| env: | |
| HEAD_REF: ${{ github.head_ref }} | |
| BASE_REF: ${{ github.base_ref }} | |
| run: | | |
| # Capture both validators WITHOUT aborting. GitHub runs `run:` | |
| # blocks under `set -e`, so a failing command substitution | |
| # (var=$(... exit 1)) kills the step -- exactly the | |
| # routing-rejected case this job exists to report. Drop `set -e` | |
| # around the captures; keep the recorded exit codes. | |
| set +e | |
| base_out=$(python3 tests/validate_pr_base.py "$HEAD_REF" "$BASE_REF" 2>&1) | |
| base_rc=$? | |
| scope_out=$(python3 tests/validate_pr_scope.py "$HEAD_REF" "origin/$BASE_REF" 2>&1) | |
| scope_rc=$? | |
| set -e | |
| if [ "$base_rc" -eq 0 ] && [ "$scope_rc" -eq 0 ]; then | |
| echo "status=pass" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "status=fail" >> "$GITHUB_OUTPUT" | |
| fi | |
| { | |
| echo "## pr-gate routing" | |
| echo | |
| echo "Branch routing is decided by the *operation*, not the file types a" | |
| echo "change happens to touch. A failure here is fixed by re-routing the" | |
| echo "PR (branch kind / base), never by editing validators or workflows." | |
| echo | |
| echo "### Allowed base for head" | |
| echo '```' | |
| echo "$base_out" | |
| echo '```' | |
| echo "### Diff within branch scope" | |
| echo '```' | |
| echo "$scope_out" | |
| echo '```' | |
| echo | |
| echo "Pre-flight next time, before opening the PR:" | |
| echo '```' | |
| echo "python tests/branch_scope.py check-pr --head <head> --base <base>" | |
| echo '```' | |
| echo "(or \`advise --op <operation>\` before \`git checkout -b\`)." | |
| echo "A failure says whether it is fixable in place -- a wrong base" | |
| echo "(retarget the PR) or a wrong head (close and re-open: a PR's" | |
| echo "head branch cannot be changed)." | |
| } > remedy.md | |
| # On a routing FAILURE, surface the remedy in the run log and | |
| # the job summary too -- actionable from the Actions UI and the | |
| # API, not only from the PR comment. | |
| if [ "$base_rc" -ne 0 ] || [ "$scope_rc" -ne 0 ]; then | |
| cat remedy.md | |
| cat remedy.md >> "$GITHUB_STEP_SUMMARY" | |
| fi | |
| - name: Post or update PR comment | |
| uses: actions/github-script@v7 | |
| env: | |
| STATUS: ${{ steps.build.outputs.status }} | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| const marker = '<!-- pr-gate-remedy -->'; | |
| const { owner, repo } = context.repo; | |
| const issue_number = context.issue.number; | |
| const comments = await github.paginate(github.rest.issues.listComments, { | |
| owner, repo, issue_number, | |
| }); | |
| const existing = comments.find(c => c.body && c.body.includes(marker)); | |
| if (process.env.STATUS === 'pass') { | |
| if (existing) { | |
| await github.rest.issues.updateComment({ | |
| owner, repo, comment_id: existing.id, | |
| body: marker + '\n## pr-gate routing\n\n✅ Routing checks pass.', | |
| }); | |
| } | |
| return; | |
| } | |
| const body = marker + '\n' + fs.readFileSync('remedy.md', 'utf8'); | |
| if (existing) { | |
| await github.rest.issues.updateComment({ | |
| owner, repo, comment_id: existing.id, body, | |
| }); | |
| } else { | |
| await github.rest.issues.createComment({ | |
| owner, repo, issue_number, body, | |
| }); | |
| } | |
| # The remedy is in the run log, the job summary, and the PR comment | |
| # by now -- this step makes "pr-gate - Routing remedy" itself go red | |
| # so a routing failure cannot be missed by a glance at the check list. | |
| - name: Fail when routing is broken | |
| if: steps.build.outputs.status == 'fail' | |
| run: | | |
| echo "::error title=pr-gate routing rejected this PR::Branch/base routing is invalid. The prescriptive fix is in this job summary, the run log above, and a PR comment. Re-route the PR (branch kind / base) -- do not edit validators or workflows." | |
| exit 1 |