fix(managed_uv): repair vulnerable SQLite runtime in .venv installs too

repair_vulnerable_runtime() hardcoded <checkout>/venv as the live venv,
so uv-default/dev checkouts installed into .venv got 'not-applicable' on
every hermes update — no repair path ever fired, leaving state.db-class
DBs on journal_mode=DELETE forever (measured 26 ms + ~5.5 fsyncs per
append vs ~0.01 ms under WAL, ~2,600x) while the WAL fallback warning
falsely promised hermes update would repair the runtime.

- _default_live_venv(): target venv/ when it has an interpreter (managed
  layout precedence), fall back to .venv/, keep not-applicable when
  neither exists. Explicit venv_dir arg unchanged; all staging/smoke/
  cutover/rollback machinery untouched.
- Rebuilt against the pruned test suite (main's test-prune waves 1+2
  rewrote test_managed_uv.py, so this reapplies cleanly): 3 new
  TestDefaultLiveVenv tests + repair neutralized in the 6 unit tests
  whose subject is uv install/self-update mechanics — with .venv now
  probed for real, CI's own vulnerable .venv made the unmocked repair
  hook fire inside those tests and re-invoke _install_uv.

33/33 tests green on the pruned suite.
This commit is contained in:
teknium1
2026-07-29 16:55:31 -07:00
committed by Teknium
parent 08e428ac8b
commit 1ec5928012
2 changed files with 88 additions and 1 deletions
+27 -1
View File
@@ -41,6 +41,7 @@ logger = logging.getLogger(__name__)
_PROJECT_ROOT = Path(__file__).resolve().parents[1]
_RUNTIME_DIR_NAME = ".hermes-runtime"
_VENV_NAME = "venv"
_ALT_VENV_NAME = ".venv"
_REPAIR_LOCK_NAME = "runtime-repair.lock"
# ---------------------------------------------------------------------------
@@ -986,6 +987,31 @@ def _refresh_managed_uv_catalog(uv_bin: str) -> bool:
return after != before
def _default_live_venv(root: Path) -> Path:
"""Return the venv that runtime repair should target for *root*.
Managed installs create ``<checkout>/venv``, but uv-default and dev
checkouts use ``<checkout>/.venv``. Historically only ``venv`` was
probed, so a ``.venv`` install linking a vulnerable SQLite returned
``not-applicable`` on every ``hermes update`` and stayed on
journal_mode=DELETE forever — even though the WAL fallback warning
promises that ``hermes update`` repairs the runtime (issue class:
2,600x slower ``state.db`` appends under DELETE).
``venv`` wins when it holds an interpreter (managed layout takes
precedence); otherwise fall back to ``.venv`` when that one does.
When neither has an interpreter, return the ``venv`` path so the
caller's existing ``not-applicable`` handling fires unchanged.
"""
primary = root / _VENV_NAME
if _venv_python(primary).is_file():
return primary
fallback = root / _ALT_VENV_NAME
if _venv_python(fallback).is_file():
return fallback
return primary
def repair_vulnerable_runtime(
uv_bin: str,
*,
@@ -998,7 +1024,7 @@ def repair_vulnerable_runtime(
post-cutover smoke failures restore the parked venv synchronously.
"""
root = Path(project_root) if project_root is not None else _PROJECT_ROOT
live = Path(venv_dir) if venv_dir is not None else root / _VENV_NAME
live = Path(venv_dir) if venv_dir is not None else _default_live_venv(root)
live_python = _venv_python(live)
if not (root / "pyproject.toml").is_file() or not live_python.is_file():
return RuntimeRepairResult("not-applicable")