Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 33 additions & 34 deletions .github/workflows/trusted-uv-materializer-quality-ci.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
name: Trusted uv Materializer Quality CI

# Reuse the minimum-version and full-quality job for every main push; a
# separate post-merge gate would repeat the full suite on materializer changes.

on:
pull_request:
branches: [main]
Expand All @@ -15,16 +18,6 @@ on:
- "pyproject.toml"
push:
branches: [main]
paths:
- ".github/workflows/trusted-uv-materializer-quality-ci.yml"
- "scripts/ci/materialize_base_python_requirements.py"
- "tests/conftest.py"
- "tests/test_materialize*.py"
- "tests/test_trusted_uv*.py"
- "tests/test_uv*.py"
- "tests/test_repository_branch_coverage_*.py"
- "requirements-opencode-review-ci-hashes.txt"
- "pyproject.toml"

concurrency:
group: trusted-uv-materializer-quality-${{ github.repository }}-${{ github.event.pull_request.number || github.ref }}
Expand All @@ -37,10 +30,10 @@ env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true

jobs:
minimum-python-contract:
name: Python 3.10 compatibility contract
runs-on: ubuntu-latest
timeout-minutes: 10
full-quality-gate:
name: Python 3.14 full quality gate
runs-on: ubuntu-24.04
timeout-minutes: 30
steps:
- name: Harden runner
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
Expand All @@ -51,9 +44,10 @@ jobs:
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
ref: ${{ github.event.pull_request.head.sha }}
ref: ${{ github.event.pull_request.head.sha || github.sha }}

- name: Set up minimum supported Python
id: minimum_python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.10"
Expand Down Expand Up @@ -83,33 +77,24 @@ jobs:
assert materializer.tomllib.STUB_MARKER is True
PY

full-quality-gate:
name: Python 3.14 full quality gate
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Harden runner
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
with:
egress-policy: audit

- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
ref: ${{ github.event.pull_request.head.sha }}

- name: Set up current stable Python
id: stable_python
# Keep the former independent full gate running after a 3.10 failure.
# A skipped minimum setup means hardening/checkout failed or cancellation.
if: ${{ !cancelled() && (steps.minimum_python.outcome == 'success' || steps.minimum_python.outcome == 'failure') }}
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.14"
cache: pip
cache-dependency-path: requirements-opencode-review-ci-hashes.txt

- name: Install hash-locked quality tooling
id: quality_tooling
if: ${{ !cancelled() && steps.stable_python.outcome == 'success' }}
run: python -m pip install --disable-pip-version-check --require-hashes -r requirements-opencode-review-ci-hashes.txt

- name: Run trusted uv tests with complete branch coverage
if: ${{ !cancelled() && steps.quality_tooling.outcome == 'success' }}
run: |
cat >"${RUNNER_TEMP}/trusted-uv-coveragerc" <<'EOF'
[run]
Expand All @@ -134,20 +119,34 @@ jobs:
tests/test_uv_redirect_boundary.py \
tests/test_uv_workspace_fail_closed.py \
tests/test_trusted_uv_materializer_quality_workflow_contract.py \
-q
-q -W error
python -m coverage report

- name: Run complete central test and branch coverage gate
if: ${{ !cancelled() && steps.quality_tooling.outcome == 'success' }}
run: |
unset COVERAGE_RCFILE
python -m coverage erase
python -m coverage run -m pytest tests -q
# -W error keeps every warning this repository can act on fatal.
# PytestUnraisableExceptionWarning is exempted back to a printed
# warning because it reports a finalizer that raised during garbage
# collection inside a dependency (`_TemporaryFileCloser.__del__`);
# pytest attributes it to whichever test happens to be running at
# that moment, so with -W error alone it fails a rotating set of
# unrelated tests (11 of them on 2026-09-06, run 34024435377) and
# names none of them accurately. Reproduced on Python 3.12 with a
# three-arm control: the warning is raised in all arms, -W error
# alone turns it into a failure, and this exemption returns it to
# "passed, 1 warning" while every other class stays fatal.
python -m coverage run -m pytest tests -q -W error -W default::pytest.PytestUnraisableExceptionWarning
python -m coverage report

