From be633c1c330ca2d5a670dabaa440a351655d0596 Mon Sep 17 00:00:00 2001 From: teknium1 <127238744+teknium1@users.noreply.github.com> Date: Fri, 24 Jul 2026 12:44:30 -0700 Subject: [PATCH] fix(runtime): request minor line for SQLite runtime repair + tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up on the #70186 salvage. The cherry-picked repair pinned the candidate to the exact current CPython patch (e.g. 3.11.14). Verified live with uv 0.11.19: every published python-build-standalone artifact for 3.11.14 links vulnerable SQLite 3.50.4 — even with --reinstall — so the exact-patch pin made the repair permanently impossible on the installs that need it most (repair_vulnerable_runtime returned 'failed: could not provision a fixed private Python runtime'). Request the minor line (3.11) instead — the same resolution a fresh 'uv python install' would make, still inside requires-python — and tighten the drift gate to 'same minor, no downgrade'. E2E-verified end-to-end on a real vulnerable venv: repair_vulnerable_runtime() provisioned 3.11.15, built + smoke-tested the sibling venv, cut over, and reported SQLite 3.50.4 → 3.53.1 with the old venv parked for rollback. --- hermes_cli/managed_uv.py | 21 ++++++-- tests/hermes_cli/test_managed_uv.py | 79 +++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 5 deletions(-) diff --git a/hermes_cli/managed_uv.py b/hermes_cli/managed_uv.py index b24815fed84..b8e1875f110 100644 --- a/hermes_cli/managed_uv.py +++ b/hermes_cli/managed_uv.py @@ -351,8 +351,17 @@ def _make_world_traversable(path: Path) -> None: def _runtime_request(info: SQLiteRuntimeInfo) -> str: - """Pin the candidate to the current exact CPython patch.""" - return ".".join(str(part) for part in info.python_version) + """Pin the candidate to the current CPython minor line (e.g. ``3.11``). + + Requesting the exact patch can never repair some installs: for a given + patch, python-build-standalone may have no artifact with fixed SQLite at + all (e.g. every published 3.11.14 build links SQLite 3.50.4; the fix + only exists from 3.11.15). A newer patch on the same minor is what + ``uv python install`` would resolve for a fresh install, stays inside + ``requires-python``, and the locked ``uv sync`` + import smoke tests gate + compatibility before any cutover. + """ + return ".".join(str(part) for part in info.python_version[:2]) def _install_safe_python_generation( @@ -438,10 +447,12 @@ def _install_safe_python_generation( logger.warning("could not probe candidate Python runtime: %s", python) _remove_tree(generation, boundary=python_root) return None - if candidate.python_version != current.python_version: + if candidate.python_version[:2] != current.python_version[:2] or ( + candidate.python_version < current.python_version + ): logger.warning( - "candidate Python patch drifted from %s to %s", - current.python_version, + "candidate Python drifted off the %s minor line or downgraded: %s", + ".".join(str(p) for p in current.python_version[:2]), candidate.python_version, ) _remove_tree(generation, boundary=python_root) diff --git a/tests/hermes_cli/test_managed_uv.py b/tests/hermes_cli/test_managed_uv.py index cb8cae06b76..f8e6465d239 100644 --- a/tests/hermes_cli/test_managed_uv.py +++ b/tests/hermes_cli/test_managed_uv.py @@ -697,3 +697,82 @@ class TestInstallUvInternals: mock_windows.assert_called_once() call_env = mock_windows.call_args[0][0] assert call_env["UV_INSTALL_DIR"] == str(tmp_path / "bin") + + +class TestRuntimeRequestMinorLine: + """The repair must request the CPython minor line, not the exact patch. + + Real-world constraint (verified live, July 2026): every published + python-build-standalone artifact for 3.11.14 links vulnerable SQLite + 3.50.4 — even with --reinstall. The fixed SQLite (3.53.1) only exists + from 3.11.15. An exact-patch pin makes the repair permanently + impossible on such installs. + """ + + def test_requests_minor_line(self): + from hermes_cli.managed_uv import _runtime_request + + info = _runtime_info(Path("/venv/bin/python"), (3, 50, 4)) + assert _runtime_request(info) == "3.11" + + @staticmethod + def _run_generation(tmp_path, monkeypatch, current_version, candidate_version): + """Drive _install_safe_python_generation with fakes; return result.""" + import hermes_cli.managed_uv as managed_uv + from hermes_cli.sqlite_runtime import SQLiteRuntimeInfo + + state = {} + + def fake_run(cmd, **kwargs): + if "install" in cmd: + state["generation"] = Path(kwargs["env"]["UV_PYTHON_INSTALL_DIR"]) + return SimpleNamespace(returncode=0, stdout="", stderr="") + # uv python find → a path inside the generation dir + python = state["generation"] / "cpython" / "bin" / "python3" + python.parent.mkdir(parents=True, exist_ok=True) + python.touch() + return SimpleNamespace(returncode=0, stdout=str(python), stderr="") + + def fake_probe(python, **kwargs): + return SQLiteRuntimeInfo( + executable=Path(python), + base_prefix=Path(python).parent.parent, + python_version=candidate_version, + sqlite_version=(3, 53, 1), + sqlite_version_string="3.53.1", + sqlite_source_id="fixed", + ) + + current = SQLiteRuntimeInfo( + executable=Path("/venv/bin/python"), + base_prefix=Path("/venv"), + python_version=current_version, + sqlite_version=(3, 50, 4), + sqlite_version_string="3.50.4", + sqlite_source_id="old", + ) + monkeypatch.setattr(managed_uv.subprocess, "run", fake_run) + monkeypatch.setattr(managed_uv, "probe_sqlite_runtime", fake_probe) + return managed_uv._install_safe_python_generation( + "uv", project_root=tmp_path, current=current + ) + + def test_accepts_newer_patch_same_minor(self, tmp_path, monkeypatch): + result = self._run_generation( + tmp_path, monkeypatch, (3, 11, 14), (3, 11, 15) + ) + assert result is not None + _, _, candidate = result + assert candidate.python_version == (3, 11, 15) + + def test_rejects_minor_drift(self, tmp_path, monkeypatch): + assert ( + self._run_generation(tmp_path, monkeypatch, (3, 11, 14), (3, 12, 1)) + is None + ) + + def test_rejects_patch_downgrade(self, tmp_path, monkeypatch): + assert ( + self._run_generation(tmp_path, monkeypatch, (3, 11, 14), (3, 11, 13)) + is None + )