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:
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user