From 48bf176701fd9b35bd6e3a23ce743f1a786c3996 Mon Sep 17 00:00:00 2001 From: Gille <4317663+helix4u@users.noreply.github.com> Date: Tue, 1 Sep 2026 19:23:05 -0600 Subject: [PATCH] fix(installer): order repository before managed Python --- scripts/install.ps1 | 6 ++- ...t_install_ps1_managed_python_provenance.py | 40 +++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/scripts/install.ps1 b/scripts/install.ps1 index 07930fd72d..6e64bc79c9 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -4733,11 +4733,15 @@ function Write-Completion { # or arrange to provide answers another way." $InstallStages = @( @{ Name = "uv"; Title = "Installing uv package manager"; Category = "prereqs"; NeedsUserInput = $false; Worker = "Stage-Uv" } - @{ Name = "python"; Title = "Verifying Python $PythonVersion"; Category = "prereqs"; NeedsUserInput = $false; Worker = "Stage-Python" } @{ Name = "git"; Title = "Installing Git"; Category = "prereqs"; NeedsUserInput = $false; Worker = "Stage-Git" } @{ Name = "node"; Title = "Detecting Node.js"; Category = "prereqs"; NeedsUserInput = $false; Worker = "Stage-Node" } @{ Name = "system-packages"; Title = "Installing ripgrep and ffmpeg"; Category = "prereqs"; NeedsUserInput = $false; Worker = "Stage-SystemPackages" } @{ Name = "repository"; Title = "Cloning Hermes repository"; Category = "install"; NeedsUserInput = $false; Worker = "Stage-Repository" } + # Managed Python lives under $InstallDir\.hermes-runtime, so the checkout + # must exist before this stage creates that directory. Otherwise the later + # repository stage treats the runtime-only directory as a broken checkout, + # parks it, and leaves Stage-Venv with no managed interpreter. + @{ Name = "python"; Title = "Verifying Python $PythonVersion"; Category = "prereqs"; NeedsUserInput = $false; Worker = "Stage-Python" } @{ Name = "venv"; Title = "Creating Python virtual environment"; Category = "install"; NeedsUserInput = $false; Worker = "Stage-Venv" } @{ Name = "dependencies"; Title = "Installing Python dependencies"; Category = "install"; NeedsUserInput = $false; Worker = "Stage-Dependencies" } @{ Name = "node-deps"; Title = "Installing Node.js dependencies"; Category = "install"; NeedsUserInput = $false; Worker = "Stage-NodeDeps" } diff --git a/tests/test_install_ps1_managed_python_provenance.py b/tests/test_install_ps1_managed_python_provenance.py index 5c91721a32..7cccce360e 100644 --- a/tests/test_install_ps1_managed_python_provenance.py +++ b/tests/test_install_ps1_managed_python_provenance.py @@ -18,6 +18,46 @@ pytestmark = pytest.mark.windows_only _INSTALL_PS1 = Path(__file__).resolve().parents[1] / "scripts" / "install.ps1" +def test_fresh_install_manifest_orders_repo_before_checkout_scoped_python( + tmp_path: Path, +) -> None: + powershell = shutil.which("powershell") + if not powershell: + pytest.skip("Windows PowerShell is required") + + install_dir = tmp_path / "install" + run = subprocess.run( + [ + powershell, + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-File", + str(_INSTALL_PS1), + "-Manifest", + "-HermesHome", + str(tmp_path / "hermes-home"), + "-InstallDir", + str(install_dir), + ], + cwd=tmp_path, + capture_output=True, + text=True, + check=False, + timeout=45, + ) + + assert run.returncode == 0, run.stdout + run.stderr + manifest = json.loads(run.stdout) + stages = [stage["name"] for stage in manifest["stages"]] + assert ( + stages.index("repository") + < stages.index("python") + < stages.index("venv") + ) + assert not install_dir.exists(), "manifest lookup must remain read-only" + + def _run_venv_stage( powershell: str, tmp_path: Path,