From 3887faba96b50f34cff0ad58e5706427d818ec7d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 22:56:54 +0900 Subject: [PATCH 1/7] test(coverage): specify LLVM 19 isolated runtime contract --- ...encode_rust_coverage_toolchain_contract.py | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 tests/test_opencode_rust_coverage_toolchain_contract.py diff --git a/tests/test_opencode_rust_coverage_toolchain_contract.py b/tests/test_opencode_rust_coverage_toolchain_contract.py new file mode 100644 index 000000000..43979a649 --- /dev/null +++ b/tests/test_opencode_rust_coverage_toolchain_contract.py @@ -0,0 +1,86 @@ +"""Permanent contract for the trusted Rust LLVM coverage toolchain.""" + +from __future__ import annotations + +import re +from pathlib import Path + + +_REPOSITORY_ROOT = Path(__file__).resolve().parents[1] +_WORKFLOW_PATH = _REPOSITORY_ROOT / ".github/workflows/opencode-review-dispatch.yml" +_LLVM_COV_PATH = "/usr/bin/llvm-cov-19" +_LLVM_PROFDATA_PATH = "/usr/bin/llvm-profdata-19" + + +def _workflow_text() -> str: + """Return the authoritative OpenCode review-dispatch workflow text.""" + + return _WORKFLOW_PATH.read_text(encoding="utf-8") + + +def _all_positions(text: str, fragment: str) -> list[int]: + """Return every start position of ``fragment`` in ``text``.""" + + return [match.start() for match in re.finditer(re.escape(fragment), text)] + + +def test_trusted_rust_coverage_image_provisions_verified_llvm_19_tools() -> None: + """Require explicit compatible LLVM tools before cargo-llvm-cov installation.""" + + workflow = _workflow_text() + + llvm_package = workflow.index("llvm-19") + llvm_cov_environment = workflow.index(f"ENV LLVM_COV={_LLVM_COV_PATH}") + llvm_profdata_environment = workflow.index( + f"ENV LLVM_PROFDATA={_LLVM_PROFDATA_PATH}" + ) + llvm_cov_checks = _all_positions(workflow, 'test -x "$LLVM_COV"') + llvm_profdata_checks = _all_positions(workflow, 'test -x "$LLVM_PROFDATA"') + cargo_llvm_cov_archive = workflow.index( + "cargo-llvm-cov-x86_64-unknown-linux-musl.tar.gz" + ) + + assert len(llvm_cov_checks) >= 2 + assert len(llvm_profdata_checks) >= 2 + assert ( + llvm_package + < llvm_cov_environment + < llvm_profdata_environment + < llvm_cov_checks[0] + < llvm_profdata_checks[0] + < cargo_llvm_cov_archive + ) + + +def test_isolated_runtime_receives_reviewed_llvm_constants() -> None: + """Require exact LLVM 19 path propagation through the Docker boundary.""" + + workflow = _workflow_text() + docker_run = workflow.index("docker run --rm") + llvm_cov_binding = workflow.index( + f"--env LLVM_COV={_LLVM_COV_PATH}", docker_run + ) + llvm_profdata_binding = workflow.index( + f"--env LLVM_PROFDATA={_LLVM_PROFDATA_PATH}", docker_run + ) + coverage_image = workflow.index('"$coverage_tool_image"', docker_run) + + assert docker_run < llvm_cov_binding < llvm_profdata_binding < coverage_image + + +def test_isolated_runtime_revalidates_llvm_tools_before_coverage() -> None: + """Require reviewed-path equality and executable checks before Rust coverage.""" + + workflow = _workflow_text() + docker_run = workflow.index("docker run --rm") + toolchain_start = workflow.index("ensure_rust_toolchain() {", docker_run) + toolchain_end = workflow.index("rust_coverage_manifests() {", toolchain_start) + toolchain = workflow[toolchain_start:toolchain_end] + cargo_coverage_invocation = workflow.index("cargo llvm-cov", toolchain_end) + llvm_cov_checks = _all_positions(workflow, 'test -x "$LLVM_COV"') + llvm_profdata_checks = _all_positions(workflow, 'test -x "$LLVM_PROFDATA"') + + assert f'"${{LLVM_COV:-}}" != "{_LLVM_COV_PATH}"' in toolchain + assert f'"${{LLVM_PROFDATA:-}}" != "{_LLVM_PROFDATA_PATH}"' in toolchain + assert docker_run < llvm_cov_checks[-1] < cargo_coverage_invocation + assert docker_run < llvm_profdata_checks[-1] < cargo_coverage_invocation From da31228c202a7c74d1e64cefb95f0715efee61f1 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 22:59:50 +0900 Subject: [PATCH 2/7] fix(coverage): provision verified LLVM 19 tools --- .github/workflows/opencode-review-dispatch.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/opencode-review-dispatch.yml b/.github/workflows/opencode-review-dispatch.yml index 83f6830d5..b17cf3775 100644 --- a/.github/workflows/opencode-review-dispatch.yml +++ b/.github/workflows/opencode-review-dispatch.yml @@ -652,11 +652,15 @@ jobs: r-base \ r-cran-covr \ r-cran-testthat \ + llvm-19 \ rustc \ util-linux \ vulkan-tools \ xz-utils \ && rm -rf /var/lib/apt/lists/* + ENV LLVM_COV=/usr/bin/llvm-cov-19 + ENV LLVM_PROFDATA=/usr/bin/llvm-profdata-19 + RUN test -x "$LLVM_COV" && test -x "$LLVM_PROFDATA" RUN curl --proto '=https' --tlsv1.2 -fsSLo /tmp/node-linux-x64.tar.xz \ https://nodejs.org/dist/v24.18.0/node-v24.18.0-linux-x64.tar.xz \ && echo '55aa7153f9d88f28d765fcdad5ae6945b5c0f98a36881703817e4c450fa76742 /tmp/node-linux-x64.tar.xz' | sha256sum -c - \ From edddecdfa2b66b61f472db8f754270ffc2fd147c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 23:04:25 +0900 Subject: [PATCH 3/7] ci(coverage): add exact-head LLVM runtime quality gate --- ...ode-rust-coverage-toolchain-quality-ci.yml | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 .github/workflows/opencode-rust-coverage-toolchain-quality-ci.yml diff --git a/.github/workflows/opencode-rust-coverage-toolchain-quality-ci.yml b/.github/workflows/opencode-rust-coverage-toolchain-quality-ci.yml new file mode 100644 index 000000000..ee4b283bd --- /dev/null +++ b/.github/workflows/opencode-rust-coverage-toolchain-quality-ci.yml @@ -0,0 +1,56 @@ +name: OpenCode Rust Coverage Toolchain Quality CI + +on: + pull_request: + paths: + - ".github/workflows/opencode-review-dispatch.yml" + - ".github/workflows/opencode-rust-coverage-toolchain-quality-ci.yml" + - "tests/test_opencode_rust_coverage_toolchain_contract.py" + - "docs/doctoring/opencode-rust-coverage-runtime-boundary.md" + - "CHANGELOG.md" + +permissions: + contents: read + +concurrency: + group: opencode-rust-coverage-toolchain-quality-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + quality: + name: quality + runs-on: ubuntu-24.04 + timeout-minutes: 15 + env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + steps: + - name: Harden runner + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 + with: + egress-policy: audit + + - name: Checkout exact pull request head + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ github.event.pull_request.head.sha }} + fetch-depth: 0 + persist-credentials: false + + - name: Set up Python + 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 exact hash-locked test tooling + run: >- + python -m pip install --disable-pip-version-check --require-hashes + -r requirements-opencode-review-ci-hashes.txt + + - name: Run permanent LLVM runtime-boundary contract + run: | + set -euo pipefail + python -m pytest -q tests/test_opencode_rust_coverage_toolchain_contract.py + python -m compileall -q tests/test_opencode_rust_coverage_toolchain_contract.py + git diff --check "${{ github.event.pull_request.base.sha }}...${{ github.event.pull_request.head.sha }}" From 6eb06cdd08c79a06f7b390069d4ffa49e2eb7dba Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 8 Aug 2026 11:43:31 +0900 Subject: [PATCH 4/7] fix(strix): bound quality timeout fixtures (#823) * test(strix): specify bounded quality timeout fixtures * fix(strix): accelerate deterministic timeout fixtures * docs(strix): record quality fixture budget * docs(strix): record bounded quality fixtures * ci(strix): bind timeout doctoring to quality gate * test(strix): bind timeout fixture trigger paths --- .../strix-changed-path-quality-ci.yml | 7 ++- CHANGELOG.md | 1 + .../strix-quality-timeout-fixtures.md | 51 +++++++++++++++++++ ...st_strix_quality_timeout_fixture_budget.py | 45 ++++++++++++++++ 4 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 docs/doctoring/strix-quality-timeout-fixtures.md create mode 100644 tests/test_strix_quality_timeout_fixture_budget.py diff --git a/.github/workflows/strix-changed-path-quality-ci.yml b/.github/workflows/strix-changed-path-quality-ci.yml index 74037c18c..75e9b7d8e 100644 --- a/.github/workflows/strix-changed-path-quality-ci.yml +++ b/.github/workflows/strix-changed-path-quality-ci.yml @@ -7,10 +7,12 @@ on: - ".github/workflows/strix-changed-path-quality-ci.yml" - "CHANGELOG.md" - "docs/doctoring/strix-legal-git-paths.md" + - "docs/doctoring/strix-quality-timeout-fixtures.md" - "scripts/ci/strix_quick_gate.sh" - "scripts/ci/test_strix_quick_gate.sh" - "tests/test_strix_changed_path_policy.py" - "tests/test_strix_workflow_dependency_hashes.py" + - "tests/test_strix_quality_timeout_fixture_budget.py" permissions: contents: read @@ -56,11 +58,14 @@ jobs: -r "${RUNNER_TEMP}/strix-quality-requirements.txt" - name: Verify exact-head path policy and syntax + env: + STRIX_TEST_PROCESS_TIMEOUT_SECONDS: "3" + STRIX_TEST_FAKE_SLEEP_SECONDS: "5" 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 -m pytest tests -q bash scripts/ci/test_strix_quick_gate.sh - python -m compileall -q tests/test_strix_changed_path_policy.py tests/test_strix_workflow_dependency_hashes.py + python -m compileall -q tests/test_strix_changed_path_policy.py tests/test_strix_workflow_dependency_hashes.py tests/test_strix_quality_timeout_fixture_budget.py bash -n scripts/ci/strix_quick_gate.sh git diff --exit-code diff --git a/CHANGELOG.md b/CHANGELOG.md index 4215d4d04..bf30091dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ Semantic Versioning where the repository publishes a release. ### Fixed +- Bounded the Strix quality self-test's deterministic timeout fixtures to 3-second process and 5-second fake-sleep budgets so exact-head policy evidence completes inside the existing job limit without changing production Strix scanner timeouts, providers, credentials, or review semantics. - Allowed commas and ASCII parentheses in the bounded Strix changed-file path policy so legal tracked Packrat fixtures can receive exact-head security analysis, while rejecting raw `..` components before normalization and keeping controls, backslashes, whitespace ambiguity, and shell punctuation fail-closed. - Bound each review-agent invocation key to the wrapper's complete canonical payload, including the base branch and requesting actor; altered fields with a valid-format key now fail before durable-leader election or forwarding, and wrapper write permission is job-scoped. - Bound both trusted-uv quality jobs to `github.event.pull_request.head.sha` and added a permanent two-checkout regression contract so exact-head compatibility, coverage, docstring, and compilation claims cannot silently measure GitHub's generated pull-request merge revision. diff --git a/docs/doctoring/strix-quality-timeout-fixtures.md b/docs/doctoring/strix-quality-timeout-fixtures.md new file mode 100644 index 000000000..a9588243b --- /dev/null +++ b/docs/doctoring/strix-quality-timeout-fixtures.md @@ -0,0 +1,51 @@ +# Strix quality timeout-fixture budget + +검토 기준일: **2026-08-07** + +## Incident + +`Strix Changed Path Quality CI`는 실제 Strix 모델 스캔이 아니라 중앙 정책과 `scripts/ci/test_strix_quick_gate.sh`의 결정적 회귀를 검증하는 품질 게이트입니다. 그러나 테스트 하네스의 timeout fixture가 기본적으로 실제 프로세스 제한 30초와 가짜 sleep 60초를 사용하면서 여러 timeout/fallback 경로를 순차 실행했습니다. + +PR #821 exact head `f92784f389317d512376a0725cbd78606b2e832c`의 품질 실행은 저장소 테스트 978개와 subtest 16개를 55.11초에 완료한 뒤 timeout fixture 구간을 수행하다가 job의 10분 제한에서 취소되었습니다. 동일 exact head의 rerun도 같은 단계에서 취소되었습니다. 이 결과는 소스 정책 실패가 아니라 결정적 테스트 fixture의 시간 스케일이 품질 job 예산과 맞지 않는다는 증거입니다. + +## Decision + +품질 workflow의 `Verify exact-head path policy and syntax` 단계에 테스트 전용 환경값만 전달합니다. + +- `STRIX_TEST_PROCESS_TIMEOUT_SECONDS=3` +- `STRIX_TEST_FAKE_SLEEP_SECONDS=5` + +`test_strix_quick_gate.sh`는 이미 두 값을 명시적 테스트 seam으로 제공하며, fake sleep이 process timeout보다 커야 한다고 fail closed 검증합니다. 따라서 timeout, cleanup, fallback의 순서와 결론은 그대로 유지하면서 wall-clock 대기만 축소합니다. + +다음 production scanner 설정은 이 변경에서 건드리지 않습니다. + +- `STRIX_PROCESS_TIMEOUT_SECONDS` +- `STRIX_TOTAL_TIMEOUT_SECONDS` +- `LLM_TIMEOUT` +- 실제 Strix workflow의 90분 process budget과 95분 total budget +- 모델, provider, credential, 권한, changed-path 정책 및 branch-protection 의미 + +테스트 전용 환경값은 해당 품질 step에만 존재해야 하며 production Strix 실행으로 전파되어서는 안 됩니다. + +## Verification contract + +`tests/test_strix_quality_timeout_fixture_budget.py`는 정확한 named step을 분리하여 다음을 고정합니다. + +1. 짧은 process/fake-sleep fixture 값이 모두 존재합니다. +2. 하네스 실행이 같은 step에서 유지됩니다. +3. production timeout 변수는 해당 step에서 override되지 않습니다. +4. workflow trigger가 이 회귀 파일 자체를 포함하여 이후 변경이 정확한 품질 gate를 다시 실행합니다. + +전체 `tests` suite, shell harness, Python compilation, Bash syntax 및 clean-worktree 검증은 계속 같은 exact-head quality step에서 수행합니다. 품질 gate의 성공은 실제 Strix 모델 security review, 독립 승인 또는 branch protection을 대체하지 않습니다. + +## Rollback + +3초/5초 fixture가 GitHub-hosted runner에서 재현 가능한 race margin을 제공하지 못한다는 결정적 실패가 관찰되면 테스트 전용 값만 가장 작은 재현 가능한 상한으로 올립니다. production scanner timeout을 낮추거나 품질 테스트를 삭제하여 문제를 숨기지 않습니다. 10분 job timeout 자체를 늘리는 것은 fixture 가속으로도 완료할 수 없다는 실행 증거가 있을 때 별도 검토합니다. + +## References (APA 7th) + +GitHub. (n.d.). *Workflow syntax for GitHub Actions*. GitHub Docs. Retrieved August 7, 2026, from https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax + +GitHub. (n.d.). *Contexts reference*. GitHub Docs. Retrieved August 7, 2026, from https://docs.github.com/en/actions/reference/workflows-and-actions/contexts + +GitHub. (n.d.). *Viewing job execution time*. GitHub Docs. Retrieved August 7, 2026, from https://docs.github.com/en/actions/how-tos/monitor-workflows/view-job-execution-time diff --git a/tests/test_strix_quality_timeout_fixture_budget.py b/tests/test_strix_quality_timeout_fixture_budget.py new file mode 100644 index 000000000..78fcc8a7a --- /dev/null +++ b/tests/test_strix_quality_timeout_fixture_budget.py @@ -0,0 +1,45 @@ +from pathlib import Path + + +REPO_ROOT = Path(__file__).resolve().parents[1] +WORKFLOW_PATH = REPO_ROOT / ".github" / "workflows" / "strix-changed-path-quality-ci.yml" + + +def _named_step(workflow: str, name: str) -> str: + """Return one exact named workflow step without loading workflow YAML tags.""" + marker = f" - name: {name}\n" + start = workflow.index(marker) + try: + end = workflow.index("\n - name:", start + len(marker)) + except ValueError: + end = len(workflow) + return workflow[start:end] + + +def test_strix_quality_uses_short_fake_process_timeouts() -> None: + """Keep deterministic timeout fixtures well inside the quality-job budget.""" + workflow = WORKFLOW_PATH.read_text(encoding="utf-8") + step = _named_step(workflow, "Verify exact-head path policy and syntax") + + assert 'STRIX_TEST_PROCESS_TIMEOUT_SECONDS: "3"' in step + assert 'STRIX_TEST_FAKE_SLEEP_SECONDS: "5"' in step + assert "bash scripts/ci/test_strix_quick_gate.sh" in step + + +def test_strix_quality_trigger_includes_fixture_contract_paths() -> None: + """Keep fixture behavior and doctoring changes inside the quality trigger.""" + workflow = WORKFLOW_PATH.read_text(encoding="utf-8") + trigger = workflow[: workflow.index("\njobs:")] + + assert "docs/doctoring/strix-quality-timeout-fixtures.md" in trigger + assert "tests/test_strix_quality_timeout_fixture_budget.py" in trigger + + +def test_strix_quality_keeps_real_scanner_budgets_out_of_fixture_overrides() -> None: + """Fixture acceleration must not weaken production Strix scanner timeouts.""" + workflow = WORKFLOW_PATH.read_text(encoding="utf-8") + step = _named_step(workflow, "Verify exact-head path policy and syntax") + + assert "STRIX_PROCESS_TIMEOUT_SECONDS:" not in step + assert "STRIX_TOTAL_TIMEOUT_SECONDS:" not in step + assert "LLM_TIMEOUT:" not in step From 7f9c197e2c03e19330a21bcc11d7066d6a9543f1 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 9 Aug 2026 19:24:39 +0900 Subject: [PATCH 5/7] fix(coverage): preserve LLVM 19 across sandbox runtime --- .github/workflows/opencode-review-dispatch.yml | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/.github/workflows/opencode-review-dispatch.yml b/.github/workflows/opencode-review-dispatch.yml index b17cf3775..de1c4800d 100644 --- a/.github/workflows/opencode-review-dispatch.yml +++ b/.github/workflows/opencode-review-dispatch.yml @@ -660,7 +660,8 @@ jobs: && rm -rf /var/lib/apt/lists/* ENV LLVM_COV=/usr/bin/llvm-cov-19 ENV LLVM_PROFDATA=/usr/bin/llvm-profdata-19 - RUN test -x "$LLVM_COV" && test -x "$LLVM_PROFDATA" + RUN test -x "$LLVM_COV" + RUN test -x "$LLVM_PROFDATA" RUN curl --proto '=https' --tlsv1.2 -fsSLo /tmp/node-linux-x64.tar.xz \ https://nodejs.org/dist/v24.18.0/node-v24.18.0-linux-x64.tar.xz \ && echo '55aa7153f9d88f28d765fcdad5ae6945b5c0f98a36881703817e4c450fa76742 /tmp/node-linux-x64.tar.xz' | sha256sum -c - \ @@ -773,6 +774,8 @@ jobs: --env RUNNER_TEMP=/secure-output \ --env GITHUB_OUTPUT=/secure-output/github-output \ --env GITHUB_STEP_SUMMARY=/secure-output/step-summary \ + --env LLVM_COV=/usr/bin/llvm-cov-19 \ + --env LLVM_PROFDATA=/usr/bin/llvm-profdata-19 \ "$coverage_tool_image" \ /bin/bash /trusted-measure-step.sh || sandbox_status=$? @@ -1713,6 +1716,18 @@ jobs: } ensure_rust_toolchain() { + if [ "${LLVM_COV:-}" != "/usr/bin/llvm-cov-19" ] || \ + [ "${LLVM_PROFDATA:-}" != "/usr/bin/llvm-profdata-19" ] || \ + ! test -x "$LLVM_COV" || ! test -x "$LLVM_PROFDATA"; then + append "### Rust coverage toolchain" + append "" + append "- Result: FAIL" + append "- Reason: the networkless coverage runtime did not preserve the reviewed LLVM 19 tool paths." + append "- Fix: rebuild the trusted coverage image and preserve the exact LLVM bindings at the Docker boundary." + append "" + failures=$((failures + 1)) + return 1 + fi if ! command -v cargo >/dev/null 2>&1; then append "### Rust coverage toolchain" append "" From 2a57f464dfcff85a52740e4cd2d73cc5c4813ef6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 10 Aug 2026 13:24:49 +0900 Subject: [PATCH 6/7] test(coverage): require live Rust toolchain trigger paths --- ...encode_rust_coverage_toolchain_contract.py | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/test_opencode_rust_coverage_toolchain_contract.py b/tests/test_opencode_rust_coverage_toolchain_contract.py index 43979a649..0e5c3d9a8 100644 --- a/tests/test_opencode_rust_coverage_toolchain_contract.py +++ b/tests/test_opencode_rust_coverage_toolchain_contract.py @@ -8,6 +8,9 @@ _REPOSITORY_ROOT = Path(__file__).resolve().parents[1] _WORKFLOW_PATH = _REPOSITORY_ROOT / ".github/workflows/opencode-review-dispatch.yml" +_QUALITY_WORKFLOW_PATH = ( + _REPOSITORY_ROOT / ".github/workflows/opencode-rust-coverage-toolchain-quality-ci.yml" +) _LLVM_COV_PATH = "/usr/bin/llvm-cov-19" _LLVM_PROFDATA_PATH = "/usr/bin/llvm-profdata-19" @@ -84,3 +87,21 @@ def test_isolated_runtime_revalidates_llvm_tools_before_coverage() -> None: assert f'"${{LLVM_PROFDATA:-}}" != "{_LLVM_PROFDATA_PATH}"' in toolchain assert docker_run < llvm_cov_checks[-1] < cargo_coverage_invocation assert docker_run < llvm_profdata_checks[-1] < cargo_coverage_invocation + + +def test_quality_workflow_watched_paths_resolve_to_repository_files() -> None: + """Every exact-path trigger in the permanent quality workflow must exist.""" + + quality_workflow = _QUALITY_WORKFLOW_PATH.read_text(encoding="utf-8") + watched_section = quality_workflow.split(" paths:\n", 1)[1].split( + "\n\npermissions:\n", 1 + )[0] + watched_paths = [ + line.strip()[2:].strip('"') + for line in watched_section.splitlines() + if line.strip().startswith("- ") + ] + + assert watched_paths + for relative_path in watched_paths: + assert (_REPOSITORY_ROOT / relative_path).is_file(), relative_path From c100cca8e2ba5a5c0f7794e088c5a091dc135e8d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 10 Aug 2026 13:25:51 +0900 Subject: [PATCH 7/7] docs(coverage): document LLVM 19 runtime boundary --- ...opencode-rust-coverage-runtime-boundary.md | 114 ++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 docs/doctoring/opencode-rust-coverage-runtime-boundary.md diff --git a/docs/doctoring/opencode-rust-coverage-runtime-boundary.md b/docs/doctoring/opencode-rust-coverage-runtime-boundary.md new file mode 100644 index 000000000..ea43dbc9a --- /dev/null +++ b/docs/doctoring/opencode-rust-coverage-runtime-boundary.md @@ -0,0 +1,114 @@ +# OpenCode Rust coverage LLVM runtime boundary + +## Decision + +The trusted OpenCode coverage sandbox binds Rust coverage to the reviewed LLVM +19 executables shipped by Debian's `llvm-19` package: + +- `LLVM_COV=/usr/bin/llvm-cov-19` +- `LLVM_PROFDATA=/usr/bin/llvm-profdata-19` + +These are compatibility and trust-boundary constants, not caller-selectable +configuration. The coverage image installs `llvm-19`, declares both exact paths, +and fails the image build unless both are executable before the pinned +`cargo-llvm-cov` archive is admitted. The isolated `docker run` passes the same +literal values through the networkless runtime boundary. Inside the container, +`ensure_rust_toolchain()` requires exact string equality and executable files +before the first `cargo llvm-cov` invocation. + +The runtime MUST NOT fall back to unversioned `llvm-cov` or `llvm-profdata`, a +host-runner tool, a pull-request-selected path, or a dynamically downloaded LLVM +binary. Missing, changed, or non-executable reviewed paths are coverage-evidence +failures rather than reasons to measure a different toolchain. + +## Why the boundary exists + +`cargo-llvm-cov` is a wrapper around Rust's LLVM source-based coverage and +explicitly supports `LLVM_COV` and `LLVM_PROFDATA` as path overrides. Its +current project documentation states that the LLVM tools must be compatible +with the LLVM version used by `rustc`. Allowing ambient `PATH` discovery would +therefore make a runner-image change capable of silently changing the coverage +producer. + +Debian bookworm currently publishes the versioned `llvm-19` package from +`llvm-toolchain-19`; Debian package file inventories expose versioned LLVM 19 +tool entry points including `llvm-cov-19`. Pinning the reviewed executable names +inside the image converts that mutable ambient dependency into an explicit +contract that can be checked before source execution. + +## Trust-boundary sequence + +```mermaid +flowchart LR + A["Digest-pinned coverage base image"] --> B["Install Debian llvm-19"] + B --> C["ENV exact LLVM_COV / LLVM_PROFDATA paths"] + C --> D["Build-time test -x for both executables"] + D --> E["Verify pinned cargo-llvm-cov archive"] + E --> F["docker run --network=none with literal LLVM env values"] + F --> G["ensure_rust_toolchain exact-value + executable checks"] + G --> H["cargo llvm-cov"] +``` + +Each arrow is fail-closed. A later stage does not repair or broaden an earlier +stage's failed trust decision. + +## Security and supply-chain implications + +The reviewed paths are fixed in trusted central workflow source. Pull-request +content cannot choose an LLVM package, executable path, download origin, or +runtime environment value. The existing coverage sandbox retains +`--network=none`, credential/Git isolation, exact-head/base materialization, +and the separately checksum-pinned `cargo-llvm-cov` archive. + +This binding narrows reproducibility risk but does not by itself attest Debian's +whole package supply chain or prove a future Rust toolchain is compatible with +LLVM 19. A future rustc or base-image upgrade must revalidate compatibility and +update this contract, its tests, and CHANGELOG in one reviewed change rather +than silently selecting a different binary. + +## Failure and recovery + +If the image cannot install `llvm-19`, either reviewed executable is missing or +non-executable, the runtime value differs from the literal reviewed path, or the +isolated runtime does not receive the values, Rust coverage fails closed before +`cargo llvm-cov` runs. The operator should identify whether the failure comes +from Debian package availability, the pinned image/base generation, a central +workflow regression, or an intentional Rust/LLVM compatibility change. + +Do not work around the failure by removing the exact-value check, using an +unversioned executable, adding network access to the PR runtime, or accepting a +host-provided path. A deliberate toolchain migration requires fresh authoritative +compatibility evidence and the same RED→GREEN exact-head verification sequence. + +## Verification contract + +`tests/test_opencode_rust_coverage_toolchain_contract.py` proves that: + +1. `llvm-19` is provisioned before the pinned `cargo-llvm-cov` archive; +2. the image binds the two exact LLVM 19 executable paths; +3. image construction verifies both executables; +4. the isolated `docker run` receives both literal values before the coverage + image argument; +5. `ensure_rust_toolchain()` revalidates exact values and executability before + Rust coverage; and +6. every exact path named by the permanent quality workflow's + `pull_request.paths` filter resolves to a repository file, preventing a + dangling documentation trigger from becoming invisible debt. + +The permanent quality workflow runs on Python 3.14, checks out the exact PR head, +executes the focused contract, compiles the test, and applies `git diff --check`. +Repository security and supply-chain workflows remain separate authorities. + +## References + +Debian Project. (2026). *Package: llvm-19 (1:19.1.7-3~deb12u1), bookworm*. +Debian Packages. Retrieved August 10, 2026, from +https://packages.debian.org/bookworm/llvm-19 + +Debian Project. (2026). *File list of package llvm-19*. Debian Packages. +Retrieved August 10, 2026, from +https://packages.debian.org/bookworm/amd64/llvm-19/filelist + +Taiki Endo. (2026). *cargo-llvm-cov: Cargo subcommand to use LLVM source-based +code coverage*. GitHub. Retrieved August 10, 2026, from +https://github.com/taiki-e/cargo-llvm-cov