From 045152a0147d96d7d24f0c23e1d58f3836cb0476 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 06:44:13 +0900 Subject: [PATCH 1/6] fix(coverage): admit exact base VCS sources --- .../workflows/opencode-review-dispatch.yml | 28 +++++- ARCHITECTURE.md | 6 ++ CHANGELOG.md | 3 + .../opencode-exact-vcs-dependency-evidence.md | 66 +++++++++++++ .../materialize_base_python_requirements.py | 99 ++++++++++++++++--- ...st_materialize_base_python_requirements.py | 94 ++++++++++++++++++ tests/test_opencode_agent_contract.py | 6 ++ ...t_pr_review_autofix_nvidia_nim_contract.py | 5 +- tests/test_uv_export_isolation_contract.py | 51 ++++++++++ 9 files changed, 342 insertions(+), 16 deletions(-) create mode 100644 docs/doctoring/opencode-exact-vcs-dependency-evidence.md diff --git a/.github/workflows/opencode-review-dispatch.yml b/.github/workflows/opencode-review-dispatch.yml index de1c4800d..35f7a381a 100644 --- a/.github/workflows/opencode-review-dispatch.yml +++ b/.github/workflows/opencode-review-dispatch.yml @@ -726,8 +726,34 @@ jobs: --only-binary=:all: \ -r /tmp/requirements-opencode-review-ci-hashes.txt \ && rm -f /tmp/requirements-opencode-review-ci-hashes.txt - COPY install-base-python-locks.py /usr/local/libexec/install-base-python-locks.py COPY base-python-requirements /tmp/base-python-requirements + RUN set -eu; \ + mkdir -p /opt/base-vcs-dependencies; \ + site_packages="$(python3 -c 'import site; print(site.getsitepackages()[0])')"; \ + path_file="${site_packages}/opencode-base-vcs-dependencies.pth"; \ + : >"$path_file"; \ + dependency_index=0; \ + jq -r '.[] | [.repository, .commit] | @tsv' \ + /tmp/base-python-requirements/vcs-manifest.json \ + | while IFS="$(printf '\t')" read -r repository commit; do \ + destination="$(printf '/opt/base-vcs-dependencies/dependency-%03d' "$dependency_index")"; \ + git init --quiet "$destination"; \ + git -C "$destination" remote add origin \ + "https://github.com/ContextualWisdomLab/${repository}.git"; \ + git -C "$destination" -c protocol.version=2 fetch \ + --quiet --no-tags --depth=1 origin "$commit"; \ + test "$(git -C "$destination" rev-parse FETCH_HEAD)" = "$commit"; \ + git -C "$destination" checkout --quiet --detach FETCH_HEAD; \ + test "$(git -C "$destination" rev-parse HEAD)" = "$commit"; \ + rm -rf -- "$destination/.git"; \ + if [ -d "$destination/src" ]; then \ + printf '%s/src\n' "$destination" >>"$path_file"; \ + else \ + printf '%s\n' "$destination" >>"$path_file"; \ + fi; \ + dependency_index=$((dependency_index + 1)); \ + done + COPY install-base-python-locks.py /usr/local/libexec/install-base-python-locks.py RUN python3 -I /usr/local/libexec/install-base-python-locks.py \ --requirements-root /tmp/base-python-requirements \ && rm -rf /tmp/base-python-requirements \ diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 285808dbe..81ded1971 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -135,6 +135,10 @@ CI installs Python tools only with `pip install --require-hashes`. Contract tests pin workflow structure and governance prose so drift fails closed. The trusted `uv` exporter is downloaded from the literal GitHub Releases URL for `uv` 0.12.1; `releases.astral.sh` is not the network sink. +An exact-base `uv.lock` may additionally expose source from an organization-owned +GitHub repository pinned to a full commit: the secret-free image build verifies +the fetched revision and makes its source importable without running package +build or installation hooks. Pull-request execution remains networkless. ## Related durable documents @@ -150,6 +154,8 @@ trusted `uv` exporter is downloaded from the literal GitHub Releases URL for — current increment's repair-worker decision and APA 7th citations. - [`docs/doctoring/opencode-llm-review-publication.md`](docs/doctoring/opencode-llm-review-publication.md) — LLM probe publication without inventing observed proof. +- [`docs/doctoring/opencode-exact-vcs-dependency-evidence.md`](docs/doctoring/opencode-exact-vcs-dependency-evidence.md) + — import-only exact source dependencies for networkless coverage. - [`docs/doctoring/fast-mlsirm-hourly-review-caller.md`](docs/doctoring/fast-mlsirm-hourly-review-caller.md) — product-specific psychometric repair heartbeat and scientific gates. - [`docs/doctoring/exact-artifact-sbom-attestation.md`](docs/doctoring/exact-artifact-sbom-attestation.md) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ab82ccea..3ab258e8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ Semantic Versioning where the repository publishes a release. ## [Unreleased] +- Fix OpenCode coverage evidence for exact-base, organization-owned Python VCS + dependencies without weakening registry hashes or the networkless PR sandbox. + ### Added - Added an hourly organization commercial-readiness coordinator that discovers writable repositories, honors enabled dedicated writer leases and fully paginated live writer runs, refetches exact repository/workflow/run/PR state before dispatch, rotates bounded review-repair and opt-in NVIDIA OpenCode product-development targets, fails nonzero on fleet-wide inspection or dispatch outages, retains three-day JSON receipts, and keeps the existing 15-minute merge scheduler authoritative. diff --git a/docs/doctoring/opencode-exact-vcs-dependency-evidence.md b/docs/doctoring/opencode-exact-vcs-dependency-evidence.md new file mode 100644 index 000000000..faa614152 --- /dev/null +++ b/docs/doctoring/opencode-exact-vcs-dependency-evidence.md @@ -0,0 +1,66 @@ +# OpenCode exact VCS dependency evidence + +## Decision + +The OpenCode coverage image may expose a Python dependency directly from source +only when the validated base branch's frozen `uv.lock` names an HTTPS GitHub +repository owned by `ContextualWisdomLab` and a full 40-character Git commit. +Registry dependencies remain exact-version, SHA-256-pinned `pip` installs. + +The trusted materializer separates those two dependency classes. The networked, +secret-free image build fetches each approved source revision, verifies that +`FETCH_HEAD` and the checked-out `HEAD` equal the locked commit, removes Git +metadata, and records only the repository root or its `src` directory in a +Python path file. It does not run dependency build or installation code. The +pull-request tree still runs later with no network and no credentials. + +## Root cause + +LineageWeave's protected base lock contains RankWeave at its immutable release +commit because the current RankWeave release is not yet available from PyPI. +`uv export` therefore emitted one exact VCS requirement alongside fully hashed +registry requirements. The former materializer rejected the complete export, +so OpenCode never reached tests and repeatedly requested changes despite the +product's current-head tests passing. + +## Safety boundary + +- Symbolic refs, abbreviated commits, non-HTTPS schemes, credentials, ports, + query strings, fragments, subdirectories, and repositories outside the exact + organization origin fail closed. +- Duplicate references to one repository must resolve to one commit; conflicting + revisions fail before the image build. +- Only metadata read from the validated base SHA can select a dependency. Pull + request source cannot modify the networked image build inputs. +- Source dependencies are import-only. No `pip install`, PEP 517 backend, setup + hook, or dependency lifecycle script runs while the network is available. +- This records exact source inputs for a test image; it does not claim a SLSA + build level or substitute for upstream package publication and attestation. + +These controls follow pip's recommendation to use full VCS commit hashes and +SLSA 1.2's treatment of Git revisions as immutable identifiers, while retaining +the isolated, ephemeral test execution boundary (Python Packaging Authority, +2026; Supply-chain Levels for Software Artifacts, 2025). They also support the +SSDF practice of preserving dependency provenance and preventing recurrence of +toolchain failures (Souppaya et al., 2022). + +## Verification + +Regression tests cover the real LineageWeave export shape, rejection of unsafe +VCS forms, conflicting commits, deterministic manifests, exact-fetch workflow +commands, source-only Python path publication, and the existing registry hash +contract. The central Python quality workflow retains 100% statement/branch and +docstring coverage. + +## References + +Python Packaging Authority. (2026). *VCS support*. pip documentation v26.2. +https://pip.pypa.io/en/stable/topics/vcs-support/ + +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 + +Supply-chain Levels for Software Artifacts. (2025). *SLSA specification +(Version 1.2)*. https://slsa.dev/spec/v1.2/ diff --git a/scripts/ci/materialize_base_python_requirements.py b/scripts/ci/materialize_base_python_requirements.py index 314668438..a15c4720d 100755 --- a/scripts/ci/materialize_base_python_requirements.py +++ b/scripts/ci/materialize_base_python_requirements.py @@ -36,6 +36,13 @@ r"==[^\s;]+(?:\s*;\s*\S(?:.*\S)?)?" ) UV_SHA256_HASH_RE = re.compile(r"--hash=sha256:[0-9a-fA-F]{64}") +UV_EXACT_ORG_VCS_RE = re.compile( + r"(?P[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?" + r"(?:\[[A-Za-z0-9._-]+(?:,[A-Za-z0-9._-]+)*\])?)\s+@\s+" + r"git\+https://github\.com/ContextualWisdomLab/" + r"(?P[A-Za-z0-9_.-]{1,100})\.git@" + r"(?P[0-9a-fA-F]{40})" +) UV_EXPORT_TIMEOUT_SECONDS = 120 TRUSTED_UV_VERSION = "0.12.1" TRUSTED_UV_TARGET_TRIPLE = "x86_64-unknown-linux-gnu" @@ -286,6 +293,39 @@ def _is_fully_hash_pinned_export(content: bytes) -> bool: return bool(lines) and all(_is_fully_hash_pinned_requirement(line) for line in lines) +def _partition_uv_export(content: bytes) -> tuple[bytes, list[dict[str, str]]]: + """Separate registry hash pins from exact organization VCS source pins.""" + registry_requirements: list[str] = [] + vcs_by_repository: dict[str, dict[str, str]] = {} + for line in _requirement_lines(content): + if _is_fully_hash_pinned_requirement(line): + registry_requirements.append(line) + continue + match = UV_EXACT_ORG_VCS_RE.fullmatch(line) + if match is None: + raise ValueError("uv export contains an unsupported dependency line") + dependency = { + "package": match.group("package"), + "repository": match.group("repository"), + "commit": match.group("commit").lower(), + } + repository_key = dependency["repository"].casefold() + previous = vcs_by_repository.get(repository_key) + if previous is not None and previous["commit"] != dependency["commit"]: + raise ValueError("uv export pins one VCS repository to conflicting commits") + vcs_by_repository[repository_key] = dependency + + registry_content = ( + ("\n".join(registry_requirements) + "\n").encode("utf-8") + if registry_requirements + else b"" + ) + return registry_content, sorted( + vcs_by_repository.values(), + key=lambda dependency: dependency["repository"].casefold(), + ) + + def _git(repo_root: pathlib.Path, *args: str) -> bytes: """Run one read-only git command in the materialized repository.""" completed = subprocess.run( @@ -492,7 +532,7 @@ def _reject_unsupported_uv_workspace( def _export_uv_lock( repo_root: pathlib.Path, base_sha: str, lock_path: str -) -> bytes | None: +) -> tuple[bytes, list[dict[str, str]]] | None: """Export one tracked base ``uv.lock`` into a trusted hash-pinned closure. The caller proves that the sibling ``pyproject.toml`` is a regular blob in @@ -534,11 +574,14 @@ def _export_uv_lock( exported = completed.stdout if not _requirement_lines(exported): return None - if not _is_fully_hash_pinned_export(exported): + try: + partitioned = _partition_uv_export(exported) + except ValueError as exc: raise RuntimeError( - f"uv export for tracked base lock {lock_path} was not fully hash-pinned" - ) - return exported + f"uv export for tracked base lock {lock_path} was not fully hash-pinned " + "or exact organization VCS-pinned" + ) from exc + return partitioned def _regular_base_blob_paths(entries: bytes) -> list[tuple[str, pathlib.PurePosixPath]]: @@ -569,8 +612,10 @@ def _regular_base_blob_paths(entries: bytes) -> list[tuple[str, pathlib.PurePosi return regular_blobs -def base_hash_locks(repo_root: pathlib.Path, base_sha: str) -> list[tuple[str, bytes]]: - """Return regular hash-lock blobs from the exact validated base commit.""" +def _base_python_inputs( + repo_root: pathlib.Path, base_sha: str +) -> tuple[list[tuple[str, bytes]], list[dict[str, str]]]: + """Return hash locks and exact VCS sources from one validated base commit.""" if not SHA_RE.fullmatch(base_sha): raise ValueError("base SHA must be exactly 40 hexadecimal characters") @@ -578,6 +623,7 @@ def base_hash_locks(repo_root: pathlib.Path, base_sha: str) -> list[tuple[str, b regular_blobs = _regular_base_blob_paths(entries) regular_paths = {path for path, _candidate in regular_blobs} locks: list[tuple[str, bytes]] = [] + vcs_by_repository: dict[str, dict[str, str]] = {} for path, candidate in regular_blobs: if _is_candidate_lock_path(candidate): content = _git(repo_root, "show", f"{base_sha}:{path}") @@ -588,8 +634,34 @@ def base_hash_locks(repo_root: pathlib.Path, base_sha: str) -> list[tuple[str, b continue exported = _export_uv_lock(repo_root, base_sha, path) if exported is not None: - locks.append((path, exported)) - return sorted(locks, key=lambda item: item[0]) + registry_content, vcs_dependencies = exported + if registry_content: + locks.append((path, registry_content)) + for dependency in vcs_dependencies: + dependency = {**dependency, "source": path} + repository_key = dependency["repository"].casefold() + previous = vcs_by_repository.get(repository_key) + if ( + previous is not None + and previous["commit"] != dependency["commit"] + ): + raise RuntimeError( + "base uv locks pin one VCS repository " + "to conflicting commits" + ) + vcs_by_repository[repository_key] = dependency + return ( + sorted(locks, key=lambda item: item[0]), + sorted( + vcs_by_repository.values(), + key=lambda dependency: dependency["repository"].casefold(), + ), + ) + + +def base_hash_locks(repo_root: pathlib.Path, base_sha: str) -> list[tuple[str, bytes]]: + """Return regular hash-lock blobs from the exact validated base commit.""" + return _base_python_inputs(repo_root, base_sha)[0] def _included_base_lock_blobs( @@ -661,10 +733,9 @@ def materialize( regular_paths = { path for path, _candidate in _regular_base_blob_paths(entries) } + locks, vcs_manifest = _base_python_inputs(resolved_repo, base_sha) manifest: list[dict[str, str]] = [] - for index, (source_path, content) in enumerate( - base_hash_locks(resolved_repo, base_sha) - ): + for index, (source_path, content) in enumerate(locks): generated_name = f"requirements-{index:03d}.txt" include_directory = f"includes-{index:03d}" included = _included_base_lock_blobs( @@ -692,6 +763,10 @@ def materialize( "".join(f"{entry['file']}\n" for entry in manifest), encoding="utf-8", ) + (output_dir / "vcs-manifest.json").write_text( + json.dumps(vcs_manifest, indent=2, sort_keys=True) + "\n", + encoding="utf-8", + ) return manifest diff --git a/tests/test_materialize_base_python_requirements.py b/tests/test_materialize_base_python_requirements.py index 6073dbdc1..072ffb909 100644 --- a/tests/test_materialize_base_python_requirements.py +++ b/tests/test_materialize_base_python_requirements.py @@ -3,6 +3,7 @@ import ast import hashlib import io +import json import runpy import shutil import subprocess @@ -100,6 +101,99 @@ def test_materializes_only_regular_hash_locks_from_exact_base(tmp_path: Path) -> assert "requirements.txt" not in (output / "manifest.json").read_text( encoding="utf-8" ) + assert (output / "vcs-manifest.json").read_text(encoding="utf-8") == "[]\n" + + +def test_materializes_exact_vcs_sources_in_a_separate_manifest( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """VCS source pins never enter a pip --require-hashes input file.""" + repository = tmp_path / "repo" + repository.mkdir() + git(repository, "init") + git(repository, "config", "user.name", "Test") + git(repository, "config", "user.email", "test@example.invalid") + (repository / "tracked.txt").write_text("base\n", encoding="utf-8") + git(repository, "add", ".") + git(repository, "commit", "-m", "base") + base_sha = git(repository, "rev-parse", "HEAD") + hash_lock = b"demo==1 --hash=sha256:" + b"a" * 64 + b"\n" + vcs_sources = [ + { + "package": "rankweave", + "repository": "RankWeave", + "commit": "61c49c50d3b4a24fc9bd7c6d3a7f2f4ba19d7be6", + "source": "uv.lock", + } + ] + monkeypatch.setattr( + materializer, + "_base_python_inputs", + lambda *_args: ([("uv.lock", hash_lock)], vcs_sources), + ) + + output = tmp_path / "output" + materializer.materialize(repository, base_sha, output) + + assert (output / "requirements-000.txt").read_bytes() == hash_lock + assert ( + json.loads((output / "vcs-manifest.json").read_text(encoding="utf-8")) + == vcs_sources + ) + + +def test_base_inputs_preserve_a_vcs_only_export( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """A source-only uv closure is useful even without registry requirements.""" + tree = ( + b"100644 blob " + b"a" * 40 + b"\tpyproject.toml\0" + b"100644 blob " + b"b" * 40 + b"\tuv.lock\0" + ) + dependency = { + "package": "rankweave", + "repository": "RankWeave", + "commit": "61c49c50d3b4a24fc9bd7c6d3a7f2f4ba19d7be6", + } + monkeypatch.setattr(materializer, "_git", lambda *_args: tree) + monkeypatch.setattr( + materializer, + "_export_uv_lock", + lambda *_args: (b"", [dependency]), + ) + + locks, vcs_sources = materializer._base_python_inputs(tmp_path, "a" * 40) + + assert locks == [] + assert vcs_sources == [{**dependency, "source": "uv.lock"}] + + +def test_base_inputs_reject_conflicting_vcs_revisions_across_locks( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Separate uv projects cannot select ambiguous revisions of one source.""" + tree = b"".join( + b"100644 blob " + bytes(character, "ascii") * 40 + b"\t" + path + b"\0" + for character, path in ( + ("a", b"first/pyproject.toml"), + ("b", b"first/uv.lock"), + ("c", b"second/pyproject.toml"), + ("d", b"second/uv.lock"), + ) + ) + monkeypatch.setattr(materializer, "_git", lambda *_args: tree) + + def export(_repo: Path, _sha: str, lock_path: str): + commit = "a" * 40 if lock_path.startswith("first/") else "b" * 40 + return b"", [{"package": "demo", "repository": "demo", "commit": commit}] + + monkeypatch.setattr(materializer, "_export_uv_lock", export) + + with pytest.raises(RuntimeError, match="conflicting commits"): + materializer._base_python_inputs(tmp_path, "a" * 40) def test_materializes_hash_pinned_locks_named_beyond_the_legacy_whitelist( diff --git a/tests/test_opencode_agent_contract.py b/tests/test_opencode_agent_contract.py index 379dded14..64074f642 100644 --- a/tests/test_opencode_agent_contract.py +++ b/tests/test_opencode_agent_contract.py @@ -679,6 +679,12 @@ def test_opencode_target_coverage_materializes_only_after_authorized_dispatch(): assert 'install -m 0755 "$trusted_base_python_installer"' in measure_step assert "COPY install-base-python-locks.py" in measure_step assert "python3 -I /usr/local/libexec/install-base-python-locks.py" in measure_step + assert '"https://github.com/ContextualWisdomLab/${repository}.git"' in measure_step + assert '--quiet --no-tags --depth=1 origin "$commit"' in measure_step + assert 'rev-parse FETCH_HEAD)" = "$commit"' in measure_step + assert 'rev-parse HEAD)" = "$commit"' in measure_step + assert "opencode-base-vcs-dependencies.pth" in measure_step + assert 'if [ -d "$destination/src" ]' in measure_step assert "docker build --pull --no-cache --network=default" in measure_step assert '"$coverage_build_dir"' in measure_step assert measure_step.index("docker build --pull --no-cache") < measure_step.index( diff --git a/tests/test_pr_review_autofix_nvidia_nim_contract.py b/tests/test_pr_review_autofix_nvidia_nim_contract.py index 335bfa13d..acfb70812 100644 --- a/tests/test_pr_review_autofix_nvidia_nim_contract.py +++ b/tests/test_pr_review_autofix_nvidia_nim_contract.py @@ -1,16 +1,15 @@ """Contract tests for the scheduled OpenCode review-autofix trust boundary.""" import hashlib -from pathlib import Path import re import subprocess +from pathlib import Path import pytest from scripts.ci import pr_review_autofix_context as context from scripts.ci import pr_review_conflict_scope as scope - AUTOFIX_WORKFLOW = Path(".github/workflows/pr-review-autofix.yml") FIX_SCHEDULER_WORKFLOW = Path(".github/workflows/pr-review-fix-scheduler.yml") HOURLY_CALLER_WORKFLOW = Path( @@ -20,7 +19,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 = "de1c4800d362bda4a90c31a0c8e39687a782afb4" +REVIEW_DISPATCH_BLOB_SHA = "35f7a381a95444a5d29a53ef9f51563edcb4ff96" def _workflow_text(path: Path) -> str: diff --git a/tests/test_uv_export_isolation_contract.py b/tests/test_uv_export_isolation_contract.py index f24a6ac8e..830277b90 100644 --- a/tests/test_uv_export_isolation_contract.py +++ b/tests/test_uv_export_isolation_contract.py @@ -97,6 +97,57 @@ def test_uv_export_accepts_exact_package_pins_with_markers_and_multiple_hashes() assert materializer._is_fully_hash_pinned_export(content) is True +def test_uv_export_partitions_hashes_and_exact_organization_vcs_sources() -> None: + """An immutable organization source pin is separated from pip hash locks.""" + content = ( + b"demo==1.2.3 --hash=sha256:" + b"a" * 64 + b"\n" + b"rankweave @ git+https://github.com/ContextualWisdomLab/RankWeave.git@" + b"61c49c50d3b4a24fc9bd7c6d3a7f2f4ba19d7be6\n" + ) + + registry, vcs_sources = materializer._partition_uv_export(content) + + assert registry == b"demo==1.2.3 --hash=sha256:" + b"a" * 64 + b"\n" + assert vcs_sources == [ + { + "package": "rankweave", + "repository": "RankWeave", + "commit": "61c49c50d3b4a24fc9bd7c6d3a7f2f4ba19d7be6", + } + ] + + +@pytest.mark.parametrize( + "requirement", + [ + "demo @ git+http://github.com/ContextualWisdomLab/demo.git@" + "a" * 40, + "demo @ git+https://github.com/other/demo.git@" + "a" * 40, + "demo @ git+https://github.com/ContextualWisdomLab/demo.git@main", + "demo @ git+https://github.com/ContextualWisdomLab/demo.git@" + + "a" * 40 + + "#subdirectory=python", + ], +) +def test_uv_export_rejects_unbounded_vcs_sources(requirement: str) -> None: + """Only the exact organization HTTPS origin and a full commit are accepted.""" + with pytest.raises(ValueError, match="unsupported dependency"): + materializer._partition_uv_export(f"{requirement}\n".encode()) + + +def test_uv_export_rejects_conflicting_commits_for_one_repository() -> None: + """One import path cannot ambiguously combine two repository revisions.""" + with pytest.raises(ValueError, match="conflicting commits"): + materializer._partition_uv_export( + ( + "first @ git+https://github.com/ContextualWisdomLab/demo.git@" + + "a" * 40 + + "\nsecond @ git+https://github.com/ContextualWisdomLab/Demo.git@" + + "b" * 40 + + "\n" + ).encode() + ) + + def test_tracked_pyproject_read_failure_is_not_misclassified_as_orphan( tmp_path: Path, monkeypatch: pytest.MonkeyPatch, From 163da580a855e45b87aeb43e28ed3c6d8403390b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 06:56:43 +0900 Subject: [PATCH 2/6] fix(coverage): fail closed on VCS import roots --- .../workflows/opencode-review-dispatch.yml | 20 +++++++++++++------ AGENTS.md | 6 +++++- .../opencode-exact-vcs-dependency-evidence.md | 7 ++++--- .../materialize_base_python_requirements.py | 3 +++ ...st_materialize_base_python_requirements.py | 2 ++ tests/test_opencode_agent_contract.py | 6 +++++- ...t_pr_review_autofix_nvidia_nim_contract.py | 2 +- tests/test_uv_export_isolation_contract.py | 5 +++-- 8 files changed, 37 insertions(+), 14 deletions(-) diff --git a/.github/workflows/opencode-review-dispatch.yml b/.github/workflows/opencode-review-dispatch.yml index 35f7a381a..19e4ef8e8 100644 --- a/.github/workflows/opencode-review-dispatch.yml +++ b/.github/workflows/opencode-review-dispatch.yml @@ -733,9 +733,10 @@ jobs: path_file="${site_packages}/opencode-base-vcs-dependencies.pth"; \ : >"$path_file"; \ dependency_index=0; \ - jq -r '.[] | [.repository, .commit] | @tsv' \ - /tmp/base-python-requirements/vcs-manifest.json \ - | while IFS="$(printf '\t')" read -r repository commit; do \ + dependency_list=/tmp/base-vcs-dependencies.tsv; \ + jq -r '.[] | [.import_name, .repository, .commit] | @tsv' \ + /tmp/base-python-requirements/vcs-manifest.json >"$dependency_list"; \ + while IFS="$(printf '\t')" read -r import_name repository commit; do \ destination="$(printf '/opt/base-vcs-dependencies/dependency-%03d' "$dependency_index")"; \ git init --quiet "$destination"; \ git -C "$destination" remote add origin \ @@ -746,13 +747,20 @@ jobs: git -C "$destination" checkout --quiet --detach FETCH_HEAD; \ test "$(git -C "$destination" rev-parse HEAD)" = "$commit"; \ rm -rf -- "$destination/.git"; \ - if [ -d "$destination/src" ]; then \ + if [ -d "$destination/src/$import_name" ] \ + || [ -f "$destination/src/$import_name.py" ]; then \ printf '%s/src\n' "$destination" >>"$path_file"; \ - else \ + elif [ -d "$destination/$import_name" ] \ + || [ -f "$destination/$import_name.py" ]; then \ printf '%s\n' "$destination" >>"$path_file"; \ + else \ + printf 'locked VCS source %s has no import root for %s\n' \ + "$repository" "$import_name" >&2; \ + exit 1; \ fi; \ dependency_index=$((dependency_index + 1)); \ - done + done <"$dependency_list"; \ + rm -f -- "$dependency_list" COPY install-base-python-locks.py /usr/local/libexec/install-base-python-locks.py RUN python3 -I /usr/local/libexec/install-base-python-locks.py \ --requirements-root /tmp/base-python-requirements \ diff --git a/AGENTS.md b/AGENTS.md index 982d24ece..4e906c47c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -3,7 +3,11 @@ > **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). +Materialize accepts only exact SHA-256 pins, a bounded relative `-r` include +(no `.`/`..`), or an organization-owned HTTPS Git source pinned to a full +commit and exposed without running build hooks; a lone `--require-hashes` +directive is not trust evidence. See +[`docs/doctoring/opencode-exact-vcs-dependency-evidence.md`](docs/doctoring/opencode-exact-vcs-dependency-evidence.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). 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). diff --git a/docs/doctoring/opencode-exact-vcs-dependency-evidence.md b/docs/doctoring/opencode-exact-vcs-dependency-evidence.md index faa614152..6ee36abdb 100644 --- a/docs/doctoring/opencode-exact-vcs-dependency-evidence.md +++ b/docs/doctoring/opencode-exact-vcs-dependency-evidence.md @@ -10,9 +10,10 @@ Registry dependencies remain exact-version, SHA-256-pinned `pip` installs. The trusted materializer separates those two dependency classes. The networked, secret-free image build fetches each approved source revision, verifies that `FETCH_HEAD` and the checked-out `HEAD` equal the locked commit, removes Git -metadata, and records only the repository root or its `src` directory in a -Python path file. It does not run dependency build or installation code. The -pull-request tree still runs later with no network and no credentials. +metadata, verifies a normalized package import root, and records only that +repository root or its `src` directory in a Python path file. It does not run +dependency build or installation code. The pull-request tree still runs later +with no network and no credentials. ## Root cause diff --git a/scripts/ci/materialize_base_python_requirements.py b/scripts/ci/materialize_base_python_requirements.py index a15c4720d..d79f34400 100755 --- a/scripts/ci/materialize_base_python_requirements.py +++ b/scripts/ci/materialize_base_python_requirements.py @@ -306,6 +306,9 @@ def _partition_uv_export(content: bytes) -> tuple[bytes, list[dict[str, str]]]: raise ValueError("uv export contains an unsupported dependency line") dependency = { "package": match.group("package"), + "import_name": re.sub( + r"[-_.]+", "_", match.group("package").partition("[")[0] + ).lower(), "repository": match.group("repository"), "commit": match.group("commit").lower(), } diff --git a/tests/test_materialize_base_python_requirements.py b/tests/test_materialize_base_python_requirements.py index 072ffb909..c8a4af53f 100644 --- a/tests/test_materialize_base_python_requirements.py +++ b/tests/test_materialize_base_python_requirements.py @@ -122,6 +122,7 @@ def test_materializes_exact_vcs_sources_in_a_separate_manifest( vcs_sources = [ { "package": "rankweave", + "import_name": "rankweave", "repository": "RankWeave", "commit": "61c49c50d3b4a24fc9bd7c6d3a7f2f4ba19d7be6", "source": "uv.lock", @@ -154,6 +155,7 @@ def test_base_inputs_preserve_a_vcs_only_export( ) dependency = { "package": "rankweave", + "import_name": "rankweave", "repository": "RankWeave", "commit": "61c49c50d3b4a24fc9bd7c6d3a7f2f4ba19d7be6", } diff --git a/tests/test_opencode_agent_contract.py b/tests/test_opencode_agent_contract.py index 64074f642..5410fbb52 100644 --- a/tests/test_opencode_agent_contract.py +++ b/tests/test_opencode_agent_contract.py @@ -684,7 +684,11 @@ def test_opencode_target_coverage_materializes_only_after_authorized_dispatch(): assert 'rev-parse FETCH_HEAD)" = "$commit"' in measure_step assert 'rev-parse HEAD)" = "$commit"' in measure_step assert "opencode-base-vcs-dependencies.pth" in measure_step - assert 'if [ -d "$destination/src" ]' in measure_step + assert 'vcs-manifest.json >"$dependency_list"' in measure_step + assert 'done <"$dependency_list"' in measure_step + assert '[ -d "$destination/src/$import_name" ]' in measure_step + assert '[ -d "$destination/$import_name" ]' in measure_step + assert "has no import root" in measure_step assert "docker build --pull --no-cache --network=default" in measure_step assert '"$coverage_build_dir"' in measure_step assert measure_step.index("docker build --pull --no-cache") < measure_step.index( diff --git a/tests/test_pr_review_autofix_nvidia_nim_contract.py b/tests/test_pr_review_autofix_nvidia_nim_contract.py index acfb70812..dc17ee379 100644 --- a/tests/test_pr_review_autofix_nvidia_nim_contract.py +++ b/tests/test_pr_review_autofix_nvidia_nim_contract.py @@ -19,7 +19,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 = "35f7a381a95444a5d29a53ef9f51563edcb4ff96" +REVIEW_DISPATCH_BLOB_SHA = "19e4ef8e8b3615ad8003632548d4cb6bd65d31e9" def _workflow_text(path: Path) -> str: diff --git a/tests/test_uv_export_isolation_contract.py b/tests/test_uv_export_isolation_contract.py index 830277b90..76b72fdc7 100644 --- a/tests/test_uv_export_isolation_contract.py +++ b/tests/test_uv_export_isolation_contract.py @@ -101,7 +101,7 @@ def test_uv_export_partitions_hashes_and_exact_organization_vcs_sources() -> Non """An immutable organization source pin is separated from pip hash locks.""" content = ( b"demo==1.2.3 --hash=sha256:" + b"a" * 64 + b"\n" - b"rankweave @ git+https://github.com/ContextualWisdomLab/RankWeave.git@" + b"rank.weave-extra[GPU] @ git+https://github.com/ContextualWisdomLab/RankWeave.git@" b"61c49c50d3b4a24fc9bd7c6d3a7f2f4ba19d7be6\n" ) @@ -110,7 +110,8 @@ def test_uv_export_partitions_hashes_and_exact_organization_vcs_sources() -> Non assert registry == b"demo==1.2.3 --hash=sha256:" + b"a" * 64 + b"\n" assert vcs_sources == [ { - "package": "rankweave", + "package": "rank.weave-extra[GPU]", + "import_name": "rank_weave_extra", "repository": "RankWeave", "commit": "61c49c50d3b4a24fc9bd7c6d3a7f2f4ba19d7be6", } From 72ad18170ee4eb5e454d1e52bbde2eb566e543cd Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 09:02:47 +0900 Subject: [PATCH 3/6] fix: harden VCS import permissions --- .github/workflows/opencode-review-dispatch.yml | 3 ++- CHANGELOG.md | 3 ++- docs/doctoring/opencode-exact-vcs-dependency-evidence.md | 9 +++++++++ tests/test_opencode_agent_contract.py | 1 + tests/test_pr_review_autofix_nvidia_nim_contract.py | 2 +- 5 files changed, 15 insertions(+), 3 deletions(-) diff --git a/.github/workflows/opencode-review-dispatch.yml b/.github/workflows/opencode-review-dispatch.yml index 19e4ef8e8..91dc5d7d2 100644 --- a/.github/workflows/opencode-review-dispatch.yml +++ b/.github/workflows/opencode-review-dispatch.yml @@ -760,7 +760,8 @@ jobs: fi; \ dependency_index=$((dependency_index + 1)); \ done <"$dependency_list"; \ - rm -f -- "$dependency_list" + rm -f -- "$dependency_list"; \ + chmod -R a+rX /opt/base-vcs-dependencies "$path_file" COPY install-base-python-locks.py /usr/local/libexec/install-base-python-locks.py RUN python3 -I /usr/local/libexec/install-base-python-locks.py \ --requirements-root /tmp/base-python-requirements \ diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a176dfd2..de9417461 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,8 @@ Semantic Versioning where the repository publishes a release. ## [Unreleased] - Fix OpenCode coverage evidence for exact-base, organization-owned Python VCS - dependencies without weakening registry hashes or the networkless PR sandbox. + dependencies without weakening registry hashes or the networkless PR sandbox, + and make the exact source roots readable by the unprivileged coverage user. ### Added diff --git a/docs/doctoring/opencode-exact-vcs-dependency-evidence.md b/docs/doctoring/opencode-exact-vcs-dependency-evidence.md index 6ee36abdb..72cc76e1e 100644 --- a/docs/doctoring/opencode-exact-vcs-dependency-evidence.md +++ b/docs/doctoring/opencode-exact-vcs-dependency-evidence.md @@ -35,6 +35,15 @@ product's current-head tests passing. request source cannot modify the networked image build inputs. - Source dependencies are import-only. No `pip install`, PEP 517 backend, setup hook, or dependency lifecycle script runs while the network is available. +- The source repository must be publicly fetchable without credentials, expose + the normalized top-level import package directly or under `src`, and remain a + pure-Python leaf dependency. Private repositories, environment-marked VCS + requirements, namespace/layout aliases, installed-distribution metadata, + entry points, compiled extensions, and registry packages that require the VCS + distribution fail closed instead of expanding the secret-free build boundary. +- The checkout roots and path file are explicitly world-readable so the later + networkless coverage container can run as UID 65532 independently of the + image builder's umask. - This records exact source inputs for a test image; it does not claim a SLSA build level or substitute for upstream package publication and attestation. diff --git a/tests/test_opencode_agent_contract.py b/tests/test_opencode_agent_contract.py index 5410fbb52..34bb0cdb7 100644 --- a/tests/test_opencode_agent_contract.py +++ b/tests/test_opencode_agent_contract.py @@ -689,6 +689,7 @@ def test_opencode_target_coverage_materializes_only_after_authorized_dispatch(): assert '[ -d "$destination/src/$import_name" ]' in measure_step assert '[ -d "$destination/$import_name" ]' in measure_step assert "has no import root" in measure_step + assert 'chmod -R a+rX /opt/base-vcs-dependencies "$path_file"' in measure_step assert "docker build --pull --no-cache --network=default" in measure_step assert '"$coverage_build_dir"' in measure_step assert measure_step.index("docker build --pull --no-cache") < measure_step.index( diff --git a/tests/test_pr_review_autofix_nvidia_nim_contract.py b/tests/test_pr_review_autofix_nvidia_nim_contract.py index dc17ee379..100757c9d 100644 --- a/tests/test_pr_review_autofix_nvidia_nim_contract.py +++ b/tests/test_pr_review_autofix_nvidia_nim_contract.py @@ -19,7 +19,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 = "19e4ef8e8b3615ad8003632548d4cb6bd65d31e9" +REVIEW_DISPATCH_BLOB_SHA = "91dc5d7d22318b3068eb05c82cb78aedc9bf2eee" def _workflow_text(path: Path) -> str: From 6e644df847bcda5bdeaaddafdcee621467018b05 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 09:17:28 +0900 Subject: [PATCH 4/6] fix: validate VCS import layouts --- .../workflows/opencode-review-dispatch.yml | 55 ++++++++++++++++--- CHANGELOG.md | 3 +- .../opencode-exact-vcs-dependency-evidence.md | 9 ++- tests/test_opencode_agent_contract.py | 15 ++++- ...t_pr_review_autofix_nvidia_nim_contract.py | 2 +- 5 files changed, 68 insertions(+), 16 deletions(-) diff --git a/.github/workflows/opencode-review-dispatch.yml b/.github/workflows/opencode-review-dispatch.yml index 91dc5d7d2..ded5dfcd3 100644 --- a/.github/workflows/opencode-review-dispatch.yml +++ b/.github/workflows/opencode-review-dispatch.yml @@ -747,17 +747,56 @@ jobs: git -C "$destination" checkout --quiet --detach FETCH_HEAD; \ test "$(git -C "$destination" rev-parse HEAD)" = "$commit"; \ rm -rf -- "$destination/.git"; \ - if [ -d "$destination/src/$import_name" ] \ - || [ -f "$destination/src/$import_name.py" ]; then \ - printf '%s/src\n' "$destination" >>"$path_file"; \ - elif [ -d "$destination/$import_name" ] \ - || [ -f "$destination/$import_name.py" ]; then \ - printf '%s\n' "$destination" >>"$path_file"; \ - else \ - printf 'locked VCS source %s has no import root for %s\n' \ + import_root=''; \ + python_root=''; \ + candidate_count=0; \ + for candidate in \ + "$destination/src/$import_name" \ + "$destination/src/$import_name.py" \ + "$destination/$import_name" \ + "$destination/$import_name.py"; do \ + if [ -e "$candidate" ] || [ -L "$candidate" ]; then \ + import_root="$candidate"; \ + candidate_count=$((candidate_count + 1)); \ + fi; \ + done; \ + if [ "$candidate_count" -ne 1 ]; then \ + printf 'locked VCS source %s has a missing or ambiguous import root for %s\n' \ "$repository" "$import_name" >&2; \ exit 1; \ fi; \ + if [ -L "$import_root" ] \ + || { [ -d "$import_root" ] \ + && { [ ! -f "$import_root/__init__.py" ] \ + || [ -L "$import_root/__init__.py" ]; }; }; then \ + printf 'locked VCS source %s has a namespace or linked import root for %s\n' \ + "$repository" "$import_name" >&2; \ + exit 1; \ + fi; \ + if find "$destination" -type l -print -quit | grep -q .; then \ + printf 'locked VCS source %s contains a symbolic-link layout\n' \ + "$repository" >&2; \ + exit 1; \ + fi; \ + if find "$destination" -type f \ + \( -name '*.so' -o -name '*.pyd' -o -name '*.dll' -o -name '*.dylib' \) \ + -print -quit | grep -q .; then \ + printf 'locked VCS source %s contains a compiled extension\n' \ + "$repository" >&2; \ + exit 1; \ + fi; \ + if find "$destination" -type d \ + \( -name '*.dist-info' -o -name '*.egg-info' \) \ + -print -quit | grep -q .; then \ + printf 'locked VCS source %s contains installed distribution metadata\n' \ + "$repository" >&2; \ + exit 1; \ + fi; \ + case "$import_root" in \ + "$destination/src/"*) python_root="$destination/src" ;; \ + *) python_root="$destination" ;; \ + esac; \ + printf '%s\n' "$python_root" >>"$path_file"; \ dependency_index=$((dependency_index + 1)); \ done <"$dependency_list"; \ rm -f -- "$dependency_list"; \ diff --git a/CHANGELOG.md b/CHANGELOG.md index de9417461..7b0c848f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,8 @@ Semantic Versioning where the repository publishes a release. - Fix OpenCode coverage evidence for exact-base, organization-owned Python VCS dependencies without weakening registry hashes or the networkless PR sandbox, - and make the exact source roots readable by the unprivileged coverage user. + reject namespace, ambiguous, linked, native-extension, and installed-metadata + layouts, and make exact roots readable by the unprivileged coverage user. ### Added diff --git a/docs/doctoring/opencode-exact-vcs-dependency-evidence.md b/docs/doctoring/opencode-exact-vcs-dependency-evidence.md index 72cc76e1e..326975338 100644 --- a/docs/doctoring/opencode-exact-vcs-dependency-evidence.md +++ b/docs/doctoring/opencode-exact-vcs-dependency-evidence.md @@ -58,9 +58,12 @@ toolchain failures (Souppaya et al., 2022). Regression tests cover the real LineageWeave export shape, rejection of unsafe VCS forms, conflicting commits, deterministic manifests, exact-fetch workflow -commands, source-only Python path publication, and the existing registry hash -contract. The central Python quality workflow retains 100% statement/branch and -docstring coverage. +commands, and the existing registry hash contract. Before source-only Python +path publication, the image build requires exactly one normalized root, a +regular `__init__.py` for packages, and no symbolic links, compiled extensions, +or installed distribution metadata. Namespace, alias/ambiguous, and native +layouts therefore fail the build. The central Python quality workflow retains +100% statement/branch and docstring coverage. ## References diff --git a/tests/test_opencode_agent_contract.py b/tests/test_opencode_agent_contract.py index 34bb0cdb7..577bf9852 100644 --- a/tests/test_opencode_agent_contract.py +++ b/tests/test_opencode_agent_contract.py @@ -686,9 +686,18 @@ def test_opencode_target_coverage_materializes_only_after_authorized_dispatch(): assert "opencode-base-vcs-dependencies.pth" in measure_step assert 'vcs-manifest.json >"$dependency_list"' in measure_step assert 'done <"$dependency_list"' in measure_step - assert '[ -d "$destination/src/$import_name" ]' in measure_step - assert '[ -d "$destination/$import_name" ]' in measure_step - assert "has no import root" in measure_step + assert 'candidate_count=$((candidate_count + 1))' 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 + assert "has a namespace or linked import root" in measure_step + assert 'find "$destination" -type l -print -quit' in measure_step + assert "contains a symbolic-link layout" in measure_step + assert "-name '*.so' -o -name '*.pyd' -o -name '*.dll' -o -name '*.dylib'" in measure_step + assert "contains a compiled extension" in measure_step + assert "-name '*.dist-info' -o -name '*.egg-info'" in measure_step + assert "contains installed distribution metadata" in measure_step + assert 'printf \'%s\\n\' "$python_root" >>"$path_file"' in measure_step assert 'chmod -R a+rX /opt/base-vcs-dependencies "$path_file"' in measure_step assert "docker build --pull --no-cache --network=default" in measure_step assert '"$coverage_build_dir"' 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 100757c9d..ec614f0d6 100644 --- a/tests/test_pr_review_autofix_nvidia_nim_contract.py +++ b/tests/test_pr_review_autofix_nvidia_nim_contract.py @@ -19,7 +19,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 = "91dc5d7d22318b3068eb05c82cb78aedc9bf2eee" +REVIEW_DISPATCH_BLOB_SHA = "ded5dfcd3789389e002ea904bcb5755e54463ba3" def _workflow_text(path: Path) -> str: From 71aed949ab385f950383eb9995aba383fec7a7d5 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 15:27:28 +0900 Subject: [PATCH 5/6] fix: isolate same-repository scheduler API credentials --- .../workflows/pr-review-merge-scheduler.yml | 4 +-- CHANGELOG.md | 6 ++++ docs/doctoring/fork-head-review-dispatch.md | 32 +++++++++++++++++++ tests/test_opencode_agent_contract.py | 18 ++++++----- .../test_required_workflow_queue_contract.py | 1 + 5 files changed, 51 insertions(+), 10 deletions(-) diff --git a/.github/workflows/pr-review-merge-scheduler.yml b/.github/workflows/pr-review-merge-scheduler.yml index 99bd6a89b..4d56721f6 100644 --- a/.github/workflows/pr-review-merge-scheduler.yml +++ b/.github/workflows/pr-review-merge-scheduler.yml @@ -485,14 +485,14 @@ jobs: GH_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || steps.scheduler_app_token.outputs.token || github.token }} TARGET_REPOSITORY: ${{ steps.targeted_dispatch.outputs.repository }} TARGET_DEFAULT_BRANCH: ${{ steps.targeted_dispatch.outputs.base_branch }} - SCHEDULER_ACTIONS_TOKEN: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.target_repository != '' && (secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || steps.scheduler_app_token.outputs.token) || github.token }} + SCHEDULER_ACTIONS_TOKEN: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.target_repository != '' && github.event.client_payload.target_repository != github.repository && (secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || steps.scheduler_app_token.outputs.token) || github.token }} # Same-repository dispatch credential: when this scheduler runs inside # ContextualWisdomLab/.github (the repository the required workflows are # dispatched on), the runner token can dispatch them without any # cross-repository PAT. The scheduler only uses it when # GITHUB_REPOSITORY equals the dispatch repository. SCHEDULER_DISPATCH_TOKEN: ${{ github.token }} - SCHEDULER_READ_TOKEN: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.target_repository != '' && (secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || steps.scheduler_app_token.outputs.token) || github.token }} + SCHEDULER_READ_TOKEN: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.target_repository != '' && github.event.client_payload.target_repository != github.repository && (secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || steps.scheduler_app_token.outputs.token) || github.token }} SCHEDULER_MUTATION_TOKEN_SOURCE: ${{ secrets.PR_REVIEW_MERGE_TOKEN != '' && 'PR_REVIEW_MERGE_TOKEN' || secrets.OPENCODE_APPROVE_TOKEN != '' && 'OPENCODE_APPROVE_TOKEN' || steps.scheduler_app_token.outputs.available == 'true' && 'opencode-app' || 'github-token' }} SCHEDULER_REQUIRED_WORKFLOW_REPOSITORY: ContextualWisdomLab/.github SCHEDULER_ALLOW_CROSS_REPO_REPOSITORY_DISPATCH: ${{ (secrets.PR_REVIEW_MERGE_TOKEN != '' || secrets.OPENCODE_APPROVE_TOKEN != '') && 'true' || 'false' }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e2e8dd6e..4e75b402f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,12 @@ Semantic Versioning where the repository publishes a release. ### Fixed +- Used the receiving repository's workflow token for same-repository scheduler + Actions inventory and read calls, while retaining the established mutation + credential chain. An exhausted organization-wide OpenCode App installation + budget can no longer prevent a central `.github` PR from dispatching its + exact-head review; cross-repository targets still require an explicit + credential. - Kept independently valid root-level Python lock environments separate during trusted base coverage installation. A directory with more than two candidate locks no longer collapses unrelated OpenCode, security, and application diff --git a/docs/doctoring/fork-head-review-dispatch.md b/docs/doctoring/fork-head-review-dispatch.md index dddef9c20..fe326459f 100644 --- a/docs/doctoring/fork-head-review-dispatch.md +++ b/docs/doctoring/fork-head-review-dispatch.md @@ -40,6 +40,31 @@ repository match, exact-head revalidation before OIDC/model work, canonical head-repository validation, and the absence of the former same-repository guard. +## Same-repository credential isolation + +Targeted scheduler runs `32556458196` and `32556607016` revalidated +`.github#1210` at the exact current head, then stopped before review dispatch +because their general Actions inventory read used an exhausted organization-wide +OpenCode App installation token. The scheduler already carried the receiving +repository's `github.token`, but selected the App for every targeted dispatch, +including the same repository. + +Same-repository `SCHEDULER_ACTIONS_TOKEN` and `SCHEDULER_READ_TOKEN` now use the +job-scoped `github.token`. Cross-repository reads retain the explicit PAT/App +chain, and `GH_TOKEN` retains the existing mutation chain, so this does not +grant the workflow token sibling-repository access or make it authoritative for +branch mutation. This separates the repository-local rate-limit bucket from the +shared App installation bucket and follows GitHub's documented authentication +rate-limit scopes (GitHub, Inc., n.d.-c). The existing +`SCHEDULER_DISPATCH_TOKEN` remains the repository token because GitHub explicitly +permits `repository_dispatch` created with `GITHUB_TOKEN` to start a workflow +(GitHub, Inc., n.d.-d). + +The static regression requires both read and Actions-control expressions to +distinguish a same-repository target from a cross-repository target. The full +Python suite, 100% statement/branch/docstring gates, and the CI-budget Strix +shell gate remain authoritative before publication. + ## APA 7th references GitHub, Inc. (n.d.-a). *REST API endpoints for pull requests*. GitHub Docs. @@ -50,6 +75,13 @@ GitHub, Inc. (n.d.-b). *Secure use reference*. GitHub Docs. Retrieved August 22, 2026, from https://docs.github.com/en/actions/reference/security/secure-use +GitHub, Inc. (n.d.-c). *Rate limits for the REST API*. GitHub Docs. Retrieved +August 22, 2026, from +https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api + +GitHub, Inc. (n.d.-d). *GITHUB_TOKEN*. GitHub Docs. Retrieved August 22, 2026, +from https://docs.github.com/en/actions/concepts/security/github_token + 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/tests/test_opencode_agent_contract.py b/tests/test_opencode_agent_contract.py index 94dbe9126..aaea3b0eb 100644 --- a/tests/test_opencode_agent_contract.py +++ b/tests/test_opencode_agent_contract.py @@ -1985,14 +1985,16 @@ def test_merge_scheduler_uses_escalating_mutation_credentials(): assert "secrets.PR_REVIEW_MERGE_TOKEN" in workflow assert "secrets.OPENCODE_APPROVE_TOKEN" in workflow assert "steps.scheduler_app_token.outputs.token" in workflow - assert ( - "SCHEDULER_READ_TOKEN: ${{ github.event_name == 'repository_dispatch' " - "&& github.event.client_payload.target_repository != '' && " - "(secrets.PR_REVIEW_MERGE_TOKEN || " - "secrets.OPENCODE_APPROVE_TOKEN || " - "steps.scheduler_app_token.outputs.token) || github.token }}" - in workflow - ) + for token_name in ("SCHEDULER_ACTIONS_TOKEN", "SCHEDULER_READ_TOKEN"): + assert ( + f"{token_name}: ${{{{ github.event_name == 'repository_dispatch' " + "&& github.event.client_payload.target_repository != '' && " + "github.event.client_payload.target_repository != github.repository && " + "(secrets.PR_REVIEW_MERGE_TOKEN || " + "secrets.OPENCODE_APPROVE_TOKEN || " + "steps.scheduler_app_token.outputs.token) || github.token }}" + in workflow + ) assert "SCHEDULER_MUTATION_TOKEN_SOURCE" in workflow assert 'default: "1"' in workflow assert 'review_dispatch_limit="-1"' in workflow diff --git a/tests/test_required_workflow_queue_contract.py b/tests/test_required_workflow_queue_contract.py index 49115b3e2..e8c7cb367 100644 --- a/tests/test_required_workflow_queue_contract.py +++ b/tests/test_required_workflow_queue_contract.py @@ -128,6 +128,7 @@ def test_targeted_scheduler_dispatch_is_allowlisted_and_exact_pr_scoped() -> Non assert ( "github.event_name == 'repository_dispatch' && " "github.event.client_payload.target_repository != '' && " + "github.event.client_payload.target_repository != github.repository && " "(secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || " "steps.scheduler_app_token.outputs.token) || github.token" ) in inspect From 3d5824d45008131f1d8aba4b71bd5f921337b95c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 15:42:13 +0900 Subject: [PATCH 6/6] fix: clarify VCS-only dependency evidence --- scripts/ci/materialize_base_python_requirements.py | 4 +++- tests/test_materialize_base_python_requirements.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/ci/materialize_base_python_requirements.py b/scripts/ci/materialize_base_python_requirements.py index d79f34400..a05212354 100755 --- a/scripts/ci/materialize_base_python_requirements.py +++ b/scripts/ci/materialize_base_python_requirements.py @@ -797,7 +797,9 @@ def main(argv: list[str] | None = None) -> int: ) else: print( - "No tracked hash-bearing Python requirement candidates exist at the validated base SHA." + "No tracked hash-bearing Python requirement candidates exist at the " + "validated base SHA; any exact VCS source pins are listed in " + "vcs-manifest.json." ) return 0 diff --git a/tests/test_materialize_base_python_requirements.py b/tests/test_materialize_base_python_requirements.py index c8a4af53f..58ded3740 100644 --- a/tests/test_materialize_base_python_requirements.py +++ b/tests/test_materialize_base_python_requirements.py @@ -616,7 +616,7 @@ def test_main_reports_when_no_locks_exist( == 0 ) assert ( - "No tracked hash-bearing Python requirement candidates exist" + "any exact VCS source pins are listed in vcs-manifest.json" in capsys.readouterr().out )