#!/usr/bin/env python3 """The kernel runtime's hash-locked requirements (eukhe-runtime/requirements-kernel.txt). The kernel venv bootstrap installs this export of `eukhe-runtime/uv.lock` with `uv pip install --require-hashes --only-binary :all:`, then the runtime itself with `--no-deps --no-index --no-build-isolation`. This module is the offline gate over that file: every entry is an exact `name==version` pin carrying sha256 hashes (no editable, path, URL, or option lines that could point uv at an unpinned source), and — with `uv.lock` beside it — every pin and its hash set match the lock exactly. Usage: python3 scripts/release/runtime_lock.py check [--runtime-dir ] `make runtime-lock` regenerates the export (`uv lock` + `uv export`, the runtime's dependencies plus its `kernel` group, without the project itself); `make runtime-lock-check` re-exports with `--frozen` (offline), diffs, and runs this module's tests. """ from __future__ import annotations import argparse import re import sys from pathlib import Path RUNTIME_LOCK_FILE = "requirements-kernel.txt" _PIN_RE = re.compile(r"^(?P[A-Za-z0-9][A-Za-z0-9._-]*)==(?P[A-Za-z0-9.+!_-]+)$") _HASH_RE = re.compile(r"^--hash=sha256:(?P[0-9a-f]{64})$") class LockError(ValueError): """The requirements file is not a fully hash-pinned lock.""" def normalize_name(name: str) -> str: return re.sub(r"[-_.]+", "-", name).lower() def _logical_lines(text: str) -> list[str]: lines: list[str] = [] pending = "" for raw in text.splitlines(): stripped = raw.strip() if not pending and (not stripped or stripped.startswith("#")): continue if stripped.endswith("\\"): pending += stripped[:-1] + " " continue lines.append((pending + stripped).strip()) pending = "" if pending: lines.append(pending.strip()) return lines def parse_requirements(text: str) -> dict[tuple[str, str], frozenset[str]]: """Map each (normalized distribution name, exact version) to its sha256 digests — a distribution may appear once per marker-selected version — and raise LockError on any entry that is not an exact, hashed pin.""" pins: dict[tuple[str, str], frozenset[str]] = {} for line in _logical_lines(text): requirement, _, options = line.partition(" --") options = ("--" + options).split() if options else [] spec = requirement.split(";", 1)[0].strip() match = _PIN_RE.match(spec) if match is None: raise LockError(f"not an exact `name==version` pin: {line!r}") digests = set() for option in options: hashed = _HASH_RE.match(option) if hashed is None: raise LockError(f"unexpected option {option!r} in {line!r}") digests.add(hashed["digest"]) if not digests: raise LockError(f"pin without sha256 hashes: {spec!r}") key = (normalize_name(match["name"]), match["version"]) if key in pins: raise LockError(f"duplicate pin {key[0]}=={key[1]}") pins[key] = frozenset(digests) if not pins: raise LockError("no pinned requirements") return pins def read_runtime_lock(runtime_dir: Path) -> dict[tuple[str, str], frozenset[str]]: path = runtime_dir / RUNTIME_LOCK_FILE if not path.is_file(): raise LockError(f"{path} is missing (run `make runtime-lock`)") return parse_requirements(path.read_text(encoding="utf-8")) def check_against_uv_lock(runtime_dir: Path) -> None: """Every exported pin names a locked version and carries exactly the lock's artifact hashes for it.""" import tomllib # Python >= 3.11; only the source-tree gate needs it. pins = read_runtime_lock(runtime_dir) lock = tomllib.loads((runtime_dir / "uv.lock").read_text(encoding="utf-8")) locked: dict[tuple[str, str], frozenset[str]] = {} for package in lock.get("package", []): artifacts = list(package.get("wheels", [])) if "sdist" in package: artifacts.append(package["sdist"]) locked[(normalize_name(package["name"]), package["version"])] = frozenset( artifact["hash"].removeprefix("sha256:") for artifact in artifacts ) for (name, version), digests in sorted(pins.items()): expected = locked.get((name, version)) if expected is None: raise LockError(f"{name}=={version} is not in uv.lock") if digests != expected: raise LockError(f"{name}=={version} hashes differ from uv.lock") def main() -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("command", choices=["check"]) parser.add_argument( "--runtime-dir", type=Path, default=Path(__file__).resolve().parents[2] / "eukhe-runtime", ) args = parser.parse_args() try: check_against_uv_lock(args.runtime_dir) except LockError as error: print(f"runtime lock check failed: {error}", file=sys.stderr) return 1 print(f"{args.runtime_dir / RUNTIME_LOCK_FILE}: hash-pinned and matches uv.lock") return 0 if __name__ == "__main__": sys.exit(main())