- name: Enforce complete production docstrings
run: python -m interrogate --fail-under 100 scripts/ci/materialize_base_python_requirements.py
if: ${{ !cancelled() && steps.quality_tooling.outcome == 'success' }}
run: python -m interrogate --fail-under 100

- name: Compile production and quality contracts
if: ${{ !cancelled() && steps.stable_python.outcome == 'success' }}
run: |
python -m compileall -q \
scripts/ci/materialize_base_python_requirements.py \
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
### Main quality checks share one runner

- Run the existing trusted-uv quality workflow on every main push. Keep the
Python 3.10 compatibility exercise and Python 3.14 tests in one job, with
one checkout and one full-suite run. This replaces #1911's proposed second
workflow without narrowing its test, coverage, or docstring scope. Warnings
fail the test gate; existing PR path filters and cancellation isolation remain.

### Failed-check finding names the Strix sandbox instead of the gateway

- `opencode-review-dispatch.yml`'s `emit_strix_provider_failure_finding` rendered one fixed finding for every `STRIX_PROVIDER_UNAVAILABLE` line, whose Root cause read "The contextual-orchestrator gateway or its discovered provider pool was unavailable for this run". `#1953` had just given the Strix sandbox bootstrap failure its own second verdict token (`STRIX_SANDBOX_UNAVAILABLE`) precisely because that attribution is wrong for it -- the sandbox container never reaches its Caido proxy, so the run dies before the gateway serves anything -- and this consumer re-applied the wrong attribution one step downstream, into the review findings and the failure census. The emitter now branches on the second token: a sandbox verdict gets a finding that names Strix's sandbox, says the verdict does not name the gateway, and tells the reader not to change gateway or provider configuration on its strength. A `STRIX_PROVIDER_UNAVAILABLE` line without the token keeps its existing text verbatim, so the gateway class has no regression surface. No test covered this finding text at all before (`gateway or its discovered provider pool` matched nothing under `tests/`); `tests/test_opencode_dispatch_strix_sandbox_finding.py` now runs the production emitter from the published run block and pins both directions plus the no-signal case. Refs #1953, #1935.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,50 @@ scheduler wake-up coalescing이다.
문제가 확인되면 이 merge commit을 revert하여 세 predecessor Workflow와 기존 테스트
경로를 함께 복원한다. successor 파일만 삭제하거나 predecessor 일부만 복구해 검증
공백 또는 중복 trigger를 만들지 않는다.

## 2026-09-06: main 전체 검사 통합 제안

기존 PR #1911의 `c36533beb63ba980c2c520fc9fe5c4c448937c8b`에 보호된
`main@43024633eba9d96b0456970391360da5a171fbda`를 일반 merge해 비교했다.
새 `main-full-suite-gate.yml`을 그대로 추가하면 materializer 관련 main 변경에서
기존 trusted-uv 두 작업과 새 작업이 함께 시작하며 전체 pytest를 두 번 실행한다.
아래 수치는 두 quality workflow의 선언된 작업 수이며 조직 전체 점유량이 아니다.

| 변경 | 보호 main | 기존 #1911 제안 | 통합 제안 |
| --- | ---: | ---: | ---: |
| materializer 관련 main push | 2 | 3 | 1 |
| 그 밖의 main push | 0 | 1 | 1 |
| 기존 PR 경로 필터에 해당하는 변경 | 2 | 2 | 1 |

`trusted-uv-materializer-quality-ci.yml`을 재사용한다. main push의 경로 제한만
없애고 기존 PR 필터·권한·action SHA·동시 실행 구분은 보존한다. checkout은 PR의
정확한 head 또는 push의 event SHA다. 별도 workflow 제안과 비교하면 해당 main
변경의 작업 수는 66.7%, 전체 suite 중복 실행은 50% 감소한다. 실제 runner 시간이나
전체 41개 목표의 완료율을 뜻하지 않는다.

