Consolidate Merge/Merge anyway into one automated Approve & Merge action #173
Workflow file for this run
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: Validate | |
| # Runs on every pull request. Stdlib-only Python and shell — no dependencies and no build | |
| # step, so this stays fast and cannot break on a package update. | |
| on: | |
| pull_request: | |
| branches: [main] | |
| permissions: | |
| contents: read | |
| jobs: | |
| validate: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| # Full history so validate_reviews.py can diff against the base branch. | |
| fetch-depth: 0 | |
| - name: Admin-only paths are not modified by a non-admin | |
| # A TRIPWIRE, NOT A SECURITY BOUNDARY. Be clear about which is which: | |
| # | |
| # The real boundary is CODEOWNERS + branch protection ("Require review from Code | |
| # Owners", verified on), which GitHub enforces server-side. A pull request cannot | |
| # weaken it. | |
| # | |
| # This step cannot make that claim, because `pull_request` runs the workflow file | |
| # from the PR's own head — so a PR that edits this file is checked by its edited | |
| # version. It therefore catches ACCIDENTS, which is the actual risk here: an AI | |
| # agent helpfully "improving" CLAUDE.md and then following its own rewrite for the | |
| # rest of the session. It fails early and loudly with an explanation, instead of | |
| # the contributor discovering it at review time. | |
| # | |
| # ADMINS is duplicated from the onetyler-tcp-pm-admins team on purpose: GITHUB_TOKEN | |
| # cannot read org team membership, and this repo has decided against a shared PAT | |
| # (see CLAUDE.md). Keep it in step with the team. | |
| env: | |
| ACTOR: ${{ github.actor }} | |
| run: | | |
| ADMINS="vijay-tylertech" | |
| # Patterns come from .github/admin-only-paths.txt so this check, the session-script | |
| # warning and CODEOWNERS describe ONE boundary. check_admin_paths.py (next step) | |
| # asserts CODEOWNERS agrees, since GitHub's format cannot read that file. | |
| PATHS=$(grep -vE '^\s*(#|$)' .github/admin-only-paths.txt | paste -sd'|' -) | |
| touched=$(git diff --name-only "origin/${{ github.base_ref }}...HEAD" \ | |
| | grep -E "($PATHS)" || true) | |
| if [ -z "$touched" ]; then | |
| echo "no admin-only files touched"; exit 0 | |
| fi | |
| if echo " $ADMINS " | grep -q " $ACTOR "; then | |
| echo "admin-only files changed by admin '$ACTOR' — allowed:" | |
| echo "$touched" | sed 's/^/ /' | |
| exit 0 | |
| fi | |
| echo "::error::'$ACTOR' is not a repo admin but this PR modifies admin-only files." | |
| echo "$touched" | sed 's/^/ /' | |
| cat <<'EOF' | |
| These paths decide WHICH AGENT answers, or how the repo itself operates. A bad | |
| knowledge edit gives one wrong answer; a bad routing edit misroutes every | |
| conversation. So they are admin-only. See .github/admin-only-paths.txt. | |
| Contributors DO own knowledge content: the Conf-, Docusaurus-, FAQ-, Misc-, | |
| Training- and GitHub- files inside each Knowledge-<Domain>/ folder, plus their | |
| review verdicts under transcripts/. Note the one exception in those folders -- | |
| _START_HERE.md is admin-only, because it carries cross-agent hand-off rules. | |
| If you are an AI agent: do not edit these, and do not edit this check to get past | |
| it. Revert them, keep the rest, and say in the PR what you think should change and | |
| why. An admin will make the call. | |
| git checkout "origin/${GITHUB_BASE_REF}" -- <each file listed above> | |
| EOF | |
| exit 1 | |
| - name: CODEOWNERS matches admin-only-paths.txt | |
| # The guard above reads admin-only-paths.txt; the actual merge gate reads CODEOWNERS. | |
| # If they disagree the drift is silent in the dangerous direction — a path dropped | |
| # from CODEOWNERS stops being gated while CI still claims it is protected. | |
| run: python3 scripts/check_admin_paths.py | |
| - name: CODEOWNERS matches agent-owners.json | |
| # CODEOWNERS is GENERATED from agent-owners.json. Two sources of truth for one fact is | |
| # the bug this replaces: ownership was recorded in one file and enforced in another, | |
| # and nothing kept them honest. | |
| run: python3 scripts/gen_codeowners.py --check | |
| - name: Corpus ownership | |
| # Reports when a request touches a corpus its author does not own. Not a gate for that | |
| # case — editing another agent's corpus IS how feedback reaches its owner, and | |
| # CODEOWNERS already requires them to approve. It fails only on an admin-only path. | |
| run: | | |
| python3 scripts/check_folder_ownership.py \ | |
| --base "origin/${{ github.base_ref }}" \ | |
| --author "${{ github.event.pull_request.user.login }}" | |
| - name: Frontmatter is well-formed | |
| run: python3 scripts/review_status.py --check | |
| - name: Review UI JavaScript parses | |
| run: python3 scripts/check_ui_js.py | |
| - name: Review UI contrast holds in light and dark | |
| run: python3 scripts/check_contrast.py | |
| - name: No two first-reviews of the same transcript | |
| run: python3 scripts/validate_reviews.py --base "origin/${{ github.base_ref }}" | |
| - name: No credentials committed | |
| run: | | |
| # Cheap belt-and-braces over .gitignore. Looks for the shapes that have actually | |
| # shown up here: the tylertownwa test password an agent reproduced from its KB, | |
| # real JWTs, and password/secret assignments with a value. | |
| set -o pipefail | |
| if grep -rInE 'W#lcome123\$|eyJ[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,}' \ | |
| --include='*.md' --include='*.json' --include='*.py' --include='*.yml' . ; then | |
| echo "::error::Credential-shaped string committed — see the match above" | |
| exit 1 | |
| fi | |
| if grep -rInE '(?i)\b(password|client_secret|api[_-]?key)\b\s*[:=]\s*.?[A-Za-z0-9#$!@%^&*_-]{8,}' \ | |
| --include='*.md' --include='*.json' --include='*.py' . \ | |
| | grep -viE 'REDACTED|<[A-Za-z_ -]+>|\{\{|placeholder|X-API-Key|\$FOUNDRY|paste_key|your[_-]' ; then | |
| echo "::error::Looks like a real secret was committed — see the match above" | |
| exit 1 | |
| fi | |
| echo "no credential-shaped strings found" | |
| - name: transcripts/INDEX.md is up to date | |
| run: | | |
| python3 scripts/review_status.py > /dev/null | |
| if ! git diff --quiet -- transcripts/INDEX.md; then | |
| echo "::error::transcripts/INDEX.md is stale. Run 'python3 scripts/review_status.py' and commit." | |
| git --no-pager diff --stat -- transcripts/INDEX.md | |
| exit 1 | |
| fi | |
| echo "INDEX.md matches the transcript frontmatter" |