"""Canonical runtime pin identity for the recipe fingerprint (DGR-025). The recipe digest (:mod:`meshnet_node.runtime_recipe`) commits to a ``runtime_version`` axis, but a string the operator typed is a label, not a pin: two workers could run different patch stacks under the same label and still hash to the same recipe. The DGR-027 lock manifest (``packages/node/native/llama``) already records the one exact upstream commit and the ordered patch stack the native runtime is built from, so the axis value is *derived* from that manifest, never asserted. The derived value has three load-bearing parts, and each is separately fatal to compatibility: the runtime name (from the upstream URL), the exact 40-character upstream commit, and a digest over the ordered patch-stack bytes. A different upstream pin, a reordered stack, or a single changed patch byte each produce a different axis value, which produces a different recipe digest, which partitions the route — exactly the fail-closed behavior DGR-025 asks for. Every consistency check here fails closed. The manifest keeps three records of the stack — ``UPSTREAM_LOCK.json``'s ``patch_series``, ``patches/series``, and ``patches/SHA256SUMS`` — plus the ``UPSTREAM_COMMIT`` convenience file, and a disagreement between any two of them means the workspace's identity is unknowable, not "probably fine". This module reads the committed manifest only; fetching and patching the actual source tree stays with ``scripts/llama_cpp_dependency.py`` (DGR-027). """ from __future__ import annotations import hashlib import json import re from dataclasses import dataclass from pathlib import Path # Domain separation, matching the runtime_recipe digest convention: a patch # stack digest can never be confused with an artifact or recipe digest. PATCH_STACK_DIGEST_DOMAIN = "meshnet.runtime-patch-stack.v1" # The UPSTREAM_LOCK.json layout this reader understands (DGR-027 schema). RUNTIME_PIN_SCHEMA_VERSION = 1 # The committed DGR-027 manifest for the llama.cpp runtime. DEFAULT_LOCK_DIR = Path(__file__).resolve().parent.parent / "native" / "llama" _HEX40 = re.compile(r"^[0-9a-f]{40}$") _HEX64 = re.compile(r"^[0-9a-f]{64}$") class RuntimePinError(ValueError): """The lock workspace is missing, malformed, or internally inconsistent.""" def _canonical_sha256(value: object) -> str: payload = json.dumps( value, sort_keys=True, separators=(",", ":"), ensure_ascii=False ) return hashlib.sha256(payload.encode("utf-8")).hexdigest() @dataclass(frozen=True) class RuntimePin: """One exact runtime: a name, an upstream commit, and an ordered patch stack.""" runtime_name: str upstream_commit: str patch_series: tuple[str, ...] patch_digests: tuple[str, ...] @property def patch_stack_digest(self) -> str: """A digest over the ordered (name, bytes-digest) stack. Order is digested deliberately: applying the same patches in a different order can produce a different tree, so a reordered stack is a different runtime. """ return _canonical_sha256( { "domain": PATCH_STACK_DIGEST_DOMAIN, "body": { "patches": [ [name, digest] for name, digest in zip(self.patch_series, self.patch_digests) ] }, } ) @property def runtime_version(self) -> str: """The exact ``runtime_version`` recipe axis value for this pin.""" return ( f"{self.runtime_name}@{self.upstream_commit}" f"+patchstack.{self.patch_stack_digest}" ) def _read_text(path: Path, what: str) -> str: try: return path.read_text(encoding="utf-8") except FileNotFoundError: raise RuntimePinError(f"{what} not found at {path}") from None except OSError as exc: raise RuntimePinError(f"{what} at {path} is unreadable: {exc}") from exc def _read_series_file(path: Path) -> list[str]: lines = _read_text(path, "patches/series").splitlines() return [line.strip() for line in lines if line.strip() and not line.startswith("#")] def _read_sums_file(path: Path) -> list[tuple[str, str]]: entries: list[tuple[str, str]] = [] for line in _read_text(path, "patches/SHA256SUMS").splitlines(): line = line.strip() if not line or line.startswith("#"): continue parts = line.split(None, 1) if len(parts) != 2 or not _HEX64.match(parts[0]): raise RuntimePinError( "patches/SHA256SUMS contains a line that is not " "' '" ) entries.append((parts[0], parts[1].strip())) return entries def load_runtime_pin(lock_dir: Path = DEFAULT_LOCK_DIR) -> RuntimePin: """Derive the exact runtime pin from a DGR-027 lock workspace, or refuse. Refuses — rather than guessing — on a missing or malformed lock, a moving commit reference, a disagreement between the lock's ``patch_series``, the ``patches/series`` file, ``patches/SHA256SUMS``, or the actual patch bytes, and on an ``UPSTREAM_COMMIT`` file that names a different commit. """ lock_path = lock_dir / "UPSTREAM_LOCK.json" raw = _read_text(lock_path, "UPSTREAM_LOCK.json") try: lock = json.loads(raw) except json.JSONDecodeError as exc: raise RuntimePinError(f"UPSTREAM_LOCK.json is not valid JSON: {exc}") from exc if not isinstance(lock, dict): raise RuntimePinError("UPSTREAM_LOCK.json must be a JSON object") schema = lock.get("schema_version") if schema != RUNTIME_PIN_SCHEMA_VERSION: raise RuntimePinError( f"UPSTREAM_LOCK.json declares schema version {schema!r}; this reader " f"understands version {RUNTIME_PIN_SCHEMA_VERSION}" ) upstream = lock.get("upstream") if not isinstance(upstream, str) or not upstream.strip(): raise RuntimePinError("UPSTREAM_LOCK.json is missing the upstream URL") runtime_name = upstream.rstrip("/").rsplit("/", 1)[-1] if runtime_name.endswith(".git"): runtime_name = runtime_name[: -len(".git")] if not runtime_name: raise RuntimePinError("the upstream URL does not name a runtime") commit = lock.get("commit") if not isinstance(commit, str) or not _HEX40.match(commit): raise RuntimePinError( f"UPSTREAM_LOCK.json commit {commit!r} is not an exact 40-character " "hexadecimal object id; a moving reference is not a pin" ) commit_file = _read_text(lock_dir / "UPSTREAM_COMMIT", "UPSTREAM_COMMIT") recorded = commit_file.strip().splitlines()[0].strip() if commit_file.strip() else "" if recorded != commit: raise RuntimePinError( "UPSTREAM_COMMIT and UPSTREAM_LOCK.json disagree on the pinned commit" ) lock_series = lock.get("patch_series") if not isinstance(lock_series, list) or not all( isinstance(name, str) and name.strip() for name in lock_series ): raise RuntimePinError( "UPSTREAM_LOCK.json patch_series must be a list of patch file names" ) if len(set(lock_series)) != len(lock_series): raise RuntimePinError("UPSTREAM_LOCK.json patch_series contains a duplicate") series = _read_series_file(lock_dir / "patches" / "series") if series != lock_series: raise RuntimePinError( "patches/series and UPSTREAM_LOCK.json patch_series disagree on the " "ordered patch stack" ) sums = _read_sums_file(lock_dir / "patches" / "SHA256SUMS") if [name for _, name in sums] != lock_series: raise RuntimePinError( "patches/SHA256SUMS does not record exactly the ordered patch stack " "named by UPSTREAM_LOCK.json" ) digests: list[str] = [] for (expected_digest, name) in sums: patch_path = lock_dir / "patches" / name try: body = patch_path.read_bytes() except FileNotFoundError: raise RuntimePinError(f"patch file {name} is named but missing") from None except OSError as exc: raise RuntimePinError(f"patch file {name} is unreadable: {exc}") from exc actual = hashlib.sha256(body).hexdigest() if actual != expected_digest: raise RuntimePinError( f"patch file {name} does not match its patches/SHA256SUMS digest" ) digests.append(actual) return RuntimePin( runtime_name=runtime_name, upstream_commit=commit, patch_series=tuple(lock_series), patch_digests=tuple(digests), )