"""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 four load-bearing parts, and each is separately fatal to compatibility: the runtime name (from the upstream URL), the exact 40-character upstream commit, a digest over the ordered patch-stack bytes, and a digest over the numerically relevant build recipe. A different upstream pin, a reordered stack, a single changed patch byte, or a changed build flag 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. The pin also records the patched source tree's git tree id, which the executing runtime's attestation is compared against (:mod:`meshnet_node.native_backend`). 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" BUILD_RECIPE_DIGEST_DOMAIN = "meshnet.runtime-build-recipe.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() def build_recipe_digest(build: object) -> str: """A digest over the numerically relevant build recipe. The lock's ``build`` object records what the runtime is compiled *as* — the configure flags, standards, and targets that select kernels and numeric behavior. Two binaries built from one patched tree with different build recipes can disagree numerically, so the recipe is part of runtime identity. This is deliberately a digest over the *recorded recipe*, not a compiler-specific binary SHA: reproducible-binary attestation is not claimed here. """ if not isinstance(build, dict) or not build: raise RuntimePinError( "the lock's 'build' section must be a non-empty JSON object; a " "runtime with an unstated build recipe has an unknowable identity" ) return _canonical_sha256( {"domain": BUILD_RECIPE_DIGEST_DOMAIN, "body": build} ) @dataclass(frozen=True) class RuntimePin: """One exact runtime: name, upstream commit, patched tree, patch stack, build. ``patched_tree`` is the git tree object id of the source tree *after* the ordered patch stack is applied — what the runtime was actually compiled from, as distinct from the upstream commit it started from. """ runtime_name: str upstream_commit: str patched_tree: str patch_series: tuple[str, ...] patch_digests: tuple[str, ...] build_recipe_digest: 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. Commits to the runtime name, the exact upstream commit, the ordered patch stack, and the numerically relevant build recipe — each separately fatal to compatibility. """ return ( f"{self.runtime_name}@{self.upstream_commit}" f"+patchstack.{self.patch_stack_digest}" f"+build.{self.build_recipe_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" ) patched_tree = lock.get("patched_tree") if not isinstance(patched_tree, str) or not _HEX40.match(patched_tree): raise RuntimePinError( "UPSTREAM_LOCK.json patched_tree must be the exact 40-character git " "tree object id of the source tree after the patch stack is applied" ) build_digest = build_recipe_digest(lock.get("build")) 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, patched_tree=patched_tree, patch_series=tuple(lock_series), patch_digests=tuple(digests), build_recipe_digest=build_digest, )