From 76c1faa2bc3d56b57c9800588efdcae44beed32f Mon Sep 17 00:00:00 2001 From: POWERFULMOVES <142271328+POWERFULMOVES@users.noreply.github.com> Date: Wed, 19 Aug 2026 15:00:39 -0400 Subject: [PATCH 1/3] fix(provider-verifier): make the gate always report, so it CAN be required Prerequisite for adding `verifier-gate` to branch protection. Adding it first would have blocked every PR in the repository. GitHub is explicit about this: "If a workflow is skipped due to path filtering, branch filtering or a commit message, then checks associated with that workflow will remain in a Pending state. A pull request that requires those checks to be successful will be blocked from merging." "You should not use path or branch filtering to skip workflow runs if the workflow is required." -- docs.github.com, Troubleshooting required status checks provider-verifier.yml had on.pull_request.paths with 4 patterns. Required, that means every PR touching none of them sits on "Waiting for status to be reported" forever -- the same repo-wide block #2623's cross-workflow `needs:` would have caused, by a different route. The distinction that makes the fix work is in the same doc: a skipped WORKFLOW never reports, but a skipped JOB reports success. So the path decision moves from the trigger into the job. The job now always runs and always reports; a `changed` step diffs base..head and sets an output, and the working steps carry `if: steps.changed.outputs.relevant == 'true'`. Path detection is a plain `run:` step, not dorny/paths-filter -- that action is not on this repo's Actions allowlist (github_owned_allowed plus 18 named patterns), so it would fail at setup. Verified the matching logic against the cases that matter: Pmoves-MiniMax-Provider-Verifier/verify.py -> true Pmoves-MiniMax-Provider-Verifier/src/a/b.py -> true (** semantics) pmoves/tools/provider_verifier_gate.py -> true .github/workflows/provider-verifier.yml -> true README.md -> false README.md + the gate helper together -> true pmoves/tools/provider_verifier_gate_helper.py -> false (near-miss) NOT DONE HERE, and it must come after this lands: adding `verifier-gate` to the required list. Sequencing matters -- required-then-fix blocks the repo, fix-then- required does not. Co-Authored-By: Claude Opus 5 --- .github/workflows/provider-verifier.yml | 55 ++++++++++++++++++++++--- 1 file changed, 49 insertions(+), 6 deletions(-) diff --git a/.github/workflows/provider-verifier.yml b/.github/workflows/provider-verifier.yml index bb54573a8..fea54735a 100644 --- a/.github/workflows/provider-verifier.yml +++ b/.github/workflows/provider-verifier.yml @@ -27,11 +27,18 @@ name: Provider Verifier Gate on: pull_request: branches: [main] - paths: - - 'Pmoves-MiniMax-Provider-Verifier/**' - - 'pmoves/tools/provider_verifier_gate.py' - - 'pmoves/tools/tests/test_provider_verifier_gate.py' - - '.github/workflows/provider-verifier.yml' + # NO paths: filter, deliberately -- see the `changed` step below. + # + # GitHub: "If a workflow is skipped due to path filtering, branch filtering + # or a commit message, then checks associated with that workflow will remain + # in a Pending state. A pull request that requires those checks to be + # successful will be blocked from merging." and "You should not use path or + # branch filtering to skip workflow runs if the workflow is required." + # + # A skipped WORKFLOW never reports. A skipped JOB reports success. Since this + # gate is meant to become a required check, the path decision has to move + # from the trigger into the job, or every PR that touches none of these paths + # would sit on "Waiting for status to be reported" forever. workflow_dispatch: inputs: providers_json: @@ -68,12 +75,47 @@ jobs: submodules: recursive fetch-depth: 1 + # Decide applicability HERE rather than in on.pull_request.paths. The job + # always runs and always reports; the work below is conditional. That is the + # shape GitHub prescribes for a workflow that is a required check. + - name: Does this PR touch the verifier surface? + id: changed + shell: bash + run: | + set -euo pipefail + base="${{ github.event.pull_request.base.sha }}" + head="${{ github.event.pull_request.head.sha }}" + if [ -z "$base" ] || [ -z "$head" ]; then + # workflow_dispatch and anything without a PR context: always run. + echo "relevant=true" >> "$GITHUB_OUTPUT"; exit 0 + fi + git fetch --no-tags --depth=1 origin "$base" >/dev/null 2>&1 || true + files="$(git diff --name-only "$base" "$head" 2>/dev/null || git diff --name-only HEAD~1 HEAD)" + relevant=false + while IFS= read -r f; do + [ -n "$f" ] || continue + case "$f" in + Pmoves-MiniMax-Provider-Verifier/*) relevant=true ;; + pmoves/tools/provider_verifier_gate.py) relevant=true ;; + pmoves/tools/tests/test_provider_verifier_gate.py) relevant=true ;; + .github/workflows/provider-verifier.yml) relevant=true ;; + esac + done <<< "$files" + echo "relevant=$relevant" >> "$GITHUB_OUTPUT" + if [ "$relevant" = "true" ]; then + echo "Verifier surface touched — running the static gate." + else + echo "No verifier-surface change; gate is a no-op and reports success." + fi + - name: Set up Python + if: steps.changed.outputs.relevant == 'true' uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: '3.12' - name: Install the verifier's runtime deps (best-effort) + if: steps.changed.outputs.relevant == 'true' # The gate's 6 static checks don't need the verifier's heavy # deps (numpy, openai, megfile). We install them only for the # full-conformance dispatch path. The 'best-effort' install @@ -85,6 +127,7 @@ jobs: echo "::warning::verifier runtime deps not installed; the static gate still runs" - name: Run the static gate + if: steps.changed.outputs.relevant == 'true' id: gate shell: bash run: | @@ -110,7 +153,7 @@ jobs: echo "verdict=PASS" >> "$GITHUB_OUTPUT" - name: Post PR comment on FAIL - if: failure() + if: failure() && steps.changed.outputs.relevant == 'true' uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | From 9132e35ba11e1d1df17dcad7db42143be7b6ba2c Mon Sep 17 00:00:00 2001 From: POWERFULMOVES <142271328+POWERFULMOVES@users.noreply.github.com> Date: Wed, 19 Aug 2026 15:11:32 -0400 Subject: [PATCH 2/3] test(provider-verifier): assert the path coverage where it now lives Two tests asserted on.pull_request.paths, which this PR removes. Rewritten rather than deleted -- the guarantee they encoded is still the right one, it just moved from the trigger into the job: test_workflow_paths_filter_covers_submodule -> test_job_condition_covers_submodule test_workflow_paths_filter_covers_helper -> test_job_condition_covers_helper Both now assert the `changed` step's case arms, so a future edit that drops submodule or helper coverage still fails. Plus two new ones: test_workflow_has_no_paths_filter the removal is now itself asserted, with GitHub's reasoning quoted, so nobody reintroduces a paths: filter on a workflow meant to be required. test_working_steps_are_gated_on_the_condition both halves matter: unconditional steps would run the gate on every PR in the repo, and a conditional JOB would stop reporting. The shape is a job that always runs with work that does not. 20 passed. Co-Authored-By: Claude Opus 5 --- .../tests/test_provider_verifier_workflow.py | 56 ++++++++++++++----- 1 file changed, 41 insertions(+), 15 deletions(-) diff --git a/pmoves/tests/test_provider_verifier_workflow.py b/pmoves/tests/test_provider_verifier_workflow.py index 2bf1df096..2205081fe 100644 --- a/pmoves/tests/test_provider_verifier_workflow.py +++ b/pmoves/tests/test_provider_verifier_workflow.py @@ -77,26 +77,52 @@ def test_workflow_triggers_on_pull_request(workflow: dict) -> None: ) -def test_workflow_paths_filter_covers_submodule(workflow: dict) -> None: - """The paths filter must include Pmoves-MiniMax-Provider-Verifier/**. - - This is the load-bearing path filter: a PR that adds a new provider - to the cascade touches the submodule, and the gate must run. +def test_workflow_has_no_paths_filter(workflow: dict) -> None: + """on.pull_request must NOT carry a paths: filter. + + This gate is intended to become a required status check, and GitHub is + explicit: "If a workflow is skipped due to path filtering ... checks + associated with that workflow will remain in a Pending state. A pull request + that requires those checks to be successful will be blocked from merging." + A skipped WORKFLOW never reports; a skipped JOB reports success. So the path + decision belongs in the job, not the trigger -- see the two tests below, + which assert the same coverage in its new home. """ pr = _triggers(workflow)["pull_request"] - paths = pr.get("paths", []) - assert any("Pmoves-MiniMax-Provider-Verifier" in p for p in paths), ( - f"pull_request.paths must include a Pmoves-MiniMax-Provider-Verifier " - f"entry; got {paths}" + assert "paths" not in (pr or {}), ( + "on.pull_request must not filter by paths: a required check that never " + "reports blocks every PR that touches none of those paths. Move the " + "condition into the job (steps.changed) instead." ) -def test_workflow_paths_filter_covers_helper(workflow: dict) -> None: - """The paths filter must include pmoves/tools/provider_verifier_gate.py.""" - pr = _triggers(workflow)["pull_request"] - paths = pr.get("paths", []) - assert any("provider_verifier_gate" in p for p in paths), ( - f"pull_request.paths must include the helper; got {paths}" +def test_job_condition_covers_submodule(workflow_text: str) -> None: + """The job-level path condition must still cover the verifier submodule. + + Same guarantee the old paths: filter carried -- a PR adding a provider to the + cascade touches the submodule and must run the gate -- asserted where the + decision now lives. + """ + assert "Pmoves-MiniMax-Provider-Verifier/*)" in workflow_text, ( + "the `changed` step's case must match Pmoves-MiniMax-Provider-Verifier/*" + ) + + +def test_job_condition_covers_helper(workflow_text: str) -> None: + """The job-level path condition must still cover the gate helper.""" + assert "pmoves/tools/provider_verifier_gate.py)" in workflow_text, ( + "the `changed` step's case must match pmoves/tools/provider_verifier_gate.py" + ) + + +def test_working_steps_are_gated_on_the_condition(workflow_text: str) -> None: + """The work must be conditional even though the job is not. + + If the steps ran unconditionally the gate would execute on every PR in the + repo; if the JOB were conditional it would stop reporting. Both halves matter. + """ + assert "steps.changed.outputs.relevant == 'true'" in workflow_text, ( + "the gate steps must be guarded by the computed condition" ) From f8097c70f926c02e098cf20c6cc8ad223ce54afd Mon Sep 17 00:00:00 2001 From: POWERFULMOVES <142271328+POWERFULMOVES@users.noreply.github.com> Date: Wed, 19 Aug 2026 15:21:59 -0400 Subject: [PATCH 3/3] fix(provider-verifier): detect changed files via the API, not a shallow diff MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The first detection attempt used `git diff base..head` with `HEAD~1` as fallback. Checkout is fetch-depth 1, so neither resolves — the run died with: fatal: ambiguous argument 'HEAD~1': unknown revision or path not in the working tree ##[error]Process completed with exit code 128 Deepening the clone would mean fetching full history for a monorepo with 72 submodules to answer a question the PR files API answers in one call. Switched to `gh api repos/\{owner}/\{repo}/pulls/N/files`, with `pull-requests: read` added alongside the existing `issues: write`. Fails OPEN TOWARD RUNNING: no PR context, or an API call that returns nothing, sets relevant=true. An unanswerable question runs the gate rather than silently skipping it — the opposite default would be a gate that reports success because it could not tell whether it applied. Co-Authored-By: Claude Opus 5 --- .github/workflows/provider-verifier.yml | 32 +++++++++++++++++++------ 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/.github/workflows/provider-verifier.yml b/.github/workflows/provider-verifier.yml index fea54735a..1b4af95f3 100644 --- a/.github/workflows/provider-verifier.yml +++ b/.github/workflows/provider-verifier.yml @@ -68,6 +68,8 @@ jobs: # comment step post on FAIL. permissions: issues: write + # the `changed` step lists PR files via the API + pull-requests: read steps: - name: Checkout repo (with submodules) uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -78,19 +80,35 @@ jobs: # Decide applicability HERE rather than in on.pull_request.paths. The job # always runs and always reports; the work below is conditional. That is the # shape GitHub prescribes for a workflow that is a required check. + # Decide applicability HERE rather than in on.pull_request.paths. The job + # always runs and always reports; the work below is conditional. That is the + # shape GitHub prescribes for a workflow that is a required check. + # + # Uses the PR files API, not a local diff: checkout is fetch-depth 1, so + # neither `git diff base..head` nor `HEAD~1` resolves -- the first attempt + # here died with "ambiguous argument 'HEAD~1'". Deepening the clone would + # mean a full history fetch of a monorepo with 72 submodules to answer a + # question the API answers in one call. + # + # FAILS OPEN TOWARD RUNNING: if the API call fails we set relevant=true, so + # an unanswerable question runs the gate rather than silently skipping it. - name: Does this PR touch the verifier surface? id: changed shell: bash + env: + GH_TOKEN: ${{ github.token }} run: | - set -euo pipefail - base="${{ github.event.pull_request.base.sha }}" - head="${{ github.event.pull_request.head.sha }}" - if [ -z "$base" ] || [ -z "$head" ]; then - # workflow_dispatch and anything without a PR context: always run. + set -uo pipefail + num="${{ github.event.pull_request.number }}" + if [ -z "$num" ]; then + echo "no PR context (workflow_dispatch) — running the gate." + echo "relevant=true" >> "$GITHUB_OUTPUT"; exit 0 + fi + files="$(gh api --paginate "repos/${{ github.repository }}/pulls/$num/files" --jq '.[].filename' 2>/dev/null)" + if [ -z "$files" ]; then + echo "::warning::could not list PR files — running the gate rather than skipping it" echo "relevant=true" >> "$GITHUB_OUTPUT"; exit 0 fi - git fetch --no-tags --depth=1 origin "$base" >/dev/null 2>&1 || true - files="$(git diff --name-only "$base" "$head" 2>/dev/null || git diff --name-only HEAD~1 HEAD)" relevant=false while IFS= read -r f; do [ -n "$f" ] || continue