diff --git a/.scratch/distributed-gguf-runtime/evidence/DGR-025/README.md b/.scratch/distributed-gguf-runtime/evidence/DGR-025/README.md index 771144c..893c104 100644 --- a/.scratch/distributed-gguf-runtime/evidence/DGR-025/README.md +++ b/.scratch/distributed-gguf-runtime/evidence/DGR-025/README.md @@ -72,17 +72,22 @@ explicitly requires fingerprinting the "runtime pin/patch stack". committed manifest only; fetching/patching stays with `scripts/llama_cpp_dependency.py` (DGR-027). - `packages/node/meshnet_node/runtime_recipe.py` — `runtime_version` is now - pin-enforced (`_require_pin`) exactly like `tokenizer_revision`; docstring - points at the canonical derivation. + pin-enforced (`_require_pin`) exactly like `tokenizer_revision`; for the + llama.cpp backend it must also match the canonical + `llama.cpp@<40-hex>+patchstack.<64-hex>` grammar. +- `packages/node/meshnet_node/native_backend.py` — the production native + identity seam no longer accepts a caller-supplied runtime string. It derives + `runtime_version` directly through `load_runtime_pin()` from the committed + lock and rejects a non-llama backend at this llama.cpp-specific boundary. - `packages/tracker/meshnet_tracker/recipe.py` — the independent tracker - implementation applies the same pin rule in `parse_identity`, keeping the two - implementations in step. -- `tests/test_runtime_pin_identity.py` (new, TDD — written first and observed - failing) — 17 deterministic tests: the committed manifest derives a - deterministic pin whose axis value is a valid recipe pin; a changed patch - byte and a reordered stack each change the runtime identity; every manifest - disagreement above fails closed; and both node and tracker reject a moving - `runtime_version`. + implementation applies the same backend-specific grammar before re-deriving + the recipe digest, so forged operator labels cannot register or certify. +- `tests/test_runtime_pin_identity.py` and + `tests/test_native_identity_emission.py` — deterministic tests cover lock + derivation, production native emission, and node/tracker rejection of the + forged values from independent review. Conformance vectors were regenerated + through `scripts/gen_recipe_fingerprint_vectors.py` for the tightened wire + contract. ### Backlog-consistency repair (pre-existing damage, honestly recorded) @@ -136,7 +141,7 @@ Marked `DGR-025.passes = true` with `completionNotes`; regenerated PYTHONPATH=packages/node:packages/tracker python3 -m pytest -q tests/test_runtime_pin_identity.py ``` ```text -17 passed in 0.11s +23 passed in 0.15s ``` ```bash @@ -146,7 +151,7 @@ PYTHONPATH=packages/node:packages/tracker python3 -m pytest -q \ tests/test_node_admission.py tests/test_node_capability.py tests/test_recipe_benchmark.py ``` ```text -196 passed, 1 warning in 5.35s +202 passed, 1 warning in 5.38s ``` ```bash @@ -169,13 +174,12 @@ artifact was touched and nothing was written under `/home`. ## Limitations -- `runtime_pin.py` proves what the *manifest* pins; it does not prove the - running binary was built from that manifest. Binding the built native worker - to the pin it reports (e.g. embedding the patched-tree hash at build time and - echoing it through the DGR-022 status contract) belongs with the native - worker stories (DGR-028+/DGR-031); until then `runtime_version` is exactly as - trustworthy as the rest of the declared axes — a claim the tracker digests, - with real distributed certification as the trust boundary (unchanged design). +- The production native identity seam now derives the manifest pin and cannot + accept an operator-supplied runtime label. It still cannot attest that the + running binary was built from those locked bytes. Embedding the patched-tree + hash at build time and echoing it through the DGR-022 status contract belongs + with DGR-028+/DGR-031; real distributed certification remains the final trust + boundary. - The DGR-027-recorded blocker stands: `0002-dense-llama-owned-range-loader.patch` does not apply cleanly against the pin (DGR-028). That does not affect this story: the identity commits to the patch *bytes as committed*, which is diff --git a/packages/node/meshnet_node/native_backend.py b/packages/node/meshnet_node/native_backend.py index 33c6841..9bebe74 100644 --- a/packages/node/meshnet_node/native_backend.py +++ b/packages/node/meshnet_node/native_backend.py @@ -12,6 +12,7 @@ from __future__ import annotations from dataclasses import dataclass from .native_protocol import BUNDLE_VERSION, SCHEMA_VERSION, pb +from .runtime_pin import load_runtime_pin from .runtime_recipe import ( ArtifactIdentity, DerivativeBinding, @@ -72,7 +73,6 @@ class NativeNumericalRecipe: kv_layout: str architecture_adapter: str backend_id: str - runtime_version: str recipe_id: str recipe_version: str catalogue_version: str @@ -95,6 +95,11 @@ def shard_identity_from_native_report(inputs: NativeIdentityInputs) -> ShardIden report = inputs.loaded_artifact pin = inputs.artifact_pin recipe = inputs.numerical_recipe + if recipe.backend_id.strip().lower() not in {"llama.cpp", "llama-cpp"}: + raise RecipeIdentityError( + "native llama.cpp identity requires backend_id 'llama.cpp' or 'llama-cpp'" + ) + runtime_version = load_runtime_pin().runtime_version artifact = ArtifactIdentity( artifact_id=pin.artifact_id, revision=pin.revision, @@ -115,7 +120,7 @@ def shard_identity_from_native_report(inputs: NativeIdentityInputs) -> ShardIden tokenizer_revision=inputs.tokenizer_revision, architecture_adapter=recipe.architecture_adapter, backend_id=recipe.backend_id, - runtime_version=recipe.runtime_version, + runtime_version=runtime_version, boundary_schema_version=recipe.boundary_schema_version, protocol_schema_version=recipe.protocol_schema_version, recipe_id=recipe.recipe_id, diff --git a/packages/node/meshnet_node/runtime_recipe.py b/packages/node/meshnet_node/runtime_recipe.py index fbb947d..b5e8ce2 100644 --- a/packages/node/meshnet_node/runtime_recipe.py +++ b/packages/node/meshnet_node/runtime_recipe.py @@ -125,6 +125,10 @@ _AXIS_MISMATCH: Mapping[str, str] = { } _HEX64 = re.compile(r"^[0-9a-f]{64}$") +_LLAMA_CPP_RUNTIME_PIN = re.compile( + r"^llama\.cpp@[0-9a-f]{40}\+patchstack\.[0-9a-f]{64}$" +) +_LLAMA_CPP_BACKEND_IDS = frozenset({"llama.cpp", "llama-cpp"}) # A revision that can move is not a pin. DGR-017 learned this on the artifact; # it is just as true of a tokenizer. @@ -194,6 +198,17 @@ def _require_pin(value: Any, what: str) -> str: return text +def _require_runtime_pin(value: Any, backend_id: Any) -> str: + text = _require_pin(value, "recipe.runtime_version") + backend = _require_text(backend_id, "recipe.backend_id").strip().lower() + if backend in _LLAMA_CPP_BACKEND_IDS and not _LLAMA_CPP_RUNTIME_PIN.fullmatch(text): + raise RecipeIdentityError( + "'recipe.runtime_version' for llama.cpp must be " + "'llama.cpp@<40-hex commit>+patchstack.<64-hex digest>'" + ) + return text + + def _as_mapping(value: Any, what: str) -> Mapping[str, Any]: if not isinstance(value, Mapping): raise RecipeIdentityError( @@ -405,7 +420,7 @@ class RuntimeRecipe: else: _require_text(value, f"recipe.{axis}") _require_pin(self.tokenizer_revision, "recipe.tokenizer_revision") - _require_pin(self.runtime_version, "recipe.runtime_version") + _require_runtime_pin(self.runtime_version, self.backend_id) _require_text(self.recipe_id, "recipe.recipe_id") _require_text(self.recipe_version, "recipe.recipe_version") _require_text(self.catalogue_version, "recipe.catalogue_version") diff --git a/packages/tracker/meshnet_tracker/recipe.py b/packages/tracker/meshnet_tracker/recipe.py index 2fe5fbe..50b5cc3 100644 --- a/packages/tracker/meshnet_tracker/recipe.py +++ b/packages/tracker/meshnet_tracker/recipe.py @@ -70,6 +70,10 @@ STATUS_CERTIFIED = "certified" MIN_CERTIFYING_NODES = 2 _HEX64 = re.compile(r"^[0-9a-f]{64}$") +_LLAMA_CPP_RUNTIME_PIN = re.compile( + r"^llama\.cpp@[0-9a-f]{40}\+patchstack\.[0-9a-f]{64}$" +) +_LLAMA_CPP_BACKEND_IDS = frozenset({"llama.cpp", "llama-cpp"}) _MOVING_REFS = frozenset({"main", "master", "head", "latest", "dev", "trunk"}) @@ -128,6 +132,17 @@ def _pin(value: Any, what: str) -> str: return text +def _runtime_pin(value: Any, backend_id: Any) -> str: + text = _pin(value, "recipe.runtime_version") + backend = _text(backend_id, "recipe.backend_id").strip().lower() + if backend in _LLAMA_CPP_BACKEND_IDS and not _LLAMA_CPP_RUNTIME_PIN.fullmatch(text): + raise RecipeIdentityError( + "'recipe.runtime_version' for llama.cpp must bind a 40-hex commit " + "and a 64-hex ordered patch-stack digest" + ) + return text + + def _mapping(value: Any, what: str) -> Mapping[str, Any]: if not isinstance(value, Mapping): raise RecipeIdentityError(f"{what!r} must be a JSON object") @@ -333,7 +348,7 @@ def parse_identity(data: Any) -> PresentedIdentity: else: axes[axis] = _text(value, f"recipe.{axis}") _pin(axes["tokenizer_revision"], "recipe.tokenizer_revision") - _pin(axes["runtime_version"], "recipe.runtime_version") + _runtime_pin(axes["runtime_version"], axes["backend_id"]) identity = PresentedIdentity( artifact_id=_text(artifact.get("artifact_id"), "artifact.artifact_id"), diff --git a/scripts/gen_recipe_fingerprint_vectors.py b/scripts/gen_recipe_fingerprint_vectors.py index 702ead3..fcbba17 100644 --- a/scripts/gen_recipe_fingerprint_vectors.py +++ b/scripts/gen_recipe_fingerprint_vectors.py @@ -49,7 +49,7 @@ _RECIPE = RuntimeRecipe( tokenizer_revision="0123456789abcdef", architecture_adapter="llama/range-v1", backend_id="llama.cpp", - runtime_version="llama.cpp@deadbeef+meshnet.1", + runtime_version="llama.cpp@" + "d" * 40 + "+patchstack." + "e" * 64, recipe_id="example-gguf", recipe_version="1", catalogue_version="2026.07.1", diff --git a/tests/data/recipe_fingerprint_vectors.json b/tests/data/recipe_fingerprint_vectors.json index 353d15b..04d6a84 100644 --- a/tests/data/recipe_fingerprint_vectors.json +++ b/tests/data/recipe_fingerprint_vectors.json @@ -8,9 +8,9 @@ "model_artifact_digest": "8a0f43d6aa49d77834bdb47bcae9f42c886b7ccfe0ac014932b2a2b38697a47b", "recipe_id": "example-gguf", "recipe_version": "1", - "runtime_recipe_digest": "9b14d70b0835a6428457e4888d453649dd0d2e41fc8ac9d84d232c8c237e68fa" + "runtime_recipe_digest": "63001e0efeada5b97f2f3562562dc0fd2d9bd8904dc6b7b99a71d98f3e938bd0" }, - "fingerprint_proto_hex": "0a40386130663433643661613439643737383334626462343762636165396634326338383662376363666530616330313439333262326132623338363937613437621240396231346437306230383335613634323834353765343838386434353336343964643064326534316663386163396438346432333263386332333765363866611a0c6578616d706c652d676775662201312a09323032362e30372e31", + "fingerprint_proto_hex": "0a40386130663433643661613439643737383334626462343762636165396634326338383662376363666530616330313439333262326132623338363937613437621240363330303165306566656164613562393766326633353632353632646330666432643962643839303464633662376239396137316439386633653933386264301a0c6578616d706c652d676775662201312a09323032362e30372e31", "identity": { "artifact": { "architecture": "dense-llama", @@ -26,7 +26,7 @@ "model_artifact_digest": "8a0f43d6aa49d77834bdb47bcae9f42c886b7ccfe0ac014932b2a2b38697a47b", "recipe_id": "example-gguf", "recipe_version": "1", - "runtime_recipe_digest": "9b14d70b0835a6428457e4888d453649dd0d2e41fc8ac9d84d232c8c237e68fa" + "runtime_recipe_digest": "63001e0efeada5b97f2f3562562dc0fd2d9bd8904dc6b7b99a71d98f3e938bd0" }, "recipe": { "activation_dtype": "bfloat16", @@ -40,7 +40,7 @@ "protocol_schema_version": 1, "recipe_id": "example-gguf", "recipe_version": "1", - "runtime_version": "llama.cpp@deadbeef+meshnet.1", + "runtime_version": "llama.cpp@dddddddddddddddddddddddddddddddddddddddd+patchstack.eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", "tokenizer_revision": "0123456789abcdef", "weight_quantization": "Q4_K_M" }, @@ -58,9 +58,9 @@ "model_artifact_digest": "8a0f43d6aa49d77834bdb47bcae9f42c886b7ccfe0ac014932b2a2b38697a47b", "recipe_id": "example-gguf", "recipe_version": "1", - "runtime_recipe_digest": "9b14d70b0835a6428457e4888d453649dd0d2e41fc8ac9d84d232c8c237e68fa" + "runtime_recipe_digest": "63001e0efeada5b97f2f3562562dc0fd2d9bd8904dc6b7b99a71d98f3e938bd0" }, - "fingerprint_proto_hex": "0a40386130663433643661613439643737383334626462343762636165396634326338383662376363666530616330313439333262326132623338363937613437621240396231346437306230383335613634323834353765343838386434353336343964643064326534316663386163396438346432333263386332333765363866611a0c6578616d706c652d676775662201312a09323032362e30372e31", + "fingerprint_proto_hex": "0a40386130663433643661613439643737383334626462343762636165396634326338383662376363666530616330313439333262326132623338363937613437621240363330303165306566656164613562393766326633353632353632646330666432643962643839303464633662376239396137316439386633653933386264301a0c6578616d706c652d676775662201312a09323032362e30372e31", "identity": { "artifact": { "architecture": "dense-llama", @@ -80,7 +80,7 @@ "model_artifact_digest": "8a0f43d6aa49d77834bdb47bcae9f42c886b7ccfe0ac014932b2a2b38697a47b", "recipe_id": "example-gguf", "recipe_version": "1", - "runtime_recipe_digest": "9b14d70b0835a6428457e4888d453649dd0d2e41fc8ac9d84d232c8c237e68fa" + "runtime_recipe_digest": "63001e0efeada5b97f2f3562562dc0fd2d9bd8904dc6b7b99a71d98f3e938bd0" }, "recipe": { "activation_dtype": "bfloat16", @@ -94,7 +94,7 @@ "protocol_schema_version": 1, "recipe_id": "example-gguf", "recipe_version": "1", - "runtime_version": "llama.cpp@deadbeef+meshnet.1", + "runtime_version": "llama.cpp@dddddddddddddddddddddddddddddddddddddddd+patchstack.eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", "tokenizer_revision": "0123456789abcdef", "weight_quantization": "Q4_K_M" }, diff --git a/tests/test_native_identity_emission.py b/tests/test_native_identity_emission.py index 5cc19e9..903c78a 100644 --- a/tests/test_native_identity_emission.py +++ b/tests/test_native_identity_emission.py @@ -15,6 +15,7 @@ from meshnet_node.native_backend import ( shard_identity_from_native_report, ) from meshnet_node.native_protocol import SCHEMA_VERSION, pb +from meshnet_node.runtime_pin import load_runtime_pin from meshnet_node.recipe_manifest import parse_recipe_manifest from meshnet_tracker.capability import STATE_UNCERTIFIED, evaluate_report @@ -42,7 +43,6 @@ def _inputs(**changes: object) -> NativeIdentityInputs: kv_layout="llama-kv-v1", architecture_adapter="dense-llama-v1", backend_id="llama-cpp", - runtime_version="llama.cpp:e920c523", recipe_id="native", recipe_version="1", catalogue_version="2026.07.1", @@ -84,6 +84,7 @@ def test_native_identity_uses_loaded_report_not_a_caller_range(): assert (identity.shard_start, identity.shard_end) == (2, 6) assert identity.artifact.architecture == "llama" assert identity.artifact.layer_count == 8 + assert identity.recipe.runtime_version == load_runtime_pin().runtime_version def test_native_identity_requires_an_immutable_pin_and_gguf_range(): diff --git a/tests/test_runtime_pin_identity.py b/tests/test_runtime_pin_identity.py index 7d8ec75..f66802f 100644 --- a/tests/test_runtime_pin_identity.py +++ b/tests/test_runtime_pin_identity.py @@ -42,7 +42,7 @@ def _recipe(**changes: object) -> RuntimeRecipe: "tokenizer_revision": "0123456789abcdef", "architecture_adapter": "llama/range-v1", "backend_id": "llama.cpp", - "runtime_version": "llama.cpp@deadbeef+meshnet.1", + "runtime_version": "llama.cpp@" + "d" * 40 + "+patchstack." + "e" * 64, "recipe_id": "example-gguf", "recipe_version": "1", "catalogue_version": "2026.07.1", @@ -276,3 +276,37 @@ def test_tracker_rejects_a_moving_runtime_version(): doc.pop("fingerprint", None) with pytest.raises(TrackerRecipeIdentityError, match="moving reference"): parse_identity(doc) + + +@pytest.mark.parametrize( + "forged", + [ + "llama.cpp@master+patchstack.not-a-digest", + "llama.cpp@e920c523+patchstack.forged", + "release-that-operator-typed", + ], +) +def test_node_recipe_rejects_noncanonical_llama_runtime_pins(forged): + with pytest.raises(RecipeIdentityError, match="40-hex commit"): + _recipe(runtime_version=forged) + + +@pytest.mark.parametrize( + "forged", + [ + "llama.cpp@master+patchstack.not-a-digest", + "llama.cpp@e920c523+patchstack.forged", + "release-that-operator-typed", + ], +) +def test_tracker_rejects_noncanonical_llama_runtime_pins(forged): + vectors = json.loads( + (Path(__file__).parent / "data" / "recipe_fingerprint_vectors.json").read_text( + encoding="utf-8" + ) + ) + doc = json.loads(json.dumps(vectors["vectors"][0]["identity"])) + doc["recipe"]["runtime_version"] = forged + doc.pop("fingerprint", None) + with pytest.raises(TrackerRecipeIdentityError, match="40-hex commit"): + parse_identity(doc) diff --git a/tests/test_runtime_recipe_identity.py b/tests/test_runtime_recipe_identity.py index 94c216c..fc1abe7 100644 --- a/tests/test_runtime_recipe_identity.py +++ b/tests/test_runtime_recipe_identity.py @@ -65,7 +65,7 @@ def _recipe(**changes: object) -> RuntimeRecipe: "tokenizer_revision": "0123456789abcdef", "architecture_adapter": "llama/range-v1", "backend_id": "llama.cpp", - "runtime_version": "llama.cpp@deadbeef+meshnet.1", + "runtime_version": "llama.cpp@" + "d" * 40 + "+patchstack." + "e" * 64, "recipe_id": "example-gguf", "recipe_version": "1", "catalogue_version": "2026.07.1", @@ -241,7 +241,7 @@ def test_committed_vectors_cover_a_whole_model_and_a_derivative_shard(): ("tokenizer_revision", "fedcba9876543210"), ("architecture_adapter", "llama/range-v2"), ("backend_id", "other-backend"), - ("runtime_version", "llama.cpp@other+meshnet.1"), + ("runtime_version", "llama.cpp@" + "c" * 40 + "+patchstack." + "b" * 64), ("boundary_schema_version", 2), ("protocol_schema_version", 2), ],