Skip to content
15 changes: 11 additions & 4 deletions scripts/ci/materialize_base_python_requirements.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,16 +145,23 @@ def _install_trusted_uv_url_opener() -> None:

def _is_candidate_lock_name(name: str) -> bool:
"""Return whether a file name is a possible pip requirements lock."""
return name == "requirements.lock" or (
fnmatch.fnmatch(name, "requirements*.txt")
and not fnmatch.fnmatch(name, "requirements-*-ci-hashes.txt")
return (
(
fnmatch.fnmatch(name, "requirements*.txt")
and not fnmatch.fnmatch(name, "requirements-*-ci-hashes.*")
)
or (
fnmatch.fnmatch(name, "requirements*.lock")
and not fnmatch.fnmatch(name, "requirements-*-ci-hashes.*")
)
)
Comment thread
seonghobae marked this conversation as resolved.
Comment thread
seonghobae marked this conversation as resolved.
Comment on lines 146 to 157

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Lock candidate change is a safe superset

_is_candidate_lock_name adds requirements*.lock and widens the hash-file exclusion to requirements-*-ci-hashes.*. For .txt names this matches the prior behavior exactly, and content still must pass _is_hash_pinned in base_hash_locks before materialization. The repo ships no .lock requirements files, so its own coverage build is unaffected.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.



def _is_candidate_lock_path(path: pathlib.PurePosixPath) -> bool:
"""Return whether one safe tracked path can name a pip requirements lock.

In addition to conventional ``requirements*.txt`` names, repositories often
In addition to conventional ``requirements*.txt`` and ``requirements*.lock``
names, repositories often
keep concrete environment closures as direct children such as
``requirements/ci.txt`` or ``service/requirements/package.txt``. Only direct
``.txt`` children of a directory named ``requirements`` gain this path-based
Comment thread
seonghobae marked this conversation as resolved.
Expand Down
3 changes: 2 additions & 1 deletion scripts/ci/organization_commercial_readiness_loop.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 client with one bounded GitHub credential."""
if not token:
raise GitHubError("GH_TOKEN is required for organization coordination")
self._token = token
Expand Down Expand Up @@ -853,4 +854,4 @@ def main(


if __name__ == "__main__": # pragma: no cover - exercised through main()
raise SystemExit(main())
raise SystemExit(main())
30 changes: 22 additions & 8 deletions scripts/ci/repair_pr827_coderabbit_comments.py
Original file line number Diff line number Diff line change
Expand Up @@ -266,14 +266,28 @@ def _rewrite_materialized_includes(
' encoding="utf-8",\n'
' )\n',
)
replace_once(
TEST,
' "requirements-test.txt",\n'
' "services/account_unification/requirements-dev.txt",\n',
' "requirements-test.txt",\n'
' "requirements/ci.txt",\n'
' "services/account_unification/requirements-dev.txt",\n',
)
test_text = Path(TEST).read_text(encoding="utf-8")
if ' "requirements/ci.txt",\n' not in test_text:
if ' "services/account_unification/requirements-dev.lock",\n' in test_text:
replace_once(
TEST,
' "requirements-test.txt",\n'
' "services/account_unification/requirements-dev.lock",\n'
' "services/account_unification/requirements-dev.txt",\n',
' "requirements-test.txt",\n'
' "requirements/ci.txt",\n'
' "services/account_unification/requirements-dev.lock",\n'
' "services/account_unification/requirements-dev.txt",\n',
)
else:
replace_once(
TEST,
' "requirements-test.txt",\n'
' "services/account_unification/requirements-dev.txt",\n',
' "requirements-test.txt",\n'
' "requirements/ci.txt",\n'
' "services/account_unification/requirements-dev.txt",\n',
)
Comment on lines +270 to +290

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Untested migration branch breaks 100% coverage gate

The new module-level if only runs its body when the staged test file lacks requirements/ci.txt. The one test that executes this driver, test_bounded_repair_driver_runs_against_a_staged_fixture, copies the current files, which already contain that line, so the condition is always false and the enclosed lines never run. scripts/ci is gated at 100% coverage, so the uncovered branch fails CI.

Prompt for agents
The repair driver at scripts/ci/repair_pr827_coderabbit_comments.py now has a module-level if/elif/else (lines 269-290) whose body only executes when the target test file does not yet contain the line for requirements/ci.txt. The only test that runs this driver, test_bounded_repair_driver_runs_against_a_staged_fixture in tests/test_materialize_base_python_requirements.py, stages the current already-migrated repo files (which already contain requirements/ci.txt and the requirements-dev.lock source line), so the branch is never taken. Because scripts/ci is measured under a hard 100% coverage gate (pyproject.toml fail_under=100, reinforced by AGENTS.md/CLAUDE.md), the unexecuted lines and the untaken branch will drop coverage below 100% and fail CI. Fix by making that test stage a pre-migration fixture that forces the migration branch (e.g., rewrite the copied test file's expected-source list back to a state without requirements/ci.txt, in both the .lock-present and .lock-absent variants) so both branches execute, or otherwise ensure the new branches are exercised.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

insert_before(
TEST,
' between_file.write_text("START old", encoding="utf-8")\n',
Expand Down
23 changes: 22 additions & 1 deletion tests/test_materialize_base_python_requirements.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""Verify trusted base lock discovery, materialization, and exporter failures."""

from __future__ import annotations

import ast
Expand Down Expand Up @@ -124,6 +126,10 @@ def test_materializes_hash_pinned_locks_named_beyond_the_legacy_whitelist(
"fastapi==1 --hash=sha256:" + ("a" * 64) + "\n",
encoding="utf-8",
)
(service / "requirements-dev.lock").write_text(
"uvicorn==1 --hash=sha256:" + ("c" * 64) + "\n",
encoding="utf-8",
)
(repo / "requirements-test.txt").write_text(
"hypothesis==6 --hash=sha256:" + ("b" * 64) + "\n",
encoding="utf-8",
Expand All @@ -148,18 +154,23 @@ def test_materializes_hash_pinned_locks_named_beyond_the_legacy_whitelist(
assert [entry["source"] for entry in manifest] == [
"requirements-test.txt",
"requirements/ci.txt",
"services/account_unification/requirements-dev.lock",
"services/account_unification/requirements-dev.txt",
]


def test_lock_name_candidates_are_pip_requirements_files() -> None:
"""Requirements files and requirements.lock are candidates; other names are not."""
"""Requirements files and lock files are candidates; other names are not."""
assert materializer._is_candidate_lock_name("requirements.lock")
assert materializer._is_candidate_lock_name("requirements-dev.lock")
assert materializer._is_candidate_lock_name("requirements-dev.txt")
assert materializer._is_candidate_lock_name("requirements.txt")
assert not materializer._is_candidate_lock_name(
"requirements-opencode-review-ci-hashes.txt"
)
assert not materializer._is_candidate_lock_name(
"requirements-opencode-review-ci-hashes.lock"
)
assert not materializer._is_candidate_lock_name("uv.lock")
assert not materializer._is_candidate_lock_name("pyproject.toml")
assert materializer._is_candidate_lock_path(
Expand Down Expand Up @@ -438,6 +449,7 @@ def test_rejects_malformed_git_tree_entries(
"""Malformed git output cannot be interpreted as a trusted lock blob."""

def fake_git(_repo_root: Path, *_args: str) -> bytes:
"""Return the malformed tree fixture for the parser under test."""
return tree_output

monkeypatch.setattr(materializer, "_git", fake_git)
Expand Down Expand Up @@ -470,6 +482,7 @@ def test_main_reports_each_materialized_lock(
def fake_materialize(
_repo_root: Path, _base_sha: str, _output_dir: Path
) -> list[dict[str, str]]:
"""Return one deterministic manifest for the CLI reporting test."""
return [
{
"file": "requirements-000.txt",
Expand Down Expand Up @@ -533,6 +546,7 @@ def test_main_fails_with_the_materialization_reason(
"""A materialization exception fails closed and remains diagnosable in CI."""

def fail_materialize(_repo_root: Path, _base_sha: str, _output_dir: Path) -> None:
"""Raise the fixture failure that the CLI must report."""
raise OSError("fixture failure")

monkeypatch.setattr(materializer, "materialize", fail_materialize)
Expand Down Expand Up @@ -592,6 +606,7 @@ def test_skips_non_blob_tree_entries(
)

def fake_git(_repo_root: Path, *args: str) -> bytes:
"""Return one regular blob and one skipped gitlink tree entry."""
if args[0] == "ls-tree":
return tree
if args[0] == "show":
Expand Down Expand Up @@ -656,6 +671,7 @@ def test_uv_lock_fails_closed_when_trusted_uv_bootstrap_fails(
repo, base_sha = _uv_repo(tmp_path, with_pyproject=True)

def fail_install() -> str:
"""Raise the bootstrap failure expected for a tracked uv project."""
raise RuntimeError("trusted uv bootstrap failed")

monkeypatch.setattr(materializer, "_install_trusted_uv", fail_install)
Expand All @@ -671,6 +687,7 @@ def test_uv_lock_skipped_when_pyproject_is_absent(
repo, base_sha = _uv_repo(tmp_path, with_pyproject=False, lock_dir="service")

def unexpected_install() -> str:
"""Fail if an orphan uv lock attempts to bootstrap the trusted exporter."""
raise AssertionError("orphan uv.lock must not bootstrap uv")

monkeypatch.setattr(materializer, "_install_trusted_uv", unexpected_install)
Expand Down Expand Up @@ -946,6 +963,7 @@ def test_install_trusted_uv_verifies_version_and_caches_path(
calls = 0

def verify(*_args: object, **_kwargs: object) -> subprocess.CompletedProcess[bytes]:
"""Return the exact pinned uv version and count verification executions."""
nonlocal calls
calls += 1
return subprocess.CompletedProcess(
Expand Down Expand Up @@ -988,6 +1006,7 @@ def test_install_trusted_uv_rejects_version_process_failures(
monkeypatch.setattr(materializer, "_verified_uv_binary", lambda _payload: b"binary")

def fail(*_args: object, **_kwargs: object) -> None:
"""Raise the parameterized process failure from the fake executable."""
raise failure

monkeypatch.setattr(materializer.subprocess, "run", fail)
Expand Down Expand Up @@ -1044,6 +1063,7 @@ def test_run_uv_export_invokes_uv_with_frozen_offline_flags(
captured: dict[str, object] = {}

def fake_run(argv: list[str], **kwargs: object) -> subprocess.CompletedProcess[bytes]:
"""Capture uv export arguments while returning a successful result."""
captured["argv"] = argv
captured["cwd"] = kwargs.get("cwd")
captured["timeout"] = kwargs.get("timeout")
Expand Down Expand Up @@ -1079,6 +1099,7 @@ def test_uv_export_process_failures_fail_closed(
monkeypatch.setattr(materializer, "_install_trusted_uv", lambda: "/usr/bin/uv")

def fail_export(_work: Path, _uv_path: str) -> None:
"""Raise the parameterized exporter failure for the fail-closed test."""
raise export_error

monkeypatch.setattr(materializer, "_run_uv_export", fail_export)
Expand Down
Loading