"""Exact Model Artifact and runtime recipe identity (DGR-003). A route is a chain of Shards that together compute one forward pass. If two Shards disagree about *anything* that moves the numbers — the weights, the quantization, the dtype the seam is serialized in, the dtype the kernels accumulate in, the KV layout, the tokenizer, the architecture adapter, the backend, or the runtime build — the route still runs and still emits tokens. They are simply the wrong tokens, and nothing upstream notices. Identity is therefore a digest over the executable semantics, not a model name. Four decisions shape this module. **The axes are separate, and each one is separately fatal.** `Q4_K_M` weights are not a `bfloat16` seam, which is not an `fp32` accumulate, which is not a `q8_0` KV cache. Collapsing them into one "precision" label is how a route ends up numerically split while every node reports agreement. :data:`RECIPE_AXES` is the full list; the digest commits to each under its own key, so swapping two axis *values* changes the digest even though the multiset of values did not. **The fingerprint deliberately excludes the Shard range.** Shards on one route own *different* ranges; if the range were digested, no two Shards on a route could ever agree. Range compatibility is a route-level coverage question (:func:`check_route`), not an identity question. **A split artifact routes under its source's identity.** A node holding layers 32–63 of a GGUF holds a *different file* with a *different content hash* than a node holding layers 0–31, and neither equals the whole model. So the digest binds :attr:`ArtifactIdentity.source_digest` — the whole-model artifact every Shard descends from — and the derivative's own hash and range are bound to that source locally (:class:`DerivativeBinding`). A derivative that cannot name the exact artifact it was cut from is not admissible: it is an anonymous blob that happens to have the right layer count. **Labels are not digested.** `recipe_id`, `recipe_version` and `catalogue_version` ride alongside the digests for diagnosis, mirroring the capability report an admitted node already registers with (ADR-0023). Renaming a recipe does not change a single number, so a rename must not partition a route; changing `kv_dtype` changes every number, so it must. The digest commits to what changes the numbers. **Certification is not here.** Recipes are registered-but-dark on arrival, and a recipe leaves the dark only when a real distributed forward over at least two distinct physical nodes has emitted real tokens. That ledger lives in the Tracker (:class:`meshnet_tracker.recipe.CertificationLedger`) and nowhere else — a node that could decide it was certified would be marking its own homework, and a second copy of the policy here would be a second thing to keep in step with it. This module builds and compares identity; the Tracker decides what may serve. The tracker re-derives the digests independently in ``meshnet_tracker.recipe`` — it does not import this package, because an admission gate that shares an implementation with the thing it admits is not an independent check. The two implementations are pinned together by a committed conformance vector (``tests/data/recipe_fingerprint_vectors.json``); if they ever drift, a test fails rather than a route silently forming. """ from __future__ import annotations import hashlib import json import re from dataclasses import dataclass from typing import Any, Iterable, Mapping, Sequence from .native_protocol import BUNDLE_VERSION, SCHEMA_VERSION, pb # Layout of the serialized identity block. Bump when the JSON shape changes. RECIPE_IDENTITY_SCHEMA_VERSION = 1 # Domain separation. A digest means nothing outside the structure it commits to; # the prefix guarantees an artifact digest can never be mistaken for — or collide # with — a recipe digest, even if their canonical JSON were somehow identical. ARTIFACT_DIGEST_DOMAIN = "meshnet.model-artifact.v1" RECIPE_DIGEST_DOMAIN = "meshnet.runtime-recipe.v1" SHARD_BINDING_DIGEST_DOMAIN = "meshnet.shard-binding.v1" # The axes of a runtime recipe. Every one of these changes the numbers a Shard # produces, so every one of them is part of identity and none of them may be # folded into another. RECIPE_AXES: tuple[str, ...] = ( "weight_quantization", "activation_dtype", "compute_dtype", "kv_dtype", "kv_layout", "tokenizer_revision", "architecture_adapter", "backend_id", "runtime_version", "boundary_schema_version", "protocol_schema_version", ) # --- Why two identities failed to match. These are the fail-closed reasons. --- MISMATCH_ARTIFACT = "artifact" MISMATCH_WEIGHT_QUANTIZATION = "weight-quantization" MISMATCH_ACTIVATION_RECIPE = "activation-recipe" MISMATCH_CACHE_LAYOUT = "cache-layout" MISMATCH_TOKENIZER = "tokenizer" MISMATCH_ARCHITECTURE = "architecture" MISMATCH_BACKEND = "backend" MISMATCH_RUNTIME = "runtime" MISMATCH_BOUNDARY_SCHEMA = "boundary-schema" MISMATCH_RANGE = "range" MISMATCH_FINGERPRINT = "fingerprint" MISMATCH_ROUTE_SESSION = "route-session" MISMATCH_ROUTE_EPOCH = "route-epoch" # Which fail-closed reason each axis reports. Several axes share a reason # because they fail for the same operational cause: `activation_dtype` and # `compute_dtype` are both "the activation recipe disagrees", and `kv_dtype` # and `kv_layout` are both "the cache layout disagrees". _AXIS_MISMATCH: Mapping[str, str] = { "weight_quantization": MISMATCH_WEIGHT_QUANTIZATION, "activation_dtype": MISMATCH_ACTIVATION_RECIPE, "compute_dtype": MISMATCH_ACTIVATION_RECIPE, "kv_dtype": MISMATCH_CACHE_LAYOUT, "kv_layout": MISMATCH_CACHE_LAYOUT, "tokenizer_revision": MISMATCH_TOKENIZER, "architecture_adapter": MISMATCH_ARCHITECTURE, "backend_id": MISMATCH_BACKEND, "runtime_version": MISMATCH_RUNTIME, "boundary_schema_version": MISMATCH_BOUNDARY_SCHEMA, "protocol_schema_version": MISMATCH_BOUNDARY_SCHEMA, } _HEX64 = re.compile(r"^[0-9a-f]{64}$") # A revision that can move is not a pin. DGR-017 learned this on the artifact; # it is just as true of a tokenizer. _MOVING_REFS = frozenset({"main", "master", "head", "latest", "dev", "trunk"}) class RecipeIdentityError(ValueError): """Malformed identity input. Messages name the field, never echo a payload.""" class RouteIncompatible(RecipeIdentityError): """Two Shards cannot form an Inference Route. Carries the structured reasons, so a caller can map them to a gRPC status rather than re-parsing a sentence. """ def __init__(self, mismatches: Sequence["RouteMismatch"]) -> None: self.mismatches = tuple(mismatches) super().__init__("; ".join(m.describe() for m in self.mismatches)) def canonical_sha256(value: Any) -> str: """SHA-256 over canonical JSON — the repository's digest convention.""" payload = json.dumps( value, sort_keys=True, separators=(",", ":"), ensure_ascii=False ) return hashlib.sha256(payload.encode("utf-8")).hexdigest() def _digest(domain: str, body: Mapping[str, Any]) -> str: return canonical_sha256({"domain": domain, "body": dict(body)}) def _require_text(value: Any, what: str) -> str: if not isinstance(value, str) or not value.strip(): raise RecipeIdentityError(f"{what!r} must be a non-empty string") return value def _require_hex64(value: Any, what: str) -> str: text = _require_text(value, what) if not _HEX64.match(text): raise RecipeIdentityError( f"{what!r} must be a lowercase 64-character SHA-256 hex digest" ) return text def _require_int(value: Any, what: str, minimum: int) -> int: if isinstance(value, bool) or not isinstance(value, int): raise RecipeIdentityError(f"{what!r} must be an integer") if value < minimum: raise RecipeIdentityError(f"{what!r} must be >= {minimum}, got {value}") return value def _require_pin(value: Any, what: str) -> str: """A revision must identify one immutable thing, not a ref that moves.""" text = _require_text(value, what) lowered = text.strip().lower() if lowered in _MOVING_REFS or lowered.startswith("refs/"): raise RecipeIdentityError( f"{what!r} is {text!r}, which is a moving reference, not a pin; " "identity requires an exact immutable revision" ) return text def _as_mapping(value: Any, what: str) -> Mapping[str, Any]: if not isinstance(value, Mapping): raise RecipeIdentityError( f"{what!r} must be a JSON object, got {type(value).__name__}" ) return value @dataclass(frozen=True) class DerivativeBinding: """A split or derived artifact, bound to the exact artifact it was cut from. Without this binding a split GGUF is an anonymous blob with a plausible layer count. With it, the Shard's own bytes are checkable *and* its identity on the route is the source model's, which is what lets a whole-model oracle and a five-way split agree on a fingerprint. The range is inclusive start, exclusive end, matching the protocol (``ShardRange`` in ``shard_runtime.proto``). """ source_artifact_digest: str shard_start: int shard_end: int def __post_init__(self) -> None: _require_hex64( self.source_artifact_digest, "derived_from.source_artifact_digest" ) _require_int(self.shard_start, "derived_from.shard_start", 0) _require_int(self.shard_end, "derived_from.shard_end", 1) if self.shard_end <= self.shard_start: raise RecipeIdentityError( f"'derived_from.shard_end' ({self.shard_end}) must be greater than " f"'derived_from.shard_start' ({self.shard_start}); an empty split " "artifact covers no layers and can prove nothing" ) def covers(self, start: int, end: int) -> bool: return self.shard_start <= start and end <= self.shard_end def to_dict(self) -> dict: return { "source_artifact_digest": self.source_artifact_digest, "shard_start": self.shard_start, "shard_end": self.shard_end, } @classmethod def from_dict(cls, data: Any) -> DerivativeBinding: doc = _as_mapping(data, "derived_from") return cls( source_artifact_digest=_require_hex64( doc.get("source_artifact_digest"), "derived_from.source_artifact_digest", ), shard_start=_require_int( doc.get("shard_start"), "derived_from.shard_start", 0 ), shard_end=_require_int(doc.get("shard_end"), "derived_from.shard_end", 1), ) @dataclass(frozen=True) class ArtifactIdentity: """Exactly which weights, config and architecture a Shard loaded. `content_digest` is *this* file's hash — the whole model for an undivided artifact, one split's bytes for a derivative. `source_digest` is what the route compares. For an undivided artifact they are the same value; that identity is the whole point, because it is what makes the DGR-018 whole-model oracle and the DGR-020 distributed route the same artifact. """ artifact_id: str revision: str content_digest: str architecture: str architecture_digest: str layer_count: int derived_from: DerivativeBinding | None = None def __post_init__(self) -> None: _require_text(self.artifact_id, "artifact.artifact_id") _require_pin(self.revision, "artifact.revision") _require_hex64(self.content_digest, "artifact.content_digest") _require_text(self.architecture, "artifact.architecture") _require_hex64(self.architecture_digest, "artifact.architecture_digest") _require_int(self.layer_count, "artifact.layer_count", 1) binding = self.derived_from if binding is None: return if binding.source_artifact_digest == self.content_digest: raise RecipeIdentityError( "'artifact.derived_from.source_artifact_digest' equals this " "artifact's own content digest; an artifact is not a split of itself" ) if binding.shard_end > self.layer_count: raise RecipeIdentityError( f"'artifact.derived_from' claims layers " f"{binding.shard_start}–{binding.shard_end}, but the source model " f"has only {self.layer_count} layers" ) @property def is_derivative(self) -> bool: return self.derived_from is not None @property def source_digest(self) -> str: """The whole-model artifact this Shard descends from — what a route compares.""" if self.derived_from is None: return self.content_digest return self.derived_from.source_artifact_digest @property def model_artifact_digest(self) -> str: """The artifact half of the compatibility fingerprint. Commits to the source weights, the architecture and its config/tokenizer snapshot, and the layer count — and deliberately *not* to the repository the bytes were fetched from. A mirror of identical bytes is the same artifact; the repo name buys diagnosis, not numerical identity. """ return _digest( ARTIFACT_DIGEST_DOMAIN, { "source_digest": self.source_digest, "architecture": self.architecture, "architecture_digest": self.architecture_digest, "layer_count": self.layer_count, }, ) def to_dict(self) -> dict: return { "artifact_id": self.artifact_id, "revision": self.revision, "content_digest": self.content_digest, "architecture": self.architecture, "architecture_digest": self.architecture_digest, "layer_count": self.layer_count, "derived_from": ( self.derived_from.to_dict() if self.derived_from else None ), } @classmethod def from_dict(cls, data: Any) -> ArtifactIdentity: doc = _as_mapping(data, "artifact") raw_binding = doc.get("derived_from") return cls( artifact_id=_require_text(doc.get("artifact_id"), "artifact.artifact_id"), revision=_require_pin(doc.get("revision"), "artifact.revision"), content_digest=_require_hex64( doc.get("content_digest"), "artifact.content_digest" ), architecture=_require_text( doc.get("architecture"), "artifact.architecture" ), architecture_digest=_require_hex64( doc.get("architecture_digest"), "artifact.architecture_digest" ), layer_count=_require_int(doc.get("layer_count"), "artifact.layer_count", 1), derived_from=( None if raw_binding is None else DerivativeBinding.from_dict(raw_binding) ), ) @dataclass(frozen=True) class RuntimeRecipe: """How a Shard executes the artifact — one field per numerically live axis. Weight quantization is not activation dtype is not compute dtype is not KV dtype. Two nodes running `UD-IQ1_S` weights, one accumulating in fp32 and one in fp16, produce different logits from the same bytes. Keeping the axes apart is the entire safety property; see :data:`RECIPE_AXES`. The three label fields are diagnosis only and are not digested. """ weight_quantization: str activation_dtype: str compute_dtype: str kv_dtype: str kv_layout: str tokenizer_revision: str architecture_adapter: str backend_id: str runtime_version: str boundary_schema_version: int = BUNDLE_VERSION protocol_schema_version: int = int(SCHEMA_VERSION) recipe_id: str = "unnamed" recipe_version: str = "0" catalogue_version: str = "0" def __post_init__(self) -> None: for axis in RECIPE_AXES: value = getattr(self, axis) if axis.endswith("_schema_version"): _require_int(value, f"recipe.{axis}", 1) else: _require_text(value, f"recipe.{axis}") _require_pin(self.tokenizer_revision, "recipe.tokenizer_revision") _require_text(self.recipe_id, "recipe.recipe_id") _require_text(self.recipe_version, "recipe.recipe_version") _require_text(self.catalogue_version, "recipe.catalogue_version") @property def axes(self) -> dict[str, Any]: """The digested fields, and only those.""" return {axis: getattr(self, axis) for axis in RECIPE_AXES} @property def runtime_recipe_digest(self) -> str: """The recipe half of the compatibility fingerprint.""" return _digest(RECIPE_DIGEST_DOMAIN, self.axes) def to_dict(self) -> dict: doc = self.axes doc.update( { "recipe_id": self.recipe_id, "recipe_version": self.recipe_version, "catalogue_version": self.catalogue_version, } ) return doc @classmethod def from_dict(cls, data: Any) -> RuntimeRecipe: doc = _as_mapping(data, "recipe") missing = [axis for axis in RECIPE_AXES if axis not in doc] if missing: raise RecipeIdentityError( "recipe is missing required axes: " + ", ".join(missing) + "; an unstated axis is an unproven one, so it cannot default" ) kwargs: dict[str, Any] = {} for axis in RECIPE_AXES: value = doc[axis] if axis.endswith("_schema_version"): kwargs[axis] = _require_int(value, f"recipe.{axis}", 1) else: kwargs[axis] = _require_text(value, f"recipe.{axis}") return cls( **kwargs, recipe_id=_require_text(doc.get("recipe_id"), "recipe.recipe_id"), recipe_version=_require_text( doc.get("recipe_version"), "recipe.recipe_version" ), catalogue_version=_require_text( doc.get("catalogue_version"), "recipe.catalogue_version" ), ) @dataclass(frozen=True) class CompatibilityFingerprint: """The stable identity two peers actually compare. This is the Python face of the protocol's ``Fingerprint`` message (DGR-002). Do not invent a second identity struct: capability admission and the gRPC handshake compare *this*, and they must compare the same thing or a route can pass one gate and fail the other. """ model_artifact_digest: str runtime_recipe_digest: str recipe_id: str recipe_version: str catalogue_version: str def __post_init__(self) -> None: _require_hex64(self.model_artifact_digest, "fingerprint.model_artifact_digest") _require_hex64( self.runtime_recipe_digest, "fingerprint.runtime_recipe_digest" ) @property def key(self) -> tuple[str, str]: """What equality means: the digests, never the labels.""" return (self.model_artifact_digest, self.runtime_recipe_digest) def matches(self, other: CompatibilityFingerprint) -> bool: return self.key == other.key def to_dict(self) -> dict: return { "model_artifact_digest": self.model_artifact_digest, "runtime_recipe_digest": self.runtime_recipe_digest, "recipe_id": self.recipe_id, "recipe_version": self.recipe_version, "catalogue_version": self.catalogue_version, } @classmethod def from_dict(cls, data: Any) -> CompatibilityFingerprint: doc = _as_mapping(data, "fingerprint") return cls( model_artifact_digest=_require_hex64( doc.get("model_artifact_digest"), "fingerprint.model_artifact_digest" ), runtime_recipe_digest=_require_hex64( doc.get("runtime_recipe_digest"), "fingerprint.runtime_recipe_digest" ), recipe_id=_require_text(doc.get("recipe_id"), "fingerprint.recipe_id"), recipe_version=_require_text( doc.get("recipe_version"), "fingerprint.recipe_version" ), catalogue_version=_require_text( doc.get("catalogue_version"), "fingerprint.catalogue_version" ), ) def to_proto(self) -> "pb.Fingerprint": """Populate the DGR-002 ``Fingerprint`` message for the gRPC handshake.""" return pb.Fingerprint( model_artifact_digest=self.model_artifact_digest, runtime_recipe_digest=self.runtime_recipe_digest, recipe_id=self.recipe_id, recipe_version=self.recipe_version, catalogue_version=self.catalogue_version, ) @classmethod def from_proto(cls, message: "pb.Fingerprint") -> CompatibilityFingerprint: return cls( model_artifact_digest=_require_hex64( message.model_artifact_digest, "fingerprint.model_artifact_digest" ), runtime_recipe_digest=_require_hex64( message.runtime_recipe_digest, "fingerprint.runtime_recipe_digest" ), recipe_id=_require_text(message.recipe_id, "fingerprint.recipe_id"), recipe_version=_require_text( message.recipe_version, "fingerprint.recipe_version" ), catalogue_version=_require_text( message.catalogue_version, "fingerprint.catalogue_version" ), ) @dataclass(frozen=True) class RouteMismatch: """One structured, fail-closed reason two identities do not compose.""" reason: str detail: str shard_index: int | None = None def describe(self) -> str: where = "" if self.shard_index is None else f"shard {self.shard_index}: " return f"{where}{self.reason}: {self.detail}" def to_dict(self) -> dict: return { "reason": self.reason, "detail": self.detail, "shard_index": self.shard_index, } @dataclass(frozen=True) class ShardIdentity: """What one Shard is: an artifact, a recipe, and the layers it owns. The owned range is inclusive start, exclusive end (protocol convention). It is *not* part of the fingerprint — see the module docstring — but it is validated against the artifact, because a Shard cannot serve layers its artifact does not contain. """ artifact: ArtifactIdentity recipe: RuntimeRecipe shard_start: int shard_end: int def __post_init__(self) -> None: _require_int(self.shard_start, "shard_start", 0) _require_int(self.shard_end, "shard_end", 1) if self.shard_end <= self.shard_start: raise RecipeIdentityError( f"'shard_end' ({self.shard_end}) must be greater than 'shard_start' " f"({self.shard_start}); a Shard owning no layer computes nothing" ) if self.shard_end > self.artifact.layer_count: raise RecipeIdentityError( f"Shard owns layers {self.shard_start}–{self.shard_end}, but the " f"artifact has only {self.artifact.layer_count} layers" ) binding = self.artifact.derived_from if binding is not None and not binding.covers(self.shard_start, self.shard_end): raise RecipeIdentityError( f"Shard advertises layers {self.shard_start}–{self.shard_end}, but its " f"split artifact only contains layers {binding.shard_start}–" f"{binding.shard_end}; it cannot serve weights it does not hold" ) @property def fingerprint(self) -> CompatibilityFingerprint: """The derived identity. Never stored, never trusted from the wire — derived.""" return CompatibilityFingerprint( model_artifact_digest=self.artifact.model_artifact_digest, runtime_recipe_digest=self.recipe.runtime_recipe_digest, recipe_id=self.recipe.recipe_id, recipe_version=self.recipe.recipe_version, catalogue_version=self.recipe.catalogue_version, ) @property def shard_binding_digest(self) -> str: """This Shard's own bytes and exact range, bound to the source artifact. The fingerprint is range-independent on purpose — see the module docstring — which means it cannot distinguish two *different splits of the same source*. Both are the same recipe on the same model, and for route compatibility that is exactly right. It is not right for certification: a derivative could otherwise assert the correct source and inherit a certification earned by bytes it does not hold. So the derivative's own content hash and exact end-exclusive range get a second digest, outside the fingerprint. The Tracker re-derives it at admission and requires certification evidence to name the same binding for every serving node. """ binding = self.artifact.derived_from return _digest( SHARD_BINDING_DIGEST_DOMAIN, { "source_digest": self.artifact.source_digest, "content_digest": self.artifact.content_digest, "architecture_digest": self.artifact.architecture_digest, "derivative_range": ( None if binding is None else [binding.shard_start, binding.shard_end] ), "shard_start": self.shard_start, "shard_end": self.shard_end, }, ) def to_dict(self) -> dict: return { "schema_version": RECIPE_IDENTITY_SCHEMA_VERSION, "artifact": self.artifact.to_dict(), "recipe": self.recipe.to_dict(), "shard_start": self.shard_start, "shard_end": self.shard_end, "fingerprint": self.fingerprint.to_dict(), } @classmethod def from_dict(cls, data: Any) -> ShardIdentity: """Parse an identity block, re-deriving — never trusting — its fingerprint. A declared fingerprint is a compatibility claim, not authentication. Recomputing it catches an inconsistent declaration; only tracker-owned distributed certification decides whether a recipe may serve. """ doc = _as_mapping(data, "identity") declared_schema = doc.get("schema_version") if declared_schema != RECIPE_IDENTITY_SCHEMA_VERSION: raise RecipeIdentityError( f"identity block declares schema version {declared_schema!r}, but this " f"node reads version {RECIPE_IDENTITY_SCHEMA_VERSION}" ) identity = cls( artifact=ArtifactIdentity.from_dict(doc.get("artifact")), recipe=RuntimeRecipe.from_dict(doc.get("recipe")), shard_start=_require_int(doc.get("shard_start"), "shard_start", 0), shard_end=_require_int(doc.get("shard_end"), "shard_end", 1), ) declared = doc.get("fingerprint") if declared is not None: claim = CompatibilityFingerprint.from_dict(declared) derived = identity.fingerprint if not claim.matches(derived): raise RouteIncompatible( [ RouteMismatch( MISMATCH_FINGERPRINT, "declared fingerprint does not match the identity it " "claims to describe; the digest is derived from the " "artifact and recipe, it is not an assertion", ) ] ) return identity def explain_mismatch( left: ShardIdentity, right: ShardIdentity, *, shard_index: int | None = None, ) -> tuple[RouteMismatch, ...]: """Name every axis on which two Shards disagree. The fingerprint comparison already answers *whether* they compose. This answers *why not*, which is the difference between an operator fixing their node in a minute and filing a bug. """ mismatches: list[RouteMismatch] = [] if left.artifact.source_digest != right.artifact.source_digest: mismatches.append( RouteMismatch( MISMATCH_ARTIFACT, f"source Model Artifact {right.artifact.source_digest[:12]}… does not " f"match the route's {left.artifact.source_digest[:12]}…", shard_index, ) ) if left.artifact.architecture != right.artifact.architecture: mismatches.append( RouteMismatch( MISMATCH_ARCHITECTURE, f"artifact architecture {right.artifact.architecture!r} does not match " f"the route's {left.artifact.architecture!r}", shard_index, ) ) if left.artifact.architecture_digest != right.artifact.architecture_digest: mismatches.append( RouteMismatch( MISMATCH_ARCHITECTURE, "architecture/config snapshot differs from the route's; the same " "architecture name with different config is a different model", shard_index, ) ) if left.artifact.layer_count != right.artifact.layer_count: mismatches.append( RouteMismatch( MISMATCH_ARTIFACT, f"artifact has {right.artifact.layer_count} layers, but the route's " f"has {left.artifact.layer_count}", shard_index, ) ) for axis in RECIPE_AXES: mine = getattr(left.recipe, axis) theirs = getattr(right.recipe, axis) if mine != theirs: mismatches.append( RouteMismatch( _AXIS_MISMATCH[axis], f"{axis} is {theirs!r}, but the route runs {mine!r}", shard_index, ) ) if not mismatches and not left.fingerprint.matches(right.fingerprint): # Unreachable via the fields above; if it ever fires, identity has grown a # digested field that `explain_mismatch` does not know about, and silently # reporting "compatible" would be far worse than an unhelpful message. mismatches.append( RouteMismatch( MISMATCH_FINGERPRINT, "fingerprints differ on a field this comparison does not cover", shard_index, ) ) return tuple(mismatches) def _coverage_gap( ranges: Iterable[tuple[int, int]], layer_count: int ) -> str | None: """Return why `ranges` fail to tile ``[0, layer_count)``, or None if they do. Ranges may overlap: ADR-0012 lets the Tracker resolve an overlap by telling a hop where the previous hop stopped, so an overlap is a routing decision, not a defect. A *hole* is a defect — the layers in it are never computed, and the route would silently emit tokens from a truncated model. """ ordered = sorted(ranges) if not ordered: return "the certifying route owns no layers" if ordered[0][0] != 0: return f"layers 0–{ordered[0][0]} are owned by no Shard on the route" covered = 0 for start, end in ordered: if start > covered: return f"layers {covered}–{start} are owned by no Shard on the route" covered = max(covered, end) if covered < layer_count: return f"layers {covered}–{layer_count} are owned by no Shard on the route" if covered > layer_count: return ( f"the route claims to cover {covered} layers, but the model has only " f"{layer_count}" ) return None def check_route( shards: Sequence[ShardIdentity], ) -> tuple[RouteMismatch, ...]: """Why these Shards may not form one Inference Route; empty when they may. This answers the *numerical* question only — do these Shards agree on every axis that moves the numbers, and do they tile the model without a hole. It deliberately does not answer "may this recipe carry user traffic": that is certification, the Tracker owns it (`meshnet_tracker.recipe`), and a node asking itself whether it is certified would be marking its own homework. Fail-closed by construction: an empty route, a hole in the coverage and a single differing dtype each produce a reason, so a caller that proceeds only on an empty tuple cannot admit any of them. """ if not shards: return ( RouteMismatch( MISMATCH_RANGE, "a route needs at least one Shard; this one has none" ), ) mismatches: list[RouteMismatch] = [] head = shards[0] for index, shard in enumerate(shards[1:], start=1): if not head.fingerprint.matches(shard.fingerprint): mismatches.extend(explain_mismatch(head, shard, shard_index=index)) gap = _coverage_gap( [(shard.shard_start, shard.shard_end) for shard in shards], head.artifact.layer_count, ) if gap is not None: mismatches.append(RouteMismatch(MISMATCH_RANGE, gap)) return tuple(mismatches) def require_route(shards: Sequence[ShardIdentity]) -> CompatibilityFingerprint: """`check_route`, raising the structured reasons instead of returning them.""" mismatches = check_route(shards) if mismatches: raise RouteIncompatible(mismatches) return shards[0].fingerprint # --------------------------------------------------------------------------- # The alpha target (DGR-017) # --------------------------------------------------------------------------- def glm_alpha_artifact( derived_from: DerivativeBinding | None = None, *, content_digest: str | None = None, ) -> ArtifactIdentity: """Artifact identity for the pinned GLM-5.2 ``UD-IQ1_S`` alpha target. Reads the locked manifest and architecture snapshot DGR-017 sealed, rather than restating their digests here — a second copy of a pinned digest is a second thing that can drift from the target it claims to pin. `layer_count` is `num_hidden_layers` (78), *not* `total_artifact_layers` (79). The 79th is the MTP/next-token-prediction layer, which is not part of the transformer stack a route tiles and which alpha explicitly defers (roadmap decision 17). Tiling 79 layers would leave every route one layer short of "covered" forever. Pass `derived_from` when this node holds a split of the target, and `content_digest` for that split's own bytes. """ from .glm_alpha import load_locked_target _contract, manifest, snapshot = load_locked_target() return ArtifactIdentity( artifact_id=manifest.gguf_repo_id, revision=manifest.gguf_revision, content_digest=content_digest or manifest.digest, architecture=str(snapshot["model_type"]), architecture_digest=snapshot.digest, layer_count=int(snapshot["num_hidden_layers"]), derived_from=derived_from, ) # --------------------------------------------------------------------------- # gRPC handshake (DGR-002 `Fingerprint`, `ERROR_CODE_FINGERPRINT_MISMATCH`) # --------------------------------------------------------------------------- def check_handshake( local: ShardIdentity, remote: "pb.Fingerprint", ) -> tuple[RouteMismatch, ...]: """Compare the fingerprint a peer opened the stream with against our own. An incompatible peer must fail at `SessionOpen`, not mid-generation: by the time a wrong activation has been folded into a KV cache, the session is already producing wrong tokens and there is nothing to salvage. """ try: presented = CompatibilityFingerprint.from_proto(remote) except RecipeIdentityError as exc: return ( RouteMismatch( MISMATCH_FINGERPRINT, f"peer presented an unusable fingerprint: {exc}", ), ) mine = local.fingerprint if presented.matches(mine): return () reasons: list[RouteMismatch] = [] if presented.model_artifact_digest != mine.model_artifact_digest: reasons.append( RouteMismatch( MISMATCH_ARTIFACT, f"peer serves Model Artifact {presented.model_artifact_digest[:12]}…, " f"this Shard serves {mine.model_artifact_digest[:12]}…", ) ) if presented.runtime_recipe_digest != mine.runtime_recipe_digest: reasons.append( RouteMismatch( MISMATCH_FINGERPRINT, f"peer runs recipe {presented.runtime_recipe_digest[:12]}… " f"({presented.recipe_id} v{presented.recipe_version}), this Shard runs " f"{mine.runtime_recipe_digest[:12]}… ({mine.recipe_id} " f"v{mine.recipe_version})", ) ) return tuple(reasons) def check_session_open( local: ShardIdentity, remote: "pb.SessionOpen", *, expected_route_session_id: str | None = None, expected_route_epoch: int | None = None, ) -> tuple[RouteMismatch, ...]: """Fail closed on the complete immutable portion of a ``SessionOpen``. ``SessionOpen`` is not just a fingerprint carrier: its route session and epoch select the tracker-issued route whose identity was checked. A worker that accepts a stale epoch can receive activations for a route that the tracker has already replaced. Callers that have the tracker assignment therefore pass both expected values; callers without it still reject the protobuf defaults, which cannot identify a live route. """ reasons = list(check_handshake(local, remote.fingerprint)) if remote.schema_version != SCHEMA_VERSION: reasons.append( RouteMismatch( MISMATCH_BOUNDARY_SCHEMA, f"peer schema version {remote.schema_version} does not match " f"{SCHEMA_VERSION}", ) ) if not remote.route_session_id: reasons.append( RouteMismatch( MISMATCH_ROUTE_SESSION, "peer omitted the tracker-issued route session id", ) ) elif ( expected_route_session_id is not None and remote.route_session_id != expected_route_session_id ): reasons.append( RouteMismatch( MISMATCH_ROUTE_SESSION, "peer route session does not match the tracker assignment", ) ) if remote.route_epoch < 1: reasons.append( RouteMismatch( MISMATCH_ROUTE_EPOCH, "peer omitted a positive tracker-issued route epoch", ) ) elif expected_route_epoch is not None and remote.route_epoch != expected_route_epoch: reasons.append( RouteMismatch( MISMATCH_ROUTE_EPOCH, "peer route epoch does not match the tracker assignment", ) ) advertised = (remote.shard_range.start_layer, remote.shard_range.end_layer) expected = (local.shard_start, local.shard_end) effective = remote.shard_range.effective_start_layer if advertised != expected: reasons.append( RouteMismatch( MISMATCH_RANGE, f"peer advertised Shard range {advertised}, expected {expected}", ) ) elif not local.shard_start <= effective < local.shard_end: reasons.append( RouteMismatch( MISMATCH_RANGE, f"effective start {effective} is outside Shard range {expected}", ) ) return tuple(reasons) def handshake_error( mismatches: Sequence[RouteMismatch], ) -> "pb.ShardError | None": """The protocol status a rejected handshake closes the stream with. Each rejection maps to the most specific DGR-002 code the reasons allow: a digest disagreement dominates (the peer is the wrong artifact or recipe, whatever else is also wrong), then a schema disagreement (a peer on a foreign schema cannot be trusted to have encoded anything else correctly), and only a pure range disagreement reports the range code. Collapsing all of these to one code would leave the initiator unable to tell "re-route me" from "renegotiate the schema" from "wrong peer entirely". """ if not mismatches: return None reasons = {mismatch.reason for mismatch in mismatches} if reasons == {MISMATCH_RANGE}: code = pb.ERROR_CODE_SHARD_RANGE_MISMATCH elif reasons <= {MISMATCH_ROUTE_SESSION, MISMATCH_ROUTE_EPOCH}: code = pb.ERROR_CODE_EPOCH_STALE elif reasons <= {MISMATCH_BOUNDARY_SCHEMA, MISMATCH_RANGE}: code = pb.ERROR_CODE_SCHEMA_UNSUPPORTED else: code = pb.ERROR_CODE_FINGERPRINT_MISMATCH return pb.ShardError( code=code, detail="; ".join(m.describe() for m in mismatches), retryable=False, )