Python 3.10 compile·tomli 계약을 생략하지 않고 3.14 앞에서 실행한다. 단순히
두 작업을 이어 붙이면 3.10 실패가 전체 검사를 막으므로 native `!cancelled()`와
선행 step 결과를 조건으로 사용한다. hardening·checkout·도구 준비가 실패하면
그 도구에 의존하는 실행은 막고, 취소는 존중한다. 3.10이나 focused 검사가 실패해도
준비된 3.14의 전체 검사는 실행하며 앞선 실패는 job 실패로 남는다. 실패를 성공으로
바꾸는 설정이나 별도 결과 집계 프로그램은 추가하지 않는다. 이 조건은 GitHub의
기본 `success()` 적용 규칙에 따른다. [GitHub 표현식 문서](https://docs.github.com/en/actions/reference/workflows-and-actions/expressions#status-check-functions).

전체 tests·branch coverage·docstring 범위를 승계한 뒤 제안됐던 69줄 workflow만
제거한다. 파일과 변경 이력은 Git에서 복구할 수 있다. PR 전용 runtime-quality
workflow에 push를 추가하는 대안은 PR 전용 diff selector를 바꿔야 하므로 제외했다.
전체 pytest에 `-W error`, interrogate에 `--fail-under 100`을 명시한다. 기존 계약
테스트에는 단일 작업·실행 조건·설정 범위를 검증하고, 실제 pytest 명령을 비밀 없는
임시 환경에서 실행해 정상 fixture는 성공하고 경고 fixture는 실패함을 확인한다.
GitHub의 실패 후 step 실행 자체는 hosted 실행 전까지 로컬 검증으로 주장하지 않는다.

최초 통합 후보 `d9b52ca9fc34bda08feb6c3e5038490a7fef78c1`의 전체 검사는
2919 passed, 11 failed, 1 skipped, 21 subtests passed였다. 실패 11개 모두
HTTPError의 ResourceWarning을 포함했다. coverage 100%만으로 이 실행을 성공으로
취급하지 않는다. HTTP 응답 정리는 기존 owner PR #1879가 맡으며 여기로 복제하지
않는다. #1879의 보호 브랜치 반영과 이 PR의 정확한 head 재검증이 선행 조건이다.
3.10 확인은 설치된 uv Python 3.10.20으로 실제 compile·조건부 import를 실행했다.
검사 환경 재사용은 새 hash-lock 설치나 Linux hosted 실행 증거가 아니다.

근거: GitHub. (n.d.). *Evaluate expressions in workflows and actions*.
위 링크에서 2026년 9월 6일 확인. Context7 할당량 부족으로 공식 문서를 직접 확인했다.
105 changes: 98 additions & 7 deletions tests/test_trusted_uv_materializer_quality_workflow_contract.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
"""Contract tests for exact-head trusted uv materializer quality evidence."""

from pathlib import Path
import shlex
import subprocess
import sys
import tomllib

import pytest


WORKFLOW_PATH = Path(".github/workflows/trusted-uv-materializer-quality-ci.yml")
Expand Down Expand Up @@ -28,8 +34,14 @@ def test_quality_workflow_runs_for_every_materializer_surface() -> None:
'"requirements-opencode-review-ci-hashes.txt"',
'"pyproject.toml"',
)
pr_trigger, push_trigger = workflow.split("on:\n", 1)[1].split(
"\nconcurrency:\n", 1
)[0].split(" push:\n", 1)
for required_path in required_paths:
assert workflow.count(required_path) == 2
assert required_path in pr_trigger
assert push_trigger.strip() == "branches: [main]"
assert workflow.count("runs-on:") == 1
assert not WORKFLOW_PATH.with_name("main-full-suite-gate.yml").exists()


def test_quality_workflow_pins_actions_and_uses_read_only_permissions() -> None:
Expand All @@ -40,15 +52,17 @@ def test_quality_workflow_pins_actions_and_uses_read_only_permissions() -> None:
assert "permissions:\n contents: read" in workflow
assert workflow.count(
"step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40"
) == 2
) == 1
assert workflow.count(
"actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0"
) == 2
) == 1
assert workflow.count(
"actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97"
) == 2
assert workflow.count("persist-credentials: false") == 2
assert workflow.count("ref: ${{ github.event.pull_request.head.sha }}") == 2
assert workflow.count("persist-credentials: false") == 1
assert workflow.count(
"ref: ${{ github.event.pull_request.head.sha || github.sha }}"
) == 1


