:: run_setup.bat -- Python vs Windows
:: Project Home: https://github.com/mixmansoundude/Python_vs_Windows
:: Description: Automated, zero-config Python environment management for Windows. Standalone and portable.
:: [VERSION_METADATA]
:: Last Verified Date: 2026-06-19
:: Verified Windows: Windows 10/11 (CI runner: Windows Server 2025)
:: Verified PowerShell: 5.1+
:: Verified Python: 3.14.6 (CI Latest)
:: NOTE: Update 'Last Verified Date' only when 'Verified Python' version changes
::   or this entry is more than 6 months stale. Do NOT update the date on every commit.

# --- head (trimmed) ---

:: RECOVERY: If a future Python version breaks auto-detection, install a Python version matching the
::   'Last Verified' metadata above, then run 'set PVW_PYTHON_EXE=C:\path\to\python.exe' in your
::   terminal before running this script to bypass the broken detection.
:: ============================================================
:: FIRST-TIME WINDOWS USERS: SmartScreen / Security Warning
:: ============================================================
:: When you double-click this file, Windows may show:
::   "Windows protected your PC"
:: This is normal for any .bat file downloaded from the internet.
::
:: TO RUN:  Click "More info"  then  "Run anyway"
::
:: IF "Run anyway" is not shown:
::   1. Close this dialog
::   2. Right-click run_setup.bat in File Explorer
::   3. Click "Properties"
::   4. At the bottom, check the "Unblock" checkbox
::   5. Click "OK"
::   6. Double-click run_setup.bat again
:: ============================================================
:: FOR CONTRIBUTORS/EDITORS OF THIS FILE (not needed to just run it):
:: Do NOT edit this file using the GitHub web editor, "Edit in place", or
:: any Mac/Linux text tool -- these silently strip the CR half of this
:: file's required Windows (CRLF) line endings, corrupting it for every
:: Windows user who later downloads it. This has broken things before
:: (see docs/agent-closed-backlog.md's Item 44). Edit only from a real
:: Windows/CRLF checkout, and run
:: `python tools/check_crlf.py --fix run_setup.bat` before committing if
:: unsure -- also enforced by a gating CI check on every PR.
:: ============================================================
@echo off
setlocal DisableDelayedExpansion
rem ============================================================
rem LINE-ENDING SELF-CHECK -- keep this first, before any goto/call in
rem this file. Defense-in-depth, not a fix for a known-broken distribution
rem channel: this file's line endings are Windows ^(CRLF^) by construction
rem and enforced in CI ^(.gitattributes' "-text" for *.bat/*.cmd plus
rem tools/check_crlf.py -- a raw/blob download via GitHub's "Raw" button or
rem a raw.githubusercontent.com link is expected to serve genuine CRLF^),
rem but an old cached download from before that fix, or a copy re-saved by
rem an editor that does not preserve CRLF, can still corrupt a user's copy.
rem cmd.exe's goto/call label lookup can silently misbehave on an LF-only
rem copy of a file this size -- wrong-label errors, skipped blocks,
rem corrupted commands -- producing a confusing partial run instead of a
rem clear failure. This check must therefore be self-reliant ^(no goto/call
rem anywhere in this file, since that is exactly what an LF-only copy
rem breaks^) and must run before anything else.
rem ============================================================
rem HP_PREFLIGHT_STATUS is script-rooted via %~dp0 (not CWD-relative, and not the
rem later %STATUS_FILE%/:write_status machinery -- neither exists yet at this point
rem in the file, and :write_status itself is call-based, unsafe to invoke before the
rem line-ending check above has passed). Written directly, only on a preflight
rem failure below, so a stale "ok" status from an earlier successful run in this
rem same folder can never be misread as this run's result if this run's copy of
rem the file cannot even get past its own preflight.
set "HP_PREFLIGHT_STATUS=%~dp0~bootstrap.status.json"
where powershell >nul 2>&1
if defined HP_TEST_FORCE_NO_POWERSHELL (
  rem derived requirement: force a nonzero errorlevel here without touching PATH,
  rem so CI can deterministically exercise the branch below on a shared runner
  rem with no risk of actually breaking PowerShell resolution for it.
  cmd /c "exit /b 1"
)
if errorlevel 1 (
  echo ***
  echo *** [ERROR] PowerShell was not found on this machine.
  echo *** This script requires PowerShell to run at all; please
  echo *** repair or reinstall it, then run this script again.
  echo ***
  echo {"state":"error","exitCode":1,"pyFiles":0}> "%HP_PREFLIGHT_STATUS%"
  if not defined HP_CI_LANE ( pause )
  exit /b 1
)
if defined HP_TEST_FORCE_LF_ONLY (
  rem derived requirement: point the check at a synthetic LF-only file instead
  rem of this running copy, so CI can exercise the invalid-line-endings branch
  rem below without corrupting the file that is actually executing right now.
  set "HP_SELF_PATH=%~dp0~test_lf_only.bat"
) else if defined HP_TEST_FORCE_PS_CHECK_FAIL (
  rem derived requirement: point at a path that does not exist, so the real
  rem PowerShell command below genuinely throws and hits its own catch branch
  rem -- exercises the real failure path instead of faking an exit code
  rem externally.
  set "HP_SELF_PATH=%~dp0~test_missing_for_ps_check.bat"
) else (
  set "HP_SELF_PATH=%~f0"
)
powershell -NoProfile -Command "try{$c=[System.IO.File]::ReadAllText($env:HP_SELF_PATH);$crlf=-join @([char]13,[char]10);$lf=[string][char]10;$cr=[string][char]13;$norm=$c.Replace($crlf,'');if($c.Contains($crlf) -and -not $norm.Contains($lf) -and -not $norm.Contains($cr)){exit 0}else{exit 1}}catch{exit 2}" >nul 2>&1
if errorlevel 2 (
  echo ***
  echo *** [ERROR] PowerShell could not check the line endings of this file.
  echo *** This may mean PowerShell is restricted on this machine -- for
  echo *** example, by a Constrained Language Mode policy -- please repair
  echo *** or unblock PowerShell, then run this script again.
  echo ***
  echo {"state":"error","exitCode":2,"pyFiles":0}> "%HP_PREFLIGHT_STATUS%"
  if not defined HP_CI_LANE ( pause )
  exit /b 2
)
if errorlevel 1 (
  echo ***
  echo *** [ERROR] This copy of run_setup.bat has invalid line endings.
  echo *** Every line ending in this file must be Windows-style ^(CRLF^); at
  echo *** least one is not, and the file will fail with confusing, partial,
  echo *** hard-to-diagnose errors if run as-is.
  echo ***
  echo ***   Easiest fix: delete this copy and download run_setup.bat again --
  echo ***   the GitHub "Raw" link and the download page are both fine now.
  echo ***   This is usually an old cached copy, or one re-saved by an editor
  echo ***   that does not preserve Windows line endings.
  echo ***
  echo ***   Or open this file in an editor that shows line endings
  echo ***   ^(e.g. Notepad++, VS Code^) and convert it to Windows ^(CRLF^)
  echo ***   line endings, then save and run this script again.
  echo ***
  echo {"state":"error","exitCode":1,"pyFiles":0}> "%HP_PREFLIGHT_STATUS%"
  if not defined HP_CI_LANE ( pause )
  exit /b 1
)
set "DEP_SOURCE=unknown"
rem [REQ-026] Argv passthrough escape hatch (docs/plan-cli-interactive-verification.md P1):
rem capture trailing arguments (%2-%9) here, before anything else touches %1-%9, and forward
rem them verbatim to the target program at every real launch site (EXE smoke, EXE fast-path
rem reuse, interpreter run, checkpoint's elective second run). Deliberately does NOT use `shift`
rem -- %~1 (the entry file) is read directly via "%~1" at multiple later points in this file
rem (the UNC check below, both :determine_entry call sites), and shifting here would silently
rem change what those later "%~1" reads see. Practical limit of 8 extra args (%2-%9): CMD has
rem no way to read beyond %9 without `shift`, and `shift` is off-limits for the reason above.
rem Each present token is individually re-quoted (not just %~2 raw) so a token containing spaces
rem survives as ONE argv element to the target program, matching ordinary Windows command-line
rem quoting; a token containing a literal " is not supported (documented limitation, not silently
rem mishandled -- see README [REQ-026]).
set "HP_APP_ARGS="
if not "%~2"=="" set "HP_APP_ARGS=%HP_APP_ARGS% "%~2""
if not "%~3"=="" set "HP_APP_ARGS=%HP_APP_ARGS% "%~3""
if not "%~4"=="" set "HP_APP_ARGS=%HP_APP_ARGS% "%~4""
if not "%~5"=="" set "HP_APP_ARGS=%HP_APP_ARGS% "%~5""
if not "%~6"=="" set "HP_APP_ARGS=%HP_APP_ARGS% "%~6""
if not "%~7"=="" set "HP_APP_ARGS=%HP_APP_ARGS% "%~7""
if not "%~8"=="" set "HP_APP_ARGS=%HP_APP_ARGS% "%~8""
if not "%~9"=="" set "HP_APP_ARGS=%HP_APP_ARGS% "%~9""
rem Boot strap renamed to run_setup.bat
set "HP_SCRIPT_LAUNCH_DIR=%~dp0"
if "%HP_SCRIPT_LAUNCH_DIR:~0,2%"=="\\" (
  rem derived requirement: parentheses must be escaped inside IF blocks in CMD or parsing breaks.
  echo *** WARNING: UNC/network paths detected ^(\\server\share^).
  echo *** This script may fail in this environment.
  echo *** Recommended: Map the network path to a drive letter and re-run.
  echo ***
)
if not exist "%~dp0" (
  echo [ERROR] Workspace path invalid: %~dp0
  exit /b 1
)
cd /d "%~dp0"
if errorlevel 1 (
  echo [ERROR] Workspace path invalid: %~dp0
  exit /b 1
)
rem derived requirement: writable-CWD preflight (CLAUDE.md Item 48) -- this is the first
rem point in the file safe to attempt a real write (CWD is now confirmed to be the app
rem folder itself, via the cd /d above), and it runs before every other write in this file
rem (the CI marker, ~setup.log, and :merge_git_config's own .gitignore/.gitattributes
rem writes) so an unwritable folder fails with one clear, named message instead of a
rem confusing cascade of silently-swallowed write failures later.
rem derived requirement: clear any pre-existing ~wtest.tmp before probing -- a crash between
rem a past successful probe and its own cleanup (or, unlikely given the tilde convention, a
rem coincidentally-named leftover) must not let a stale entry at this exact path masquerade as
rem this run's own successful write. del only removes a FILE at this path; if a directory of
rem the same name exists instead (however unlikely), del is a silent no-op against it, so rd
rem /s /q runs too whenever the entry is still present afterward -- between the two, either
rem shape of stale leftover is cleared before the real probe below ever runs.
del /f /q "~wtest.tmp" >nul 2>&1
if exist "~wtest.tmp" rd /s /q "~wtest.tmp" >nul 2>&1
if defined HP_TEST_FORCE_CWD_NOT_WRITABLE (
  rem derived requirement: force the not-writable branch deterministically for CI, without
  rem actually revoking filesystem permissions on a shared runner -- skip the real write
  rem attempt entirely so ~wtest.tmp is never created, the same signal a genuine write
  rem failure would produce. Intentionally empty otherwise: doing nothing IS the forcing
  rem mechanism here.
) else (
  type nul > "~wtest.tmp" 2>nul
)
if not exist "~wtest.tmp" (
  echo ***
  echo *** [ERROR] This folder does not appear to be writable: "%CD%"
  echo *** This script needs to create files here -- logs, a dependency cache, and
  echo *** eventually a standalone program. Move this script and your .py files to
  echo *** a folder you have write access to, then run it again.
  echo ***
  echo {"state":"error","exitCode":1,"pyFiles":0}> "%HP_PREFLIGHT_STATUS%"
  if not defined HP_CI_LANE ( pause )
  exit /b 1
)
del /f /q "~wtest.tmp" >nul 2>&1
rem derived requirement: PowerShell capability preflight (CLAUDE.md Item 47) -- placed after
rem the CWD-writable check above so a real folder-permission failure is diagnosed by that
rem check first, not misattributed to this one. Bare PowerShell presence, checked earlier in
rem the line-ending self-check, only proves powershell.exe exists on PATH, not that it can do
rem what this bootstrapper actually needs: :emit_from_base64, used to write every embedded
rem ~*.py/~*.ps1 helper, needs Convert.FromBase64String plus IO.File.WriteAllBytes, and the
rem failfast-probe/exe-smokerun helpers need System.Diagnostics.ProcessStartInfo -- all of
rem which a locked-down corporate image, such as AppLocker, WDAC, or Constrained Language
rem Mode, can block even with PowerShell itself present. Probing all three together here turns
rem five-plus later opaque "Could not write ~x" failures into one clear, named diagnostic.
rem derived requirement: %RANDOM% suffix (two draws) makes the probe path unique per process --
rem this preflight runs before :acquire_lock below, so two genuinely concurrent instances in the
rem same folder must not be able to race on (delete/overwrite) each other's probe file and
rem misreport a capability failure that isn't real. Not a full mutex, just enough entropy that a
rem same-folder collision within one preflight's brief lifetime is negligible.
set "HP_PS_PROBE_NAME=~ps_capability_probe.%RANDOM%%RANDOM%.tmp"
set "HP_PS_PROBE_FILE=%~dp0%HP_PS_PROBE_NAME%"
if defined HP_TEST_FORCE_PS_CAPABILITY_FAIL (
  rem derived requirement: point the probe at a nonexistent directory so the real
  rem WriteAllBytes call genuinely throws and hits its own catch branch -- exercises the
  rem real failure path instead of faking an exit code externally, matching
  rem HP_TEST_FORCE_PS_CHECK_FAIL's established technique above.
  set "HP_PS_PROBE_FILE=%~dp0~nonexistent_dir_xyz\%HP_PS_PROBE_NAME%"
)
del /f /q "%HP_PS_PROBE_FILE%" >nul 2>&1
if exist "%HP_PS_PROBE_FILE%" rd /s /q "%HP_PS_PROBE_FILE%" >nul 2>&1
rem derived requirement: [IO.File]::Exists/Delete, not Test-Path/Remove-Item -- the latter two
rem PowerShell cmdlets treat "[" and "]" in a path as wildcard syntax by default, so a folder
rem name containing literal brackets could make Test-Path report a genuinely-written file as
rem missing, misreporting a capability failure that isn't real. The .NET IO.File methods used
rem here never glob-expand, so the app folder's own path can contain any legal Windows filename
rem character without affecting this probe's outcome.
powershell -NoProfile -Command "try{$b=[Convert]::FromBase64String('cHZ3');[IO.File]::WriteAllBytes($env:HP_PS_PROBE_FILE,$b);if(-not [IO.File]::Exists($env:HP_PS_PROBE_FILE)){exit 1};[IO.File]::Delete($env:HP_PS_PROBE_FILE);$psi=New-Object System.Diagnostics.ProcessStartInfo;exit 0}catch{exit 1}" >nul 2>&1
if errorlevel 1 (
  echo ***
  echo *** [ERROR] PowerShell on this machine cannot perform an operation this
  echo *** script needs -- decoding embedded data, writing files, or preparing
  echo *** to launch a process. This is usually caused by a restrictive policy
  echo *** such as Constrained Language Mode, AppLocker, or WDAC on a managed
  echo *** or corporate machine. Ask an administrator to allow PowerShell Full
  echo *** Language Mode for this script, or run it on an unrestricted machine.
  echo ***
  echo {"state":"error","exitCode":1,"pyFiles":0}> "%HP_PREFLIGHT_STATUS%"
  del /f /q "%HP_PS_PROBE_FILE%" >nul 2>&1
  if exist "%HP_PS_PROBE_FILE%" rd /s /q "%HP_PS_PROBE_FILE%" >nul 2>&1
  if not defined HP_CI_LANE ( pause )
  exit /b 1
)
del /f /q "%HP_PS_PROBE_FILE%" >nul 2>&1
if exist "%HP_PS_PROBE_FILE%" rd /s /q "%HP_PS_PROBE_FILE%" >nul 2>&1
set "HP_SCRIPT_ROOT=%~dp0"
for %%R in ("%HP_SCRIPT_ROOT%") do set "HP_SCRIPT_ROOT=%%~fR"
if not "%HP_SCRIPT_ROOT:~-1%"=="\" set "HP_SCRIPT_ROOT=%HP_SCRIPT_ROOT%\"
set "HP_CI_MARKER=.ci_bootstrap_marker"
type nul > "%HP_CI_MARKER%" 2>nul
set "LOG=~setup.log"
set "LOGPREV=~setup.prev.log"
set "STATUS_FILE=~bootstrap.status.json"
if not exist "%LOG%" (type nul > "%LOG%")
rem derived requirement (real CI failure, PR #455): :merge_git_config now needs an HP_*
rem payload (HP_MIGRATE_GITATTRIBUTES, Item 60) at its own :emit_from_base64 call site, so
rem :define_helper_payloads must run BEFORE this call, not at its old position ~300 lines
rem later -- see the paired removal below, near the old ":rotate_log"/"CI fast path" site.
call :define_helper_payloads
call :merge_git_config
if "%HP_TEST_FORCE_CONNECTIVITY_CHECK%"=="1" call :check_net_after_dl_fail
if "%HP_TEST_FORCE_CONSENT_CHECK%"=="1" (
  call :system_python_consent_gate
  if errorlevel 1 (
    call :log "[INFO] REQ-014: Consent gate test: user declined."
    exit /b 1
  )
  call :log "[INFO] REQ-014: Consent gate test: user accepted."
)
rem --- PVW_ super-user overrides (inherit from calling terminal; logged before detection runs) ---
rem derived requirement: PVW_ variables let a super-user pre-set values to bypass auto-detection.
rem Single-line if form avoids parse-time expansion issues in block-form if-statements when a
rem variable value contains parentheses, such as a path under "C:\Program Files (x86)\...".
if defined PVW_PYTHON_EXE call :log "[DEBUG] Using super-user override for PVW_PYTHON_EXE: %PVW_PYTHON_EXE%"
if defined PVW_UV_EXE call :log "[DEBUG] Using super-user override for PVW_UV_EXE: %PVW_UV_EXE%"
if defined PVW_CONDA_EXE call :log "[DEBUG] Using super-user override for PVW_CONDA_EXE: %PVW_CONDA_EXE%"
if defined PVW_TARGET_PY call :log "[DEBUG] Using super-user override for PVW_TARGET_PY: %PVW_TARGET_PY%"
if defined PVW_WORKSPACE call :log "[DEBUG] Using super-user override for PVW_WORKSPACE: %PVW_WORKSPACE%"
rem --- Path-length guard: warn if script root path approaches the 260-char cmd.exe limit ---
for /f "usebackq delims=" %%L in (`powershell -NoProfile -ExecutionPolicy Bypass -Command "$env:HP_SCRIPT_ROOT.Length" 2^>nul`) do set "HP_PATH_LEN=%%L"
if defined HP_PATH_LEN if %HP_PATH_LEN% GEQ 200 (
  echo *** WARNING: Script path is %HP_PATH_LEN% chars. Paths near 260 chars may cause cmd.exe failures.
  call :log "[WARN] Script path is %HP_PATH_LEN% chars; paths near 260 chars may cause failures."
)
set "HP_PATH_LEN="
rem --- Synced-folder guard: warn when running from a OneDrive or similar sync folder ---
if /I not "%HP_SCRIPT_ROOT:OneDrive=%"=="%HP_SCRIPT_ROOT%" (
  echo *** WARNING: Script appears to be in a OneDrive folder. File locking may cause failures.
  call :log "[WARN] OneDrive path detected; file locking may cause failures."
)

# --- trimmed ---

rem panel above, instead of staying purely generic. Three wording buckets, not one -- a review
rem pass on PR #414 (see docs/agent-interconnect.md's DLL-bundling section) found detection and
rem repair are NOT the same event, so the wording must not claim more than each state actually
rem means: "skipped" states never attempted a fix; "failed"/"exhausted" states attempted but did
rem not fully resolve it; "repaired" means the DLL itself was fixed (this caveat firing anyway
rem means something ELSE is still wrong -- HP_DLL_REPAIRED can trigger a bounded follow-up
rem :hidden_import_recover pass that still leaves HP_EXE_EXIT non-zero for an unrelated reason,
rem so this is rare but real, not dead code). goto-based dispatch (not a parenthesized block) so
rem each %VAR% reads its runtime value -- see docs/agent-lessons-learned.md's "Provider-cascade
rem dispatch is goto-based on purpose". HP_DLL_DETECTED_SAFE was already sanitized (ampersand,
rem pipe, angle brackets, percent, and caret all stripped) for :log's own unquoted echo, so it
rem is equally safe on this panel's plain echo.
if "%HP_DLL_HINT_STATE%"=="repaired" goto :pfb_dll_hint_repaired
if "%HP_DLL_HINT_STATE%"=="failed_rebuild" goto :pfb_dll_hint_failed
if "%HP_DLL_HINT_STATE%"=="failed_missing_exe" goto :pfb_dll_hint_failed
if "%HP_DLL_HINT_STATE%"=="exhausted" goto :pfb_dll_hint_failed
goto :pfb_dll_hint_skipped
:pfb_dll_hint_skipped
rem Covers skipped_nuitka / skipped_non_conda / unlocatable: detection happened, but no repair
rem was ever attempted for it -- must NOT claim "we tried to fix it automatically."
echo.
echo  NOTE: a missing native library (%HP_DLL_DETECTED_SAFE%) was detected while
echo  packaging your app, but no automatic repair for it ran during this build.
echo  This may or may not be related to the caveat above.
goto :pfb_dll_hint_done
:pfb_dll_hint_failed
echo.
echo  NOTE: a missing native library (%HP_DLL_DETECTED_SAFE%) was detected while
echo  packaging your app, and an automatic repair was attempted but did not fully
echo  resolve it. This may be related to the caveat above.
goto :pfb_dll_hint_done
:pfb_dll_hint_repaired
echo.
echo  NOTE: a missing native library (%HP_DLL_DETECTED_SAFE%) was detected while
echo  packaging your app and was automatically repaired; it is likely not the
echo  cause of the caveat above.
:pfb_dll_hint_done
exit /b 0

:pfb_gui_hint
rem CLAUDE.md Active Backlog Item 41: the ~30s verification kill only fires when ZERO
rem stdout/stderr bytes were observed before the deadline (~exe_smokerun.ps1's own $sawOutput
rem gate, see docs/agent-interconnect.md "Activity-aware EXE-smoke kill") -- exactly the shape
rem of a correctly-behaving GUI app (tkinter, PyQt) with a mainloop and no console output, not
rem just a genuinely hung program. Distinguish the two in the panel text itself, not just the
rem pre-launch warning (:warn_user_code_launch already discloses the kill beforehand, but that
rem disclosure and this panel are both worded for a console-program mental model otherwise).
echo.
echo  NOTE: this can happen for a GUI app, e.g. tkinter or PyQt, that opens its
echo  own window and prints nothing to the console -- that produces the exact
echo  same silence as a genuinely hung program, so we cannot tell them apart
echo  automatically. If a window appeared and worked normally, this caveat
echo  does not necessarily mean anything is wrong.
exit /b 0

:print_no_exe_briefing
rem docs/open-questions.md item 1: when PyInstaller AND the Nuitka fallback both fail outright
rem (no dist\%ENVNAME%.exe at all), the run still succeeds via the interpreter fallback and the
rem final [STATUS] line reads identically to a real EXE success, with the only prior signal
rem being one [ERROR] line several seconds earlier. This panel closes that gap -- purely
rem additive, does not touch success/failure semantics (~bootstrap.status.json already
rem correctly records state=error via :die's own HP_BOOTSTRAP_STATE=error).
rem [REQ-027] P2 honest messaging: the header text below used to unconditionally claim "your
rem code ran successfully" regardless of whether the interpreter run (:verify_no_exe_interpreter,
rem which always runs immediately before this panel on the no-EXE path) actually exited 0 -- a
rem real, pre-existing dishonest claim, confirmed by tracing that HP_NOEXE_VERIFY_FAILED is the
rem ONLY signal this panel previously ignored. Branches on it now, mirroring
rem :print_postflight_briefing's own :pfb_caveat dispatch shape.
echo.
echo ============================================================
if defined HP_NOEXE_VERIFY_FAILED goto :noexe_caveat
echo  YOUR CODE RAN -- BUT NO STANDALONE .EXE WAS PRODUCED
echo ============================================================
echo  We could not package your app into a double-clickable .exe
echo  (see the ERROR message above for why), but your code ran
echo  successfully just now using the prepared Python environment.
echo  Your environment and dependencies ARE installed correctly.
goto :noexe_runapp
:noexe_caveat
echo  NO STANDALONE .EXE -- AND WE CAN'T CONFIRM YOUR CODE RAN CLEANLY
echo ============================================================
echo  We could not package your app into a double-clickable .exe
echo  (see the ERROR message above for why). We also just ran it
echo  directly via the prepared Python environment, and it exited
echo  with an error (see the [STATUS] line above) -- so we can't
echo  tell whether that's a bug in the Python code we tried to run
echo  or something this bootstrapper missed. Your environment and
echo  dependencies ARE still installed correctly; run it yourself
echo  below to see the full output.
:noexe_runapp
echo.
echo  RUNNING YOUR APP (without an .exe) -- the most direct option
echo    "%HP_PY%" "%HP_ENTRY%"
echo    Need launch arguments? Add them directly after that command, e.g.
echo      "%HP_PY%" "%HP_ENTRY%" --input file.csv
echo.
if defined HP_NOEXE_VERIFY_FAILED (
  echo  Want to try different arguments through the bootstrapper itself
  echo  instead? Your already-installed environment is reused either
  echo  way; it will just attempt the .exe build again too:
  echo    run_setup.bat "%HP_ENTRY%" arg1 arg2
  echo.
)
echo  KEEP these files with your project:
echo    requirements.txt  -- packages your app depends on
echo    runtime.txt       -- Python version pin
echo.
echo  SAFE TO DELETE to reclaim disk space:
echo    .*_env\ folders   -- environment directories
echo    ~* files          -- tilde-prefix work files (e.g. ~setup.log)
echo ============================================================
echo.
if defined HP_NOEXE_VERIFY_FAILED (
  call :log "[WARN] REQ-016: Post-flight briefing printed; no EXE produced, interpreter run also unverified."
) else (
  call :log "[WARN] REQ-016: Post-flight briefing printed; no EXE produced, advised direct interpreter run."
)
exit /b 0

:print_fastpath_ambiguous_note
rem [REQ-027] P2 honest messaging (docs/plan-cli-interactive-verification.md): the cached-EXE
rem fast path is deliberately zero-friction for PROMPTS (never a consent gate, per
rem docs/agent-interconnect.md's "Fast path = ZERO friction" design requirement) -- this is a
rem plain informational print, not a question, so it does not violate that requirement. Fires
rem only when the fail-fast probe classified the reused EXE as alive/healthy (so it was kept,
rem not discarded+rebuilt) and it later exited non-zero -- see :try_fast_exe. Whether that
rem non-zero exit means a real bug in the user's own code, an unresolved dependency, or
rem something else entirely is NOT something this bootstrapper can determine (Open Question 3,
rem same plan doc) -- this note is deliberately honest about that limit, not a diagnosis.
echo.
echo ============================================================
echo  SETUP COMPLETE -- BUT WE CAN'T CONFIRM YOUR LAST RUN WORKED
echo ============================================================
echo  Your existing standalone application was reused (dist\%ENVNAME%.exe),
echo  and it exited with an error just now (see the [STATUS] line
echo  above) -- so we can't tell whether that's a bug in the Python
echo  code we tried to run, or something else. Your environment and
echo  dependencies ARE still installed correctly.
echo.
echo  RUNNING YOUR APP
echo    Double-click dist\%ENVNAME%.exe to run it, or run it from a
echo    Command Prompt to see the full output.
echo.
echo  WANT TO TRY AGAIN? You do not have to start over from scratch --
echo    just run this bootstrapper again the same way you did before;
echo    your already-installed environment and built .exe are reused.
echo.
echo  WANT A FRESH BUILD instead (re-checks all dependencies from scratch)?
echo    Delete dist\%ENVNAME%.exe and run this bootstrapper again.
echo ============================================================
echo.
call :log "[WARN] REQ-016: Post-flight note printed; cached EXE kept despite a non-zero exit after the fail-fast probe."
exit /b 0

:check_net_after_dl_fail
rem REQ-013: called after a primary download fails. Pings 8.8.8.8 to distinguish
rem no-internet (Scenario A) from specific-URL-failed (Scenario B).
rem HP_TEST_OFFLINE=1 simulates ping failure for CI branch coverage.
rem derived requirement: both the ping and curl reachability checks retry once (2 total
rem attempts each) before concluding "no internet" -- a single dropped ICMP echo or a
rem momentarily-contended curl connect on a busy shared CI runner is enough to misclassify a
rem genuinely-online host as offline (root-caused from a real self.ux.connectivity.online CI
rem flake). Mirrors the REQ-022 detect-transient-then-retry-once idiom used elsewhere in this
rem file (:try_conda_create/:conda_bulk_install). The counter-based loop below is safe because
rem these lines are top-level (not nested in a parenthesized block); the "Y" retry branch
rem further down uses literal duplication instead of a counter, since it IS nested in a block
rem and a counter set+read inside the same block would hit CMD's parse-time %VAR% expansion
rem trap (see docs/agent-lessons-learned.md).
if "%HP_TEST_OFFLINE%"=="1" (
  call :log "[TEST] HP_TEST_OFFLINE: simulating ping failure for REQ-013."
  goto :cndf_ping_failed
)
set "HP_CONN_PING_ATTEMPT=0"
:cndf_ping_retry
set /a HP_CONN_PING_ATTEMPT+=1
ping -n 1 8.8.8.8 >nul 2>&1
if not errorlevel 1 (
  call :log "[INFO] REQ-013: Connectivity check: internet reachable. Cascading to fallback."
  exit /b 0
)
if %HP_CONN_PING_ATTEMPT% LSS 2 goto :cndf_ping_retry
rem ICMP may be blocked on corporate networks; try HTTPS as secondary reachability check.
set "HP_CONN_CURL_ATTEMPT=0"
:cndf_curl_retry
set /a HP_CONN_CURL_ATTEMPT+=1
curl -s --connect-timeout 5 --max-time 8 -o nul "https://conda.anaconda.org" >nul 2>&1
if not errorlevel 1 (
  call :log "[INFO] REQ-013: Connectivity check: internet reachable via HTTPS (ICMP blocked). Cascading to fallback."
  exit /b 0
)
if %HP_CONN_CURL_ATTEMPT% LSS 2 goto :cndf_curl_retry
:cndf_ping_failed
call :log "[WARN] REQ-013: Connectivity check: no internet detected (ICMP and HTTPS check failed)."
:cndf_prompt_loop
rem CLAUDE.md Active Backlog Item 50: this prompt previously had no CI-safe auto-decline at
rem all, unlike every sibling consent gate -- against a genuinely open, interactive console
rem with nobody present to answer, it would block on set /p indefinitely. Fixed with
rem HP_TEST_CNDF_ANSWER (deterministic test override) and NOINPUT/HP_NONINTERACTIVE
rem auto-decline (the same authoritative "no human will answer" signals :run_postexec_
rem checkpoint already treats this way -- see docs/agent-interconnect.md).
rem
rem Deliberately does NOT auto-decline on bare HP_CI_LANE, unlike most sibling gates: two
rem existing regression tests (self.ux.connectivity.offline.n/.prompt.shown/.retry in
rem selfapps_ux_hardening.ps1) already run this exact gate under HP_CI_LANE=test while
rem piping real Y/N answers via stdin, specifically to exercise the interactive Y-retry
rem loop -- a static HP_CI_LANE auto-decline would silently bypass set /p and break that
rem coverage. A genuinely detached CI job (closed/EOF stdin) already resolves safely via the
rem pre-existing empty-input default below regardless of HP_CI_LANE; NOINPUT/HP_NONINTERACTIVE
rem are the correct signal for "deliberately unattended," matching this repo's own convention
rem that those two flags -- not HP_CI_LANE -- represent an explicit non-interactivity
rem declaration (see docs/agent-lessons-learned.md's "Env-var flags are scaffolding" entry).
echo WARNING: No internet connection detected. Remote providers may fail. Retry? (Fix connection then press Y) or proceed offline (N):
set "HP_CONN_CHOICE="
if defined HP_TEST_CNDF_ANSWER (
  set "HP_CONN_CHOICE=%HP_TEST_CNDF_ANSWER%"
) else if defined NOINPUT (
  set "HP_CONN_CHOICE=n"
) else if defined HP_NONINTERACTIVE (
  set "HP_CONN_CHOICE=n"
) else (
  set /p "HP_CONN_CHOICE=Your choice [y/n]: "
)
if "%HP_CONN_CHOICE:~0,1%"=="" (
  call :log "[INFO] REQ-013: Connectivity prompt: empty input; defaulting offline."
  set "HP_OFFLINE_MODE=1"
  exit /b 1
)
if /I "%HP_CONN_CHOICE:~0,1%"=="y" (
  if "%HP_TEST_OFFLINE%"=="1" (
    call :log "[TEST] HP_TEST_OFFLINE: Y selected; still simulating offline."
    goto :cndf_ping_failed
  )
  rem derived requirement: 2 literal attempts each, not a counter var -- this whole branch is
  rem nested inside the "y" parenthesized block, and a counter set+read inside the same block
  rem would be frozen at the block's pre-execution value by CMD's parse-time %VAR% expansion,
  rem see docs/agent-lessons-learned.md; literal duplication has no such variable to freeze.
  ping -n 1 8.8.8.8 >nul 2>&1
  if not errorlevel 1 (
    call :log "[INFO] REQ-013: Connectivity restored after retry."
    exit /b 0
  )
  ping -n 1 8.8.8.8 >nul 2>&1
  if not errorlevel 1 (
    call :log "[INFO] REQ-013: Connectivity restored after retry."
    exit /b 0
  )
  curl -s --connect-timeout 5 --max-time 8 -o nul "https://conda.anaconda.org" >nul 2>&1
  if not errorlevel 1 (
    call :log "[INFO] REQ-013: Connectivity restored after retry (HTTPS, ICMP blocked)."
    exit /b 0
  )
  curl -s --connect-timeout 5 --max-time 8 -o nul "https://conda.anaconda.org" >nul 2>&1
  if not errorlevel 1 (
    call :log "[INFO] REQ-013: Connectivity restored after retry (HTTPS, ICMP blocked)."
    exit /b 0
  )
  call :log "[INFO] REQ-013: Still offline after Y; re-prompting."
  goto :cndf_prompt_loop
)
call :log "[INFO] REQ-013: Connectivity prompt: user chose offline (N)."
set "HP_OFFLINE_MODE=1"
exit /b 1

:system_python_consent_gate
rem REQ-014: halt and require explicit consent before using global system Python.
rem CI-safe (mirrors :cascade_consent_gate): HP_TEST_SYSCON_ANSWER (Y/N) overrides; otherwise
rem HP_CI_LANE auto-declines with no prompt (no set /p hang in CI); interactive users get a
rem y/n prompt. The prompt string is echoed unconditionally so it appears even on the
rem auto-decline path. exit 0 = accepted, exit 1 = declined.
echo.
echo *** WARNING: System Python Execution ***
echo *** Using global system Python may pollute shared packages. ***
echo.
echo Proceed with System Python? (Global pollution risk) [y/n]: y to accept, n to decline.
set "HP_SYSCON_CHOICE="
if defined HP_TEST_SYSCON_ANSWER (
  set "HP_SYSCON_CHOICE=%HP_TEST_SYSCON_ANSWER%"
) else if defined HP_CI_LANE (
  set "HP_SYSCON_CHOICE=n"
) else (
  set /p "HP_SYSCON_CHOICE=Your choice [y/n]: "
)
if "%HP_SYSCON_CHOICE:~0,1%"=="" (
  call :log "[INFO] REQ-014: System Python consent: empty input; declining."
  exit /b 1
)
if /I "%HP_SYSCON_CHOICE:~0,1%"=="y" (
  call :log "[INFO] REQ-014: System Python consent: user accepted."
  exit /b 0
)
call :log "[INFO] REQ-014: System Python consent: user declined."
exit /b 1

:hp_test_conda_fail
call :log "[TEST] HP_TEST_FORCE_CONDA_FAIL: simulating conda env creation failure."
set "HP_ENV_READY="
call :handle_conda_failure "[TEST] conda env create forced to fail."
if defined HP_ENV_READY goto :after_env_mode_selection
call :die "[ERROR] conda env create failed."
goto :after_env_mode_selection