From 4f8c786ab910845d14bc5cbdaf0b2b5ea6c29e1a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 09:28:31 +0900 Subject: [PATCH 01/18] fix(coverage): scope Rust evidence to changed packages --- .github/workflows/opencode-review-dispatch.yml | 9 +++++++-- tests/test_opencode_agent_contract.py | 3 +++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/opencode-review-dispatch.yml b/.github/workflows/opencode-review-dispatch.yml index 83f6830d5c..8dd7184e6a 100644 --- a/.github/workflows/opencode-review-dispatch.yml +++ b/.github/workflows/opencode-review-dispatch.yml @@ -1734,7 +1734,11 @@ jobs: } rust_coverage_manifests() { - if [ -f Cargo.toml ]; then + # A repository-wide coverage run is required when the workspace + # manifest or lockfile changed. Otherwise measure only the + # changed Rust package(s); building every workspace member at once + # can exhaust the review runner disk before tests begin. + if has_changed_tracked_files Cargo.toml Cargo.lock; then printf '%s\n' Cargo.toml return 0 fi @@ -1745,11 +1749,12 @@ jobs: *) continue ;; esac candidate_dir="$(dirname "$changed_path")" - while [ "$candidate_dir" != "." ] && [ "$candidate_dir" != "/" ]; do + while :; do if [ -f "${candidate_dir}/Cargo.toml" ]; then printf '%s\n' "${candidate_dir}/Cargo.toml" break fi + [ "$candidate_dir" = "." ] && break next_dir="$(dirname "$candidate_dir")" if [ "$next_dir" = "$candidate_dir" ]; then break diff --git a/tests/test_opencode_agent_contract.py b/tests/test_opencode_agent_contract.py index 379dded147..a6135f0129 100644 --- a/tests/test_opencode_agent_contract.py +++ b/tests/test_opencode_agent_contract.py @@ -750,6 +750,9 @@ def test_opencode_target_coverage_materializes_only_after_authorized_dispatch(): assert "rust_coverage_fail_under_lines()" in measure_step assert "package.metadata.opencode.coverage.minimum_lines" in measure_step assert "workspace.metadata.opencode.coverage.minimum_lines" in measure_step + assert "has_changed_tracked_files Cargo.toml Cargo.lock" in measure_step + assert "changed Rust package(s)" in measure_step + assert 'candidate_dir="$(dirname "$changed_path")"' in measure_step assert "scripts/ci/rust_coverage_threshold.py" in measure_step assert '--fail-under-lines "$threshold"' in measure_step assert "uv sync --project" not in measure_step From e2d7c1466ff25d6991c6b1d52eb9061ff619d6b7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 09:34:33 +0900 Subject: [PATCH 02/18] docs(changelog): record scoped Rust coverage --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f4903c2f3f..a77debf37a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,10 @@ Semantic Versioning where the repository publishes a release. ### Fixed +- Scope OpenCode Rust coverage evidence to changed Cargo packages while + retaining full-workspace coverage for root workspace and lockfile changes, + preventing large repositories from exhausting the review runner before + coverage starts. - Parsed `opencode.jsonc` as JSONC (stripping `//` and `/* */` comments outside string literals) in the reasoning-effort guard and its contract tests, instead of raw `json.loads`, which rejected the file the moment it carried its first explanatory comment (added for the `contextual-orchestrator` provider block) with `Expecting property name enclosed in double quotes`. Comment markers inside string values, such as the `$schema` URL, are left untouched. - 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). From 9cb5a409343d69c4821311a29367729029ef2045 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 09:38:57 +0900 Subject: [PATCH 03/18] test(opencode): refresh review workflow snapshot --- tests/test_pr_review_autofix_nvidia_nim_contract.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_pr_review_autofix_nvidia_nim_contract.py b/tests/test_pr_review_autofix_nvidia_nim_contract.py index 1bbd987507..873222d968 100644 --- a/tests/test_pr_review_autofix_nvidia_nim_contract.py +++ b/tests/test_pr_review_autofix_nvidia_nim_contract.py @@ -20,7 +20,7 @@ DOCTORING_RECORD = Path("docs/doctoring/hourly-nvidia-nim-autofix.md") CHANGELOG = Path("CHANGELOG.md") REVIEW_DISPATCH_WORKFLOW = Path(".github/workflows/opencode-review-dispatch.yml") -REVIEW_DISPATCH_BLOB_SHA = "83f6830d5c21a324b4dbcd4e5c21a07968994b81" +REVIEW_DISPATCH_BLOB_SHA = "8dd7184e6afcb45f7adb0d6fa1c03b04853795e3" def _workflow_text(path: Path) -> str: From 8ec2c9f6af2267d71fdd377ceaa91357294bdd86 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 09:44:38 +0900 Subject: [PATCH 04/18] fix(coverage): discover nested Rust manifests --- .github/workflows/opencode-review-dispatch.yml | 2 +- tests/test_opencode_agent_contract.py | 1 + tests/test_pr_review_autofix_nvidia_nim_contract.py | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/opencode-review-dispatch.yml b/.github/workflows/opencode-review-dispatch.yml index 8dd7184e6a..05fccd9cdb 100644 --- a/.github/workflows/opencode-review-dispatch.yml +++ b/.github/workflows/opencode-review-dispatch.yml @@ -1745,7 +1745,7 @@ jobs: changed_files_for_coverage \ | while IFS= read -r changed_path; do case "$changed_path" in - Cargo.toml|Cargo.lock|*.rs) ;; + Cargo.toml|Cargo.lock|*/Cargo.toml|*/Cargo.lock|*.rs) ;; *) continue ;; esac candidate_dir="$(dirname "$changed_path")" diff --git a/tests/test_opencode_agent_contract.py b/tests/test_opencode_agent_contract.py index a6135f0129..c071cacb31 100644 --- a/tests/test_opencode_agent_contract.py +++ b/tests/test_opencode_agent_contract.py @@ -753,6 +753,7 @@ def test_opencode_target_coverage_materializes_only_after_authorized_dispatch(): assert "has_changed_tracked_files Cargo.toml Cargo.lock" in measure_step assert "changed Rust package(s)" in measure_step assert 'candidate_dir="$(dirname "$changed_path")"' in measure_step + assert "*/Cargo.toml|*/Cargo.lock|*.rs" in measure_step assert "scripts/ci/rust_coverage_threshold.py" in measure_step assert '--fail-under-lines "$threshold"' in measure_step assert "uv sync --project" not in measure_step diff --git a/tests/test_pr_review_autofix_nvidia_nim_contract.py b/tests/test_pr_review_autofix_nvidia_nim_contract.py index 873222d968..008ba8b4c5 100644 --- a/tests/test_pr_review_autofix_nvidia_nim_contract.py +++ b/tests/test_pr_review_autofix_nvidia_nim_contract.py @@ -20,7 +20,7 @@ DOCTORING_RECORD = Path("docs/doctoring/hourly-nvidia-nim-autofix.md") CHANGELOG = Path("CHANGELOG.md") REVIEW_DISPATCH_WORKFLOW = Path(".github/workflows/opencode-review-dispatch.yml") -REVIEW_DISPATCH_BLOB_SHA = "8dd7184e6afcb45f7adb0d6fa1c03b04853795e3" +REVIEW_DISPATCH_BLOB_SHA = "05fccd9cdbc0ad221a277faf6725223a9c167ac3" def _workflow_text(path: Path) -> str: From 6aa186b31ec0f6bae0ab0372fe62d7290fb680ba Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 09:58:28 +0900 Subject: [PATCH 05/18] test(coverage): execute Rust manifest selection contract --- tests/test_opencode_agent_contract.py | 81 +++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/tests/test_opencode_agent_contract.py b/tests/test_opencode_agent_contract.py index c071cacb31..b2139180a5 100644 --- a/tests/test_opencode_agent_contract.py +++ b/tests/test_opencode_agent_contract.py @@ -1079,6 +1079,87 @@ def test_opencode_coverage_discovers_changed_nested_javascript_package(tmp_path) assert result.stdout.splitlines() == ["ADFS 연동 라이브러리/Node.JS/Node App"] +def test_opencode_rust_coverage_selects_changed_manifests(tmp_path): + """Select only the Rust manifests whose packages contain changed files.""" + bash = shutil.which("bash") + if bash is None: + pytest.skip("bash is required for the extracted workflow function regression test") + + workflow = Path(".github/workflows/opencode-review-dispatch.yml").read_text( + encoding="utf-8" + ) + measure_start = workflow.index( + " - name: Measure test and docstring evidence\n" + ) + measure_end = workflow.index("\n - name:", measure_start + 1) + measure_step = workflow[measure_start:measure_end] + changed_start = measure_step.index(" has_changed_tracked_files() {\n") + changed_end = measure_step.index( + "\n\n tracked_python_projects_with_tests()", changed_start + ) + rust_start = measure_step.index(" rust_coverage_manifests() {\n") + rust_end = measure_step.index( + "\n\n rust_coverage_fail_under_lines()", rust_start + ) + shell = "\n".join( + ( + "set -euo pipefail", + "trusted_git() { git \"$@\"; }", + "changed_files_for_coverage() { cat \"$CHANGED_FILE_LIST\"; }", + textwrap.dedent(measure_step[changed_start:changed_end]), + textwrap.dedent(measure_step[rust_start:rust_end]), + "rust_coverage_manifests", + ) + ) + + repo = tmp_path / "repo" + (repo / "crates" / "alpha" / "src").mkdir(parents=True) + (repo / "src").mkdir() + for relative_path, content in { + "Cargo.toml": "[workspace]\nmembers = [\"crates/alpha\"]\n", + "Cargo.lock": "# lock\n", + "crates/alpha/Cargo.toml": "[package]\nname = \"alpha\"\n", + "crates/alpha/src/lib.rs": "pub fn alpha() {}\n", + "src/main.rs": "fn main() {}\n", + "README.md": "unrelated\n", + }.items(): + path = repo / relative_path + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(content, encoding="utf-8") + subprocess.run(["git", "init", "-q"], cwd=repo, check=True) + subprocess.run(["git", "config", "user.name", "Coverage Test"], cwd=repo, check=True) + subprocess.run( + ["git", "config", "user.email", "coverage@example.invalid"], + cwd=repo, + check=True, + ) + subprocess.run(["git", "add", "."], cwd=repo, check=True) + subprocess.run(["git", "commit", "-qm", "fixtures"], cwd=repo, check=True) + + def select(changed_paths: str) -> list[str]: + """Run the extracted selector against one changed-file inventory.""" + changed_file_list = repo / "changed-files.txt" + changed_file_list.write_text(changed_paths, encoding="utf-8") + result = subprocess.run( + [bash, "-c", shell], + cwd=repo, + env={**os.environ, "CHANGED_FILE_LIST": str(changed_file_list)}, + capture_output=True, + text=True, + timeout=30, + ) + assert result.returncode == 0, result.stderr + return result.stdout.splitlines() + + assert select("Cargo.toml\n") == ["Cargo.toml"] + assert select("Cargo.lock\n") == ["Cargo.toml"] + assert select("crates/alpha/Cargo.toml\n") == ["crates/alpha/Cargo.toml"] + assert select("crates/alpha/Cargo.lock\n") == ["crates/alpha/Cargo.toml"] + assert select("crates/alpha/src/lib.rs\n") == ["crates/alpha/Cargo.toml"] + assert select("src/main.rs\n") == ["./Cargo.toml"] + assert select("README.md\n") == [] + + def test_opencode_runtime_pin_supports_reasoning_options(): """Keep OpenCode runtime new enough to apply model-level reasoning settings.""" review_workflow = Path(".github/workflows/opencode-review-dispatch.yml").read_text( From 92810bf66a51c5e302cd655a46c3b8a4eed0e9ad Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 18:00:44 -0700 Subject: [PATCH 06/18] test(coverage): fail on deleted root Rust lockfile --- tests/test_opencode_agent_contract.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/test_opencode_agent_contract.py b/tests/test_opencode_agent_contract.py index b2139180a5..73eff639f9 100644 --- a/tests/test_opencode_agent_contract.py +++ b/tests/test_opencode_agent_contract.py @@ -1159,6 +1159,13 @@ def select(changed_paths: str) -> list[str]: assert select("src/main.rs\n") == ["./Cargo.toml"] assert select("README.md\n") == [] + # Deleting the root lockfile still changes the whole workspace dependency graph. + subprocess.run(["git", "rm", "-q", "Cargo.lock"], cwd=repo, check=True) + subprocess.run( + ["git", "commit", "-qm", "delete root lock"], cwd=repo, check=True + ) + assert select("Cargo.lock\n") == ["Cargo.toml"] + def test_opencode_runtime_pin_supports_reasoning_options(): """Keep OpenCode runtime new enough to apply model-level reasoning settings.""" From 94ab7c0f4649e2c53bc6a85abbbec57a45d360e3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 18:05:15 -0700 Subject: [PATCH 07/18] fix(coverage): keep deleted root lockfile workspace-wide --- .github/workflows/opencode-review-dispatch.yml | 3 ++- tests/test_opencode_agent_contract.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/opencode-review-dispatch.yml b/.github/workflows/opencode-review-dispatch.yml index 05fccd9cdb..09a8ad632b 100644 --- a/.github/workflows/opencode-review-dispatch.yml +++ b/.github/workflows/opencode-review-dispatch.yml @@ -1738,7 +1738,8 @@ jobs: # manifest or lockfile changed. Otherwise measure only the # changed Rust package(s); building every workspace member at once # can exhaust the review runner disk before tests begin. - if has_changed_tracked_files Cargo.toml Cargo.lock; then + if changed_files_for_coverage \ + | awk '$0 == "Cargo.toml" || $0 == "Cargo.lock" { found=1 } END { exit found ? 0 : 1 }'; then printf '%s\n' Cargo.toml return 0 fi diff --git a/tests/test_opencode_agent_contract.py b/tests/test_opencode_agent_contract.py index 73eff639f9..0007947f4b 100644 --- a/tests/test_opencode_agent_contract.py +++ b/tests/test_opencode_agent_contract.py @@ -750,7 +750,8 @@ def test_opencode_target_coverage_materializes_only_after_authorized_dispatch(): assert "rust_coverage_fail_under_lines()" in measure_step assert "package.metadata.opencode.coverage.minimum_lines" in measure_step assert "workspace.metadata.opencode.coverage.minimum_lines" in measure_step - assert "has_changed_tracked_files Cargo.toml Cargo.lock" in measure_step + assert "if changed_files_for_coverage" in measure_step + assert '$0 == "Cargo.toml" || $0 == "Cargo.lock"' in measure_step assert "changed Rust package(s)" in measure_step assert 'candidate_dir="$(dirname "$changed_path")"' in measure_step assert "*/Cargo.toml|*/Cargo.lock|*.rs" in measure_step From 91c16ebf5187daad749ae57ec01d16cb7afec7b3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 18:08:13 -0700 Subject: [PATCH 08/18] test(coverage): refresh exact review workflow pin --- tests/test_pr_review_autofix_nvidia_nim_contract.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_pr_review_autofix_nvidia_nim_contract.py b/tests/test_pr_review_autofix_nvidia_nim_contract.py index 008ba8b4c5..b128391826 100644 --- a/tests/test_pr_review_autofix_nvidia_nim_contract.py +++ b/tests/test_pr_review_autofix_nvidia_nim_contract.py @@ -20,7 +20,7 @@ DOCTORING_RECORD = Path("docs/doctoring/hourly-nvidia-nim-autofix.md") CHANGELOG = Path("CHANGELOG.md") REVIEW_DISPATCH_WORKFLOW = Path(".github/workflows/opencode-review-dispatch.yml") -REVIEW_DISPATCH_BLOB_SHA = "05fccd9cdbc0ad221a277faf6725223a9c167ac3" +REVIEW_DISPATCH_BLOB_SHA = "09a8ad632b2a788dc485e4ebaca99678cb16e475" def _workflow_text(path: Path) -> str: From e960321389d1b5858464ed7781fe5954c5f99624 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 12:39:39 +0900 Subject: [PATCH 09/18] test(ci): keep coverage contracts composable --- tests/test_opencode_agent_contract.py | 4 +++ ...t_pr_review_autofix_nvidia_nim_contract.py | 27 +++++++++++-------- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/tests/test_opencode_agent_contract.py b/tests/test_opencode_agent_contract.py index 0007947f4b..2fcfb32f62 100644 --- a/tests/test_opencode_agent_contract.py +++ b/tests/test_opencode_agent_contract.py @@ -1,3 +1,5 @@ +"""Executable contracts for the trusted OpenCode review configuration.""" + import json import os import re @@ -341,6 +343,7 @@ def test_opencode_model_pool_sets_high_effort_for_capable_candidates(): assert f'"{model_name}": {{' in workflow def is_reasoning_capable(model_name: str) -> bool: + """Return whether a model supports the high-effort reasoning contract.""" return ( model_name.startswith("gpt-5") or model_name.startswith("openai/gpt-5") @@ -1946,6 +1949,7 @@ def test_opencode_job_timeout_contains_full_sequential_review_budget(): workflow = Path(".github/workflows/opencode-review-dispatch.yml").read_text(encoding="utf-8") def timeout_minutes(pattern: str) -> int: + """Extract a required workflow timeout and fail if it is absent.""" match = re.search(pattern, workflow, re.MULTILINE) assert match, f"missing timeout contract: {pattern}" return int(match.group(1)) diff --git a/tests/test_pr_review_autofix_nvidia_nim_contract.py b/tests/test_pr_review_autofix_nvidia_nim_contract.py index b128391826..1c1086f12d 100644 --- a/tests/test_pr_review_autofix_nvidia_nim_contract.py +++ b/tests/test_pr_review_autofix_nvidia_nim_contract.py @@ -3,7 +3,6 @@ import hashlib from pathlib import Path import re -import subprocess import pytest @@ -20,7 +19,6 @@ DOCTORING_RECORD = Path("docs/doctoring/hourly-nvidia-nim-autofix.md") CHANGELOG = Path("CHANGELOG.md") REVIEW_DISPATCH_WORKFLOW = Path(".github/workflows/opencode-review-dispatch.yml") -REVIEW_DISPATCH_BLOB_SHA = "09a8ad632b2a788dc485e4ebaca99678cb16e475" def _workflow_text(path: Path) -> str: @@ -157,15 +155,22 @@ def test_missing_nvidia_nim_secret_fails_closed_before_model_execution() -> None def test_independent_review_agent_key_system_is_unchanged() -> None: - """Pin the existing read-only reviewer workflow byte-for-byte.""" - result = subprocess.run( - ["git", "hash-object", str(REVIEW_DISPATCH_WORKFLOW)], - check=True, - capture_output=True, - text=True, - ) - assert result.stdout.strip() == REVIEW_DISPATCH_BLOB_SHA - assert "pr-review-autofix" not in _workflow_text(REVIEW_DISPATCH_WORKFLOW) + """Keep review-write credentials separate while allowing gateway wiring.""" + workflow = _workflow_text(REVIEW_DISPATCH_WORKFLOW) + for expression in ( + "GH_TOKEN: $" + "{{ secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || github.token }}", + "GH_TOKEN: $" + "{{ secrets.OPENCODE_APPROVE_TOKEN || github.token }}", + "GH_TOKEN: $" + "{{ steps.opencode_app_token.outputs.token || secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || github.token }}", + ): + assert expression in workflow + assert "pr-review-autofix" not in workflow + assert "COPILOT_GITHUB_TOKEN" not in workflow + + model_step_start = workflow.index(" - name: Run OpenCode PR Review model pool") + model_step_end = workflow.index(" - name: Publish OpenCode review outcome", model_step_start) + model_step = workflow[model_step_start:model_step_end] + assert "PR_REVIEW_MERGE_TOKEN" not in model_step + assert "OPENCODE_APPROVE_TOKEN" not in model_step def test_ordinary_autofix_uses_the_same_exact_write_scope_as_conflict_repair() -> None: From 80d13a1693afbcc74b964b3e7f4418de8487c775 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 16:49:33 +0900 Subject: [PATCH 10/18] fix(coverage): inherit Rust workspace baseline for crates --- scripts/ci/rust_coverage_threshold.py | 28 ++++++++++++--- tests/test_rust_coverage_threshold.py | 50 +++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 4 deletions(-) diff --git a/scripts/ci/rust_coverage_threshold.py b/scripts/ci/rust_coverage_threshold.py index 910d7f1047..4d25a8315d 100644 --- a/scripts/ci/rust_coverage_threshold.py +++ b/scripts/ci/rust_coverage_threshold.py @@ -36,18 +36,38 @@ def resolve_minimum_lines(document: dict[str, Any]) -> float | None: break if selected_path is None: return None + return _validate_minimum_lines(selected_path, value) + + +def _validate_minimum_lines(path: str, value: Any) -> float: + """Validate one repository-owned coverage baseline and normalize it.""" if isinstance(value, bool) or not isinstance(value, (int, float)): - raise ValueError(f"{selected_path} must be a number from 0 to 100") + raise ValueError(f"{path} must be a number from 0 to 100") threshold = float(value) if not 0 <= threshold <= 100: - raise ValueError(f"{selected_path} must be between 0 and 100") + raise ValueError(f"{path} must be between 0 and 100") return threshold def read_minimum_lines(manifest: Path) -> float | None: - """Read and resolve one Cargo manifest's line-coverage baseline.""" + """Read a package baseline, falling back to its nearest workspace baseline.""" + manifest = manifest.resolve() document = tomllib.loads(manifest.read_text(encoding="utf-8")) - return resolve_minimum_lines(document) + threshold = resolve_minimum_lines(document) + if threshold is not None: + return threshold + + for parent in manifest.parents: + workspace_manifest = parent / "Cargo.toml" + if not workspace_manifest.is_file(): + continue + workspace_document = tomllib.loads(workspace_manifest.read_text(encoding="utf-8")) + if "workspace" not in workspace_document: + continue + workspace_value = _nested_value(workspace_document, METADATA_PATHS[1]) + if workspace_value is not None: + return _validate_minimum_lines(METADATA_PATHS[1], workspace_value) + return None def main() -> int: diff --git a/tests/test_rust_coverage_threshold.py b/tests/test_rust_coverage_threshold.py index c3ed47bdf0..72e1e64d3a 100644 --- a/tests/test_rust_coverage_threshold.py +++ b/tests/test_rust_coverage_threshold.py @@ -31,6 +31,56 @@ def test_virtual_workspace_metadata_is_supported(tmp_path: Path) -> None: assert threshold.read_minimum_lines(manifest) == 90.0 +def test_nested_package_inherits_nearest_workspace_baseline(tmp_path: Path) -> None: + """A crate without a local override must use the workspace baseline.""" + workspace = tmp_path / "Cargo.toml" + workspace.write_text( + '[workspace]\nmembers = ["crates/core"]\n\n' + "[workspace.metadata.opencode.coverage]\nminimum_lines = 87\n", + encoding="utf-8", + ) + manifest = tmp_path / "crates" / "core" / "Cargo.toml" + manifest.parent.mkdir(parents=True) + manifest.write_text('[package]\nname = "core"\nversion = "0.1.0"\n', encoding="utf-8") + + assert threshold.read_minimum_lines(manifest) == 87.0 + + +def test_nested_package_override_beats_workspace_baseline(tmp_path: Path) -> None: + """A crate-specific baseline remains stronger than inherited metadata.""" + workspace = tmp_path / "Cargo.toml" + workspace.write_text( + '[workspace]\nmembers = ["crates/core"]\n\n' + "[workspace.metadata.opencode.coverage]\nminimum_lines = 87\n", + encoding="utf-8", + ) + manifest = tmp_path / "crates" / "core" / "Cargo.toml" + manifest.parent.mkdir(parents=True) + manifest.write_text( + '[package]\nname = "core"\nversion = "0.1.0"\n\n' + "[package.metadata.opencode.coverage]\nminimum_lines = 93\n", + encoding="utf-8", + ) + + assert threshold.read_minimum_lines(manifest) == 93.0 + + +def test_nested_package_rejects_invalid_workspace_baseline(tmp_path: Path) -> None: + """An inherited malformed baseline cannot silently restore the central default.""" + workspace = tmp_path / "Cargo.toml" + workspace.write_text( + '[workspace]\nmembers = ["crates/core"]\n\n' + '[workspace.metadata.opencode.coverage]\nminimum_lines = "high"\n', + encoding="utf-8", + ) + manifest = tmp_path / "crates" / "core" / "Cargo.toml" + manifest.parent.mkdir(parents=True) + manifest.write_text('[package]\nname = "core"\nversion = "0.1.0"\n', encoding="utf-8") + + with pytest.raises(ValueError, match="workspace.metadata.opencode.coverage.minimum_lines"): + threshold.read_minimum_lines(manifest) + + @pytest.mark.parametrize("value", [True, "90", -1, 101]) def test_invalid_thresholds_fail_closed(value: object) -> None: """Non-numeric and out-of-range baselines cannot weaken the coverage gate.""" From db1802fc56a9a3e675b6da7ce4a4fb672217d5f1 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 16:52:58 +0900 Subject: [PATCH 11/18] fix(coverage): normalize root Rust workspace manifest --- .github/workflows/opencode-review-dispatch.yml | 6 +++++- tests/test_opencode_agent_contract.py | 5 ++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/opencode-review-dispatch.yml b/.github/workflows/opencode-review-dispatch.yml index 09a8ad632b..0a6f0ed809 100644 --- a/.github/workflows/opencode-review-dispatch.yml +++ b/.github/workflows/opencode-review-dispatch.yml @@ -1752,7 +1752,11 @@ jobs: candidate_dir="$(dirname "$changed_path")" while :; do if [ -f "${candidate_dir}/Cargo.toml" ]; then - printf '%s\n' "${candidate_dir}/Cargo.toml" + if [ "$candidate_dir" = "." ]; then + printf '%s\n' Cargo.toml + else + printf '%s\n' "${candidate_dir}/Cargo.toml" + fi break fi [ "$candidate_dir" = "." ] && break diff --git a/tests/test_opencode_agent_contract.py b/tests/test_opencode_agent_contract.py index 2fcfb32f62..d5340e160d 100644 --- a/tests/test_opencode_agent_contract.py +++ b/tests/test_opencode_agent_contract.py @@ -1160,9 +1160,12 @@ def select(changed_paths: str) -> list[str]: assert select("crates/alpha/Cargo.toml\n") == ["crates/alpha/Cargo.toml"] assert select("crates/alpha/Cargo.lock\n") == ["crates/alpha/Cargo.toml"] assert select("crates/alpha/src/lib.rs\n") == ["crates/alpha/Cargo.toml"] - assert select("src/main.rs\n") == ["./Cargo.toml"] + assert select("src/main.rs\n") == ["Cargo.toml"] assert select("README.md\n") == [] + (repo / "crates" / "alpha" / "Cargo.toml").unlink() + assert select("crates/alpha/Cargo.toml\n") == ["Cargo.toml"] + # Deleting the root lockfile still changes the whole workspace dependency graph. subprocess.run(["git", "rm", "-q", "Cargo.lock"], cwd=repo, check=True) subprocess.run( From 2a5ab45719781687b6ad9931072df10a1c04d738 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 18:01:44 +0900 Subject: [PATCH 12/18] fix(coverage): avoid duplicate Rust workspace runs --- .github/workflows/opencode-review-dispatch.yml | 13 +++++++++++-- tests/test_opencode_agent_contract.py | 1 + 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/.github/workflows/opencode-review-dispatch.yml b/.github/workflows/opencode-review-dispatch.yml index 0a6f0ed809..e855077f2f 100644 --- a/.github/workflows/opencode-review-dispatch.yml +++ b/.github/workflows/opencode-review-dispatch.yml @@ -1743,7 +1743,8 @@ jobs: printf '%s\n' Cargo.toml return 0 fi - changed_files_for_coverage \ + local manifests + manifests="$(changed_files_for_coverage \ | while IFS= read -r changed_path; do case "$changed_path" in Cargo.toml|Cargo.lock|*/Cargo.toml|*/Cargo.lock|*.rs) ;; @@ -1767,7 +1768,15 @@ jobs: candidate_dir="$next_dir" done done \ - | sort -u + | sort -u)" + # A root workspace run already covers every member; do not repeat + # nested package runs when root and member sources changed together. + if printf '%s\n' "$manifests" \ + | awk '$0 == "Cargo.toml" { found=1 } END { exit found ? 0 : 1 }'; then + printf '%s\n' Cargo.toml + elif [ -n "$manifests" ]; then + printf '%s\n' "$manifests" + fi } rust_coverage_fail_under_lines() { diff --git a/tests/test_opencode_agent_contract.py b/tests/test_opencode_agent_contract.py index d5340e160d..8651dfae51 100644 --- a/tests/test_opencode_agent_contract.py +++ b/tests/test_opencode_agent_contract.py @@ -1161,6 +1161,7 @@ def select(changed_paths: str) -> list[str]: assert select("crates/alpha/Cargo.lock\n") == ["crates/alpha/Cargo.toml"] assert select("crates/alpha/src/lib.rs\n") == ["crates/alpha/Cargo.toml"] assert select("src/main.rs\n") == ["Cargo.toml"] + assert select("src/main.rs\ncrates/alpha/src/lib.rs\n") == ["Cargo.toml"] assert select("README.md\n") == [] (repo / "crates" / "alpha" / "Cargo.toml").unlink() From f71db4b3d9906b241145b7ea9cc5e628aa3cf77e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 03:32:52 +0900 Subject: [PATCH 13/18] fix(ci): refresh audit lock and scheduler assertion --- requirements-pip-audit-ci-hashes.txt | 6 +++--- scripts/ci/test_strix_quick_gate.sh | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/requirements-pip-audit-ci-hashes.txt b/requirements-pip-audit-ci-hashes.txt index ade197a49a..0ae099d8fe 100644 --- a/requirements-pip-audit-ci-hashes.txt +++ b/requirements-pip-audit-ci-hashes.txt @@ -213,9 +213,9 @@ packaging==26.2 \ # via # pip-audit # pip-requirements-parser -pip==26.1.2 \ - --hash=sha256:382ff9f685ee3bc25864f820aa50505825f10f5458ffff07e30a6d96e5715cab \ - --hash=sha256:f49cd134c61cf2fd75e0ce2676db03e4054504a5a4986d00f8299ae632dc4605 +pip==26.2.1 \ + --hash=sha256:71138adf1f4ca900cdb7d289c21b7494329f2332b6d85f0e1c42108c0384ed3e \ + --hash=sha256:f6ad667e89a1fe78046c8f13232b247200f5258d7828f3f7883d660878e0813f # via pip-api pip-api==0.0.34 \ --hash=sha256:8b2d7d7c37f2447373aa2cf8b1f60a2f2b27a84e1e9e0294a3f6ef10eb3ba6bb \ diff --git a/scripts/ci/test_strix_quick_gate.sh b/scripts/ci/test_strix_quick_gate.sh index ac9ce1d8bd..bbe9aa8b21 100755 --- a/scripts/ci/test_strix_quick_gate.sh +++ b/scripts/ci/test_strix_quick_gate.sh @@ -1506,8 +1506,8 @@ assert_pr_review_merge_scheduler_uses_github_actions_bot_token() { assert_file_contains "$workflow_file" "github.event_name == 'pull_request_target' && format('pr-{0}', github.event.pull_request.number)" "scheduler scopes pull_request_target concurrency to the active PR" assert_file_contains "$workflow_file" "github.event_name == 'workflow_run' && github.event.workflow_run.pull_requests[0].number && format('pr-{0}', github.event.workflow_run.pull_requests[0].number)" "scheduler scopes workflow_run concurrency to the completed review PR" assert_file_contains "$workflow_file" "github.event_name == 'schedule' && format('schedule-{0}', github.event.schedule)" "scheduler isolates the 15-minute organization sweep from the separate 30-minute scheduled scan" - assert_file_contains "$workflow_file" "github.event_name == 'repository_dispatch' && github.run_id" "scheduler keeps manual queue scans isolated per run" - assert_file_contains "$workflow_file" "cancel-in-progress: \${{ github.event_name == 'pull_request_target' || github.event_name == 'pull_request_review' || github.event_name == 'repository_dispatch' }}" "scheduler cancels stale PR/review/manual queue scans instead of accumulating merge/update attempts" + assert_file_contains "$workflow_file" "github.event_name == 'repository_dispatch' && format('repo-dispatch-{0}', github.repository)" "scheduler keeps manual queue scans isolated per repository" + assert_file_contains "$workflow_file" "cancel-in-progress: \${{ github.event_name == 'pull_request_target' || github.event_name == 'pull_request_review' || github.event_name == 'repository_dispatch' || (github.event_name == 'workflow_run' && !github.event.workflow_run.pull_requests[0].number) }}" "scheduler cancels stale PR/review/manual queue scans and orphaned workflow runs without cancelling active workflow-run evidence" assert_file_contains "$workflow_file" "timeout-minutes: 60" "organization sweep has enough headroom to finish the complete repository walk" assert_file_contains "$workflow_file" "ORG_SWEEP_TRIGGER_REVIEWS: \${{ github.event_name == 'schedule' ||" "scheduled organization sweeps retry missing current-head OpenCode reviews" assert_file_contains "$workflow_file" "ORG_SWEEP_ENABLE_AUTO_MERGE: \${{ github.event_name == 'schedule' ||" "scheduled organization sweeps merge approved current heads" From be6534b4e04d0856f31c732c1eff84a2aacd269e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 03:48:49 +0900 Subject: [PATCH 14/18] test(ci): restore complete docstring coverage --- organization_commercial_readiness_fixtures.py | 1 + scripts/ci/organization_commercial_readiness_loop.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/organization_commercial_readiness_fixtures.py b/organization_commercial_readiness_fixtures.py index d865961962..4275ea3dcb 100644 --- a/organization_commercial_readiness_fixtures.py +++ b/organization_commercial_readiness_fixtures.py @@ -90,6 +90,7 @@ def __init__( repositories: list[dict[str, Any]], snapshots: dict[str, list[RepositorySnapshot | Exception]], ) -> None: + """Initialize deterministic repository and dispatch fixtures.""" self.repositories = repositories self.snapshots = snapshots self.dispatched_repairs: list[tuple[str, str]] = [] diff --git a/scripts/ci/organization_commercial_readiness_loop.py b/scripts/ci/organization_commercial_readiness_loop.py index c00cfa1e0a..a6ffc0895c 100644 --- a/scripts/ci/organization_commercial_readiness_loop.py +++ b/scripts/ci/organization_commercial_readiness_loop.py @@ -239,6 +239,7 @@ class GitHubClient: """Use the GitHub CLI as an authenticated, bounded REST transport.""" def __init__(self, token: str, *, timeout_seconds: int = 60) -> None: + """Initialize the bounded authenticated transport.""" if not token: raise GitHubError("GH_TOKEN is required for organization coordination") self._token = token @@ -853,4 +854,4 @@ def main( if __name__ == "__main__": # pragma: no cover - exercised through main() - raise SystemExit(main()) \ No newline at end of file + raise SystemExit(main()) From 471f34d3e1359793bee27868c33738f242c8a2ab Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 14:04:51 +0900 Subject: [PATCH 15/18] fix(review): close coverage and credential contract gaps --- .../workflows/opencode-review-dispatch.yml | 2 +- tests/test_opencode_agent_contract.py | 1 + ...t_pr_review_autofix_nvidia_nim_contract.py | 50 +++++++++++++++---- tests/test_rust_coverage_threshold.py | 4 +- 4 files changed, 45 insertions(+), 12 deletions(-) diff --git a/.github/workflows/opencode-review-dispatch.yml b/.github/workflows/opencode-review-dispatch.yml index e855077f2f..d263b048bc 100644 --- a/.github/workflows/opencode-review-dispatch.yml +++ b/.github/workflows/opencode-review-dispatch.yml @@ -1980,7 +1980,7 @@ jobs: run_r_test_coverage fi - if has_changed_tracked_files 'Cargo.toml' 'Cargo.lock' '*.rs'; then + if has_changed_tracked_files 'Cargo.toml' 'Cargo.lock' '*/Cargo.toml' '*/Cargo.lock' '*.rs'; then measured_any=1 run_rust_test_coverage fi diff --git a/tests/test_opencode_agent_contract.py b/tests/test_opencode_agent_contract.py index 8651dfae51..d24c7d29f8 100644 --- a/tests/test_opencode_agent_contract.py +++ b/tests/test_opencode_agent_contract.py @@ -755,6 +755,7 @@ def test_opencode_target_coverage_materializes_only_after_authorized_dispatch(): assert "workspace.metadata.opencode.coverage.minimum_lines" in measure_step assert "if changed_files_for_coverage" in measure_step assert '$0 == "Cargo.toml" || $0 == "Cargo.lock"' in measure_step + assert "has_changed_tracked_files 'Cargo.toml' 'Cargo.lock' '*/Cargo.toml' '*/Cargo.lock' '*.rs'" in measure_step assert "changed Rust package(s)" in measure_step assert 'candidate_dir="$(dirname "$changed_path")"' in measure_step assert "*/Cargo.toml|*/Cargo.lock|*.rs" in measure_step diff --git a/tests/test_pr_review_autofix_nvidia_nim_contract.py b/tests/test_pr_review_autofix_nvidia_nim_contract.py index 1c1086f12d..c161a50ae6 100644 --- a/tests/test_pr_review_autofix_nvidia_nim_contract.py +++ b/tests/test_pr_review_autofix_nvidia_nim_contract.py @@ -1,5 +1,6 @@ """Contract tests for the scheduled OpenCode review-autofix trust boundary.""" +from collections import Counter import hashlib from pathlib import Path import re @@ -157,20 +158,51 @@ def test_missing_nvidia_nim_secret_fails_closed_before_model_execution() -> None def test_independent_review_agent_key_system_is_unchanged() -> None: """Keep review-write credentials separate while allowing gateway wiring.""" workflow = _workflow_text(REVIEW_DISPATCH_WORKFLOW) - for expression in ( - "GH_TOKEN: $" + "{{ secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || github.token }}", - "GH_TOKEN: $" + "{{ secrets.OPENCODE_APPROVE_TOKEN || github.token }}", - "GH_TOKEN: $" + "{{ steps.opencode_app_token.outputs.token || secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || github.token }}", - ): - assert expression in workflow + approved_gh_token_assignments = ( + "${{ steps.metadata_read_app_token.outputs.token || secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || github.token }}", + "${{ steps.coverage_read_app_token.outputs.token || secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || github.token }}", + "${{ secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || github.token }}", + "${{ steps.review_read_app_token.outputs.token || secrets.OPENCODE_APPROVE_TOKEN || github.token }}", + "${{ secrets.OPENCODE_APPROVE_TOKEN || github.token }}", + "${{ secrets.OPENCODE_APPROVE_TOKEN || steps.review_read_app_token.outputs.token || github.token }}", + "${{ steps.opencode_app_token.outputs.token }}", + "${{ steps.opencode_app_token.outputs.token }}", + "${{ steps.opencode_app_token.outputs.token || secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || github.token }}", + "${{ secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || steps.opencode_app_token.outputs.token || github.token }}", + "${{ secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || steps.opencode_app_token.outputs.token || github.token }}", + "${{ secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || steps.opencode_app_token.outputs.token || github.token }}", + ) + assignments = re.findall(r"^ {10}GH_TOKEN:\s*(.+)$", workflow, flags=re.MULTILINE) + assert Counter(assignments) == Counter(approved_gh_token_assignments) + assert not re.search(r"^ {2,9}(?:GH_TOKEN|GITHUB_TOKEN):", workflow, flags=re.MULTILINE) assert "pr-review-autofix" not in workflow assert "COPILOT_GITHUB_TOKEN" not in workflow model_step_start = workflow.index(" - name: Run OpenCode PR Review model pool") - model_step_end = workflow.index(" - name: Publish OpenCode review outcome", model_step_start) + model_step_end = workflow.index("\n - name:", model_step_start + 1) model_step = workflow[model_step_start:model_step_end] - assert "PR_REVIEW_MERGE_TOKEN" not in model_step - assert "OPENCODE_APPROVE_TOKEN" not in model_step + for provider_credential in ( + "STRIX_GITHUB_MODELS_TOKEN:", + "OPENCODE_API_KEY:", + "OPENAI_API_KEY:", + "NVIDIA_API_KEY:", + "OPENROUTER_API_KEY:", + "NVIDIA_NIM_API_KEY:", + ): + assert provider_credential in model_step + for forbidden_credential in ( + "GH_TOKEN:", + "GITHUB_TOKEN:", + "CHECK_LOOKUP_GH_TOKEN:", + "CODE_SCANNING_GH_TOKEN:", + "OPENCODE_APP_TOKEN:", + "PR_REVIEW_MERGE_TOKEN", + "OPENCODE_APPROVE_TOKEN", + "COPILOT_GITHUB_TOKEN", + "ACTIONS_ID_TOKEN_REQUEST_TOKEN", + "ACTIONS_ID_TOKEN_REQUEST_URL", + ): + assert forbidden_credential not in model_step def test_ordinary_autofix_uses_the_same_exact_write_scope_as_conflict_repair() -> None: diff --git a/tests/test_rust_coverage_threshold.py b/tests/test_rust_coverage_threshold.py index 72e1e64d3a..2083324725 100644 --- a/tests/test_rust_coverage_threshold.py +++ b/tests/test_rust_coverage_threshold.py @@ -77,7 +77,7 @@ def test_nested_package_rejects_invalid_workspace_baseline(tmp_path: Path) -> No manifest.parent.mkdir(parents=True) manifest.write_text('[package]\nname = "core"\nversion = "0.1.0"\n', encoding="utf-8") - with pytest.raises(ValueError, match="workspace.metadata.opencode.coverage.minimum_lines"): + with pytest.raises(ValueError, match=r"workspace\.metadata\.opencode\.coverage\.minimum_lines"): threshold.read_minimum_lines(manifest) @@ -88,7 +88,7 @@ def test_invalid_thresholds_fail_closed(value: object) -> None: "workspace": {"metadata": {"opencode": {"coverage": {"minimum_lines": value}}}} } - with pytest.raises(ValueError, match="workspace.metadata.opencode.coverage.minimum_lines"): + with pytest.raises(ValueError, match=r"workspace\.metadata\.opencode\.coverage\.minimum_lines"): threshold.resolve_minimum_lines(document) From 5772721698297d7688624f00bea1553618f02478 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 14:09:06 +0900 Subject: [PATCH 16/18] fix(review): trigger coverage for deleted manifests --- .../workflows/opencode-review-dispatch.yml | 7 ++++- tests/test_opencode_agent_contract.py | 27 +++++++++++++++++-- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/.github/workflows/opencode-review-dispatch.yml b/.github/workflows/opencode-review-dispatch.yml index d263b048bc..5fd7d1df6d 100644 --- a/.github/workflows/opencode-review-dispatch.yml +++ b/.github/workflows/opencode-review-dispatch.yml @@ -1060,6 +1060,11 @@ jobs: return "$rc" } + has_changed_rust_files() { + changed_files_for_coverage | + awk '$0 ~ /(^|\/)Cargo\.(toml|lock)$/ || $0 ~ /\.rs$/ { found=1 } END { exit found ? 0 : 1 }' + } + tracked_python_projects_with_tests() { trusted_git ls-files 'pyproject.toml' '*/pyproject.toml' 'requirements.txt' '*/requirements.txt' \ | while IFS= read -r pyproject_file; do @@ -1980,7 +1985,7 @@ jobs: run_r_test_coverage fi - if has_changed_tracked_files 'Cargo.toml' 'Cargo.lock' '*/Cargo.toml' '*/Cargo.lock' '*.rs'; then + if has_changed_rust_files; then measured_any=1 run_rust_test_coverage fi diff --git a/tests/test_opencode_agent_contract.py b/tests/test_opencode_agent_contract.py index d24c7d29f8..3843dfecb9 100644 --- a/tests/test_opencode_agent_contract.py +++ b/tests/test_opencode_agent_contract.py @@ -755,7 +755,8 @@ def test_opencode_target_coverage_materializes_only_after_authorized_dispatch(): assert "workspace.metadata.opencode.coverage.minimum_lines" in measure_step assert "if changed_files_for_coverage" in measure_step assert '$0 == "Cargo.toml" || $0 == "Cargo.lock"' in measure_step - assert "has_changed_tracked_files 'Cargo.toml' 'Cargo.lock' '*/Cargo.toml' '*/Cargo.lock' '*.rs'" in measure_step + assert "has_changed_rust_files" in measure_step + assert "Cargo\\.(toml|lock)" in measure_step assert "changed Rust package(s)" in measure_step assert 'candidate_dir="$(dirname "$changed_path")"' in measure_step assert "*/Cargo.toml|*/Cargo.lock|*.rs" in measure_step @@ -1124,6 +1125,7 @@ def test_opencode_rust_coverage_selects_changed_manifests(tmp_path): "Cargo.toml": "[workspace]\nmembers = [\"crates/alpha\"]\n", "Cargo.lock": "# lock\n", "crates/alpha/Cargo.toml": "[package]\nname = \"alpha\"\n", + "crates/alpha/Cargo.lock": "# nested lock\n", "crates/alpha/src/lib.rs": "pub fn alpha() {}\n", "src/main.rs": "fn main() {}\n", "README.md": "unrelated\n", @@ -1165,9 +1167,30 @@ def select(changed_paths: str) -> list[str]: assert select("src/main.rs\ncrates/alpha/src/lib.rs\n") == ["Cargo.toml"] assert select("README.md\n") == [] - (repo / "crates" / "alpha" / "Cargo.toml").unlink() + nested_manifest = repo / "crates" / "alpha" / "Cargo.toml" + nested_manifest.unlink() assert select("crates/alpha/Cargo.toml\n") == ["Cargo.toml"] + subprocess.run(["git", "rm", "-q", "crates/alpha/Cargo.lock"], cwd=repo, check=True) + subprocess.run(["git", "commit", "-qm", "delete nested rust manifests"], cwd=repo, check=True) + trigger_start = measure_step.index(" has_changed_rust_files() {\n") + trigger_end = measure_step.index( + "\n\n tracked_python_projects_with_tests()", trigger_start + ) + trigger_shell = "\n".join( + ( + "set -euo pipefail", + "trusted_git() { git \"$@\"; }", + "changed_files_for_coverage() { git diff --name-only HEAD~1 HEAD; }", + textwrap.dedent(measure_step[trigger_start:trigger_end]), + "has_changed_rust_files", + ) + ) + trigger = subprocess.run( + [bash, "-c", trigger_shell], cwd=repo, capture_output=True, text=True, timeout=30 + ) + assert trigger.returncode == 0, trigger.stderr + # Deleting the root lockfile still changes the whole workspace dependency graph. subprocess.run(["git", "rm", "-q", "Cargo.lock"], cwd=repo, check=True) subprocess.run( From 047ad58d2f5a286a1de8854cd3892ca0dcc94735 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 30 Aug 2026 14:38:10 +0000 Subject: [PATCH 17/18] fix(rust-coverage): include rename endpoints; enforce workspace boundaries Devin findings on PR #1187: 1. .github/workflows/opencode-review-dispatch.yml: the shared changed_files_for_coverage() inventory used `git diff --name-only --find-renames`, which collapses a detected rename to a single line naming only the destination path. A .rs file renamed to a non-Rust extension therefore vanished from the inventory entirely (has_changed_rust_files missed it, bypassing Rust coverage), and a .rs file moved between two Cargo packages credited only the destination package's manifest. Fixed by switching to `git diff --name-status --find-renames` and emitting both the old and new path for R/C status lines (identified by the extra tab-separated field), while every other status still emits its single path unchanged -- so exact root Cargo.toml/Cargo.lock deletion detection (fixed earlier in this PR) is unaffected. This is the shared primitive behind has_changed_rust_files, rust_coverage_manifests, javascript_coverage_package_dirs, and the implementation-completeness scan's changed-file list; all four benefit from the same correctness fix, and none regress (verified each call site's assumptions). Added test_opencode_rust_coverage_inventory_includes_both_rename_endpoints in tests/test_opencode_agent_contract.py, covering both a .rs-to-non-Rust rename (has_changed_rust_files must still fire) and a cross-package move (rust_coverage_manifests must select both package manifests). Verified it fails against the pre-fix --name-only form and passes against the fix. 2. scripts/ci/rust_coverage_threshold.py: read_minimum_lines walked every ancestor Cargo.toml and used the first [workspace] table it found, without checking actual Cargo workspace membership or exclusion boundaries. This let (a) a nested manifest declaring its own independent [workspace] with no coverage metadata fall through to an unrelated outer workspace's metadata instead of stopping at its own (nested) workspace boundary, and (b) a package excluded from an outer workspace via `exclude = [...]` still inherit that workspace's metadata, even though Cargo does not consider it a member. Fixed by adding _workspace_excludes_package (checks the ancestor's workspace.exclude patterns, including glob and subdirectory-prefix matches, against the package's path relative to that ancestor) and changing the walk to: skip an ancestor whose workspace excludes the package (continue searching further out, matching Cargo's own root-discovery rule for excluded members) and otherwise stop at the first non-excluding ancestor workspace found -- using its metadata if set, or None if not, but never continuing past it to a further, unrelated workspace. Added six regression tests in tests/test_rust_coverage_threshold.py: nested-independent-workspace-with-no-metadata (must not inherit outer), nested-independent-workspace-with-its-own-metadata (must win), excluded-package (must not inherit), excluded-package-with-a-further- ancestor-workspace (must still inherit that one), plus two focused unit tests on _workspace_excludes_package's malformed-input and subdirectory-of-excluded-path branches for full branch coverage. Verified the two boundary-condition tests fail against the pre-fix first-found-wins walk and pass against the fix. Also updated the independent reviewer-workflow blob pin (REVIEW_DISPATCH_BLOB_SHA in tests/test_pr_review_autofix_nvidia_nim_contract.py) to match opencode-review-dispatch.yml's new git blob hash after the above edit, per this PR's existing "blob pin must move with the file" contract (test_review_dispatch_blob_sha_stays_paired_with_trusted_workflow). Evidence: PYTHONPATH=. python3 -m pytest tests -q -> 1913 passed, 1 skipped, 21 subtests passed; coverage 100% statements/branches on scripts/ci; interrogate 100% docstrings; git diff --check clean; YAML parses; python -m compileall clean. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01KPmJErfkcHer4UVEgrQxUX --- .../workflows/opencode-review-dispatch.yml | 13 +- scripts/ci/rust_coverage_threshold.py | 56 ++++++ tests/test_opencode_agent_contract.py | 156 +++++++++++++++ ...t_pr_review_autofix_nvidia_nim_contract.py | 2 +- tests/test_rust_coverage_threshold.py | 185 ++++++++++++++++++ 5 files changed, 410 insertions(+), 2 deletions(-) diff --git a/.github/workflows/opencode-review-dispatch.yml b/.github/workflows/opencode-review-dispatch.yml index 4e6c0da195..1544d357ef 100644 --- a/.github/workflows/opencode-review-dispatch.yml +++ b/.github/workflows/opencode-review-dispatch.yml @@ -1173,7 +1173,18 @@ jobs: if [ -n "${PR_BASE_SHA:-}" ] && [ -n "${PR_HEAD_SHA:-}" ] \ && trusted_git rev-parse --verify --quiet "$PR_BASE_SHA^{commit}" >/dev/null \ && trusted_git rev-parse --verify --quiet "$PR_HEAD_SHA^{commit}" >/dev/null; then - trusted_git diff --name-only --find-renames "$PR_BASE_SHA" "$PR_HEAD_SHA" + # --name-status (not --name-only) so a detected rename/copy + # surfaces BOTH its old and new path. --name-only collapses a + # rename to a single line naming only the destination, so a + # Rust source file renamed away to a non-Rust extension (or + # moved into a different Cargo package) would otherwise vanish + # from every consumer's changed-file inventory on the origin + # side -- has_changed_rust_files would miss it entirely, and + # rust_coverage_manifests would credit only the destination + # package. R/C status lines carry a third tab-separated field + # (the new path); every other status carries exactly the path. + trusted_git diff --name-status --find-renames "$PR_BASE_SHA" "$PR_HEAD_SHA" | + awk -F'\t' 'NF >= 3 { print $2; print $3; next } { print $2 }' else trusted_git ls-files fi diff --git a/scripts/ci/rust_coverage_threshold.py b/scripts/ci/rust_coverage_threshold.py index 4d25a8315d..9bb9727521 100644 --- a/scripts/ci/rust_coverage_threshold.py +++ b/scripts/ci/rust_coverage_threshold.py @@ -4,6 +4,7 @@ from __future__ import annotations import argparse +import fnmatch import tomllib from pathlib import Path from typing import Any @@ -49,6 +50,46 @@ def _validate_minimum_lines(path: str, value: Any) -> float: return threshold +def _relative_posix_path(path: Path, base: Path) -> str | None: + """Return ``path`` relative to ``base`` as a POSIX string, or None if unrelated.""" + try: + return path.relative_to(base).as_posix() + except ValueError: + return None + + +def _workspace_excludes_package( + workspace_dir: Path, package_dir: Path, workspace_document: dict[str, Any] +) -> bool: + """Return whether a workspace's ``exclude`` patterns cover the package directory. + + Cargo's own automatic workspace-root discovery walks upward from a + package's manifest and skips an ancestor workspace that excludes it, + continuing the search further out rather than treating the excluded + workspace as authoritative. Mirroring that here keeps an excluded (or + otherwise independent) package from inheriting a coverage baseline that + was never configured for it. + """ + workspace_table = workspace_document.get("workspace") + if not isinstance(workspace_table, dict): + return False + excludes = workspace_table.get("exclude") + if not isinstance(excludes, list): + return False + relative = _relative_posix_path(package_dir, workspace_dir) + if relative is None: + return False + for pattern in excludes: + if not isinstance(pattern, str): + continue + normalized_pattern = pattern.rstrip("/") + if relative == normalized_pattern or fnmatch.fnmatch(relative, normalized_pattern): + return True + if relative.startswith(f"{normalized_pattern}/"): + return True + return False + + def read_minimum_lines(manifest: Path) -> float | None: """Read a package baseline, falling back to its nearest workspace baseline.""" manifest = manifest.resolve() @@ -57,6 +98,7 @@ def read_minimum_lines(manifest: Path) -> float | None: if threshold is not None: return threshold + package_dir = manifest.parent for parent in manifest.parents: workspace_manifest = parent / "Cargo.toml" if not workspace_manifest.is_file(): @@ -64,9 +106,23 @@ def read_minimum_lines(manifest: Path) -> float | None: workspace_document = tomllib.loads(workspace_manifest.read_text(encoding="utf-8")) if "workspace" not in workspace_document: continue + if _workspace_excludes_package(parent, package_dir, workspace_document): + # This ancestor's workspace explicitly excludes the package, so + # it is not this package's workspace root. Keep walking further + # out for an unrelated ancestor workspace that might still + # legitimately claim it, matching Cargo's own root-discovery + # rule for excluded members. + continue + # This is the package's actual (nearest, non-excluding) Cargo + # workspace root -- whether the sole enclosing workspace or a + # nested, independent one. Stop here even when it configures no + # baseline: crossing this boundary to search an even-more-outer, + # unrelated workspace would attribute a threshold that was never + # configured for this package's own workspace. workspace_value = _nested_value(workspace_document, METADATA_PATHS[1]) if workspace_value is not None: return _validate_minimum_lines(METADATA_PATHS[1], workspace_value) + return None return None diff --git a/tests/test_opencode_agent_contract.py b/tests/test_opencode_agent_contract.py index 958ffe365a..c92873e203 100644 --- a/tests/test_opencode_agent_contract.py +++ b/tests/test_opencode_agent_contract.py @@ -1664,6 +1664,162 @@ def select(changed_paths: str) -> list[str]: assert select("Cargo.lock\n") == ["Cargo.toml"] +def _extract_measure_step(workflow_text: str) -> str: + """Return the source of the Measure test and docstring evidence step.""" + measure_start = workflow_text.index( + " - name: Measure test and docstring evidence\n" + ) + measure_end = workflow_text.index("\n - name:", measure_start + 1) + return workflow_text[measure_start:measure_end] + + +def test_opencode_rust_coverage_inventory_includes_both_rename_endpoints(tmp_path): + """A detected rename must not make Rust coverage lose its origin side. + + ``git diff --name-only`` collapses a rename to a single line naming only + the destination path. If the shared changed-file inventory used that + form, a ``.rs`` file renamed to a non-Rust extension would disappear + from the inventory entirely (bypassing Rust coverage), and a ``.rs`` + file moved between two Cargo packages would only credit the destination + package's manifest, leaving the origin package's coverage unmeasured. + """ + bash = shutil.which("bash") + if bash is None: + pytest.skip("bash is required for the extracted workflow function regression test") + try: + subprocess.run( + [bash, "--version"], capture_output=True, text=True, timeout=5, check=True + ) + except (OSError, subprocess.SubprocessError) as exc: + pytest.skip(f"bash is not usable for this regression test: {exc}") + + workflow = Path(".github/workflows/opencode-review-dispatch.yml").read_text( + encoding="utf-8" + ) + measure_step = _extract_measure_step(workflow) + + inventory_start = measure_step.index(" trusted_git() {\n") + inventory_end = measure_step.index( + "\n\n has_changed_tracked_files()", inventory_start + ) + trigger_start = measure_step.index(" has_changed_rust_files() {\n") + trigger_end = measure_step.index( + "\n\n tracked_python_projects_with_tests()", trigger_start + ) + rust_start = measure_step.index(" rust_coverage_manifests() {\n") + rust_end = measure_step.index( + "\n\n rust_coverage_fail_under_lines()", rust_start + ) + shell = "\n".join( + ( + "set -euo pipefail", + textwrap.dedent(measure_step[inventory_start:inventory_end]), + textwrap.dedent(measure_step[trigger_start:trigger_end]), + textwrap.dedent(measure_step[rust_start:rust_end]), + 'if [ "$MODE" = rust_files ]; then', + " has_changed_rust_files && echo RUST_FILES_CHANGED || echo RUST_FILES_UNCHANGED", + "else", + " rust_coverage_manifests", + "fi", + ) + ) + + def run_shell(repo: Path, base_sha: str, head_sha: str, mode: str) -> subprocess.CompletedProcess: + """Run the extracted inventory/detector against one commit range.""" + env = { + **os.environ, + "PR_BASE_SHA": base_sha, + "PR_HEAD_SHA": head_sha, + "MODE": mode, + } + return subprocess.run( + [bash, "-c", shell], + cwd=repo, + env=env, + capture_output=True, + text=True, + timeout=30, + ) + + def init_repo(tmp_subdir: str) -> Path: + """Create an isolated git repository for one rename scenario.""" + repo = tmp_path / tmp_subdir + repo.mkdir() + subprocess.run(["git", "init", "-q"], cwd=repo, check=True) + subprocess.run(["git", "config", "user.name", "Coverage Test"], cwd=repo, check=True) + subprocess.run( + ["git", "config", "user.email", "coverage@example.invalid"], + cwd=repo, + check=True, + ) + return repo + + def commit_all(repo: Path, message: str) -> str: + """Stage every change and commit, returning the new commit SHA.""" + subprocess.run(["git", "add", "-A"], cwd=repo, check=True) + subprocess.run(["git", "commit", "-qm", message], cwd=repo, check=True) + return subprocess.check_output( + ["git", "rev-parse", "HEAD"], cwd=repo, text=True + ).strip() + + # Scenario 1: a .rs file renamed to a non-Rust extension must still be + # detected as a Rust change (the file's removal from Rust source is + # itself a Rust-relevant change, and --name-only would otherwise report + # only the destination path, which does not match the .rs pattern). + rename_away_repo = init_repo("rename-away") + (rename_away_repo / "src").mkdir() + (rename_away_repo / "Cargo.toml").write_text('[package]\nname = "demo"\n', encoding="utf-8") + (rename_away_repo / "src" / "legacy.rs").write_text( + "pub fn legacy() {}\n" * 5, encoding="utf-8" + ) + base_sha = commit_all(rename_away_repo, "base") + subprocess.run( + ["git", "mv", "src/legacy.rs", "src/legacy.rs.disabled"], + cwd=rename_away_repo, + check=True, + ) + head_sha = commit_all(rename_away_repo, "rename rs away from Rust") + + renamed_away = run_shell(rename_away_repo, base_sha, head_sha, "rust_files") + assert renamed_away.returncode == 0, renamed_away.stderr + assert renamed_away.stdout.strip() == "RUST_FILES_CHANGED", renamed_away.stdout + + # Scenario 2: a .rs file moved between two Cargo packages in the same + # workspace must credit BOTH the origin and destination package + # manifests, not only the destination that --name-only would report. + cross_package_repo = init_repo("cross-package-move") + (cross_package_repo / "crates" / "alpha" / "src").mkdir(parents=True) + (cross_package_repo / "crates" / "beta" / "src").mkdir(parents=True) + (cross_package_repo / "Cargo.toml").write_text( + '[workspace]\nmembers = ["crates/alpha", "crates/beta"]\n', encoding="utf-8" + ) + (cross_package_repo / "crates" / "alpha" / "Cargo.toml").write_text( + '[package]\nname = "alpha"\n', encoding="utf-8" + ) + (cross_package_repo / "crates" / "beta" / "Cargo.toml").write_text( + '[package]\nname = "beta"\n', encoding="utf-8" + ) + (cross_package_repo / "crates" / "alpha" / "src" / "shared.rs").write_text( + "pub fn shared() {}\n" * 5, encoding="utf-8" + ) + (cross_package_repo / "crates" / "beta" / "src" / "lib.rs").write_text( + "pub fn beta_lib() {}\n", encoding="utf-8" + ) + base_sha = commit_all(cross_package_repo, "base") + subprocess.run( + ["git", "mv", "crates/alpha/src/shared.rs", "crates/beta/src/shared.rs"], + cwd=cross_package_repo, + check=True, + ) + head_sha = commit_all(cross_package_repo, "move crate module across packages") + + moved = run_shell(cross_package_repo, base_sha, head_sha, "manifests") + assert moved.returncode == 0, moved.stderr + assert sorted(moved.stdout.splitlines()) == sorted( + ["crates/alpha/Cargo.toml", "crates/beta/Cargo.toml"] + ), moved.stdout + + def test_opencode_runtime_pin_supports_reasoning_options(): """Keep OpenCode runtime new enough to apply model-level reasoning settings.""" review_workflow = Path(".github/workflows/opencode-review-dispatch.yml").read_text( diff --git a/tests/test_pr_review_autofix_nvidia_nim_contract.py b/tests/test_pr_review_autofix_nvidia_nim_contract.py index f938ff1876..7abfd28f90 100644 --- a/tests/test_pr_review_autofix_nvidia_nim_contract.py +++ b/tests/test_pr_review_autofix_nvidia_nim_contract.py @@ -20,7 +20,7 @@ DOCTORING_RECORD = Path("docs/doctoring/hourly-nvidia-nim-autofix.md") CHANGELOG = Path("CHANGELOG.md") REVIEW_DISPATCH_WORKFLOW = Path(".github/workflows/opencode-review-dispatch.yml") -REVIEW_DISPATCH_BLOB_SHA = "4e6c0da1954acf95a67f8840fb3165cee7bc5075" +REVIEW_DISPATCH_BLOB_SHA = "1544d357eff326261fe57b47734efbb69d4bef12" def _workflow_text(path: Path) -> str: diff --git a/tests/test_rust_coverage_threshold.py b/tests/test_rust_coverage_threshold.py index 2083324725..6090bef49a 100644 --- a/tests/test_rust_coverage_threshold.py +++ b/tests/test_rust_coverage_threshold.py @@ -100,6 +100,191 @@ def test_missing_metadata_keeps_central_default() -> None: ) == 0.0 +def test_nested_independent_workspace_does_not_inherit_outer_baseline( + tmp_path: Path, +) -> None: + """A nested [workspace] boundary with no baseline must not leak an outer one. + + Regression for Devin finding "Independent crates inherit unrelated + thresholds": the walk must stop at the nearest ancestor workspace even + when that workspace configures no coverage metadata of its own, rather + than continuing past it to an unrelated, further-out workspace. + """ + outer_workspace = tmp_path / "Cargo.toml" + outer_workspace.write_text( + '[workspace]\nmembers = ["libs/*"]\n\n' + "[workspace.metadata.opencode.coverage]\nminimum_lines = 70\n", + encoding="utf-8", + ) + inner_workspace = tmp_path / "libs" / "independent" / "Cargo.toml" + inner_workspace.parent.mkdir(parents=True) + inner_workspace.write_text('[workspace]\nmembers = ["crate_a"]\n', encoding="utf-8") + manifest = tmp_path / "libs" / "independent" / "crate_a" / "Cargo.toml" + manifest.parent.mkdir(parents=True) + manifest.write_text('[package]\nname = "crate_a"\nversion = "0.1.0"\n', encoding="utf-8") + + assert threshold.read_minimum_lines(manifest) is None + + +def test_nested_independent_workspace_own_baseline_wins(tmp_path: Path) -> None: + """A nested workspace's own baseline is used, never the outer workspace's.""" + outer_workspace = tmp_path / "Cargo.toml" + outer_workspace.write_text( + '[workspace]\nmembers = ["libs/*"]\n\n' + "[workspace.metadata.opencode.coverage]\nminimum_lines = 70\n", + encoding="utf-8", + ) + inner_workspace = tmp_path / "libs" / "independent" / "Cargo.toml" + inner_workspace.parent.mkdir(parents=True) + inner_workspace.write_text( + '[workspace]\nmembers = ["crate_a"]\n\n' + "[workspace.metadata.opencode.coverage]\nminimum_lines = 95\n", + encoding="utf-8", + ) + manifest = tmp_path / "libs" / "independent" / "crate_a" / "Cargo.toml" + manifest.parent.mkdir(parents=True) + manifest.write_text('[package]\nname = "crate_a"\nversion = "0.1.0"\n', encoding="utf-8") + + assert threshold.read_minimum_lines(manifest) == 95.0 + + +def test_excluded_package_does_not_inherit_outer_workspace_baseline( + tmp_path: Path, +) -> None: + """A package excluded from an outer workspace must not inherit its baseline. + + Regression for Devin finding "Independent crates inherit unrelated + thresholds": ``exclude`` removes the package from that workspace, so its + metadata must not apply -- even though the excluded package still lives + in a directory beneath the workspace root. + """ + workspace = tmp_path / "Cargo.toml" + workspace.write_text( + '[workspace]\nmembers = ["crates/*"]\nexclude = ["crates/excluded"]\n\n' + "[workspace.metadata.opencode.coverage]\nminimum_lines = 70\n", + encoding="utf-8", + ) + manifest = tmp_path / "crates" / "excluded" / "Cargo.toml" + manifest.parent.mkdir(parents=True) + manifest.write_text('[package]\nname = "excluded"\nversion = "0.1.0"\n', encoding="utf-8") + + assert threshold.read_minimum_lines(manifest) is None + + +def test_excluded_package_still_inherits_further_ancestor_workspace( + tmp_path: Path, +) -> None: + """A package excluded from one workspace may still inherit a further one. + + Mirrors Cargo's own root-discovery rule: an ancestor workspace that + excludes the package is not its root, so the search must continue + upward rather than stopping at the excluding workspace. + """ + grandparent_workspace = tmp_path / "Cargo.toml" + grandparent_workspace.write_text( + '[workspace]\nmembers = ["nested/crates/*"]\n\n' + "[workspace.metadata.opencode.coverage]\nminimum_lines = 60\n", + encoding="utf-8", + ) + nested_workspace = tmp_path / "nested" / "Cargo.toml" + nested_workspace.parent.mkdir(parents=True) + nested_workspace.write_text( + '[workspace]\nmembers = ["crates/*"]\nexclude = ["crates/excluded"]\n', + encoding="utf-8", + ) + manifest = tmp_path / "nested" / "crates" / "excluded" / "Cargo.toml" + manifest.parent.mkdir(parents=True) + manifest.write_text('[package]\nname = "excluded"\nversion = "0.1.0"\n', encoding="utf-8") + + assert threshold.read_minimum_lines(manifest) == 60.0 + + +def test_relative_posix_path_returns_none_for_unrelated_paths(tmp_path: Path) -> None: + """An unrelated path pair cannot be expressed as a relative path.""" + workspace_dir = tmp_path / "workspace" + other_dir = tmp_path / "elsewhere" / "package" + workspace_dir.mkdir() + other_dir.mkdir(parents=True) + + assert threshold._relative_posix_path(other_dir, workspace_dir) is None + + +def test_workspace_excludes_package_ignores_non_dict_workspace_table() -> None: + """A malformed non-table [workspace] value never signals exclusion.""" + assert ( + threshold._workspace_excludes_package( + Path("/repo"), Path("/repo/crates/a"), {"workspace": "not-a-table"} + ) + is False + ) + + +def test_workspace_excludes_package_ignores_non_list_exclude() -> None: + """A malformed non-list exclude value never signals exclusion.""" + assert ( + threshold._workspace_excludes_package( + Path("/repo"), + Path("/repo/crates/a"), + {"workspace": {"exclude": "crates/a"}}, + ) + is False + ) + + +def test_workspace_excludes_package_ignores_unrelated_package_directory( + tmp_path: Path, +) -> None: + """A package outside the workspace directory can never be excluded by it.""" + workspace_dir = tmp_path / "workspace" + other_dir = tmp_path / "elsewhere" / "package" + + assert ( + threshold._workspace_excludes_package( + workspace_dir, other_dir, {"workspace": {"exclude": ["package"]}} + ) + is False + ) + + +def test_workspace_excludes_package_skips_non_string_patterns_and_checks_rest() -> None: + """A non-string exclude entry is skipped rather than raising or matching.""" + assert ( + threshold._workspace_excludes_package( + Path("/repo"), + Path("/repo/crates/a"), + {"workspace": {"exclude": [42, "crates/a"]}}, + ) + is True + ) + assert ( + threshold._workspace_excludes_package( + Path("/repo"), + Path("/repo/crates/other"), + {"workspace": {"exclude": [42, "crates/a"]}}, + ) + is False + ) + + +def test_excluded_package_directory_beneath_excluded_subtree(tmp_path: Path) -> None: + """A package nested beneath an excluded directory inherits its exclusion. + + Covers the exclude-prefix branch: the package path is not an exact or + glob match for the exclude entry, only a path beneath it. + """ + workspace = tmp_path / "Cargo.toml" + workspace.write_text( + '[workspace]\nmembers = ["tools/legacy/*"]\nexclude = ["tools/legacy"]\n\n' + "[workspace.metadata.opencode.coverage]\nminimum_lines = 70\n", + encoding="utf-8", + ) + manifest = tmp_path / "tools" / "legacy" / "sub" / "Cargo.toml" + manifest.parent.mkdir(parents=True) + manifest.write_text('[package]\nname = "sub"\nversion = "0.1.0"\n', encoding="utf-8") + + assert threshold.read_minimum_lines(manifest) is None + + def test_cli_prints_normalized_workspace_threshold( monkeypatch: pytest.MonkeyPatch, tmp_path: Path, capsys: pytest.CaptureFixture[str] ) -> None: From 3add872b815e01c350bc0dfa6cad5d34e3b5598d Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 4 Sep 2026 01:31:23 +0000 Subject: [PATCH 18/18] fix(tests): sync approved_gh_token_assignments with merged workflow The merge-conflict repair in the prior commit only fixed the REVIEW_DISPATCH_BLOB_SHA pin; test_independent_review_agent_key_system_is_unchanged (authored by this branch, absent on plain main) still pinned a stale approved_gh_token_assignments tuple that predates main's already-merged "prefer job-scoped github.token for same-repo status publication" change. Updated the tuple to match the merged opencode-review-dispatch.yml (14 GH_TOKEN: assignments, up from 12) after verifying the 2 new entries are exactly that already-reviewed main-side addition. Full suite: 2777 passed, 1 skipped, 21 subtests. Coverage 100%, interrogate 100%. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01BV96rXhqoR3tYZ9AeAVur4 --- tests/test_pr_review_autofix_nvidia_nim_contract.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/test_pr_review_autofix_nvidia_nim_contract.py b/tests/test_pr_review_autofix_nvidia_nim_contract.py index 050813330d..1b11dfc453 100644 --- a/tests/test_pr_review_autofix_nvidia_nim_contract.py +++ b/tests/test_pr_review_autofix_nvidia_nim_contract.py @@ -198,7 +198,12 @@ def test_independent_review_agent_key_system_is_unchanged() -> None: "${{ steps.opencode_app_token.outputs.token }}", "${{ steps.opencode_app_token.outputs.token || secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || github.token }}", "${{ steps.opencode_app_token.outputs.token || secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || github.token }}", - "${{ secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || steps.opencode_app_token.outputs.token || github.token }}", + # Prefer the job-scoped github.token when the central OpenCode dispatch + # publishes a commit status back to the same .github repository (see + # CHANGELOG.md): the job's own statuses: write permission then reaches + # the endpoint instead of an unrelated App installation token. + "${{ needs.validate-pr-metadata.outputs.target_repository == github.repository && github.token || secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN }}", + "${{ needs.validate-pr-metadata.outputs.target_repository == github.repository && github.token || secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || steps.opencode_app_token.outputs.token || github.token }}", "${{ secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || steps.opencode_app_token.outputs.token || github.token }}", "${{ secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || steps.opencode_app_token.outputs.token || github.token }}", )