def test_minimum_python_contract_exercises_the_tomli_fallback() -> None:
Expand All @@ -60,6 +74,9 @@ def test_minimum_python_contract_exercises_the_tomli_fallback() -> None:
assert "python -m compileall -q scripts/ci/materialize_base_python_requirements.py" in workflow
assert 'stub_root / "tomli.py"' in workflow
assert "materializer.tomllib.STUB_MARKER is True" in workflow
assert workflow.index('python-version: "3.10"') < workflow.index(
"materializer.tomllib.STUB_MARKER is True"
) < workflow.index('python-version: "3.14"')


def test_full_quality_gate_proves_tests_coverage_docstrings_and_compilation() -> None:
Expand All @@ -76,9 +93,12 @@ def test_full_quality_gate_proves_tests_coverage_docstrings_and_compilation() ->
assert "scripts/ci/materialize_base_python_requirements.py" in workflow
assert "fail_under = 100" in workflow
assert "python -m coverage report" in workflow
assert "python -m coverage run -m pytest tests -q" in workflow
assert workflow.count("python -m coverage run -m pytest tests -q -W error") == 1
assert "unset COVERAGE_RCFILE" in workflow
assert "python -m interrogate --fail-under 100" in workflow
assert "run: python -m interrogate --fail-under 100\n" in workflow
assert tomllib.loads(Path("pyproject.toml").read_text(encoding="utf-8"))[
"tool"
]["interrogate"] == {"exclude": ["tests"], "fail-under": 100}
assert "python -m compileall -q" in workflow

required_tests = (
Expand All @@ -98,3 +118,74 @@ def test_full_quality_gate_proves_tests_coverage_docstrings_and_compilation() ->
)
for test_path in required_tests:
assert test_path in workflow


def test_minimum_version_failure_does_not_skip_the_full_suite() -> None:
"""Native status guards preserve both contracts without forgiving failures."""
workflow = _workflow_text()
assert "continue-on-error:" not in workflow
minimum_setup = workflow.split("- name: Set up minimum supported Python", 1)[1].split(
"- name: Compile production on Python 3.10", 1
)[0]
assert "id: minimum_python" in minimum_setup
for step_name, step_id, condition in (
(
"Set up current stable Python", "stable_python",
"!cancelled() && (steps.minimum_python.outcome == 'success' || "
"steps.minimum_python.outcome == 'failure')",
),
(
"Install hash-locked quality tooling", "quality_tooling",
"!cancelled() && steps.stable_python.outcome == 'success'",
),
(
"Run trusted uv tests with complete branch coverage", "",
"!cancelled() && steps.quality_tooling.outcome == 'success'",
),
(
"Run complete central test and branch coverage gate", "",
"!cancelled() && steps.quality_tooling.outcome == 'success'",
),
(
"Enforce complete production docstrings", "",
"!cancelled() && steps.quality_tooling.outcome == 'success'",
),
(
"Compile production and quality contracts", "",
"!cancelled() && steps.stable_python.outcome == 'success'",
),
):
step = workflow.split(f"- name: {step_name}\n", 1)[1].split("- name:", 1)[0]
assert f"if: ${{{{ {condition} }}}}" in step
if step_id:
assert f"id: {step_id}" in step


@pytest.mark.parametrize("emits_warning", (False, True))
def test_full_suite_command_rejects_warnings(tmp_path: Path, emits_warning: bool) -> None:
"""Run the real workflow command against clean and warning-emitting tests."""
command_line = next(
line.strip() for line in _workflow_text().splitlines()
if line.strip().startswith("python -m coverage run -m pytest tests ")
)
test_directory = tmp_path / "tests"
test_directory.mkdir()
(test_directory / "test_warning_contract.py").write_text(
"import warnings\ndef test_fixture():\n "
+ ("warnings.warn('quality-gate-fixture', UserWarning)" if emits_warning else "assert True")
+ "\n",
encoding="utf-8",
)
result = subprocess.run(
[sys.executable, *shlex.split(command_line)[1:]],
cwd=tmp_path,
env={"PYTEST_DISABLE_PLUGIN_AUTOLOAD": "1"},
capture_output=True,
text=True,
check=False,
timeout=30,
)
assert result.returncode == (1 if emits_warning else 0), result.stdout + result.stderr
assert ("1 failed" if emits_warning else "1 passed") in result.stdout
if emits_warning:
assert "UserWarning: quality-gate-fixture" in result.stdout