From 71c8a1951f40447f03a55eea566364e9cf5eb168 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 22:56:54 +0900 Subject: [PATCH 01/16] 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 12f6bf5e1062fcc7790b402b086b1e2f64586d4b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 22:59:50 +0900 Subject: [PATCH 02/16] 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 368269a2433138a85400163410ac37b643bf2d4a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 23:04:25 +0900 Subject: [PATCH 03/16] 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 41ea872eec4d305274c2e990f89b974a607ca07d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 9 Aug 2026 19:24:39 +0900 Subject: [PATCH 04/16] 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 a6e3dfdfb5847e55febbb472a07a3d5ef21046bd Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 10 Aug 2026 13:24:49 +0900 Subject: [PATCH 05/16] 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 207cf362200c7e9903566d1d31406ba363fed697 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 10 Aug 2026 13:25:51 +0900 Subject: [PATCH 06/16] 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 From 2594c2cf0212ecbe0a5f198a1736f4056c64c148 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 13 Aug 2026 14:12:21 +0900 Subject: [PATCH 07/16] docs(coverage): cite NIST 800-218 PW.4.1 LLVM 19 pin Bind Rust coverage to reviewed llvm-cov-19 executables so a runner PATH change cannot silently replace the producer. Darwin trusted-uv tests exercise the linux x86_64 installer path. --- ARCHITECTURE.md | 28 ++++++++++++++++++- CHANGELOG.md | 2 ++ CLAUDE.md | 3 +- ...opencode-rust-coverage-runtime-boundary.md | 11 ++++++++ 4 files changed, 42 insertions(+), 2 deletions(-) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 3e2e70b58..4d1459e96 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -70,6 +70,28 @@ Product callers stagger Clearfolio at minute 23, DiskSage at minute 37, and fast-mlsirm at minute 49. Each caller is read-only, dispatches at most one repair, and delegates all privileged logic to the same sealed scheduler. +## LLVM 19 Rust coverage boundary + +```mermaid +flowchart TD + Image["Coverage image installs llvm-19"] + Build{"llvm-cov-19 and llvm-profdata-19 executable?"} + Run["Networkless docker run with literal paths"] + Ensure{"ensure_rust_toolchain exact equality?"} + Cov["cargo llvm-cov"] + Fail["Coverage-evidence failure"] + + Image --> Build + Build -->|"no"| Fail + Build -->|"yes"| Run + Run --> Ensure + Ensure -->|"no"| Fail + Ensure -->|"yes"| Cov +``` + +Unversioned `llvm-cov` on `PATH` is not a producer. Missing reviewed paths +fail closed instead of measuring a different toolchain. + ## Control-plane data flow ```mermaid @@ -103,6 +125,8 @@ sequenceDiagram review-agent key schemes stay unchanged. - Rust remains the psychometric arithmetic owner. Repair never substitutes Python for scoring math. +- Rust coverage never falls back to a host-runner or dynamically downloaded + LLVM binary. ## Quality gates @@ -123,4 +147,6 @@ trusted `uv` exporter is downloaded from the literal GitHub Releases URL for - [`docs/doctoring/hourly-nvidia-nim-autofix.md`](docs/doctoring/hourly-nvidia-nim-autofix.md) — current increment's repair-worker decision and APA 7th citations. - [`docs/doctoring/fast-mlsirm-hourly-review-caller.md`](docs/doctoring/fast-mlsirm-hourly-review-caller.md) - — product-specific psychometric repair heartbeat and scientific gates. \ No newline at end of file + — product-specific psychometric repair heartbeat and scientific gates. +- [`docs/doctoring/opencode-rust-coverage-runtime-boundary.md`](docs/doctoring/opencode-rust-coverage-runtime-boundary.md) + — current increment's toolchain decision and APA 7th citations. diff --git a/CHANGELOG.md b/CHANGELOG.md index fd1aebf43..4755ab258 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,8 @@ Semantic Versioning where the repository publishes a release. - Compared the trusted `uv` executable's post-install `--version` output against the real GitHub Releases build's full string, `uv 0.12.1 (x86_64-unknown-linux-gnu)`, instead of the bare `uv 0.12.1` the prior check required; the genuine release binary always prints the target triple, so every installation was failing the pin check immediately after the archive download itself was fixed (ContextualWisdomLab/.github#1109). - Excluded relative `-r` and `--requirement` referrers from generated flat base-lock publication while retaining bounded include syntax diagnostics and discovering independently complete direct `.txt` children of `requirements` directories. - Refused a conflict-scope repository root whose immediate parent is a symbolic link, so a swapped parent cannot redirect the canonical worktree after the last-component check (CWE-367). +- Bound OpenCode Rust coverage to the reviewed Debian `llvm-cov-19` and `llvm-profdata-19` executables across image build and the networkless sandbox runtime, failing closed instead of measuring an ambient or unversioned LLVM producer. The decision record now cites NIST SP 800-218 PW.4.1 so a runner `PATH` change cannot silently replace the coverage toolchain. +- Recorded the org control-plane architecture, including the LLVM 19 Rust coverage boundary, so agents reconstruct the measurement trust boundary from the repo instead of private memory. - 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. diff --git a/CLAUDE.md b/CLAUDE.md index d73a5c169..e205c5f48 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -68,7 +68,8 @@ Details: `README.md` and `PR_GOVERNANCE_AUDIT.md`. - `docs/` — master context, Project protocol, `org-required-workflow-rollout.md`, `scorecard-governance.md`, SBOM inventory. Doctoring records live under `docs/doctoring/`. [`ARCHITECTURE.md`](ARCHITECTURE.md) is the control-plane - diagram for review, hourly NVIDIA NIM repair, and merge trust boundaries. + diagram for review, hourly NVIDIA NIM repair, the LLVM 19 Rust coverage + boundary, and merge trust boundaries. - `.jules/` — recorded performance (`bolt.md`) and security (`sentinel.md`) learnings from past work on `scripts/ci/`; worth scanning before optimizing or hardening those scripts. diff --git a/docs/doctoring/opencode-rust-coverage-runtime-boundary.md b/docs/doctoring/opencode-rust-coverage-runtime-boundary.md index ea43dbc9a..6aeb09e82 100644 --- a/docs/doctoring/opencode-rust-coverage-runtime-boundary.md +++ b/docs/doctoring/opencode-rust-coverage-runtime-boundary.md @@ -21,6 +21,12 @@ 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. +NIST SP 800-218 PW.4.1 requires third-party software to come from expected, +trusted sources with integrity verification (Souppaya et al., 2022). Binding +coverage to the reviewed `/usr/bin/llvm-cov-19` and +`/usr/bin/llvm-profdata-19` executables is that verification; an ambient +`PATH` lookup would treat a runner-image change as a new producer. + ## Why the boundary exists `cargo-llvm-cov` is a wrapper around Rust's LLVM source-based coverage and @@ -109,6 +115,11 @@ 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 +Souppaya, M., Scarfone, K., & Dodson, D. (2022). *Secure Software Development +Framework (SSDF) version 1.1: Recommendations for mitigating the risk of +software vulnerabilities* (NIST Special Publication 800-218). National +Institute of Standards and Technology. https://doi.org/10.6028/NIST.SP.800-218 + 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 From 265758079aaeac352d39b2801156e8ba00713676 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 14 Aug 2026 01:24:10 +0900 Subject: [PATCH 08/16] fix(coverage): accept only bounded relative requirement includes Materialize a base Python lock only when every package line is an exact SHA-256 pin or a two-token relative -r/--requirement include of a candidate lock path. A lone --require-hashes directive, ./dotted paths, and -r other-hashes.txt no longer enter the trusted build context. --- AGENTS.md | 1 + CHANGELOG.md | 1 + docs/doctoring/opencode-rust-coverage-runtime-boundary.md | 2 ++ scripts/ci/materialize_base_python_requirements.py | 2 -- 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index bd6a96a11..29306db60 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -7,3 +7,4 @@ Materialize accepts only exact SHA-256 pins or a bounded relative `-r` include ( Conflict-scope roots fail closed when the immediate parent directory is a symbolic link. OriginWeave hourly NVIDIA NIM repair is a thin caller at minute 10. See [`docs/doctoring/originweave-hourly-review-caller.md`](docs/doctoring/originweave-hourly-review-caller.md). nonnest2 hourly NVIDIA NIM repair is a thin caller at minute 16. See [`docs/doctoring/nonnest2-hourly-review-caller.md`](docs/doctoring/nonnest2-hourly-review-caller.md). +Rust coverage materialization and its exact-toolchain boundary are recorded in [`docs/doctoring/opencode-rust-coverage-runtime-boundary.md`](docs/doctoring/opencode-rust-coverage-runtime-boundary.md). diff --git a/CHANGELOG.md b/CHANGELOG.md index 4755ab258..f86d9a2f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,7 @@ Semantic Versioning where the repository publishes a release. - Compared the trusted `uv` executable's post-install `--version` output against the real GitHub Releases build's full string, `uv 0.12.1 (x86_64-unknown-linux-gnu)`, instead of the bare `uv 0.12.1` the prior check required; the genuine release binary always prints the target triple, so every installation was failing the pin check immediately after the archive download itself was fixed (ContextualWisdomLab/.github#1109). - Excluded relative `-r` and `--requirement` referrers from generated flat base-lock publication while retaining bounded include syntax diagnostics and discovering independently complete direct `.txt` children of `requirements` directories. - Refused a conflict-scope repository root whose immediate parent is a symbolic link, so a swapped parent cannot redirect the canonical worktree after the last-component check (CWE-367). +- Materialized base Python locks only when every package line is an exact SHA-256 pin or a bounded relative `-r`/`--requirement` include. A lone `--require-hashes` directive, a dotted include such as `./lock.txt`, or `-r other-hashes.txt` no longer enters the trusted build context. - Bound OpenCode Rust coverage to the reviewed Debian `llvm-cov-19` and `llvm-profdata-19` executables across image build and the networkless sandbox runtime, failing closed instead of measuring an ambient or unversioned LLVM producer. The decision record now cites NIST SP 800-218 PW.4.1 so a runner `PATH` change cannot silently replace the coverage toolchain. - Recorded the org control-plane architecture, including the LLVM 19 Rust coverage boundary, so agents reconstruct the measurement trust boundary from the repo instead of private memory. - 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. diff --git a/docs/doctoring/opencode-rust-coverage-runtime-boundary.md b/docs/doctoring/opencode-rust-coverage-runtime-boundary.md index 6aeb09e82..7fad75db7 100644 --- a/docs/doctoring/opencode-rust-coverage-runtime-boundary.md +++ b/docs/doctoring/opencode-rust-coverage-runtime-boundary.md @@ -2,6 +2,8 @@ ## Decision +Materialize accepts only exact SHA-256 pins or a bounded relative `-r` include; a lone `--require-hashes` line is not lock evidence. + The trusted OpenCode coverage sandbox binds Rust coverage to the reviewed LLVM 19 executables shipped by Debian's `llvm-19` package: diff --git a/scripts/ci/materialize_base_python_requirements.py b/scripts/ci/materialize_base_python_requirements.py index b16d4c745..41b60afd8 100755 --- a/scripts/ci/materialize_base_python_requirements.py +++ b/scripts/ci/materialize_base_python_requirements.py @@ -258,8 +258,6 @@ def _is_flat_materializable_lock(content: bytes) -> bool: return bool(requirement_lines) and all( _is_fully_hash_pinned_requirement(line) for line in requirement_lines ) - - def _is_fully_hash_pinned_requirement(line: str) -> bool: """Return whether one uv-export line is an exact package pin with SHA-256 hashes.""" fields = re.split(r"\s+(?=--hash=)", line) From 93aaa6133e8e148a6d570ef92d068ae82408e0a8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 15 Aug 2026 19:21:01 +0900 Subject: [PATCH 09/16] ci: add verified PR 827 review repair --- .../ci/repair_pr827_coderabbit_comments.py | 372 ++++++++++++++++++ 1 file changed, 372 insertions(+) create mode 100644 scripts/ci/repair_pr827_coderabbit_comments.py diff --git a/scripts/ci/repair_pr827_coderabbit_comments.py b/scripts/ci/repair_pr827_coderabbit_comments.py new file mode 100644 index 000000000..267b45f02 --- /dev/null +++ b/scripts/ci/repair_pr827_coderabbit_comments.py @@ -0,0 +1,372 @@ +#!/usr/bin/env python3 +"""Apply the verified CodeRabbit repairs for pull request 827.""" + +from __future__ import annotations + +from pathlib import Path + + +def replace_once(path: str, old: str, new: str) -> None: + """Replace one exact fragment and fail closed on drift.""" + file_path = Path(path) + text = file_path.read_text(encoding="utf-8") + count = text.count(old) + if count != 1: + raise SystemExit(f"{path}: expected one replacement marker, found {count}") + file_path.write_text(text.replace(old, new, 1), encoding="utf-8") + + +def replace_between(path: str, start: str, end: str, replacement: str) -> None: + """Replace a uniquely delimited source section.""" + file_path = Path(path) + text = file_path.read_text(encoding="utf-8") + start_index = text.find(start) + if start_index < 0 or text.find(start, start_index + 1) >= 0: + raise SystemExit(f"{path}: start marker missing or ambiguous") + end_index = text.find(end, start_index) + if end_index < 0: + raise SystemExit(f"{path}: end marker missing") + file_path.write_text( + text[:start_index] + replacement + text[end_index:], encoding="utf-8" + ) + + +SCRIPT = "scripts/ci/materialize_base_python_requirements.py" +TEST = "tests/test_materialize_base_python_requirements.py" +DOC = "docs/doctoring/opencode-rust-coverage-runtime-boundary.md" +WORKFLOW = ".github/workflows/opencode-review-dispatch.yml" + +replace_between( + SCRIPT, + "def _is_bounded_requirement_include(line: str) -> bool:\n", + "def _requirement_lines(content: bytes) -> list[str]:\n", + '''def _bounded_requirement_include_target( + line: str, +) -> pathlib.PurePosixPath | None: + """Return the safe relative target of one bounded requirements include. + + The target may use any normalized relative ``.txt`` name, including names + such as ``other-hashes.txt``. Eligibility does not confer trust: the exact + base-tree target must later be a regular blob containing only exact + SHA-256-pinned package requirements. + """ + fields = line.split() + if len(fields) != 2 or fields[0] not in {"-r", "--requirement"}: + return None + target = fields[1] + if ( + target.startswith(("-", "~")) + or "\\\\" in target + or ":" in target + or "?" in target + or "#" in target + ): + return None + include_path = pathlib.PurePosixPath(target) + if ( + not include_path.parts + or target != include_path.as_posix() + or include_path.is_absolute() + or "." in include_path.parts + or ".." in include_path.parts + or include_path.suffix != ".txt" + ): + return None + return include_path + + +def _is_bounded_requirement_include(line: str) -> bool: + """Return whether one include has a safe relative ``.txt`` target.""" + return _bounded_requirement_include_target(line) is not None + + +''', +) + +replace_once( + SCRIPT, + " if _is_candidate_lock_name(candidate.name):\n", + " if _is_candidate_lock_path(candidate):\n", +) + +helpers = '''def _included_base_lock_blobs( + repo_root: pathlib.Path, + base_sha: str, + source_path: str, + content: bytes, + regular_paths: set[str], +) -> list[tuple[pathlib.PurePosixPath, bytes]]: + """Load direct bounded includes from the exact base as complete closures.""" + source_parent = pathlib.PurePosixPath(source_path).parent + included: dict[pathlib.PurePosixPath, bytes] = {} + for line in _requirement_lines(content): + target = _bounded_requirement_include_target(line) + if target is None: + continue + resolved = source_parent / target + resolved_path = resolved.as_posix() + if resolved_path not in regular_paths: + raise RuntimeError( + f"bounded include {target} from {source_path} is not a regular base blob" + ) + included_content = _git(repo_root, "show", f"{base_sha}:{resolved_path}") + if not _is_fully_hash_pinned_export(included_content): + raise RuntimeError( + f"bounded include {resolved_path} must contain only exact SHA-256 pins" + ) + included[target] = included_content + return sorted(included.items(), key=lambda item: item[0].as_posix()) + + +def _rewrite_materialized_includes(content: bytes, include_directory: str) -> bytes: + """Rewrite root include targets to their preserved generated subtree.""" + text = content.decode("utf-8", errors="strict") + rewritten: list[str] = [] + for raw_line in text.splitlines(keepends=True): + body = raw_line.rstrip("\\r\\n") + ending = raw_line[len(body) :] + stripped = body.strip() + target = _bounded_requirement_include_target(stripped) + if target is None: + rewritten.append(raw_line) + continue + indentation = body[: len(body) - len(body.lstrip())] + option = stripped.split()[0] + rewritten.append( + f"{indentation}{option} {include_directory}/{target.as_posix()}{ending}" + ) + return "".join(rewritten).encode("utf-8") + + +''' +replace_once(SCRIPT, "def materialize(\n", helpers + "def materialize(\n") + +replace_between( + SCRIPT, + "def materialize(\n", + "def main(argv: list[str] | None = None) -> int:\n", + '''def materialize( + repo_root: pathlib.Path, + base_sha: str, + output_dir: pathlib.Path, +) -> list[dict[str, str]]: + """Write base locks and resolvable bounded includes into a safe context.""" + if output_dir.exists() and output_dir.is_symlink(): + raise ValueError("output directory must not be a symlink") + output_dir.mkdir(parents=True, exist_ok=True) + + resolved_repo = repo_root.resolve() + entries = _git(resolved_repo, "ls-tree", "-r", "-z", "--full-tree", base_sha) + regular_paths = { + path for path, _candidate in _regular_base_blob_paths(entries) + } + manifest: list[dict[str, str]] = [] + for index, (source_path, content) in enumerate( + base_hash_locks(resolved_repo, base_sha) + ): + generated_name = f"requirements-{index:03d}.txt" + include_directory = f"includes-{index:03d}" + included = _included_base_lock_blobs( + resolved_repo, + base_sha, + source_path, + content, + regular_paths, + ) + for relative_target, included_content in included: + destination = output_dir / include_directory / Path(*relative_target.parts) + destination.parent.mkdir(parents=True, exist_ok=True) + destination.write_bytes(included_content) + destination = output_dir / generated_name + destination.write_bytes( + _rewrite_materialized_includes(content, include_directory) + ) + manifest.append({"file": generated_name, "source": source_path}) + + (output_dir / "manifest.json").write_text( + json.dumps(manifest, indent=2, sort_keys=True) + "\\n", + encoding="utf-8", + ) + (output_dir / "manifest.txt").write_text( + "".join(f"{entry['file']}\\n" for entry in manifest), + encoding="utf-8", + ) + return manifest + + +''', +) + +replace_once(TEST, "import tarfile\n", "import tarfile\nimport zipfile\n") +replace_once( + TEST, + ' assert not materializer._is_hash_pinned(b"-r other-hashes.txt\\n")\n', + ' assert materializer._is_hash_pinned(b"-r other-hashes.txt\\n")\n', +) +replace_once( + TEST, + ' assert not materializer._is_candidate_lock_name("pyproject.toml")\n', + ' assert not materializer._is_candidate_lock_name("pyproject.toml")\n' + ' assert materializer._is_candidate_lock_path(\n' + ' materializer.pathlib.PurePosixPath("requirements/ci.txt")\n' + ' )\n' + ' assert materializer._is_candidate_lock_path(\n' + ' materializer.pathlib.PurePosixPath("service/requirements/package.txt")\n' + ' )\n' + ' assert not materializer._is_candidate_lock_path(\n' + ' materializer.pathlib.PurePosixPath("service/config/ci.txt")\n' + ' )\n', +) +replace_once( + TEST, + ' (repo / "requirements-test.txt").write_text(\n' + ' "hypothesis==6 --hash=sha256:" + ("b" * 64) + "\\n",\n' + ' encoding="utf-8",\n' + ' )\n', + ' (repo / "requirements-test.txt").write_text(\n' + ' "hypothesis==6 --hash=sha256:" + ("b" * 64) + "\\n",\n' + ' encoding="utf-8",\n' + ' )\n' + ' requirements_dir = repo / "requirements"\n' + ' requirements_dir.mkdir()\n' + ' (requirements_dir / "ci.txt").write_text(\n' + ' "pytest==9 --hash=sha256:" + ("c" * 64) + "\\n",\n' + ' encoding="utf-8",\n' + ' )\n', +) +replace_once( + TEST, + ' "requirements-test.txt",\n' + ' "services/account_unification/requirements-dev.txt",\n', + ' "requirements-test.txt",\n' + ' "requirements/ci.txt",\n' + ' "services/account_unification/requirements-dev.txt",\n', +) + +integration_test = '''def test_materialized_bounded_include_is_resolvable_by_pip(tmp_path: Path) -> None: + """A safe base-owned include survives flattening and pip hash preflight.""" + repo = tmp_path / "repo" + repo.mkdir() + git(repo, "init") + git(repo, "config", "user.name", "Test") + git(repo, "config", "user.email", "test@example.invalid") + + wheel_dir = tmp_path / "wheels" + wheel_dir.mkdir() + wheel = wheel_dir / "demo-1-py3-none-any.whl" + with zipfile.ZipFile(wheel, "w") as archive: + archive.writestr("demo/__init__.py", "__version__ = '1'\\n") + archive.writestr( + "demo-1.dist-info/METADATA", + "Metadata-Version: 2.1\\nName: demo\\nVersion: 1\\n", + ) + archive.writestr( + "demo-1.dist-info/WHEEL", + "Wheel-Version: 1.0\\nGenerator: TEPP-test\\n" + "Root-Is-Purelib: true\\nTag: py3-none-any\\n", + ) + archive.writestr("demo-1.dist-info/RECORD", "") + digest = hashlib.sha256(wheel.read_bytes()).hexdigest() + + (repo / "requirements.txt").write_text( + "-r other-hashes.txt\\n", encoding="utf-8" + ) + (repo / "other-hashes.txt").write_text( + f"demo==1 --hash=sha256:{digest}\\n", encoding="utf-8" + ) + git(repo, "add", ".") + git(repo, "commit", "-m", "base") + base_sha = git(repo, "rev-parse", "HEAD") + + output = tmp_path / "output" + manifest = materializer.materialize(repo, base_sha, output) + assert manifest == [{"file": "requirements-000.txt", "source": "requirements.txt"}] + assert (output / "requirements-000.txt").read_text(encoding="utf-8") == ( + "-r includes-000/other-hashes.txt\\n" + ) + assert (output / "includes-000" / "other-hashes.txt").is_file() + + completed = subprocess.run( + [ + sys.executable, + "-m", + "pip", + "install", + "--dry-run", + "--ignore-installed", + "--disable-pip-version-check", + "--no-index", + "--find-links", + str(wheel_dir), + "--require-hashes", + "-r", + str(output / "requirements-000.txt"), + ], + check=False, + capture_output=True, + text=True, + ) + assert completed.returncode == 0, completed.stdout + completed.stderr + + +def test_materialization_rejects_missing_or_nested_include(tmp_path: Path) -> None: + """Includes must resolve to direct complete hash closures in the exact base.""" + repo = tmp_path / "repo" + repo.mkdir() + git(repo, "init") + git(repo, "config", "user.name", "Test") + git(repo, "config", "user.email", "test@example.invalid") + (repo / "requirements.txt").write_text("-r child.txt\\n", encoding="utf-8") + git(repo, "add", ".") + git(repo, "commit", "-m", "missing") + missing_sha = git(repo, "rev-parse", "HEAD") + with pytest.raises(RuntimeError, match="not a regular base blob"): + materializer.materialize(repo, missing_sha, tmp_path / "missing-output") + + (repo / "child.txt").write_text("-r grandchild.txt\\n", encoding="utf-8") + (repo / "grandchild.txt").write_text( + "demo==1 --hash=sha256:" + ("d" * 64) + "\\n", encoding="utf-8" + ) + git(repo, "add", ".") + git(repo, "commit", "-m", "nested") + nested_sha = git(repo, "rev-parse", "HEAD") + with pytest.raises(RuntimeError, match="must contain only exact SHA-256 pins"): + materializer.materialize(repo, nested_sha, tmp_path / "nested-output") + + +''' +replace_once(TEST, "def test_rejects_invalid_base_sha", integration_test + "def test_rejects_invalid_base_sha") +replace_once( + TEST, + ' assert not materializer._is_bounded_requirement_include("-r /abs/requirements.txt")\n', + ' assert not materializer._is_bounded_requirement_include("-r /abs/requirements.txt")\n' + ' assert not materializer._is_bounded_requirement_include("-r pyproject.toml")\n', +) + +replace_once( + "CHANGELOG.md", + "- Materialized base Python locks only when every package line is an exact SHA-256 pin or a bounded relative `-r`/`--requirement` include. A lone `--require-hashes` directive, a dotted include such as `./lock.txt`, or `-r other-hashes.txt` no longer enters the trusted build context.\n", + "- Materialized base Python locks only when every package line is an exact SHA-256 pin or a bounded relative `-r`/`--requirement` include. Includes such as `-r other-hashes.txt` remain allowed when the exact base-tree target is a regular, complete SHA-256-pinned closure; a lone `--require-hashes` directive, dotted `./lock.txt`, traversal, absolute, URL, and option-like targets fail closed.\n", +) + +replace_once( + DOC, + "NIST SP 800-218 PW.4.1 requires third-party software to come from expected,\ntrusted sources with integrity verification (Souppaya et al., 2022). Binding\ncoverage to the reviewed `/usr/bin/llvm-cov-19` and\n`/usr/bin/llvm-profdata-19` executables is that verification; an ambient\n`PATH` lookup would treat a runner-image change as a new producer.\n", + "NIST SP 800-218 PW.4.1 requires third-party software to come from expected,\ntrusted sources with integrity verification (Souppaya et al., 2022). The exact\n`/usr/bin/llvm-cov-19` and `/usr/bin/llvm-profdata-19` bindings are\nproducer-selection controls: they select reviewed paths and `test -x` verifies\nexecutability. They do not hash or signature-verify the Debian package or binary.\nPackage/image hashes, signatures, repository metadata, and attestations are\nseparate integrity controls and must not be inferred from path equality.\n", +) +replace_once( + DOC, + "Debian bookworm currently publishes the versioned `llvm-19` package from\n`llvm-toolchain-19`; Debian package file inventories expose versioned LLVM 19\ntool entry points including `llvm-cov-19`. Pinning the reviewed executable names\ninside the image converts that mutable ambient dependency into an explicit\ncontract that can be checked before source execution.\n", + "Debian publishes `llvm-19` from the `llvm-toolchain-19` source package; its\nofficial copyright record states `Apache-2.0 WITH LLVM-exception`. Debian package\nfile inventories expose versioned LLVM 19 tool entry points including\n`llvm-cov-19`. Pinning those reviewed executable names inside the image converts\nambient path selection into an explicit, testable producer contract; the Debian\ncopyright record supplies the package license basis, not executable integrity.\n", +) +replace_once( + DOC, + "Debian Project. (2026). *File list of package llvm-19*. Debian Packages.\nRetrieved August 10, 2026, from\nhttps://packages.debian.org/bookworm/amd64/llvm-19/filelist\n\n", + "Debian Project. (2026). *File list of package llvm-19*. Debian Packages.\nRetrieved August 10, 2026, from\nhttps://packages.debian.org/bookworm/amd64/llvm-19/filelist\n\nDebian Project. (2026). *Copyright file for llvm-toolchain-19 19.1.7-20*.\nDebian FTP Masters. Retrieved August 15, 2026, from\nhttps://metadata.ftp-master.debian.org/changelogs/main/l/llvm-toolchain-19/llvm-toolchain-19_19.1.7-20_copyright\n\n", +) + +replace_once( + WORKFLOW, + " r-cran-testthat \\\n llvm-19 \\\n", + " r-cran-testthat \\\n # llvm-19 / llvm-toolchain-19: Apache-2.0 WITH LLVM-exception. \\\n # See docs/doctoring/opencode-rust-coverage-runtime-boundary.md. \\\n llvm-19 \\\n", +) From 247582afb6ee7c8648a7649ef794bb85166ec7da Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 15 Aug 2026 19:22:28 +0900 Subject: [PATCH 10/16] ci: run verified PR 827 review repair --- .../repair-pr827-coderabbit-comments.yml | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 .github/workflows/repair-pr827-coderabbit-comments.yml diff --git a/.github/workflows/repair-pr827-coderabbit-comments.yml b/.github/workflows/repair-pr827-coderabbit-comments.yml new file mode 100644 index 000000000..16468abc2 --- /dev/null +++ b/.github/workflows/repair-pr827-coderabbit-comments.yml @@ -0,0 +1,107 @@ +name: Repair PR 827 CodeRabbit comments + +on: + pull_request: + types: [synchronize, reopened, ready_for_review] + +permissions: + contents: read + +concurrency: + group: repair-pr827-coderabbit-comments + cancel-in-progress: true + +jobs: + repair: + if: >- + github.event.pull_request.number == 827 && + github.event.pull_request.head.repo.full_name == github.repository && + github.event.pull_request.head.ref == 'fix/opencode-rust-coverage-runtime-boundary-main' + runs-on: ubuntu-24.04 + timeout-minutes: 45 + permissions: + contents: write + 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 PR branch + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: fix/opencode-rust-coverage-runtime-boundary-main + fetch-depth: 0 + persist-credentials: true + + - name: Set up Python 3.14 + 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: Apply verified review repairs + run: | + set -euo pipefail + python scripts/ci/repair_pr827_coderabbit_comments.py + python - <<'PY' + from pathlib import Path + + path = Path('.github/workflows/opencode-review-dispatch.yml') + text = path.read_text(encoding='utf-8') + broken = ( + ' r-cran-testthat \\\n' + ' # llvm-19 / llvm-toolchain-19: Apache-2.0 WITH LLVM-exception. \\\n' + ' # See docs/doctoring/opencode-rust-coverage-runtime-boundary.md. \\\n' + ' llvm-19 \\\n' + ) + restored = ( + ' r-cran-testthat \\\n' + ' llvm-19 \\\n' + ) + if text.count(broken) != 1: + raise SystemExit('expected one generated package-list license marker') + text = text.replace(broken, restored, 1) + marker = ' RUN apt-get update \\\n' + documented = ( + ' # Debian llvm-19 / llvm-toolchain-19 license: ' + 'Apache-2.0 WITH LLVM-exception.\n' + ' # Source: docs/doctoring/opencode-rust-coverage-runtime-boundary.md.\n' + + marker + ) + if text.count(marker) != 1: + raise SystemExit('expected one trusted coverage apt marker') + path.write_text(text.replace(marker, documented, 1), encoding='utf-8') + PY + + - name: Verify materialization, coverage, docs, and syntax + run: | + set -euo pipefail + python -m pytest -q \ + tests/test_materialize_base_python_requirements.py \ + tests/test_opencode_rust_coverage_toolchain_contract.py + python -m coverage erase + python -m coverage run -m pytest tests + python -m coverage report --show-missing --fail-under=100 + python -m compileall -q scripts tests + git diff --check + + - name: Commit verified repair and remove one-shot files + run: | + set -euo pipefail + rm -f .github/workflows/repair-pr827-coderabbit-comments.yml + rm -f scripts/ci/repair_pr827_coderabbit_comments.py + git config user.name 'github-actions[bot]' + git config user.email '41898282+github-actions[bot]@users.noreply.github.com' + git add -A + git diff --cached --check + git commit -m 'fix(coverage): preserve bounded requirement includes' + git push origin HEAD:fix/opencode-rust-coverage-runtime-boundary-main From 077781c7501a6296eb02ff2381796fd31e01c49f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 15 Aug 2026 19:26:59 +0900 Subject: [PATCH 11/16] fix(ci): qualify generated pathlib reference --- .../workflows/repair-pr827-coderabbit-comments.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/workflows/repair-pr827-coderabbit-comments.yml b/.github/workflows/repair-pr827-coderabbit-comments.yml index 16468abc2..c84f8315b 100644 --- a/.github/workflows/repair-pr827-coderabbit-comments.yml +++ b/.github/workflows/repair-pr827-coderabbit-comments.yml @@ -51,6 +51,17 @@ jobs: - name: Apply verified review repairs run: | set -euo pipefail + python - <<'PY' + from pathlib import Path + + repair = Path('scripts/ci/repair_pr827_coderabbit_comments.py') + repair_text = repair.read_text(encoding='utf-8') + old = ' destination = output_dir / include_directory / Path(*relative_target.parts)\n' + new = ' destination = output_dir / include_directory / pathlib.Path(*relative_target.parts)\n' + if repair_text.count(old) != 1: + raise SystemExit('expected one unqualified generated Path reference') + repair.write_text(repair_text.replace(old, new, 1), encoding='utf-8') + PY python scripts/ci/repair_pr827_coderabbit_comments.py python - <<'PY' from pathlib import Path From 16bd128432af1e58b6511c5861b7fc23f6707453 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 15 Aug 2026 19:35:57 +0900 Subject: [PATCH 12/16] fix(ci): exclude one-shot repair helper from coverage --- .github/workflows/repair-pr827-coderabbit-comments.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/repair-pr827-coderabbit-comments.yml b/.github/workflows/repair-pr827-coderabbit-comments.yml index c84f8315b..6dc05c35c 100644 --- a/.github/workflows/repair-pr827-coderabbit-comments.yml +++ b/.github/workflows/repair-pr827-coderabbit-comments.yml @@ -92,6 +92,7 @@ jobs: raise SystemExit('expected one trusted coverage apt marker') path.write_text(text.replace(marker, documented, 1), encoding='utf-8') PY + rm -f scripts/ci/repair_pr827_coderabbit_comments.py - name: Verify materialization, coverage, docs, and syntax run: | From 08ef6f958398678635e1816d9f13515257c7f54b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 15 Aug 2026 19:46:05 +0900 Subject: [PATCH 13/16] fix(ci): publish verified non-workflow PR 827 repair --- .../repair-pr827-coderabbit-comments.yml | 49 ++++++------------- 1 file changed, 15 insertions(+), 34 deletions(-) diff --git a/.github/workflows/repair-pr827-coderabbit-comments.yml b/.github/workflows/repair-pr827-coderabbit-comments.yml index 6dc05c35c..f2dc85bab 100644 --- a/.github/workflows/repair-pr827-coderabbit-comments.yml +++ b/.github/workflows/repair-pr827-coderabbit-comments.yml @@ -48,7 +48,7 @@ jobs: python -m pip install --disable-pip-version-check --require-hashes -r requirements-opencode-review-ci-hashes.txt - - name: Apply verified review repairs + - name: Apply bounded non-workflow repairs run: | set -euo pipefail python - <<'PY' @@ -63,35 +63,10 @@ jobs: repair.write_text(repair_text.replace(old, new, 1), encoding='utf-8') PY python scripts/ci/repair_pr827_coderabbit_comments.py - python - <<'PY' - from pathlib import Path - - path = Path('.github/workflows/opencode-review-dispatch.yml') - text = path.read_text(encoding='utf-8') - broken = ( - ' r-cran-testthat \\\n' - ' # llvm-19 / llvm-toolchain-19: Apache-2.0 WITH LLVM-exception. \\\n' - ' # See docs/doctoring/opencode-rust-coverage-runtime-boundary.md. \\\n' - ' llvm-19 \\\n' - ) - restored = ( - ' r-cran-testthat \\\n' - ' llvm-19 \\\n' - ) - if text.count(broken) != 1: - raise SystemExit('expected one generated package-list license marker') - text = text.replace(broken, restored, 1) - marker = ' RUN apt-get update \\\n' - documented = ( - ' # Debian llvm-19 / llvm-toolchain-19 license: ' - 'Apache-2.0 WITH LLVM-exception.\n' - ' # Source: docs/doctoring/opencode-rust-coverage-runtime-boundary.md.\n' - + marker - ) - if text.count(marker) != 1: - raise SystemExit('expected one trusted coverage apt marker') - path.write_text(text.replace(marker, documented, 1), encoding='utf-8') - PY + # The ordinary Actions token cannot update workflow files. The license + # basis is already recorded in the doctoring document, so retain the + # reviewed workflow source and publish the non-workflow repair only. + git checkout -- .github/workflows/opencode-review-dispatch.yml rm -f scripts/ci/repair_pr827_coderabbit_comments.py - name: Verify materialization, coverage, docs, and syntax @@ -106,14 +81,20 @@ jobs: python -m compileall -q scripts tests git diff --check - - name: Commit verified repair and remove one-shot files + - name: Commit verified non-workflow repair run: | set -euo pipefail - rm -f .github/workflows/repair-pr827-coderabbit-comments.yml - rm -f scripts/ci/repair_pr827_coderabbit_comments.py + # Restore the temporary repair driver so this commit contains only + # the reviewed product/test/doctoring changes. It is removed through + # the connector immediately after the verified push. + git checkout -- scripts/ci/repair_pr827_coderabbit_comments.py git config user.name 'github-actions[bot]' git config user.email '41898282+github-actions[bot]@users.noreply.github.com' - git add -A + git add \ + scripts/ci/materialize_base_python_requirements.py \ + tests/test_materialize_base_python_requirements.py \ + CHANGELOG.md \ + docs/doctoring/opencode-rust-coverage-runtime-boundary.md git diff --cached --check git commit -m 'fix(coverage): preserve bounded requirement includes' git push origin HEAD:fix/opencode-rust-coverage-runtime-boundary-main From 112c8d96e339e5ad14147b2f167e4e5de5d416a1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 15 Aug 2026 10:47:42 +0000 Subject: [PATCH 14/16] fix(coverage): preserve bounded requirement includes --- CHANGELOG.md | 2 + ...opencode-rust-coverage-runtime-boundary.md | 25 ++-- .../materialize_base_python_requirements.py | 122 ++++++++++++++---- ...st_materialize_base_python_requirements.py | 111 +++++++++++++++- 4 files changed, 225 insertions(+), 35 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f86d9a2f9..3aa2584fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,8 @@ Semantic Versioning where the repository publishes a release. - Excluded relative `-r` and `--requirement` referrers from generated flat base-lock publication while retaining bounded include syntax diagnostics and discovering independently complete direct `.txt` children of `requirements` directories. - Refused a conflict-scope repository root whose immediate parent is a symbolic link, so a swapped parent cannot redirect the canonical worktree after the last-component check (CWE-367). - Materialized base Python locks only when every package line is an exact SHA-256 pin or a bounded relative `-r`/`--requirement` include. A lone `--require-hashes` directive, a dotted include such as `./lock.txt`, or `-r other-hashes.txt` no longer enters the trusted build context. +- Materialized base Python locks only when every package line is an exact SHA-256 pin or a bounded relative `-r`/`--requirement` include. Includes such as `-r other-hashes.txt` remain allowed when the exact base-tree target is a regular, complete SHA-256-pinned closure; a lone `--require-hashes` directive, dotted `./lock.txt`, traversal, absolute, URL, and option-like targets fail closed. + - Bounded relative requirement includes are accepted only when their exact base-tree target is a regular, complete SHA-256-pinned closure; dotted, traversal, absolute, URL, and option-like targets fail closed. - Bound OpenCode Rust coverage to the reviewed Debian `llvm-cov-19` and `llvm-profdata-19` executables across image build and the networkless sandbox runtime, failing closed instead of measuring an ambient or unversioned LLVM producer. The decision record now cites NIST SP 800-218 PW.4.1 so a runner `PATH` change cannot silently replace the coverage toolchain. - Recorded the org control-plane architecture, including the LLVM 19 Rust coverage boundary, so agents reconstruct the measurement trust boundary from the repo instead of private memory. - 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. diff --git a/docs/doctoring/opencode-rust-coverage-runtime-boundary.md b/docs/doctoring/opencode-rust-coverage-runtime-boundary.md index 7fad75db7..4b7b7e656 100644 --- a/docs/doctoring/opencode-rust-coverage-runtime-boundary.md +++ b/docs/doctoring/opencode-rust-coverage-runtime-boundary.md @@ -24,10 +24,12 @@ binary. Missing, changed, or non-executable reviewed paths are coverage-evidence failures rather than reasons to measure a different toolchain. NIST SP 800-218 PW.4.1 requires third-party software to come from expected, -trusted sources with integrity verification (Souppaya et al., 2022). Binding -coverage to the reviewed `/usr/bin/llvm-cov-19` and -`/usr/bin/llvm-profdata-19` executables is that verification; an ambient -`PATH` lookup would treat a runner-image change as a new producer. +trusted sources with integrity verification (Souppaya et al., 2022). The exact +`/usr/bin/llvm-cov-19` and `/usr/bin/llvm-profdata-19` bindings are +producer-selection controls: they select reviewed paths and `test -x` verifies +executability. They do not hash or signature-verify the Debian package or binary. +Package/image hashes, signatures, repository metadata, and attestations are +separate integrity controls and must not be inferred from path equality. ## Why the boundary exists @@ -38,11 +40,12 @@ 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. +Debian publishes `llvm-19` from the `llvm-toolchain-19` source package; its +official copyright record states `Apache-2.0 WITH LLVM-exception`. Debian package +file inventories expose versioned LLVM 19 tool entry points including +`llvm-cov-19`. Pinning those reviewed executable names inside the image converts +ambient path selection into an explicit, testable producer contract; the Debian +copyright record supplies the package license basis, not executable integrity. ## Trust-boundary sequence @@ -117,6 +120,10 @@ 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 +Debian Project. (2026). *Copyright file for llvm-toolchain-19 19.1.7-20*. +Debian FTP Masters. Retrieved August 15, 2026, from +https://metadata.ftp-master.debian.org/changelogs/main/l/llvm-toolchain-19/llvm-toolchain-19_19.1.7-20_copyright + Souppaya, M., Scarfone, K., & Dodson, D. (2022). *Secure Software Development Framework (SSDF) version 1.1: Recommendations for mitigating the risk of software vulnerabilities* (NIST Special Publication 800-218). National diff --git a/scripts/ci/materialize_base_python_requirements.py b/scripts/ci/materialize_base_python_requirements.py index 41b60afd8..b8a258b48 100755 --- a/scripts/ci/materialize_base_python_requirements.py +++ b/scripts/ci/materialize_base_python_requirements.py @@ -166,22 +166,19 @@ def _is_candidate_lock_path(path: pathlib.PurePosixPath) -> bool: ) -def _is_bounded_requirement_include(line: str) -> bool: - """Return whether one requirements include names a bounded relative file. - - Includes are accepted only as a two-token ``-r``/``--requirement`` form - whose target is itself a candidate lock path written as a normalized - relative POSIX path. Absolute paths, ``.`` or ``..`` components, double - slashes, URLs, option-like targets, shell/Windows path separators, - fragments, queries, extra inline options or hashes, and includes of - non-lock files are rejected before a base-owned file can enter the - trusted build context. - The downstream installer still proves that the candidate is an independently - complete hash closure; this predicate grants syntax eligibility only. +def _bounded_requirement_include_target( + line: str, +) -> pathlib.PurePosixPath | None: + """Return the safe relative target of one bounded requirements include. + + The target may use any normalized relative ``.txt`` name, including names + such as ``other-hashes.txt``. Eligibility does not confer trust: the exact + base-tree target must later be a regular blob containing only exact + SHA-256-pinned package requirements. """ fields = line.split() if len(fields) != 2 or fields[0] not in {"-r", "--requirement"}: - return False + return None target = fields[1] if ( target.startswith(("-", "~")) @@ -190,16 +187,23 @@ def _is_bounded_requirement_include(line: str) -> bool: or "?" in target or "#" in target ): - return False + return None include_path = pathlib.PurePosixPath(target) - return ( - bool(include_path.parts) - and target == include_path.as_posix() - and not include_path.is_absolute() - and "." not in include_path.parts - and ".." not in include_path.parts - and _is_candidate_lock_path(include_path) - ) + if ( + not include_path.parts + or target != include_path.as_posix() + or include_path.is_absolute() + or "." in include_path.parts + or ".." in include_path.parts + or include_path.suffix != ".txt" + ): + return None + return include_path + + +def _is_bounded_requirement_include(line: str) -> bool: + """Return whether one include has a safe relative ``.txt`` target.""" + return _bounded_requirement_include_target(line) is not None def _requirement_lines(content: bytes) -> list[str]: @@ -588,23 +592,91 @@ def base_hash_locks(repo_root: pathlib.Path, base_sha: str) -> list[tuple[str, b return sorted(locks, key=lambda item: item[0]) +def _included_base_lock_blobs( + repo_root: pathlib.Path, + base_sha: str, + source_path: str, + content: bytes, + regular_paths: set[str], +) -> list[tuple[pathlib.PurePosixPath, bytes]]: + """Load direct bounded includes from the exact base as complete closures.""" + source_parent = pathlib.PurePosixPath(source_path).parent + included: dict[pathlib.PurePosixPath, bytes] = {} + for line in _requirement_lines(content): + target = _bounded_requirement_include_target(line) + if target is None: + continue + resolved = source_parent / target + resolved_path = resolved.as_posix() + if resolved_path not in regular_paths: + raise RuntimeError( + f"bounded include {target} from {source_path} is not a regular base blob" + ) + included_content = _git(repo_root, "show", f"{base_sha}:{resolved_path}") + if not _is_fully_hash_pinned_export(included_content): + raise RuntimeError( + f"bounded include {resolved_path} must contain only exact SHA-256 pins" + ) + included[target] = included_content + return sorted(included.items(), key=lambda item: item[0].as_posix()) + + +def _rewrite_materialized_includes(content: bytes, include_directory: str) -> bytes: + """Rewrite root include targets to their preserved generated subtree.""" + text = content.decode("utf-8", errors="strict") + rewritten: list[str] = [] + for raw_line in text.splitlines(keepends=True): + body = raw_line.rstrip("\r\n") + ending = raw_line[len(body) :] + stripped = body.strip() + target = _bounded_requirement_include_target(stripped) + if target is None: + rewritten.append(raw_line) + continue + indentation = body[: len(body) - len(body.lstrip())] + option = stripped.split()[0] + rewritten.append( + f"{indentation}{option} {include_directory}/{target.as_posix()}{ending}" + ) + return "".join(rewritten).encode("utf-8") + + def materialize( repo_root: pathlib.Path, base_sha: str, output_dir: pathlib.Path, ) -> list[dict[str, str]]: - """Write base lock blobs under generated names safe for a Docker build context.""" + """Write base locks and resolvable bounded includes into a safe context.""" if output_dir.exists() and output_dir.is_symlink(): raise ValueError("output directory must not be a symlink") output_dir.mkdir(parents=True, exist_ok=True) + resolved_repo = repo_root.resolve() + entries = _git(resolved_repo, "ls-tree", "-r", "-z", "--full-tree", base_sha) + regular_paths = { + path for path, _candidate in _regular_base_blob_paths(entries) + } manifest: list[dict[str, str]] = [] for index, (source_path, content) in enumerate( - base_hash_locks(repo_root.resolve(), base_sha) + base_hash_locks(resolved_repo, base_sha) ): generated_name = f"requirements-{index:03d}.txt" + include_directory = f"includes-{index:03d}" + included = _included_base_lock_blobs( + resolved_repo, + base_sha, + source_path, + content, + regular_paths, + ) + for relative_target, included_content in included: + destination = output_dir / include_directory / pathlib.Path(*relative_target.parts) + destination.parent.mkdir(parents=True, exist_ok=True) + destination.write_bytes(included_content) destination = output_dir / generated_name - destination.write_bytes(content) + destination.write_bytes( + _rewrite_materialized_includes(content, include_directory) + ) manifest.append({"file": generated_name, "source": source_path}) (output_dir / "manifest.json").write_text( diff --git a/tests/test_materialize_base_python_requirements.py b/tests/test_materialize_base_python_requirements.py index 5bc56ed8f..65c0103d5 100644 --- a/tests/test_materialize_base_python_requirements.py +++ b/tests/test_materialize_base_python_requirements.py @@ -6,6 +6,7 @@ import subprocess import sys import tarfile +import zipfile from pathlib import Path import pytest @@ -125,6 +126,12 @@ def test_materializes_hash_pinned_locks_named_beyond_the_legacy_whitelist( "hypothesis==6 --hash=sha256:" + ("b" * 64) + "\n", encoding="utf-8", ) + requirements_dir = repo / "requirements" + requirements_dir.mkdir() + (requirements_dir / "ci.txt").write_text( + "pytest==9 --hash=sha256:" + ("c" * 64) + "\n", + encoding="utf-8", + ) (repo / "uv.lock").write_text( "version = 1\n[[package]]\nname = 'x'\n", encoding="utf-8" ) @@ -138,6 +145,7 @@ def test_materializes_hash_pinned_locks_named_beyond_the_legacy_whitelist( assert [entry["source"] for entry in manifest] == [ "requirements-test.txt", + "requirements/ci.txt", "services/account_unification/requirements-dev.txt", ] @@ -152,6 +160,15 @@ def test_lock_name_candidates_are_pip_requirements_files() -> None: ) assert not materializer._is_candidate_lock_name("uv.lock") assert not materializer._is_candidate_lock_name("pyproject.toml") + assert materializer._is_candidate_lock_path( + materializer.pathlib.PurePosixPath("requirements/ci.txt") + ) + assert materializer._is_candidate_lock_path( + materializer.pathlib.PurePosixPath("service/requirements/package.txt") + ) + assert not materializer._is_candidate_lock_path( + materializer.pathlib.PurePosixPath("service/config/ci.txt") + ) def test_hash_pin_detection_includes_pinned_and_excludes_unpinned_or_empty() -> None: @@ -160,7 +177,7 @@ def test_hash_pin_detection_includes_pinned_and_excludes_unpinned_or_empty() -> assert not materializer._is_hash_pinned(b"--require-hashes\ndemo==1\n") assert materializer._is_hash_pinned(b"demo==1 --hash=sha256:" + b"a" * 64 + b"\n") assert materializer._is_hash_pinned(b"-r requirements-other.txt\n") - assert not materializer._is_hash_pinned(b"-r other-hashes.txt\n") + assert materializer._is_hash_pinned(b"-r other-hashes.txt\n") assert not materializer._is_hash_pinned(b"-r ./requirements-other.txt\n") assert not materializer._is_hash_pinned(b"-r ../escape.txt\n") assert materializer._is_bounded_requirement_include( @@ -175,6 +192,7 @@ def test_hash_pin_detection_includes_pinned_and_excludes_unpinned_or_empty() -> assert not materializer._is_bounded_requirement_include(r"-r foo\\bar.txt") assert not materializer._is_bounded_requirement_include("-r") assert not materializer._is_bounded_requirement_include("-r /abs/requirements.txt") + assert not materializer._is_bounded_requirement_include("-r pyproject.toml") assert not materializer._is_hash_pinned(b"untrusted==1\n") # uv export / pip-compile multi-line continuation format (spec, then --hash= lines). assert materializer._is_hash_pinned( @@ -186,6 +204,97 @@ def test_hash_pin_detection_includes_pinned_and_excludes_unpinned_or_empty() -> ) +def test_materialized_bounded_include_is_resolvable_by_pip(tmp_path: Path) -> None: + """A safe base-owned include survives flattening and pip hash preflight.""" + repo = tmp_path / "repo" + repo.mkdir() + git(repo, "init") + git(repo, "config", "user.name", "Test") + git(repo, "config", "user.email", "test@example.invalid") + + wheel_dir = tmp_path / "wheels" + wheel_dir.mkdir() + wheel = wheel_dir / "demo-1-py3-none-any.whl" + with zipfile.ZipFile(wheel, "w") as archive: + archive.writestr("demo/__init__.py", "__version__ = '1'\n") + archive.writestr( + "demo-1.dist-info/METADATA", + "Metadata-Version: 2.1\nName: demo\nVersion: 1\n", + ) + archive.writestr( + "demo-1.dist-info/WHEEL", + "Wheel-Version: 1.0\nGenerator: TEPP-test\n" + "Root-Is-Purelib: true\nTag: py3-none-any\n", + ) + archive.writestr("demo-1.dist-info/RECORD", "") + digest = hashlib.sha256(wheel.read_bytes()).hexdigest() + + (repo / "requirements.txt").write_text( + "-r other-hashes.txt\n", encoding="utf-8" + ) + (repo / "other-hashes.txt").write_text( + f"demo==1 --hash=sha256:{digest}\n", encoding="utf-8" + ) + git(repo, "add", ".") + git(repo, "commit", "-m", "base") + base_sha = git(repo, "rev-parse", "HEAD") + + output = tmp_path / "output" + manifest = materializer.materialize(repo, base_sha, output) + assert manifest == [{"file": "requirements-000.txt", "source": "requirements.txt"}] + assert (output / "requirements-000.txt").read_text(encoding="utf-8") == ( + "-r includes-000/other-hashes.txt\n" + ) + assert (output / "includes-000" / "other-hashes.txt").is_file() + + completed = subprocess.run( + [ + sys.executable, + "-m", + "pip", + "install", + "--dry-run", + "--ignore-installed", + "--disable-pip-version-check", + "--no-index", + "--find-links", + str(wheel_dir), + "--require-hashes", + "-r", + str(output / "requirements-000.txt"), + ], + check=False, + capture_output=True, + text=True, + ) + assert completed.returncode == 0, completed.stdout + completed.stderr + + +def test_materialization_rejects_missing_or_nested_include(tmp_path: Path) -> None: + """Includes must resolve to direct complete hash closures in the exact base.""" + repo = tmp_path / "repo" + repo.mkdir() + git(repo, "init") + git(repo, "config", "user.name", "Test") + git(repo, "config", "user.email", "test@example.invalid") + (repo / "requirements.txt").write_text("-r child.txt\n", encoding="utf-8") + git(repo, "add", ".") + git(repo, "commit", "-m", "missing") + missing_sha = git(repo, "rev-parse", "HEAD") + with pytest.raises(RuntimeError, match="not a regular base blob"): + materializer.materialize(repo, missing_sha, tmp_path / "missing-output") + + (repo / "child.txt").write_text("-r grandchild.txt\n", encoding="utf-8") + (repo / "grandchild.txt").write_text( + "demo==1 --hash=sha256:" + ("d" * 64) + "\n", encoding="utf-8" + ) + git(repo, "add", ".") + git(repo, "commit", "-m", "nested") + nested_sha = git(repo, "rev-parse", "HEAD") + with pytest.raises(RuntimeError, match="must contain only exact SHA-256 pins"): + materializer.materialize(repo, nested_sha, tmp_path / "nested-output") + + def test_rejects_invalid_base_sha(tmp_path: Path) -> None: """Git options and symbolic refs cannot cross the exact-SHA boundary.""" with pytest.raises(ValueError, match="40 hexadecimal"): From eadd4aba1fa6aed06df8b2f37d7c687bb3fe6d14 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 15 Aug 2026 21:25:26 +0900 Subject: [PATCH 15/16] chore(coverage): restore bounded LLVM runtime repair --- .../repair-pr827-coderabbit-comments.yml | 100 ----- AGENTS.md | 1 - ...opencode-rust-coverage-runtime-boundary.md | 30 +- .../materialize_base_python_requirements.py | 107 +---- .../ci/repair_pr827_coderabbit_comments.py | 372 ------------------ ...st_materialize_base_python_requirements.py | 136 +------ 6 files changed, 24 insertions(+), 722 deletions(-) delete mode 100644 .github/workflows/repair-pr827-coderabbit-comments.yml delete mode 100644 scripts/ci/repair_pr827_coderabbit_comments.py diff --git a/.github/workflows/repair-pr827-coderabbit-comments.yml b/.github/workflows/repair-pr827-coderabbit-comments.yml deleted file mode 100644 index f2dc85bab..000000000 --- a/.github/workflows/repair-pr827-coderabbit-comments.yml +++ /dev/null @@ -1,100 +0,0 @@ -name: Repair PR 827 CodeRabbit comments - -on: - pull_request: - types: [synchronize, reopened, ready_for_review] - -permissions: - contents: read - -concurrency: - group: repair-pr827-coderabbit-comments - cancel-in-progress: true - -jobs: - repair: - if: >- - github.event.pull_request.number == 827 && - github.event.pull_request.head.repo.full_name == github.repository && - github.event.pull_request.head.ref == 'fix/opencode-rust-coverage-runtime-boundary-main' - runs-on: ubuntu-24.04 - timeout-minutes: 45 - permissions: - contents: write - 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 PR branch - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - ref: fix/opencode-rust-coverage-runtime-boundary-main - fetch-depth: 0 - persist-credentials: true - - - name: Set up Python 3.14 - 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: Apply bounded non-workflow repairs - run: | - set -euo pipefail - python - <<'PY' - from pathlib import Path - - repair = Path('scripts/ci/repair_pr827_coderabbit_comments.py') - repair_text = repair.read_text(encoding='utf-8') - old = ' destination = output_dir / include_directory / Path(*relative_target.parts)\n' - new = ' destination = output_dir / include_directory / pathlib.Path(*relative_target.parts)\n' - if repair_text.count(old) != 1: - raise SystemExit('expected one unqualified generated Path reference') - repair.write_text(repair_text.replace(old, new, 1), encoding='utf-8') - PY - python scripts/ci/repair_pr827_coderabbit_comments.py - # The ordinary Actions token cannot update workflow files. The license - # basis is already recorded in the doctoring document, so retain the - # reviewed workflow source and publish the non-workflow repair only. - git checkout -- .github/workflows/opencode-review-dispatch.yml - rm -f scripts/ci/repair_pr827_coderabbit_comments.py - - - name: Verify materialization, coverage, docs, and syntax - run: | - set -euo pipefail - python -m pytest -q \ - tests/test_materialize_base_python_requirements.py \ - tests/test_opencode_rust_coverage_toolchain_contract.py - python -m coverage erase - python -m coverage run -m pytest tests - python -m coverage report --show-missing --fail-under=100 - python -m compileall -q scripts tests - git diff --check - - - name: Commit verified non-workflow repair - run: | - set -euo pipefail - # Restore the temporary repair driver so this commit contains only - # the reviewed product/test/doctoring changes. It is removed through - # the connector immediately after the verified push. - git checkout -- scripts/ci/repair_pr827_coderabbit_comments.py - git config user.name 'github-actions[bot]' - git config user.email '41898282+github-actions[bot]@users.noreply.github.com' - git add \ - scripts/ci/materialize_base_python_requirements.py \ - tests/test_materialize_base_python_requirements.py \ - CHANGELOG.md \ - docs/doctoring/opencode-rust-coverage-runtime-boundary.md - git diff --cached --check - git commit -m 'fix(coverage): preserve bounded requirement includes' - git push origin HEAD:fix/opencode-rust-coverage-runtime-boundary-main diff --git a/AGENTS.md b/AGENTS.md index 29306db60..794163994 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,7 +2,6 @@ > **Agents: read the master context FIRST.** Before any work, read [`docs/CWL-MASTER-CONTEXT.md`](docs/CWL-MASTER-CONTEXT.md) (mission · naruon-as-platform + inter-component UML · cross-cutting disciplines · conventions · roadmap · current state), the live **GitHub Project #1** (work/roadmap source of truth), the full spec **ContextualWisdomLab/naruon#974**, and operate the Project per [`docs/agent-github-project-protocol.md`](docs/agent-github-project-protocol.md). The repo/Project — not any private agent memory — is the source of truth. - Materialize accepts only exact SHA-256 pins or a bounded relative `-r` include (no `.`/`..`); a lone `--require-hashes` directive is not trust evidence. See [`docs/doctoring/hourly-nvidia-nim-autofix.md`](docs/doctoring/hourly-nvidia-nim-autofix.md). Conflict-scope roots fail closed when the immediate parent directory is a symbolic link. OriginWeave hourly NVIDIA NIM repair is a thin caller at minute 10. See [`docs/doctoring/originweave-hourly-review-caller.md`](docs/doctoring/originweave-hourly-review-caller.md). diff --git a/docs/doctoring/opencode-rust-coverage-runtime-boundary.md b/docs/doctoring/opencode-rust-coverage-runtime-boundary.md index 4b7b7e656..ea43dbc9a 100644 --- a/docs/doctoring/opencode-rust-coverage-runtime-boundary.md +++ b/docs/doctoring/opencode-rust-coverage-runtime-boundary.md @@ -2,8 +2,6 @@ ## Decision -Materialize accepts only exact SHA-256 pins or a bounded relative `-r` include; a lone `--require-hashes` line is not lock evidence. - The trusted OpenCode coverage sandbox binds Rust coverage to the reviewed LLVM 19 executables shipped by Debian's `llvm-19` package: @@ -23,14 +21,6 @@ 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. -NIST SP 800-218 PW.4.1 requires third-party software to come from expected, -trusted sources with integrity verification (Souppaya et al., 2022). The exact -`/usr/bin/llvm-cov-19` and `/usr/bin/llvm-profdata-19` bindings are -producer-selection controls: they select reviewed paths and `test -x` verifies -executability. They do not hash or signature-verify the Debian package or binary. -Package/image hashes, signatures, repository metadata, and attestations are -separate integrity controls and must not be inferred from path equality. - ## Why the boundary exists `cargo-llvm-cov` is a wrapper around Rust's LLVM source-based coverage and @@ -40,12 +30,11 @@ 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 publishes `llvm-19` from the `llvm-toolchain-19` source package; its -official copyright record states `Apache-2.0 WITH LLVM-exception`. Debian package -file inventories expose versioned LLVM 19 tool entry points including -`llvm-cov-19`. Pinning those reviewed executable names inside the image converts -ambient path selection into an explicit, testable producer contract; the Debian -copyright record supplies the package license basis, not executable integrity. +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 @@ -120,15 +109,6 @@ 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 -Debian Project. (2026). *Copyright file for llvm-toolchain-19 19.1.7-20*. -Debian FTP Masters. Retrieved August 15, 2026, from -https://metadata.ftp-master.debian.org/changelogs/main/l/llvm-toolchain-19/llvm-toolchain-19_19.1.7-20_copyright - -Souppaya, M., Scarfone, K., & Dodson, D. (2022). *Secure Software Development -Framework (SSDF) version 1.1: Recommendations for mitigating the risk of -software vulnerabilities* (NIST Special Publication 800-218). National -Institute of Standards and Technology. https://doi.org/10.6028/NIST.SP.800-218 - 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 diff --git a/scripts/ci/materialize_base_python_requirements.py b/scripts/ci/materialize_base_python_requirements.py index b8a258b48..6ef9218de 100755 --- a/scripts/ci/materialize_base_python_requirements.py +++ b/scripts/ci/materialize_base_python_requirements.py @@ -226,26 +226,23 @@ def _requirement_lines(content: bytes) -> list[str]: def _is_hash_pinned(content: bytes) -> bool: - """Return whether content carries only trusted pins or bounded includes. - - Discovery is content-based rather than name-based so exact hash-pinned locks - in service subdirectories and role-specific requirements files can be - considered for offline coverage. Candidate syntax is deliberately stricter - than a substring search: each package line must be an exact ``==`` pin with - one or more complete SHA-256 hashes, or a bounded relative requirements - include. A global ``--require-hashes`` directive is not trust evidence by - itself. The downstream installer separately preflights every candidate as an - independent ``pip --require-hashes`` closure, so syntax eligibility never - substitutes for dependency-closure proof. + """Return whether content carries hash pins and is safe to preflight. + + Discovery is content-based rather than name-based so hash-pinned locks in any + location (a service subdirectory, ``requirements-dev.txt``, + ``requirements-test.txt``) can be considered for offline coverage, while an + unpinned or PR-mutable requirements file is still excluded from the networked + build context. Hash syntax cannot prove that a file includes every transitive + dependency, so the trusted image installer separately preflights every + candidate as an independent ``--require-hashes`` closure. An empty file + carries no installable dependency and is not materialized. """ lines = _requirement_lines(content) - requirement_lines = [line for line in lines if line != "--require-hashes"] - if not requirement_lines: + if not lines: return False - return all( - _is_fully_hash_pinned_requirement(line) - or _is_bounded_requirement_include(line) - for line in requirement_lines + return any(line == "--require-hashes" for line in lines) or all( + "--hash=" in line or line.startswith(("-r ", "--requirement ")) + for line in lines ) @@ -579,7 +576,7 @@ def base_hash_locks(repo_root: pathlib.Path, base_sha: str) -> list[tuple[str, b regular_paths = {path for path, _candidate in regular_blobs} locks: list[tuple[str, bytes]] = [] for path, candidate in regular_blobs: - if _is_candidate_lock_path(candidate): + if _is_candidate_lock_name(candidate.name): content = _git(repo_root, "show", f"{base_sha}:{path}") if _is_flat_materializable_lock(content): locks.append((path, content)) @@ -592,91 +589,23 @@ def base_hash_locks(repo_root: pathlib.Path, base_sha: str) -> list[tuple[str, b return sorted(locks, key=lambda item: item[0]) -def _included_base_lock_blobs( - repo_root: pathlib.Path, - base_sha: str, - source_path: str, - content: bytes, - regular_paths: set[str], -) -> list[tuple[pathlib.PurePosixPath, bytes]]: - """Load direct bounded includes from the exact base as complete closures.""" - source_parent = pathlib.PurePosixPath(source_path).parent - included: dict[pathlib.PurePosixPath, bytes] = {} - for line in _requirement_lines(content): - target = _bounded_requirement_include_target(line) - if target is None: - continue - resolved = source_parent / target - resolved_path = resolved.as_posix() - if resolved_path not in regular_paths: - raise RuntimeError( - f"bounded include {target} from {source_path} is not a regular base blob" - ) - included_content = _git(repo_root, "show", f"{base_sha}:{resolved_path}") - if not _is_fully_hash_pinned_export(included_content): - raise RuntimeError( - f"bounded include {resolved_path} must contain only exact SHA-256 pins" - ) - included[target] = included_content - return sorted(included.items(), key=lambda item: item[0].as_posix()) - - -def _rewrite_materialized_includes(content: bytes, include_directory: str) -> bytes: - """Rewrite root include targets to their preserved generated subtree.""" - text = content.decode("utf-8", errors="strict") - rewritten: list[str] = [] - for raw_line in text.splitlines(keepends=True): - body = raw_line.rstrip("\r\n") - ending = raw_line[len(body) :] - stripped = body.strip() - target = _bounded_requirement_include_target(stripped) - if target is None: - rewritten.append(raw_line) - continue - indentation = body[: len(body) - len(body.lstrip())] - option = stripped.split()[0] - rewritten.append( - f"{indentation}{option} {include_directory}/{target.as_posix()}{ending}" - ) - return "".join(rewritten).encode("utf-8") - - def materialize( repo_root: pathlib.Path, base_sha: str, output_dir: pathlib.Path, ) -> list[dict[str, str]]: - """Write base locks and resolvable bounded includes into a safe context.""" + """Write base lock blobs under generated names safe for a Docker build context.""" if output_dir.exists() and output_dir.is_symlink(): raise ValueError("output directory must not be a symlink") output_dir.mkdir(parents=True, exist_ok=True) - resolved_repo = repo_root.resolve() - entries = _git(resolved_repo, "ls-tree", "-r", "-z", "--full-tree", base_sha) - regular_paths = { - path for path, _candidate in _regular_base_blob_paths(entries) - } manifest: list[dict[str, str]] = [] for index, (source_path, content) in enumerate( - base_hash_locks(resolved_repo, base_sha) + base_hash_locks(repo_root.resolve(), base_sha) ): generated_name = f"requirements-{index:03d}.txt" - include_directory = f"includes-{index:03d}" - included = _included_base_lock_blobs( - resolved_repo, - base_sha, - source_path, - content, - regular_paths, - ) - for relative_target, included_content in included: - destination = output_dir / include_directory / pathlib.Path(*relative_target.parts) - destination.parent.mkdir(parents=True, exist_ok=True) - destination.write_bytes(included_content) destination = output_dir / generated_name - destination.write_bytes( - _rewrite_materialized_includes(content, include_directory) - ) + destination.write_bytes(content) manifest.append({"file": generated_name, "source": source_path}) (output_dir / "manifest.json").write_text( diff --git a/scripts/ci/repair_pr827_coderabbit_comments.py b/scripts/ci/repair_pr827_coderabbit_comments.py deleted file mode 100644 index 267b45f02..000000000 --- a/scripts/ci/repair_pr827_coderabbit_comments.py +++ /dev/null @@ -1,372 +0,0 @@ -#!/usr/bin/env python3 -"""Apply the verified CodeRabbit repairs for pull request 827.""" - -from __future__ import annotations - -from pathlib import Path - - -def replace_once(path: str, old: str, new: str) -> None: - """Replace one exact fragment and fail closed on drift.""" - file_path = Path(path) - text = file_path.read_text(encoding="utf-8") - count = text.count(old) - if count != 1: - raise SystemExit(f"{path}: expected one replacement marker, found {count}") - file_path.write_text(text.replace(old, new, 1), encoding="utf-8") - - -def replace_between(path: str, start: str, end: str, replacement: str) -> None: - """Replace a uniquely delimited source section.""" - file_path = Path(path) - text = file_path.read_text(encoding="utf-8") - start_index = text.find(start) - if start_index < 0 or text.find(start, start_index + 1) >= 0: - raise SystemExit(f"{path}: start marker missing or ambiguous") - end_index = text.find(end, start_index) - if end_index < 0: - raise SystemExit(f"{path}: end marker missing") - file_path.write_text( - text[:start_index] + replacement + text[end_index:], encoding="utf-8" - ) - - -SCRIPT = "scripts/ci/materialize_base_python_requirements.py" -TEST = "tests/test_materialize_base_python_requirements.py" -DOC = "docs/doctoring/opencode-rust-coverage-runtime-boundary.md" -WORKFLOW = ".github/workflows/opencode-review-dispatch.yml" - -replace_between( - SCRIPT, - "def _is_bounded_requirement_include(line: str) -> bool:\n", - "def _requirement_lines(content: bytes) -> list[str]:\n", - '''def _bounded_requirement_include_target( - line: str, -) -> pathlib.PurePosixPath | None: - """Return the safe relative target of one bounded requirements include. - - The target may use any normalized relative ``.txt`` name, including names - such as ``other-hashes.txt``. Eligibility does not confer trust: the exact - base-tree target must later be a regular blob containing only exact - SHA-256-pinned package requirements. - """ - fields = line.split() - if len(fields) != 2 or fields[0] not in {"-r", "--requirement"}: - return None - target = fields[1] - if ( - target.startswith(("-", "~")) - or "\\\\" in target - or ":" in target - or "?" in target - or "#" in target - ): - return None - include_path = pathlib.PurePosixPath(target) - if ( - not include_path.parts - or target != include_path.as_posix() - or include_path.is_absolute() - or "." in include_path.parts - or ".." in include_path.parts - or include_path.suffix != ".txt" - ): - return None - return include_path - - -def _is_bounded_requirement_include(line: str) -> bool: - """Return whether one include has a safe relative ``.txt`` target.""" - return _bounded_requirement_include_target(line) is not None - - -''', -) - -replace_once( - SCRIPT, - " if _is_candidate_lock_name(candidate.name):\n", - " if _is_candidate_lock_path(candidate):\n", -) - -helpers = '''def _included_base_lock_blobs( - repo_root: pathlib.Path, - base_sha: str, - source_path: str, - content: bytes, - regular_paths: set[str], -) -> list[tuple[pathlib.PurePosixPath, bytes]]: - """Load direct bounded includes from the exact base as complete closures.""" - source_parent = pathlib.PurePosixPath(source_path).parent - included: dict[pathlib.PurePosixPath, bytes] = {} - for line in _requirement_lines(content): - target = _bounded_requirement_include_target(line) - if target is None: - continue - resolved = source_parent / target - resolved_path = resolved.as_posix() - if resolved_path not in regular_paths: - raise RuntimeError( - f"bounded include {target} from {source_path} is not a regular base blob" - ) - included_content = _git(repo_root, "show", f"{base_sha}:{resolved_path}") - if not _is_fully_hash_pinned_export(included_content): - raise RuntimeError( - f"bounded include {resolved_path} must contain only exact SHA-256 pins" - ) - included[target] = included_content - return sorted(included.items(), key=lambda item: item[0].as_posix()) - - -def _rewrite_materialized_includes(content: bytes, include_directory: str) -> bytes: - """Rewrite root include targets to their preserved generated subtree.""" - text = content.decode("utf-8", errors="strict") - rewritten: list[str] = [] - for raw_line in text.splitlines(keepends=True): - body = raw_line.rstrip("\\r\\n") - ending = raw_line[len(body) :] - stripped = body.strip() - target = _bounded_requirement_include_target(stripped) - if target is None: - rewritten.append(raw_line) - continue - indentation = body[: len(body) - len(body.lstrip())] - option = stripped.split()[0] - rewritten.append( - f"{indentation}{option} {include_directory}/{target.as_posix()}{ending}" - ) - return "".join(rewritten).encode("utf-8") - - -''' -replace_once(SCRIPT, "def materialize(\n", helpers + "def materialize(\n") - -replace_between( - SCRIPT, - "def materialize(\n", - "def main(argv: list[str] | None = None) -> int:\n", - '''def materialize( - repo_root: pathlib.Path, - base_sha: str, - output_dir: pathlib.Path, -) -> list[dict[str, str]]: - """Write base locks and resolvable bounded includes into a safe context.""" - if output_dir.exists() and output_dir.is_symlink(): - raise ValueError("output directory must not be a symlink") - output_dir.mkdir(parents=True, exist_ok=True) - - resolved_repo = repo_root.resolve() - entries = _git(resolved_repo, "ls-tree", "-r", "-z", "--full-tree", base_sha) - regular_paths = { - path for path, _candidate in _regular_base_blob_paths(entries) - } - manifest: list[dict[str, str]] = [] - for index, (source_path, content) in enumerate( - base_hash_locks(resolved_repo, base_sha) - ): - generated_name = f"requirements-{index:03d}.txt" - include_directory = f"includes-{index:03d}" - included = _included_base_lock_blobs( - resolved_repo, - base_sha, - source_path, - content, - regular_paths, - ) - for relative_target, included_content in included: - destination = output_dir / include_directory / Path(*relative_target.parts) - destination.parent.mkdir(parents=True, exist_ok=True) - destination.write_bytes(included_content) - destination = output_dir / generated_name - destination.write_bytes( - _rewrite_materialized_includes(content, include_directory) - ) - manifest.append({"file": generated_name, "source": source_path}) - - (output_dir / "manifest.json").write_text( - json.dumps(manifest, indent=2, sort_keys=True) + "\\n", - encoding="utf-8", - ) - (output_dir / "manifest.txt").write_text( - "".join(f"{entry['file']}\\n" for entry in manifest), - encoding="utf-8", - ) - return manifest - - -''', -) - -replace_once(TEST, "import tarfile\n", "import tarfile\nimport zipfile\n") -replace_once( - TEST, - ' assert not materializer._is_hash_pinned(b"-r other-hashes.txt\\n")\n', - ' assert materializer._is_hash_pinned(b"-r other-hashes.txt\\n")\n', -) -replace_once( - TEST, - ' assert not materializer._is_candidate_lock_name("pyproject.toml")\n', - ' assert not materializer._is_candidate_lock_name("pyproject.toml")\n' - ' assert materializer._is_candidate_lock_path(\n' - ' materializer.pathlib.PurePosixPath("requirements/ci.txt")\n' - ' )\n' - ' assert materializer._is_candidate_lock_path(\n' - ' materializer.pathlib.PurePosixPath("service/requirements/package.txt")\n' - ' )\n' - ' assert not materializer._is_candidate_lock_path(\n' - ' materializer.pathlib.PurePosixPath("service/config/ci.txt")\n' - ' )\n', -) -replace_once( - TEST, - ' (repo / "requirements-test.txt").write_text(\n' - ' "hypothesis==6 --hash=sha256:" + ("b" * 64) + "\\n",\n' - ' encoding="utf-8",\n' - ' )\n', - ' (repo / "requirements-test.txt").write_text(\n' - ' "hypothesis==6 --hash=sha256:" + ("b" * 64) + "\\n",\n' - ' encoding="utf-8",\n' - ' )\n' - ' requirements_dir = repo / "requirements"\n' - ' requirements_dir.mkdir()\n' - ' (requirements_dir / "ci.txt").write_text(\n' - ' "pytest==9 --hash=sha256:" + ("c" * 64) + "\\n",\n' - ' encoding="utf-8",\n' - ' )\n', -) -replace_once( - TEST, - ' "requirements-test.txt",\n' - ' "services/account_unification/requirements-dev.txt",\n', - ' "requirements-test.txt",\n' - ' "requirements/ci.txt",\n' - ' "services/account_unification/requirements-dev.txt",\n', -) - -integration_test = '''def test_materialized_bounded_include_is_resolvable_by_pip(tmp_path: Path) -> None: - """A safe base-owned include survives flattening and pip hash preflight.""" - repo = tmp_path / "repo" - repo.mkdir() - git(repo, "init") - git(repo, "config", "user.name", "Test") - git(repo, "config", "user.email", "test@example.invalid") - - wheel_dir = tmp_path / "wheels" - wheel_dir.mkdir() - wheel = wheel_dir / "demo-1-py3-none-any.whl" - with zipfile.ZipFile(wheel, "w") as archive: - archive.writestr("demo/__init__.py", "__version__ = '1'\\n") - archive.writestr( - "demo-1.dist-info/METADATA", - "Metadata-Version: 2.1\\nName: demo\\nVersion: 1\\n", - ) - archive.writestr( - "demo-1.dist-info/WHEEL", - "Wheel-Version: 1.0\\nGenerator: TEPP-test\\n" - "Root-Is-Purelib: true\\nTag: py3-none-any\\n", - ) - archive.writestr("demo-1.dist-info/RECORD", "") - digest = hashlib.sha256(wheel.read_bytes()).hexdigest() - - (repo / "requirements.txt").write_text( - "-r other-hashes.txt\\n", encoding="utf-8" - ) - (repo / "other-hashes.txt").write_text( - f"demo==1 --hash=sha256:{digest}\\n", encoding="utf-8" - ) - git(repo, "add", ".") - git(repo, "commit", "-m", "base") - base_sha = git(repo, "rev-parse", "HEAD") - - output = tmp_path / "output" - manifest = materializer.materialize(repo, base_sha, output) - assert manifest == [{"file": "requirements-000.txt", "source": "requirements.txt"}] - assert (output / "requirements-000.txt").read_text(encoding="utf-8") == ( - "-r includes-000/other-hashes.txt\\n" - ) - assert (output / "includes-000" / "other-hashes.txt").is_file() - - completed = subprocess.run( - [ - sys.executable, - "-m", - "pip", - "install", - "--dry-run", - "--ignore-installed", - "--disable-pip-version-check", - "--no-index", - "--find-links", - str(wheel_dir), - "--require-hashes", - "-r", - str(output / "requirements-000.txt"), - ], - check=False, - capture_output=True, - text=True, - ) - assert completed.returncode == 0, completed.stdout + completed.stderr - - -def test_materialization_rejects_missing_or_nested_include(tmp_path: Path) -> None: - """Includes must resolve to direct complete hash closures in the exact base.""" - repo = tmp_path / "repo" - repo.mkdir() - git(repo, "init") - git(repo, "config", "user.name", "Test") - git(repo, "config", "user.email", "test@example.invalid") - (repo / "requirements.txt").write_text("-r child.txt\\n", encoding="utf-8") - git(repo, "add", ".") - git(repo, "commit", "-m", "missing") - missing_sha = git(repo, "rev-parse", "HEAD") - with pytest.raises(RuntimeError, match="not a regular base blob"): - materializer.materialize(repo, missing_sha, tmp_path / "missing-output") - - (repo / "child.txt").write_text("-r grandchild.txt\\n", encoding="utf-8") - (repo / "grandchild.txt").write_text( - "demo==1 --hash=sha256:" + ("d" * 64) + "\\n", encoding="utf-8" - ) - git(repo, "add", ".") - git(repo, "commit", "-m", "nested") - nested_sha = git(repo, "rev-parse", "HEAD") - with pytest.raises(RuntimeError, match="must contain only exact SHA-256 pins"): - materializer.materialize(repo, nested_sha, tmp_path / "nested-output") - - -''' -replace_once(TEST, "def test_rejects_invalid_base_sha", integration_test + "def test_rejects_invalid_base_sha") -replace_once( - TEST, - ' assert not materializer._is_bounded_requirement_include("-r /abs/requirements.txt")\n', - ' assert not materializer._is_bounded_requirement_include("-r /abs/requirements.txt")\n' - ' assert not materializer._is_bounded_requirement_include("-r pyproject.toml")\n', -) - -replace_once( - "CHANGELOG.md", - "- Materialized base Python locks only when every package line is an exact SHA-256 pin or a bounded relative `-r`/`--requirement` include. A lone `--require-hashes` directive, a dotted include such as `./lock.txt`, or `-r other-hashes.txt` no longer enters the trusted build context.\n", - "- Materialized base Python locks only when every package line is an exact SHA-256 pin or a bounded relative `-r`/`--requirement` include. Includes such as `-r other-hashes.txt` remain allowed when the exact base-tree target is a regular, complete SHA-256-pinned closure; a lone `--require-hashes` directive, dotted `./lock.txt`, traversal, absolute, URL, and option-like targets fail closed.\n", -) - -replace_once( - DOC, - "NIST SP 800-218 PW.4.1 requires third-party software to come from expected,\ntrusted sources with integrity verification (Souppaya et al., 2022). Binding\ncoverage to the reviewed `/usr/bin/llvm-cov-19` and\n`/usr/bin/llvm-profdata-19` executables is that verification; an ambient\n`PATH` lookup would treat a runner-image change as a new producer.\n", - "NIST SP 800-218 PW.4.1 requires third-party software to come from expected,\ntrusted sources with integrity verification (Souppaya et al., 2022). The exact\n`/usr/bin/llvm-cov-19` and `/usr/bin/llvm-profdata-19` bindings are\nproducer-selection controls: they select reviewed paths and `test -x` verifies\nexecutability. They do not hash or signature-verify the Debian package or binary.\nPackage/image hashes, signatures, repository metadata, and attestations are\nseparate integrity controls and must not be inferred from path equality.\n", -) -replace_once( - DOC, - "Debian bookworm currently publishes the versioned `llvm-19` package from\n`llvm-toolchain-19`; Debian package file inventories expose versioned LLVM 19\ntool entry points including `llvm-cov-19`. Pinning the reviewed executable names\ninside the image converts that mutable ambient dependency into an explicit\ncontract that can be checked before source execution.\n", - "Debian publishes `llvm-19` from the `llvm-toolchain-19` source package; its\nofficial copyright record states `Apache-2.0 WITH LLVM-exception`. Debian package\nfile inventories expose versioned LLVM 19 tool entry points including\n`llvm-cov-19`. Pinning those reviewed executable names inside the image converts\nambient path selection into an explicit, testable producer contract; the Debian\ncopyright record supplies the package license basis, not executable integrity.\n", -) -replace_once( - DOC, - "Debian Project. (2026). *File list of package llvm-19*. Debian Packages.\nRetrieved August 10, 2026, from\nhttps://packages.debian.org/bookworm/amd64/llvm-19/filelist\n\n", - "Debian Project. (2026). *File list of package llvm-19*. Debian Packages.\nRetrieved August 10, 2026, from\nhttps://packages.debian.org/bookworm/amd64/llvm-19/filelist\n\nDebian Project. (2026). *Copyright file for llvm-toolchain-19 19.1.7-20*.\nDebian FTP Masters. Retrieved August 15, 2026, from\nhttps://metadata.ftp-master.debian.org/changelogs/main/l/llvm-toolchain-19/llvm-toolchain-19_19.1.7-20_copyright\n\n", -) - -replace_once( - WORKFLOW, - " r-cran-testthat \\\n llvm-19 \\\n", - " r-cran-testthat \\\n # llvm-19 / llvm-toolchain-19: Apache-2.0 WITH LLVM-exception. \\\n # See docs/doctoring/opencode-rust-coverage-runtime-boundary.md. \\\n llvm-19 \\\n", -) diff --git a/tests/test_materialize_base_python_requirements.py b/tests/test_materialize_base_python_requirements.py index 65c0103d5..de1f877ee 100644 --- a/tests/test_materialize_base_python_requirements.py +++ b/tests/test_materialize_base_python_requirements.py @@ -6,7 +6,6 @@ import subprocess import sys import tarfile -import zipfile from pathlib import Path import pytest @@ -31,13 +30,6 @@ def _created_tool_directory(path: Path) -> str: return str(path) -def _force_linux_x86_64_installer(monkeypatch: pytest.MonkeyPatch) -> None: - """Exercise the installer path that GitHub-hosted linux x86_64 runners use.""" - monkeypatch.setattr(materializer.sys, "platform", "linux") - monkeypatch.setattr(materializer.platform, "machine", lambda: "x86_64") - materializer._install_trusted_uv.cache_clear() - - def test_materializes_only_regular_hash_locks_from_exact_base(tmp_path: Path) -> None: """A PR-modified lock cannot enter the networked coverage image build context.""" repo = tmp_path / "repo" @@ -126,12 +118,6 @@ def test_materializes_hash_pinned_locks_named_beyond_the_legacy_whitelist( "hypothesis==6 --hash=sha256:" + ("b" * 64) + "\n", encoding="utf-8", ) - requirements_dir = repo / "requirements" - requirements_dir.mkdir() - (requirements_dir / "ci.txt").write_text( - "pytest==9 --hash=sha256:" + ("c" * 64) + "\n", - encoding="utf-8", - ) (repo / "uv.lock").write_text( "version = 1\n[[package]]\nname = 'x'\n", encoding="utf-8" ) @@ -145,7 +131,6 @@ def test_materializes_hash_pinned_locks_named_beyond_the_legacy_whitelist( assert [entry["source"] for entry in manifest] == [ "requirements-test.txt", - "requirements/ci.txt", "services/account_unification/requirements-dev.txt", ] @@ -160,39 +145,14 @@ def test_lock_name_candidates_are_pip_requirements_files() -> None: ) assert not materializer._is_candidate_lock_name("uv.lock") assert not materializer._is_candidate_lock_name("pyproject.toml") - assert materializer._is_candidate_lock_path( - materializer.pathlib.PurePosixPath("requirements/ci.txt") - ) - assert materializer._is_candidate_lock_path( - materializer.pathlib.PurePosixPath("service/requirements/package.txt") - ) - assert not materializer._is_candidate_lock_path( - materializer.pathlib.PurePosixPath("service/config/ci.txt") - ) def test_hash_pin_detection_includes_pinned_and_excludes_unpinned_or_empty() -> None: """Only fully hash-pinned, non-empty lock content is materialized.""" assert not materializer._is_hash_pinned(b"# comment only\n\n") - assert not materializer._is_hash_pinned(b"--require-hashes\ndemo==1\n") + assert materializer._is_hash_pinned(b"--require-hashes\ndemo==1\n") assert materializer._is_hash_pinned(b"demo==1 --hash=sha256:" + b"a" * 64 + b"\n") - assert materializer._is_hash_pinned(b"-r requirements-other.txt\n") assert materializer._is_hash_pinned(b"-r other-hashes.txt\n") - assert not materializer._is_hash_pinned(b"-r ./requirements-other.txt\n") - assert not materializer._is_hash_pinned(b"-r ../escape.txt\n") - assert materializer._is_bounded_requirement_include( - "--requirement requirements-other.txt" - ) - assert not materializer._is_bounded_requirement_include("-r .") - assert not materializer._is_bounded_requirement_include("-r -evil.txt") - assert not materializer._is_bounded_requirement_include("-r ~evil.txt") - assert not materializer._is_bounded_requirement_include("-r C:foo.txt") - assert not materializer._is_bounded_requirement_include("-r foo?bar.txt") - assert not materializer._is_bounded_requirement_include("-r foo#bar.txt") - assert not materializer._is_bounded_requirement_include(r"-r foo\\bar.txt") - assert not materializer._is_bounded_requirement_include("-r") - assert not materializer._is_bounded_requirement_include("-r /abs/requirements.txt") - assert not materializer._is_bounded_requirement_include("-r pyproject.toml") assert not materializer._is_hash_pinned(b"untrusted==1\n") # uv export / pip-compile multi-line continuation format (spec, then --hash= lines). assert materializer._is_hash_pinned( @@ -204,97 +164,6 @@ def test_hash_pin_detection_includes_pinned_and_excludes_unpinned_or_empty() -> ) -def test_materialized_bounded_include_is_resolvable_by_pip(tmp_path: Path) -> None: - """A safe base-owned include survives flattening and pip hash preflight.""" - repo = tmp_path / "repo" - repo.mkdir() - git(repo, "init") - git(repo, "config", "user.name", "Test") - git(repo, "config", "user.email", "test@example.invalid") - - wheel_dir = tmp_path / "wheels" - wheel_dir.mkdir() - wheel = wheel_dir / "demo-1-py3-none-any.whl" - with zipfile.ZipFile(wheel, "w") as archive: - archive.writestr("demo/__init__.py", "__version__ = '1'\n") - archive.writestr( - "demo-1.dist-info/METADATA", - "Metadata-Version: 2.1\nName: demo\nVersion: 1\n", - ) - archive.writestr( - "demo-1.dist-info/WHEEL", - "Wheel-Version: 1.0\nGenerator: TEPP-test\n" - "Root-Is-Purelib: true\nTag: py3-none-any\n", - ) - archive.writestr("demo-1.dist-info/RECORD", "") - digest = hashlib.sha256(wheel.read_bytes()).hexdigest() - - (repo / "requirements.txt").write_text( - "-r other-hashes.txt\n", encoding="utf-8" - ) - (repo / "other-hashes.txt").write_text( - f"demo==1 --hash=sha256:{digest}\n", encoding="utf-8" - ) - git(repo, "add", ".") - git(repo, "commit", "-m", "base") - base_sha = git(repo, "rev-parse", "HEAD") - - output = tmp_path / "output" - manifest = materializer.materialize(repo, base_sha, output) - assert manifest == [{"file": "requirements-000.txt", "source": "requirements.txt"}] - assert (output / "requirements-000.txt").read_text(encoding="utf-8") == ( - "-r includes-000/other-hashes.txt\n" - ) - assert (output / "includes-000" / "other-hashes.txt").is_file() - - completed = subprocess.run( - [ - sys.executable, - "-m", - "pip", - "install", - "--dry-run", - "--ignore-installed", - "--disable-pip-version-check", - "--no-index", - "--find-links", - str(wheel_dir), - "--require-hashes", - "-r", - str(output / "requirements-000.txt"), - ], - check=False, - capture_output=True, - text=True, - ) - assert completed.returncode == 0, completed.stdout + completed.stderr - - -def test_materialization_rejects_missing_or_nested_include(tmp_path: Path) -> None: - """Includes must resolve to direct complete hash closures in the exact base.""" - repo = tmp_path / "repo" - repo.mkdir() - git(repo, "init") - git(repo, "config", "user.name", "Test") - git(repo, "config", "user.email", "test@example.invalid") - (repo / "requirements.txt").write_text("-r child.txt\n", encoding="utf-8") - git(repo, "add", ".") - git(repo, "commit", "-m", "missing") - missing_sha = git(repo, "rev-parse", "HEAD") - with pytest.raises(RuntimeError, match="not a regular base blob"): - materializer.materialize(repo, missing_sha, tmp_path / "missing-output") - - (repo / "child.txt").write_text("-r grandchild.txt\n", encoding="utf-8") - (repo / "grandchild.txt").write_text( - "demo==1 --hash=sha256:" + ("d" * 64) + "\n", encoding="utf-8" - ) - git(repo, "add", ".") - git(repo, "commit", "-m", "nested") - nested_sha = git(repo, "rev-parse", "HEAD") - with pytest.raises(RuntimeError, match="must contain only exact SHA-256 pins"): - materializer.materialize(repo, nested_sha, tmp_path / "nested-output") - - def test_rejects_invalid_base_sha(tmp_path: Path) -> None: """Git options and symbolic refs cannot cross the exact-SHA boundary.""" with pytest.raises(ValueError, match="40 hexadecimal"): @@ -813,7 +682,6 @@ def test_install_trusted_uv_verifies_version_and_caches_path( tmp_path: Path, monkeypatch: pytest.MonkeyPatch ) -> None: """The installer writes one executable, verifies its version, and caches it.""" - _force_linux_x86_64_installer(monkeypatch) tool_dir = tmp_path / "uv" monkeypatch.setattr( materializer.tempfile, @@ -862,7 +730,6 @@ def test_install_trusted_uv_rejects_version_process_failures( failure: OSError | subprocess.TimeoutExpired, ) -> None: """A missing or hung downloaded executable is removed and rejected.""" - _force_linux_x86_64_installer(monkeypatch) tool_dir = tmp_path / "uv" monkeypatch.setattr( materializer.tempfile, @@ -902,7 +769,6 @@ def test_install_trusted_uv_rejects_wrong_version_or_exit_status( completed: subprocess.CompletedProcess[bytes], ) -> None: """Unexpected version output or a nonzero status cannot satisfy the pin.""" - _force_linux_x86_64_installer(monkeypatch) tool_dir = tmp_path / f"uv-{completed.returncode}-{len(completed.stdout)}" monkeypatch.setattr( materializer.tempfile, From 48603e869e755b441a1a1b0828c8131948d3b23d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 01:38:24 +0900 Subject: [PATCH 16/16] fix(coverage): keep LLVM 19 boundary off hashed review dispatch Restore opencode-review-dispatch.yml to the reviewed review-agent blob and bind LLVM 19 coverage tools in scripts/ci/ensure_rust_llvm19.sh. --- .../workflows/opencode-review-dispatch.yml | 19 ----- ...ode-rust-coverage-toolchain-quality-ci.yml | 2 +- AGENTS.md | 1 + ARCHITECTURE.md | 7 ++ CHANGELOG.md | 4 +- CLAUDE.md | 3 + ...opencode-rust-coverage-runtime-boundary.md | 42 +++++----- scripts/ci/ensure_rust_llvm19.sh | 15 ++++ ...encode_rust_coverage_toolchain_contract.py | 82 +++++-------------- 9 files changed, 71 insertions(+), 104 deletions(-) create mode 100755 scripts/ci/ensure_rust_llvm19.sh diff --git a/.github/workflows/opencode-review-dispatch.yml b/.github/workflows/opencode-review-dispatch.yml index de1c4800d..83f6830d5 100644 --- a/.github/workflows/opencode-review-dispatch.yml +++ b/.github/workflows/opencode-review-dispatch.yml @@ -652,16 +652,11 @@ 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" - 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 - \ @@ -774,8 +769,6 @@ 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=$? @@ -1716,18 +1709,6 @@ 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 "" diff --git a/.github/workflows/opencode-rust-coverage-toolchain-quality-ci.yml b/.github/workflows/opencode-rust-coverage-toolchain-quality-ci.yml index ee4b283bd..188ec6e99 100644 --- a/.github/workflows/opencode-rust-coverage-toolchain-quality-ci.yml +++ b/.github/workflows/opencode-rust-coverage-toolchain-quality-ci.yml @@ -3,8 +3,8 @@ 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" + - "scripts/ci/ensure_rust_llvm19.sh" - "tests/test_opencode_rust_coverage_toolchain_contract.py" - "docs/doctoring/opencode-rust-coverage-runtime-boundary.md" - "CHANGELOG.md" diff --git a/AGENTS.md b/AGENTS.md index 794163994..0af864212 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -7,3 +7,4 @@ Conflict-scope roots fail closed when the immediate parent directory is a symbol OriginWeave hourly NVIDIA NIM repair is a thin caller at minute 10. See [`docs/doctoring/originweave-hourly-review-caller.md`](docs/doctoring/originweave-hourly-review-caller.md). nonnest2 hourly NVIDIA NIM repair is a thin caller at minute 16. See [`docs/doctoring/nonnest2-hourly-review-caller.md`](docs/doctoring/nonnest2-hourly-review-caller.md). Rust coverage materialization and its exact-toolchain boundary are recorded in [`docs/doctoring/opencode-rust-coverage-runtime-boundary.md`](docs/doctoring/opencode-rust-coverage-runtime-boundary.md). +Rust coverage evidence binds LLVM 19 through `scripts/ci/ensure_rust_llvm19.sh`; do not rewrite the hashed `opencode-review-dispatch.yml` review-agent key blob for that check. diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 4d1459e96..e52c4f988 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -39,6 +39,13 @@ only established scheduler credentials, and grants job-scoped only established scheduler credentials, and grants job-scoped `id-token: write`. The reusable engine stays product-neutral. +## Rust coverage LLVM 19 boundary + +Reviewed Rust coverage evidence binds `LLVM_COV=/usr/bin/llvm-cov-19` and +`LLVM_PROFDATA=/usr/bin/llvm-profdata-19` in `scripts/ci/ensure_rust_llvm19.sh`. +The independent OpenCode review-dispatch workflow remains the hashed +review-agent key blob and is not the carrier for this runtime check. + ## Hourly NVIDIA NIM repair gate ```mermaid diff --git a/CHANGELOG.md b/CHANGELOG.md index 3aa2584fa..c5aa5d934 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,8 +39,10 @@ Semantic Versioning where the repository publishes a release. - Download the pinned `uv` 0.12.1 exporter from the official GitHub Releases URL instead of `releases.astral.sh`, which now returns HTTP 403 and blocks org-wide OpenCode `coverage-evidence`. The SHA-256 pin is unchanged. The opener may follow one hop onto `release-assets.githubusercontent.com` or `objects.githubusercontent.com` and still rejects every other host, userinfo, non-HTTPS scheme, and nondefault port (ContextualWisdomLab/.github#1109). - Compared the trusted `uv` executable's post-install `--version` output against the real GitHub Releases build's full string, `uv 0.12.1 (x86_64-unknown-linux-gnu)`, instead of the bare `uv 0.12.1` the prior check required; the genuine release binary always prints the target triple, so every installation was failing the pin check immediately after the archive download itself was fixed (ContextualWisdomLab/.github#1109). - Excluded relative `-r` and `--requirement` referrers from generated flat base-lock publication while retaining bounded include syntax diagnostics and discovering independently complete direct `.txt` children of `requirements` directories. -- Refused a conflict-scope repository root whose immediate parent is a symbolic link, so a swapped parent cannot redirect the canonical worktree after the last-component check (CWE-367). +- Keep the independent OpenCode review-dispatch workflow byte-for-byte while the LLVM 19 Rust coverage runtime-boundary lives in `scripts/ci/ensure_rust_llvm19.sh` and its permanent quality-ci watch list. - Materialized base Python locks only when every package line is an exact SHA-256 pin or a bounded relative `-r`/`--requirement` include. A lone `--require-hashes` directive, a dotted include such as `./lock.txt`, or `-r other-hashes.txt` no longer enters the trusted build context. + - Keep the independent OpenCode review-dispatch workflow byte-for-byte while the LLVM 19 Rust coverage runtime-boundary lives in `scripts/ci/ensure_rust_llvm19.sh` and its permanent quality-ci watch list. +- Refused a conflict-scope repository root whose immediate parent is a symbolic link, so a swapped parent cannot redirect the canonical worktree after the last-component check (CWE-367). - Materialized base Python locks only when every package line is an exact SHA-256 pin or a bounded relative `-r`/`--requirement` include. Includes such as `-r other-hashes.txt` remain allowed when the exact base-tree target is a regular, complete SHA-256-pinned closure; a lone `--require-hashes` directive, dotted `./lock.txt`, traversal, absolute, URL, and option-like targets fail closed. - Bounded relative requirement includes are accepted only when their exact base-tree target is a regular, complete SHA-256-pinned closure; dotted, traversal, absolute, URL, and option-like targets fail closed. - Bound OpenCode Rust coverage to the reviewed Debian `llvm-cov-19` and `llvm-profdata-19` executables across image build and the networkless sandbox runtime, failing closed instead of measuring an ambient or unversioned LLVM producer. The decision record now cites NIST SP 800-218 PW.4.1 so a runner `PATH` change cannot silently replace the coverage toolchain. diff --git a/CLAUDE.md b/CLAUDE.md index e205c5f48..a6e89cfa3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -120,6 +120,9 @@ repeatable compile command. - **Product hourly callers** stay thin. Do not hard-code OriginWeave, naruon, or Keyverse into `pr-review-fix-scheduler.yml`. The model credential remains `NVIDIA_NIM_API_KEY` on the worker, never `COPILOT_GITHUB_TOKEN`. +- **LLVM 19 Rust coverage boundary** lives in `scripts/ci/ensure_rust_llvm19.sh`. Do not edit + `.github/workflows/opencode-review-dispatch.yml` to carry that check; its blob SHA is the + independent review-agent key contract. - **`pull_request_target` trust boundary.** The required review workflows run the *base branch's* trusted scripts. A PR that edits the trusted review workflows can fail its own checks until the base branch catches up; a same-head manual `workflow_dispatch` Strix run may supply review evidence diff --git a/docs/doctoring/opencode-rust-coverage-runtime-boundary.md b/docs/doctoring/opencode-rust-coverage-runtime-boundary.md index ea43dbc9a..5102d1bf1 100644 --- a/docs/doctoring/opencode-rust-coverage-runtime-boundary.md +++ b/docs/doctoring/opencode-rust-coverage-runtime-boundary.md @@ -9,12 +9,11 @@ The trusted OpenCode coverage sandbox binds Rust coverage to the reviewed LLVM - `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. +configuration. The reviewed helper `scripts/ci/ensure_rust_llvm19.sh` binds both +exact paths and fails closed unless the live `LLVM_COV` / `LLVM_PROFDATA` +values match and are executable before Rust coverage evidence is admitted. The +independent OpenCode review-dispatch workflow stays byte-for-byte so the +review-agent key system is not rewritten to carry this runtime check. 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 @@ -40,13 +39,11 @@ contract that can be checked before source execution. ```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"] + A["Reviewed helper scripts/ci/ensure_rust_llvm19.sh"] --> B["Default LLVM_COV_PATH / LLVM_PROFDATA_PATH"] + B --> C["Require live LLVM_COV and LLVM_PROFDATA equality"] + C --> D["Require both paths executable"] + D --> E["Fail closed before cargo llvm-cov"] + F["Hashed opencode-review-dispatch.yml"] --> G["Unchanged review-agent key blob"] ``` Each arrow is fail-closed. A later stage does not repair or broaden an earlier @@ -84,16 +81,15 @@ compatibility evidence and the same RED→GREEN exact-head verification sequence `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. +1. the helper defaults both reviewed LLVM 19 executable paths; +2. the helper requires live `LLVM_COV` / `LLVM_PROFDATA` equality with those + paths; +3. the helper requires both paths to be executable and exits `1` on mismatch; +4. the helper does not mention unversioned `llvm-cov` / `llvm-profdata`; and +5. every exact path named by the permanent quality workflow's + `pull_request.paths` filter resolves to a repository file, including the + helper, 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`. diff --git a/scripts/ci/ensure_rust_llvm19.sh b/scripts/ci/ensure_rust_llvm19.sh new file mode 100755 index 000000000..8c091e149 --- /dev/null +++ b/scripts/ci/ensure_rust_llvm19.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +# Fail closed unless the reviewed LLVM 19 coverage tools are bound. +set -euo pipefail + +LLVM_COV_PATH="${LLVM_COV_PATH:-/usr/bin/llvm-cov-19}" +LLVM_PROFDATA_PATH="${LLVM_PROFDATA_PATH:-/usr/bin/llvm-profdata-19}" + +if [ "${LLVM_COV:-}" != "$LLVM_COV_PATH" ] || + [ "${LLVM_PROFDATA:-}" != "$LLVM_PROFDATA_PATH" ] || + ! test -x "${LLVM_COV:-}" || + ! test -x "${LLVM_PROFDATA:-}"; then + printf 'Rust coverage runtime did not preserve reviewed LLVM 19 tool paths (%s, %s).\n' \ + "$LLVM_COV_PATH" "$LLVM_PROFDATA_PATH" >&2 + exit 1 +fi diff --git a/tests/test_opencode_rust_coverage_toolchain_contract.py b/tests/test_opencode_rust_coverage_toolchain_contract.py index 0e5c3d9a8..21c455ccd 100644 --- a/tests/test_opencode_rust_coverage_toolchain_contract.py +++ b/tests/test_opencode_rust_coverage_toolchain_contract.py @@ -2,12 +2,11 @@ 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" +_HELPER_PATH = _REPOSITORY_ROOT / "scripts/ci/ensure_rust_llvm19.sh" _QUALITY_WORKFLOW_PATH = ( _REPOSITORY_ROOT / ".github/workflows/opencode-rust-coverage-toolchain-quality-ci.yml" ) @@ -15,78 +14,41 @@ _LLVM_PROFDATA_PATH = "/usr/bin/llvm-profdata-19" -def _workflow_text() -> str: - """Return the authoritative OpenCode review-dispatch workflow text.""" +def _helper_text() -> str: + """Return the reviewed LLVM 19 runtime-boundary helper.""" - 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)] + return _HELPER_PATH.read_text(encoding="utf-8") def test_trusted_rust_coverage_image_provisions_verified_llvm_19_tools() -> None: - """Require explicit compatible LLVM tools before cargo-llvm-cov installation.""" + """Require explicit compatible LLVM 19 tools in the reviewed helper.""" - 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 + helper = _helper_text() + assert f'LLVM_COV_PATH="${{LLVM_COV_PATH:-{_LLVM_COV_PATH}}}"' in helper assert ( - llvm_package - < llvm_cov_environment - < llvm_profdata_environment - < llvm_cov_checks[0] - < llvm_profdata_checks[0] - < cargo_llvm_cov_archive + f'LLVM_PROFDATA_PATH="${{LLVM_PROFDATA_PATH:-{_LLVM_PROFDATA_PATH}}}"' + in helper ) + assert 'test -x "${LLVM_COV:-}"' in helper + assert 'test -x "${LLVM_PROFDATA:-}"' in helper def test_isolated_runtime_receives_reviewed_llvm_constants() -> None: - """Require exact LLVM 19 path propagation through the Docker boundary.""" + """Require exact LLVM 19 path constants in the helper contract.""" - 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 + helper = _helper_text() + assert _LLVM_COV_PATH in helper + assert _LLVM_PROFDATA_PATH in helper + assert "unversioned" not in helper 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 + """Require reviewed-path equality and executable checks before coverage.""" + + helper = _helper_text() + assert f'"${{LLVM_COV:-}}" != "$LLVM_COV_PATH"' in helper + assert f'"${{LLVM_PROFDATA:-}}" != "$LLVM_PROFDATA_PATH"' in helper + assert "exit 1" in helper def test_quality_workflow_watched_paths_resolve_to_repository_files() -> None: