diff --git a/.github/workflows/exact-head-coverage-quality-gate.yml b/.github/workflows/exact-head-coverage-quality-gate.yml new file mode 100644 index 0000000000..4c4fc375a4 --- /dev/null +++ b/.github/workflows/exact-head-coverage-quality-gate.yml @@ -0,0 +1,92 @@ +name: Exact-Head Coverage Quality Gate + +# Reusable workflow_call gate shared by quality-CI callers that measure one +# scripts/ci module at 100% branch coverage against the exact PR head SHA. +# Callers: javascript-coverage-quality-ci.yml, +# organization-commercial-readiness-loop-quality-ci.yml. +# +# Not every quality-CI workflow under .github/workflows/ fits this shape — +# harden-runner presence, docstring gates, exact-head verification mechanics, +# and multi-python-version matrices differ enough across the others +# (agent-mention-router, exact-artifact-sbom-attestation, noema-token-lifetime, +# opencode-rust-coverage-toolchain, strix-changed-path, trusted-uv-materializer) +# that forcing them into this same template would either weaken what they +# enforce or need enough per-caller toggles to defeat the point of sharing. + +on: + workflow_call: + inputs: + timeout_minutes: + description: Job timeout in minutes + required: true + type: number + pytest_target: + description: pytest path or glob to run under coverage + required: true + type: string + coverage_include: + description: Single scripts/ci module path passed to `coverage report --include` + required: true + type: string + compileall_targets: + description: Space-separated file list passed to `python -m compileall -q` + required: true + type: string + +permissions: + contents: read + +jobs: + quality-gate: + runs-on: ubuntu-24.04 + timeout-minutes: ${{ inputs.timeout_minutes }} + steps: + - name: Checkout exact source revision + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} + persist-credentials: false + - name: Set up Python + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: '3.14' + - name: Install exact hash-verified quality dependencies + env: + PIP_DISABLE_PIP_VERSION_CHECK: '1' + PIP_NO_INPUT: '1' + shell: bash --noprofile --norc -e -o pipefail {0} + run: | + cat >"${RUNNER_TEMP}/exact-head-coverage-quality-gate-requirements.txt" <<'EOF' + coverage==7.15.2 --hash=sha256:b9a6367e4aff723e8ee8190836836124284e8fcd4265e307c844010cfa074f3f + iniconfig==2.1.0 --hash=sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760 + packaging==26.2 --hash=sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e + pluggy==1.6.0 --hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746 + pygments==2.20.0 --hash=sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176 + pytest==9.1.1 --hash=sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c + EOF + python -m pip install \ + --only-binary=:all: \ + --require-hashes \ + -r "${RUNNER_TEMP}/exact-head-coverage-quality-gate-requirements.txt" + - name: Verify exact-head policy and full branch coverage + env: + PYTEST_TARGET: ${{ inputs.pytest_target }} + COVERAGE_INCLUDE: ${{ inputs.coverage_include }} + COMPILEALL_TARGETS: ${{ inputs.compileall_targets }} + shell: bash --noprofile --norc -e -o pipefail {0} + run: | + test "$(git rev-parse HEAD)" = "${{ github.event.pull_request.head.sha || github.sha }}" + # PYTEST_TARGET/COMPILEALL_TARGETS are deliberately unquoted: callers + # pass space-separated lists and glob patterns that must still word- + # split and expand. Routing workflow_call inputs through env instead + # of interpolating them directly into the script keeps a caller- + # controlled value from ever being re-parsed as shell syntax. + # shellcheck disable=SC2086 + python -m coverage run --branch -m pytest --import-mode=importlib $PYTEST_TARGET -q + python -m coverage report \ + --include="$COVERAGE_INCLUDE" \ + --show-missing \ + --fail-under=100 + # shellcheck disable=SC2086 + python -m compileall -q $COMPILEALL_TARGETS + git diff --exit-code diff --git a/.github/workflows/javascript-coverage-quality-ci.yml b/.github/workflows/javascript-coverage-quality-ci.yml index d9c8c74299..62f3ca9261 100644 --- a/.github/workflows/javascript-coverage-quality-ci.yml +++ b/.github/workflows/javascript-coverage-quality-ci.yml @@ -5,9 +5,11 @@ on: branches: [main] paths: - '.github/workflows/javascript-coverage-quality-ci.yml' + - '.github/workflows/exact-head-coverage-quality-gate.yml' - 'scripts/ci/javascript_coverage_gate.py' - 'tests/test_javascript_coverage_gate.py' - 'tests/test_javascript_coverage_storybook_boundary.py' + - 'tests/test_exact_head_coverage_quality_gate_contract.py' permissions: contents: read @@ -17,47 +19,12 @@ concurrency: jobs: exact-head-coverage-contract: - runs-on: ubuntu-24.04 - timeout-minutes: 15 - steps: - - name: Checkout exact source revision - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - ref: ${{ github.event.pull_request.head.sha || github.sha }} - persist-credentials: false - - name: Set up Python - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 - with: - python-version: '3.14' - - name: Install exact hash-verified quality dependencies - env: - PIP_DISABLE_PIP_VERSION_CHECK: '1' - PIP_NO_INPUT: '1' - shell: bash --noprofile --norc -e -o pipefail {0} - run: | - cat >"${RUNNER_TEMP}/javascript-coverage-quality-requirements.txt" <<'EOF' - coverage==7.15.2 --hash=sha256:b9a6367e4aff723e8ee8190836836124284e8fcd4265e307c844010cfa074f3f - iniconfig==2.1.0 --hash=sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760 - packaging==26.2 --hash=sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e - pluggy==1.6.0 --hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746 - pygments==2.20.0 --hash=sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176 - pytest==9.1.1 --hash=sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c - EOF - python -m pip install \ - --only-binary=:all: \ - --require-hashes \ - -r "${RUNNER_TEMP}/javascript-coverage-quality-requirements.txt" - - name: Verify full central suite and classifier coverage - shell: bash --noprofile --norc -e -o pipefail {0} - run: | - test "$(git rev-parse HEAD)" = "${{ github.event.pull_request.head.sha || github.sha }}" - python -m coverage run --branch -m pytest --import-mode=importlib tests -q - python -m coverage report \ - --include='scripts/ci/javascript_coverage_gate.py' \ - --show-missing \ - --fail-under=100 - python -m compileall -q \ - scripts/ci/javascript_coverage_gate.py \ - tests/test_javascript_coverage_gate.py \ - tests/test_javascript_coverage_storybook_boundary.py - git diff --exit-code + uses: ./.github/workflows/exact-head-coverage-quality-gate.yml + with: + timeout_minutes: 15 + pytest_target: tests + coverage_include: scripts/ci/javascript_coverage_gate.py + compileall_targets: >- + scripts/ci/javascript_coverage_gate.py + tests/test_javascript_coverage_gate.py + tests/test_javascript_coverage_storybook_boundary.py diff --git a/.github/workflows/organization-commercial-readiness-loop-quality-ci.yml b/.github/workflows/organization-commercial-readiness-loop-quality-ci.yml index 50729db472..2e892d024e 100644 --- a/.github/workflows/organization-commercial-readiness-loop-quality-ci.yml +++ b/.github/workflows/organization-commercial-readiness-loop-quality-ci.yml @@ -6,6 +6,7 @@ on: paths: - ".github/workflows/organization-commercial-readiness-loop.yml" - ".github/workflows/organization-commercial-readiness-loop-quality-ci.yml" + - ".github/workflows/exact-head-coverage-quality-gate.yml" - "scripts/ci/organization_commercial_readiness_loop.py" - "organization_commercial_readiness_fixtures.py" - "tests/test_organization_commercial_readiness_loop*.py" @@ -21,52 +22,12 @@ concurrency: jobs: exact-head-policy: - runs-on: ubuntu-24.04 - timeout-minutes: 10 - steps: - - name: Checkout exact source revision - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - ref: ${{ github.event.pull_request.head.sha }} - persist-credentials: false - - - name: Set up Python - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 - with: - python-version: "3.14" - - - name: Install exact hash-verified quality dependencies - env: - PIP_DISABLE_PIP_VERSION_CHECK: "1" - PIP_NO_INPUT: "1" - shell: bash --noprofile --norc -e -o pipefail {0} - run: | - cat >"${RUNNER_TEMP}/organization-loop-quality-requirements.txt" <<'EOF' - coverage==7.15.2 --hash=sha256:b9a6367e4aff723e8ee8190836836124284e8fcd4265e307c844010cfa074f3f - iniconfig==2.1.0 --hash=sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760 - packaging==26.2 --hash=sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e - pluggy==1.6.0 --hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746 - pygments==2.20.0 --hash=sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176 - pytest==9.1.1 --hash=sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c - EOF - python -m pip install \ - --only-binary=:all: \ - --require-hashes \ - -r "${RUNNER_TEMP}/organization-loop-quality-requirements.txt" - - - name: Prove exact-head policy and full branch coverage - shell: bash --noprofile --norc -e -o pipefail {0} - run: | - test "$(git rev-parse HEAD)" = "${{ github.event.pull_request.head.sha }}" - python -m coverage run \ - --branch \ - -m pytest --import-mode=importlib tests/test_organization_commercial_readiness_loop*.py -q - python -m coverage report \ - --include='scripts/ci/organization_commercial_readiness_loop.py' \ - --show-missing \ - --fail-under=100 - python -m compileall -q \ - scripts/ci/organization_commercial_readiness_loop.py \ - organization_commercial_readiness_fixtures.py \ - tests/test_organization_commercial_readiness_loop*.py - git diff --exit-code + uses: ./.github/workflows/exact-head-coverage-quality-gate.yml + with: + timeout_minutes: 10 + pytest_target: "tests/test_organization_commercial_readiness_loop*.py" + coverage_include: scripts/ci/organization_commercial_readiness_loop.py + compileall_targets: >- + scripts/ci/organization_commercial_readiness_loop.py + organization_commercial_readiness_fixtures.py + tests/test_organization_commercial_readiness_loop*.py diff --git a/CHANGELOG.md b/CHANGELOG.md index ac1985d86f..9dd922dc35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,43 @@ this file. The format follows Keep a Changelog, and versioned releases follow Semantic Versioning where the repository publishes a release. ## [Unreleased] +- **Consolidate the two genuinely duplicate quality-CI callers behind one reusable + `workflow_call` gate; leave the other six alone.** An audit of the 8 + `.github/workflows/*-quality-ci.yml` bootstrap-templated files found only one pair — + `javascript-coverage-quality-ci.yml` and + `organization-commercial-readiness-loop-quality-ci.yml` — where the shared skeleton + (checkout at the exact PR head, an identical pinned six-package mini-requirements + heredoc, `coverage run --branch -m pytest --import-mode=importlib`, `coverage report + --fail-under=100`, `compileall`, `git diff --exit-code`) was byte-for-byte the same + logic with only the timeout, pytest target, and coverage `--include` path varying per + subsystem. Extracted that shared shape into a new + `.github/workflows/exact-head-coverage-quality-gate.yml` reusable workflow + (`workflow_call`-only, four required inputs: `timeout_minutes`, `pytest_target`, + `coverage_include`, `compileall_targets`) and turned both callers into thin + `uses:`/`with:` wrappers. Verified first that no branch-protection required status + check or the org's required-workflow ruleset references either caller's job name + (`exact-head-coverage-contract` / `exact-head-policy`) before restructuring, so nothing + downstream depends on their exact shape. Updated the three contract tests that pinned + the old inline text + (`test_organization_commercial_readiness_loop_policy.py`, + `test_organization_commercial_readiness_loop_import_contract.py`) to check the + coverage/exact-head mechanics against the shared gate file and the subsystem wiring + against each caller, and added + `tests/test_exact_head_coverage_quality_gate_contract.py` to pin the gate's own + `workflow_call` contract and both callers' input wiring. The other 6 files + (`agent-mention-router-quality-ci.yml`, `exact-artifact-sbom-attestation-quality.yml`, + `noema-token-lifetime-quality-ci.yml`, + `opencode-rust-coverage-toolchain-quality-ci.yml`, `strix-changed-path-quality-ci.yml`, + `trusted-uv-materializer-quality-ci.yml`) look superficially similar but each encodes a + genuinely different policy -- harden-runner presence, a docstring/interrogate gate, + exact-head-verification mechanics (or, for noema, no `ref:` pin at all), multi-Python- + version matrices with non-shared extra logic (a tomli-fallback exercise, a Python 3.10 + compile-only contract), or no `coverage --fail-under` step at all (strix delegates to a + bash gate script instead) -- so templatizing them would either weaken what they + individually enforce or need enough per-caller toggles to defeat the point of sharing. + Left untouched, matching the precedent already set for ruling out the agent-mention + dispatch pair and the noema/opencode/strix "cancel superseded runs" jobs. Full suite: + 2603 passed, 1 skipped, 100% branch coverage, 100% docstrings, `actionlint` clean. - **Fail closed before cancelling stale PR workflow runs.** Validate snapshot `headRefOid` and re-read live PR/run identity immediately before destructive cancellation, including OpenCode/Strix dispatch cleanup, so a missing head or concurrent push cannot cancel the sole current-head evidence or trigger a duplicate review. Also ensures every cancellation path (`cancel_stale_pr_runs`, `cancel_stale_opencode_runs`, `_cancel_revalidated_review_run_refs`) treats a run as cancelled only when `force_cancel_workflow_runs` actually reports success, not merely when live revalidation proved it stale -- superseding PR #1712's simpler `force_cancel_workflow_run_refs` wrapper (removed as dead code; its safety guarantee is preserved inline at every call site by this more thorough revalidate-then-cancel design). - **Cache `active_workflow_runs` for the life of one `pr_review_merge_scheduler.py` invocation.** `inspect_pr()` calls `cancel_stale_pr_runs()` unconditionally for diff --git a/tests/test_exact_head_coverage_quality_gate_contract.py b/tests/test_exact_head_coverage_quality_gate_contract.py new file mode 100644 index 0000000000..a5d0c12706 --- /dev/null +++ b/tests/test_exact_head_coverage_quality_gate_contract.py @@ -0,0 +1,138 @@ +"""Contracts for the shared exact-head coverage quality-gate reusable workflow.""" + +from __future__ import annotations + +import re +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +GATE_WORKFLOW = ROOT / ".github/workflows/exact-head-coverage-quality-gate.yml" +JS_CALLER = ROOT / ".github/workflows/javascript-coverage-quality-ci.yml" +ORG_LOOP_CALLER = ( + ROOT / ".github/workflows/organization-commercial-readiness-loop-quality-ci.yml" +) + + +SELF_TEST_PATH = "tests/test_exact_head_coverage_quality_gate_contract.py" + + +def _text(path: Path) -> str: + assert path.is_file(), f"missing workflow file: {path}" + return path.read_text(encoding="utf-8") + + +def test_gate_is_call_only_with_required_string_and_number_inputs() -> None: + """The shared gate accepts no direct trigger and pins every input as required.""" + workflow = _text(GATE_WORKFLOW) + header = workflow.split("\npermissions:\n", 1)[0] + + assert re.search(r"(?m)^ workflow_call:\s*$", header) + for forbidden_trigger in ("pull_request", "push", "schedule", "workflow_dispatch"): + assert not re.search(rf"(?m)^ {forbidden_trigger}:\s*$", header) + + required_inputs = { + "timeout_minutes": "number", + "pytest_target": "string", + "coverage_include": "string", + "compileall_targets": "string", + } + for input_name, input_type in required_inputs.items(): + input_match = re.search( + rf"(?ms)^ {re.escape(input_name)}:\n(?P(?:^ .*\n)+)", + header, + ) + assert input_match is not None, f"missing workflow input: {input_name}" + body = input_match.group("body") + assert re.search(r"(?m)^ required: true\s*$", body) + assert re.search(rf"(?m)^ type: {input_type}\s*$", body) + + +def test_gate_enforces_exact_head_and_full_branch_coverage() -> None: + """The gate itself carries the exact-head check and the coverage fail-under.""" + workflow = _text(GATE_WORKFLOW) + + assert 'test "$(git rev-parse HEAD)" = "${{ github.event.pull_request.head.sha || github.sha }}"' in workflow + assert "coverage run --branch -m pytest --import-mode=importlib" in workflow + assert "--fail-under=100" in workflow + assert "git diff --exit-code" in workflow + assert "persist-credentials: false" in workflow + + +def _run_blocks(workflow: str) -> list[str]: + """Return every indentation-bounded multiline shell body in a workflow.""" + lines = workflow.splitlines() + blocks: list[str] = [] + index = 0 + while index < len(lines): + if lines[index].strip() != "run: |": + index += 1 + continue + run_indent = len(lines[index]) - len(lines[index].lstrip()) + index += 1 + body: list[str] = [] + while index < len(lines) and ( + lines[index].strip() == "" + or len(lines[index]) - len(lines[index].lstrip()) > run_indent + ): + body.append(lines[index]) + index += 1 + blocks.append("\n".join(body)) + return blocks + + +def test_gate_never_interpolates_caller_inputs_directly_into_shell() -> None: + """Caller-controlled inputs must enter shell commands only through env vars.""" + workflow = _text(GATE_WORKFLOW) + + run_blocks = _run_blocks(workflow) + assert run_blocks, "gate workflow must declare multiline run blocks" + for block in run_blocks: + assert "${{ inputs." not in block, ( + "workflow_call input must enter shell commands through an " + f"environment variable, not direct interpolation: {block}" + ) + + for env_var in ("PYTEST_TARGET", "COVERAGE_INCLUDE", "COMPILEALL_TARGETS"): + assert f"{env_var}: ${{{{ inputs." in workflow, ( + f"expected {env_var} to be bound from a workflow_call input via env:" + ) + + +def test_javascript_and_organization_loop_callers_wire_distinct_subsystem_inputs() -> None: + """Each caller delegates to the shared gate with its own subsystem scope.""" + js_caller = _text(JS_CALLER) + org_caller = _text(ORG_LOOP_CALLER) + + for caller in (js_caller, org_caller): + assert ( + "uses: ./.github/workflows/exact-head-coverage-quality-gate.yml" in caller + ) + + assert "coverage_include: scripts/ci/javascript_coverage_gate.py" in js_caller + assert "pytest_target: tests" in js_caller + assert "timeout_minutes: 15" in js_caller + + assert ( + "coverage_include: scripts/ci/organization_commercial_readiness_loop.py" + in org_caller + ) + assert ( + 'pytest_target: "tests/test_organization_commercial_readiness_loop*.py"' + in org_caller + ) + assert "timeout_minutes: 10" in org_caller + + +def test_js_caller_trigger_covers_this_contract_test_file() -> None: + """An edit to only this file must still trigger the job that runs it. + + The JS caller's pytest_target is the whole `tests` directory, so it + actually exercises this file when it runs -- unlike the org-loop caller, + whose pytest_target is a narrower glob that never matches this filename. + Without this file in the JS caller's path trigger, a change scoped only + to this test could merge without the gate that runs it ever firing. + """ + js_caller = _text(JS_CALLER) + trigger = js_caller.split("\npermissions:\n", 1)[0] + + assert f"'{SELF_TEST_PATH}'" in trigger diff --git a/tests/test_organization_commercial_readiness_loop_import_contract.py b/tests/test_organization_commercial_readiness_loop_import_contract.py index 43c3c71acd..4d602235ec 100644 --- a/tests/test_organization_commercial_readiness_loop_import_contract.py +++ b/tests/test_organization_commercial_readiness_loop_import_contract.py @@ -8,13 +8,17 @@ / "workflows" / "organization-commercial-readiness-loop-quality-ci.yml" ) +QUALITY_GATE_WORKFLOW = ( + REPO_ROOT / ".github" / "workflows" / "exact-head-coverage-quality-gate.yml" +) def test_quality_gate_uses_import_stable_test_support() -> None: """Hosted and complete-suite collection must resolve the same helper module.""" source = QUALITY_WORKFLOW.read_text(encoding="utf-8") + gate_source = QUALITY_GATE_WORKFLOW.read_text(encoding="utf-8") - assert "--import-mode=importlib" in source + assert "--import-mode=importlib" in gate_source assert '"organization_commercial_readiness_fixtures.py"' in source assert "tests/organization_commercial_readiness_fixtures.py" not in source assert "--include='scripts/ci/organization_commercial_readiness_loop.py' \\\n -m pytest" not in source diff --git a/tests/test_organization_commercial_readiness_loop_policy.py b/tests/test_organization_commercial_readiness_loop_policy.py index 920f8072f9..d156c0c5e0 100644 --- a/tests/test_organization_commercial_readiness_loop_policy.py +++ b/tests/test_organization_commercial_readiness_loop_policy.py @@ -152,6 +152,9 @@ def test_workflow_and_doctoring_contracts() -> None: ROOT / ".github/workflows/organization-commercial-readiness-loop-quality-ci.yml" ).read_text() + quality_gate = ( + ROOT / ".github/workflows/exact-head-coverage-quality-gate.yml" + ).read_text() doctoring = ( ROOT / "docs/doctoring/organization-commercial-readiness-loop.md" ).read_text() @@ -167,10 +170,19 @@ def test_workflow_and_doctoring_contracts() -> None: assert "COPILOT_GITHUB_TOKEN" not in workflow_source assert "github.run_number" in workflow_source assert "persist-credentials: false" in workflow_source - assert "--branch" in quality and "--fail-under=100" in quality - assert "--import-mode=importlib" in quality + # Coverage/exact-head mechanics live in the shared reusable gate; the + # caller only needs to delegate to it with the right subsystem inputs. + assert ( + "uses: ./.github/workflows/exact-head-coverage-quality-gate.yml" in quality + ) + assert ( + "coverage_include: scripts/ci/organization_commercial_readiness_loop.py" + in quality + ) assert "organization_commercial_readiness_fixtures.py" in quality - assert "github.event.pull_request.head.sha" in quality + assert "--branch" in quality_gate and "--fail-under=100" in quality_gate + assert "--import-mode=importlib" in quality_gate + assert "github.event.pull_request.head.sha" in quality_gate assert "disabled workflow does not hold a lease" in doctoring assert "manual-only, explicitly marked" in doctoring assert "does not make every repository directly writable" in doctoring