mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-31 19:16:29 +00:00
fix(runtime): request minor line for SQLite runtime repair + tests
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.
This commit is contained in:
parent
05a799e41c
commit
be633c1c33
2 changed files with 95 additions and 5 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue