From 174a76cb52cc5aab06524ce8265794e7f2f6815d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 4 Aug 2026 14:52:01 +0900 Subject: [PATCH 1/5] test(ci): reproduce stale base-lock pin deadlock --- ...st_install_base_python_lock_missing_pin.py | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 tests/test_install_base_python_lock_missing_pin.py diff --git a/tests/test_install_base_python_lock_missing_pin.py b/tests/test_install_base_python_lock_missing_pin.py new file mode 100644 index 000000000..317dc065b --- /dev/null +++ b/tests/test_install_base_python_lock_missing_pin.py @@ -0,0 +1,27 @@ +"""Regression tests for unavailable pins in trusted base Python locks.""" + +from scripts.ci import install_base_python_locks as installer + + +def test_reachable_index_missing_pin_is_deferable() -> None: + """A reachable index proving newer versions exist may defer a stale pin.""" + + output = ( + "ERROR: Could not find a version that satisfies the requirement " + "pypdf==6.13.3 (from versions: 6.14.1, 6.14.2)\n" + "ERROR: No matching distribution found for pypdf==6.13.3" + ) + + assert installer._is_deferable_preflight_failure(output) + + +def test_empty_index_missing_pin_remains_fatal() -> None: + """An empty or unreachable package index must never be treated as optional.""" + + output = ( + "ERROR: Could not find a version that satisfies the requirement " + "pypdf==6.13.3 (from versions: none)\n" + "ERROR: No matching distribution found for pypdf==6.13.3" + ) + + assert not installer._is_deferable_preflight_failure(output) From 4aa69060358b5af6c2f4e33652036527a08e0f87 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 4 Aug 2026 14:55:51 +0900 Subject: [PATCH 2/5] fix(ci): defer stale pins from a reachable index --- scripts/ci/install_base_python_locks.py | 37 +++++++++++++++++-------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/scripts/ci/install_base_python_locks.py b/scripts/ci/install_base_python_locks.py index 518fcd689..0d498540f 100644 --- a/scripts/ci/install_base_python_locks.py +++ b/scripts/ci/install_base_python_locks.py @@ -38,6 +38,17 @@ re.IGNORECASE, ), re.compile(r"requires a different Python", re.IGNORECASE), + # A base lock can pin a version that has since been yanked or that offers no + # wheel for the pinned coverage-image interpreter. pip proves the index was + # reachable by listing the versions it *did* find, so this is an + # interpreter/availability incompatibility (defer to the later coverage run), + # not a registry outage. The "(from versions: none)" shape — an empty or + # unreachable index — is deliberately excluded and stays fatal. + re.compile( + r"Could not find a version that satisfies the requirement[^\n]*" + r"\(from versions:(?! none\))", + re.IGNORECASE, + ), ) Runner = Callable[..., subprocess.CompletedProcess[str]] @@ -150,12 +161,16 @@ def _is_deferable_preflight_failure(output: str) -> bool: """Return whether a failed candidate may be grouped or safely skipped. A hash-bearing supplement can fail pip's independent-closure check because a - transitive pin/hash lives in a sibling lock, and a base lock can explicitly - reject the pinned coverage-image interpreter. Those states are safe to - recover through a same-directory group or defer to the later networkless - coverage run. Hash mismatches, resolver crashes, empty diagnostics, and - registry/network failures remain fatal so a broken trusted build cannot be - mistaken for an optional lock. + transitive pin/hash lives in a sibling lock, a base lock can explicitly + reject the pinned coverage-image interpreter, and a base lock can pin a + version the reachable index no longer offers for that interpreter (yanked or + no matching wheel). Those states are safe to recover through a same-directory + group or defer to the later networkless coverage run. Hash mismatches, + resolver crashes, empty diagnostics, and registry/network failures — including + the "(from versions: none)" empty/unreachable-index shape — remain fatal so a + broken trusted build cannot be mistaken for an optional lock. Deferred paths + retain a warning and bounded pip diagnostics so the incompatibility stays + visible without blocking unrelated coverage evidence. """ return bool(output.strip()) and any( pattern.search(output) for pattern in DEFERABLE_PREFLIGHT_FAILURES @@ -171,8 +186,9 @@ def _report_fatal_preflight_failure( """Publish one bounded, source-aware fatal preflight failure.""" print( "::error::Trusted base Python lock preflight failed for " - f"{entry_label}; only incomplete hash closures or explicit Python " - "interpreter incompatibility may be deferred.", + f"{entry_label}; only incomplete hash closures, explicit Python " + "interpreter incompatibility, or a reachable-index version that is no " + "longer available for the coverage interpreter may be deferred.", file=stderr, ) failure_output = _bounded_failure_output(output) @@ -280,9 +296,8 @@ def install_materialized_locks( skipped += 1 print( "::warning::Skipping trusted base Python requirement candidate " - f"{entry.source}: hash-bearing content is not an independently " - "installable dependency closure and no same-directory lock group " - "completed it.", + f"{entry.source}: it could not be installed independently for the " + "coverage interpreter and no same-directory lock group completed it.", file=stderr, ) failure_output = _bounded_failure_output( From 9d27d3c07a37b5bb274c567f1ae0f744df6b28d3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 4 Aug 2026 15:01:12 +0900 Subject: [PATCH 3/5] test(ci): reject blank reachable-index evidence --- tests/test_install_base_python_lock_missing_pin.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/tests/test_install_base_python_lock_missing_pin.py b/tests/test_install_base_python_lock_missing_pin.py index 317dc065b..96f9af5e7 100644 --- a/tests/test_install_base_python_lock_missing_pin.py +++ b/tests/test_install_base_python_lock_missing_pin.py @@ -16,7 +16,7 @@ def test_reachable_index_missing_pin_is_deferable() -> None: def test_empty_index_missing_pin_remains_fatal() -> None: - """An empty or unreachable package index must never be treated as optional.""" + """An explicitly empty package index must never be treated as optional.""" output = ( "ERROR: Could not find a version that satisfies the requirement " @@ -25,3 +25,15 @@ def test_empty_index_missing_pin_remains_fatal() -> None: ) assert not installer._is_deferable_preflight_failure(output) + + +def test_blank_version_list_missing_pin_remains_fatal() -> None: + """A blank version list is not affirmative proof that the index is reachable.""" + + output = ( + "ERROR: Could not find a version that satisfies the requirement " + "pypdf==6.13.3 (from versions: )\n" + "ERROR: No matching distribution found for pypdf==6.13.3" + ) + + assert not installer._is_deferable_preflight_failure(output) From 73d0474ceebf28e9e8d4a558e13c19c04cdedb2a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 4 Aug 2026 15:03:10 +0900 Subject: [PATCH 4/5] fix(ci): require affirmative version-list evidence --- scripts/ci/install_base_python_locks.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/scripts/ci/install_base_python_locks.py b/scripts/ci/install_base_python_locks.py index 0d498540f..db5b40df5 100644 --- a/scripts/ci/install_base_python_locks.py +++ b/scripts/ci/install_base_python_locks.py @@ -40,13 +40,11 @@ re.compile(r"requires a different Python", re.IGNORECASE), # A base lock can pin a version that has since been yanked or that offers no # wheel for the pinned coverage-image interpreter. pip proves the index was - # reachable by listing the versions it *did* find, so this is an - # interpreter/availability incompatibility (defer to the later coverage run), - # not a registry outage. The "(from versions: none)" shape — an empty or - # unreachable index — is deliberately excluded and stays fatal. + # reachable only when it lists at least one concrete version. Empty lists, + # ``none``, and unreachable-index diagnostics remain fatal. re.compile( r"Could not find a version that satisfies the requirement[^\n]*" - r"\(from versions:(?! none\))", + r"\(from versions:\s*(?!none\b)(?=[A-Za-z0-9])[^)\n]+\)", re.IGNORECASE, ), ) @@ -167,10 +165,10 @@ def _is_deferable_preflight_failure(output: str) -> bool: no matching wheel). Those states are safe to recover through a same-directory group or defer to the later networkless coverage run. Hash mismatches, resolver crashes, empty diagnostics, and registry/network failures — including - the "(from versions: none)" empty/unreachable-index shape — remain fatal so a - broken trusted build cannot be mistaken for an optional lock. Deferred paths - retain a warning and bounded pip diagnostics so the incompatibility stays - visible without blocking unrelated coverage evidence. + empty or ``none`` version lists — remain fatal so a broken trusted build cannot + be mistaken for an optional lock. Deferred paths retain a warning and bounded + pip diagnostics so the incompatibility stays visible without blocking + unrelated coverage evidence. """ return bool(output.strip()) and any( pattern.search(output) for pattern in DEFERABLE_PREFLIGHT_FAILURES From 2133b5e1725124e3f9b233b59559f5a3be5d5564 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 4 Aug 2026 15:11:51 +0900 Subject: [PATCH 5/5] test(ci): exercise stale-pin deferral end to end --- ...st_install_base_python_lock_missing_pin.py | 70 +++++++++++++++++-- 1 file changed, 64 insertions(+), 6 deletions(-) diff --git a/tests/test_install_base_python_lock_missing_pin.py b/tests/test_install_base_python_lock_missing_pin.py index 96f9af5e7..63186bf7c 100644 --- a/tests/test_install_base_python_lock_missing_pin.py +++ b/tests/test_install_base_python_lock_missing_pin.py @@ -1,9 +1,55 @@ """Regression tests for unavailable pins in trusted base Python locks.""" +from __future__ import annotations + +import io +import json +import subprocess +from pathlib import Path + from scripts.ci import install_base_python_locks as installer -def test_reachable_index_missing_pin_is_deferable() -> None: +def _write_candidate(root: Path) -> None: + """Write one trusted materialized lock candidate and its manifest.""" + + (root / "manifest.json").write_text( + json.dumps( + [ + { + "file": "requirements-000.txt", + "source": "requirements-hashes.txt", + } + ] + ), + encoding="utf-8", + ) + (root / "requirements-000.txt").write_text( + "pypdf==6.13.3 --hash=sha256:" + ("a" * 64) + "\n", + encoding="utf-8", + ) + + +def _run_preflight_failure(root: Path, output: str) -> tuple[int, str, str]: + """Run the installer with one deterministic pip preflight failure.""" + + _write_candidate(root) + + def fake_runner(command: list[str], **kwargs): + return subprocess.CompletedProcess(command, 1, stdout=output) + + stdout = io.StringIO() + stderr = io.StringIO() + result = installer.install_materialized_locks( + root, + runner=fake_runner, + stdout=stdout, + stderr=stderr, + ) + return result, stdout.getvalue(), stderr.getvalue() + + +def test_reachable_index_missing_pin_is_visible_and_nonfatal(tmp_path: Path) -> None: """A reachable index proving newer versions exist may defer a stale pin.""" output = ( @@ -12,10 +58,14 @@ def test_reachable_index_missing_pin_is_deferable() -> None: "ERROR: No matching distribution found for pypdf==6.13.3" ) - assert installer._is_deferable_preflight_failure(output) + result, stdout, stderr = _run_preflight_failure(tmp_path, output) + + assert result == 0 + assert "candidates=1 installed=0 skipped=1" in stdout + assert "Could not find a version that satisfies the requirement" in stderr -def test_empty_index_missing_pin_remains_fatal() -> None: +def test_empty_index_missing_pin_remains_fatal(tmp_path: Path) -> None: """An explicitly empty package index must never be treated as optional.""" output = ( @@ -24,10 +74,14 @@ def test_empty_index_missing_pin_remains_fatal() -> None: "ERROR: No matching distribution found for pypdf==6.13.3" ) - assert not installer._is_deferable_preflight_failure(output) + result, stdout, stderr = _run_preflight_failure(tmp_path, output) + assert result == 1 + assert "preflight failed" in stderr + assert "installed=" not in stdout -def test_blank_version_list_missing_pin_remains_fatal() -> None: + +def test_blank_version_list_missing_pin_remains_fatal(tmp_path: Path) -> None: """A blank version list is not affirmative proof that the index is reachable.""" output = ( @@ -36,4 +90,8 @@ def test_blank_version_list_missing_pin_remains_fatal() -> None: "ERROR: No matching distribution found for pypdf==6.13.3" ) - assert not installer._is_deferable_preflight_failure(output) + result, stdout, stderr = _run_preflight_failure(tmp_path, output) + + assert result == 1 + assert "preflight failed" in stderr + assert "installed=" not in stdout