diff --git a/.github/workflows/opencode-review-dispatch.yml b/.github/workflows/opencode-review-dispatch.yml index d86497b3f4..f315683208 100644 --- a/.github/workflows/opencode-review-dispatch.yml +++ b/.github/workflows/opencode-review-dispatch.yml @@ -828,6 +828,8 @@ jobs: python_root=''; \ candidate_count=0; \ for candidate in \ + "$destination/python/$import_name" \ + "$destination/python/$import_name.py" \ "$destination/src/$import_name" \ "$destination/src/$import_name.py" \ "$destination/$import_name" \ @@ -870,6 +872,7 @@ jobs: exit 1; \ fi; \ case "$import_root" in \ + "$destination/python/"*) python_root="$destination/python" ;; \ "$destination/src/"*) python_root="$destination/src" ;; \ *) python_root="$destination" ;; \ esac; \ diff --git a/CHANGELOG.md b/CHANGELOG.md index d118a4a47a..72e3efc807 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +### OpenCode coverage admits immutable `python/` VCS source roots + +- Central OpenCode coverage run [34701472466](https://github.com/ContextualWisdomLab/.github/actions/runs/34701472466) failed before executing `contextual-orchestrator#1149`: the trusted image builder resolved VCS packages only from repository root or `src/`, while the exact immutable `fast-mlsirm@09f762ded35786dd1078222a4577ff09d649816f` exposes `fast_mlsirm` from `python/fast_mlsirm`. The builder now admits the explicit `python/` source root, retains the one-and-only-one import-root invariant, symlink/namespace/compiled-artifact/installed-metadata rejection, exact commit verification, and the later credential-free networkless sandbox. Contract tests pin both package and single-module `python/` layouts. Refs `contextual-orchestrator#1149`. Exact-head Runtime Quality [job 103581110552](https://github.com/ContextualWisdomLab/.github/actions/runs/34704176931/job/103581110552) then caught the required independent workflow-blob trust pin still naming the predecessor blob; `683cb053` advances only that pin to exact blob `f315683208d57ba89a2942502c525abe7355e2fd`. + ### Contextual-orchestrator pin advance removes the implicit 90 s model request timeout - Advanced the central sidecar's pinned immutable CO revision from `414f2297` to protected `main@767e67fbc6b881a452761f32abb69b9971b9b03b`, carrying contextual-orchestrator#1053 into Strix, OpenCode, and Noema. Root cause: `ModelClient.__init__` defaulted `timeout=90`, and the review gateway constructed its client without a timeout, so long structured-output completions on NVIDIA NIM (`google/gemma-4-31b-it`) hit `TimeoutError` at exactly 90 s on every attempt; the orchestrator then cycled circuit open/reset on the same route for ~20 min and answered `502 provider_connection_error` (fast-mlsirm#1860 run 34748511702, sidecar artifact 10315556637: 15 of 27 failed attempts at 90.0 s; fast-mlsirm#1825 run 34752130895 same signature). #1053 removes the implicit deadline (null by default, administrator `model_timeout_seconds` per model) and was merged under the infrastructure exception because the pre-fix sidecar was failing its own Noema/OpenCode gates. Hosted acceptance is the first Noema/OpenCode/Strix run on this pin against a consumer PR; not claimed here. Refs ContextualWisdomLab/contextual-orchestrator#1053, ContextualWisdomLab/fast-mlsirm#1860. diff --git a/docs/doctoring/opencode-vcs-python-source-root.md b/docs/doctoring/opencode-vcs-python-source-root.md new file mode 100644 index 0000000000..8e6cb3d51c --- /dev/null +++ b/docs/doctoring/opencode-vcs-python-source-root.md @@ -0,0 +1,28 @@ +# OpenCode immutable VCS `python/` source-root RCA + +Status: **Proposed** — source repair exists on an open pull request; it is not protected-branch authority until merged. + +## Incident and user-visible failure + +On 2026-09-12 UTC, central OpenCode dispatch [run 34701472466](https://github.com/ContextualWisdomLab/.github/actions/runs/34701472466) validated `ContextualWisdomLab/contextual-orchestrator#1149` at exact head `684cf28fa59e800c0db4886a08f25dd2edd156fc`. Its `coverage-source-tree` job succeeded, but `coverage-evidence` job `103574547257` failed while building the trusted tool image, before any pull-request test or coverage command ran. OpenCode therefore published only a non-approving COMMENTED review, and the required receipt remained fail-closed. + +The failing dependency was the exact VCS pin `fast-mlsirm@09f762ded35786dd1078222a4577ff09d649816f` from the consumer's validated `pyproject.toml`. That commit contains `python/fast_mlsirm/__init__.py`; it does not expose the import package at repository root or under `src/`. + +## Root cause and boundary + +`opencode-review-dispatch.yml` enumerated only four trusted candidates: `src/`, `src/.py`, ``, and `.py`. The materializer had already authenticated the target repository, bound the dependency to an immutable commit, fetched that commit without tags, and verified `FETCH_HEAD` and `HEAD`; the failure was solely an incomplete source-layout contract in the central owner. + +The selected repair adds only `python/` and `python/.py`, then maps a match to the repository's `python/` directory. It preserves the invariant that exactly one candidate may exist and continues to reject symlinked/namespace imports, any symlink layout, compiled extensions, installed distribution metadata, and ambiguous roots. It does not infer arbitrary paths from untrusted packaging metadata and does not execute dependency lifecycle code. + +Rejected alternatives were: changing the consumer's valid immutable dependency pin; copying `fast-mlsirm` into the consumer; adding the whole repository to `PYTHONPATH`; recursively searching for a matching directory; or weakening/bypassing the OpenCode coverage gate. Each would move ownership, admit ambiguity, or hide the central defect. + +## RED → repair → verification gate + +- RED commit `b1fe97c477b56e148afbeeaed9a6b74338994b6b` requires both package and single-module `python/` candidates in the published workflow contract. +- Repair commit `af04581cea4ffc038c881c6ad101ea3e5842a664` adds those candidates and the corresponding `python_root` mapping. +- Hosted Runtime Quality [job `103581110552`](https://github.com/ContextualWisdomLab/.github/actions/runs/34704176931/job/103581110552) then failed the independent pairing contract because the changed workflow blob `f315683208d57ba89a2942502c525abe7355e2fd` no longer matched the reviewed predecessor pin. Commit `683cb053b3c6f1c7b3f293a74263ac9b13e9bdf1` advances only that exact pin; no hash check is removed or relaxed. +- Hosted current-head tests, security, CodeQL, and independent review remain required. Only after ordinary protected-main integration may affected consumers rerun OpenCode; the predecessor run is never transferable as GREEN evidence. + +## Follow-up + +After merge, rerun only consumer failures whose cause changed, beginning with `contextual-orchestrator#1149`. Verify that the trusted image builds from the same `fast-mlsirm` commit, the PR sandbox remains networkless and credential-free, coverage/docstring evidence executes, and a substantive exact-head review is published. If any additional conventional source root is needed, add it through its own immutable fixture and one-root regression rather than generalized path discovery. diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index 1cc9e20313..a251563016 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -7,6 +7,12 @@ 이 문서는 제품·기술·운영 Gap을 현재 문서와 현재 GitHub 상태에 묶어 두는 기준선이다. 새 작업은 먼저 이 문서의 Gap ID를 PR 설명과 테스트 증거에 연결하고, PR의 정확한 exact HEAD·Checks·리뷰를 다시 수집한 뒤 구현한다. 표의 상태는 작성 시점의 관측값이므로, 병합 판단에는 재사용하지 않는다. 이 인벤토리는 스냅샷이며 merge authorization이 아니다. +### 2026-09-13 current-head incident delta + +| Gap ID | 상태 | exact-head evidence | causal owner / next gate | +|---|---|---|---| +| CONTROL-OPENCODE-VCS-PYROOT-01 | **Proposed / source repaired; hosted exact-head validation pending** | `contextual-orchestrator#1149@684cf28f`의 중앙 [OpenCode run 34701472466](https://github.com/ContextualWisdomLab/.github/actions/runs/34701472466) `coverage-evidence` job `103574547257`은 PR 코드를 실행하기 전에 immutable `fast-mlsirm@09f762d`의 `python/fast_mlsirm` import root를 찾지 못해 종료했다. 같은 head의 제품 테스트는 `3602 passed, 2 skipped`, native CodeQL·fuzz·SBOM·SAST·Strix는 성공했다. | `.github`의 `opencode-review-dispatch.yml`이 root/`src/`만 허용한 계약 drift를 소유한다. RED contract `b1fe97c4`, 최소 source repair `af04581c`, exact workflow-blob trust pin `683cb053` 뒤, 이 문서 head의 integrated CI가 GREEN이고 protected `main`에 ordinary merge된 다음 affected consumer exact head를 다시 검증한다. | + ## 1. 근거와 범위 ### 1.1 우선순위가 높은 근거 diff --git a/tests/test_opencode_agent_contract.py b/tests/test_opencode_agent_contract.py index d68a8e2e2c..9460255888 100644 --- a/tests/test_opencode_agent_contract.py +++ b/tests/test_opencode_agent_contract.py @@ -749,6 +749,10 @@ def test_opencode_target_coverage_materializes_only_after_authorized_dispatch(): assert 'vcs-manifest.json >"$dependency_list"' in measure_step assert 'done <"$dependency_list"' in measure_step assert 'candidate_count=$((candidate_count + 1))' in measure_step + # Immutable VCS packages may expose their import package from a project-specific + # ``python/`` source root (fast-mlsirm is the live protected-base fixture). + assert '"$destination/python/$import_name"' in measure_step + assert '"$destination/python/$import_name.py"' in measure_step assert '[ "$candidate_count" -ne 1 ]' in measure_step assert "has a missing or ambiguous import root" in measure_step assert '[ ! -f "$import_root/__init__.py" ]' 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 2e733ac9e9..ddf06bfbd1 100644 --- a/tests/test_pr_review_autofix_nvidia_nim_contract.py +++ b/tests/test_pr_review_autofix_nvidia_nim_contract.py @@ -17,7 +17,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 = "d86497b3f43bebbabbb4f504eb5132cdf3b7b293" +REVIEW_DISPATCH_BLOB_SHA = "f315683208d57ba89a2942502c525abe7355e2fd" def _workflow_text(path: Path) -> str: