fix(setup): preserve exact bootstrap pin values
Read quoted lock fields directly instead of stripping delimiters with an empty-matching expression. Bound target selection to the uv row so missing targets cannot select unrelated artifacts. Preserve registry arguments in Git Bash when detecting Windows ARM64. Real cold-script tests intercept only the download boundary. They verify the exact native pin and refuse a missing target before download. A deliberate digest mismatch stops before any downloaded tool executes. No full dependency installation or user-state change was performed.
This commit is contained in:
@@ -52,7 +52,7 @@ esac
|
||||
if [ "$os" = win32 ]; then
|
||||
# PROCESSOR_ARCHITECTURE lies under an emulated shell (x64 msys on a
|
||||
# WoA box reports AMD64); the registry carries the machine's truth.
|
||||
winarch="$(reg.exe query 'HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment' /v PROCESSOR_ARCHITECTURE 2>/dev/null | tr -d '\r' | awk '/PROCESSOR_ARCHITECTURE/ {print $NF}')"
|
||||
winarch="$(MSYS2_ARG_CONV_EXCL='*' reg.exe query 'HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment' /v PROCESSOR_ARCHITECTURE 2>/dev/null | tr -d '\r' | awk '/PROCESSOR_ARCHITECTURE/ {print $NF}')"
|
||||
case "${winarch:-${PROCESSOR_ARCHITECTURE:-}}" in
|
||||
ARM64) arch=arm64 ;;
|
||||
*) arch=x64 ;;
|
||||
@@ -69,19 +69,20 @@ target="$os-$arch"
|
||||
# lock.json is machine-written (sorted keys, 2-space indent): read the uv
|
||||
# pin's version + this target's url/sha256 with awk — no python yet.
|
||||
pin() { # $1 = field (url | sha256)
|
||||
awk -v target="$target" -v field="$1" '
|
||||
awk -F '"' -v target="$target" -v field="$1" '
|
||||
/^ "uv": \{/ { in_uv = 1 }
|
||||
in_uv && $0 ~ "^ \"" target "\": \\{" { in_t = 1 }
|
||||
in_t && $0 ~ "^ \"" field "\":" {
|
||||
gsub(/.*: "|,?$/, ""); print; exit
|
||||
}' "$lock"
|
||||
in_uv && /^ }/ { exit }
|
||||
in_uv && /^ "/ { in_t = ($2 == target) }
|
||||
in_t && $2 == field { print $4; exit }' "$lock"
|
||||
}
|
||||
uv_version="$(awk '
|
||||
uv_version="$(awk -F '"' '
|
||||
/^ "uv": \{/ { in_uv = 1 }
|
||||
in_uv && /^ "version":/ { gsub(/.*: "|,?$/, ""); print; exit }' "$lock")"
|
||||
py_version="$(awk '
|
||||
in_uv && /^ }/ { exit }
|
||||
in_uv && $2 == "version" { print $4; exit }' "$lock")"
|
||||
py_version="$(awk -F '"' '
|
||||
/^ "python": \{/ { in_py = 1 }
|
||||
in_py && /^ "version":/ { gsub(/.*: "|"$|",$/, ""); print; exit }' "$lock" \
|
||||
in_py && /^ }/ { exit }
|
||||
in_py && $2 == "version" { print $4; exit }' "$lock" \
|
||||
| cut -d+ -f1 | cut -d. -f1,2)"
|
||||
[ -n "$uv_version" ] || { echo -e "${RED}✗${NC} no uv pin in pm/lock.json" >&2; exit 1; }
|
||||
|
||||
|
||||
58
tests/hermes_cli/test_setup_pin_values.py
Normal file
58
tests/hermes_cli/test_setup_pin_values.py
Normal file
@@ -0,0 +1,58 @@
|
||||
"""Cold setup consumes lock values before executing any downloaded tool."""
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
import pytest
|
||||
|
||||
from pm.store import current_target
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
|
||||
|
||||
@pytest.mark.platforms("windows", "posix")
|
||||
@pytest.mark.parametrize("missing_target", [False, True])
|
||||
def test_cold_setup_uses_exact_lock_values(tmp_path, missing_target):
|
||||
checkout = tmp_path / "checkout"
|
||||
(checkout / "pm").mkdir(parents=True)
|
||||
shutil.copy2(ROOT / "setup-hermes.sh", checkout / "setup-hermes.sh")
|
||||
lock = json.loads((ROOT / "pm" / "lock.json").read_text(encoding="utf-8"))
|
||||
target = current_target()
|
||||
uv_pin = lock["packages"]["uv"]
|
||||
artifact = uv_pin["artifacts"][target]
|
||||
if missing_target:
|
||||
del uv_pin["artifacts"][target]
|
||||
(checkout / "pm" / "lock.json").write_text(json.dumps(lock, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
||||
corrupt = tmp_path / "corrupt-download"
|
||||
corrupt.write_bytes(b"intentional digest mismatch; this must never be executed\n")
|
||||
args = tmp_path / "curl-args"
|
||||
hook = tmp_path / "transport.sh"
|
||||
hook.write_text('''curl() {
|
||||
printf '%s\\n' "$@" > "$PROBE_ARGS"
|
||||
while [ "$#" -gt 0 ]; do
|
||||
if [ "$1" = -o ]; then
|
||||
cp "$PROBE_DOWNLOAD" "$2"
|
||||
return
|
||||
fi
|
||||
shift
|
||||
done
|
||||
return 37
|
||||
}
|
||||
''', encoding="utf-8")
|
||||
env = dict(os.environ, HOME=tmp_path.as_posix(), HERMES_HOME=(tmp_path / "home").as_posix(),
|
||||
HERMES_RUNTIME_DIR=(tmp_path / "tools").as_posix(), BASH_ENV=hook.as_posix(),
|
||||
PROBE_ARGS=args.as_posix(), PROBE_DOWNLOAD=corrupt.as_posix())
|
||||
result = subprocess.run(["bash", str(checkout / "setup-hermes.sh")], cwd=tmp_path,
|
||||
env=env, capture_output=True, text=True, encoding="utf-8", timeout=30)
|
||||
assert result.returncode != 0, result.stdout + result.stderr
|
||||
if missing_target:
|
||||
assert not args.exists(), "a missing target must not select a sibling artifact"
|
||||
assert f"no uv artifact for {target}" in result.stderr
|
||||
else:
|
||||
assert args.read_text(encoding="utf-8").splitlines()[-1] == artifact["url"]
|
||||
assert f"pinned uv {uv_pin['version']} ({target})" in result.stdout
|
||||
assert f"pinned {artifact['sha256']})" in result.stderr
|
||||
assert not (checkout / ".env").exists()
|
||||
Reference in New Issue
Block a user