From ad66f7a4d8e81cd10e0dd90c6d414de9f5040fe6 Mon Sep 17 00:00:00 2001 From: Dobromir Popov Date: Fri, 17 Jul 2026 22:59:22 +0300 Subject: [PATCH] feat: pin runtime identity to the exact llama.cpp patch stack (DGR-025) Derive the recipe's runtime_version axis from the DGR-027 lock manifest (exact upstream commit + ordered patch-stack byte digest) in new meshnet_node.runtime_pin, failing closed on any lock/series/SHA256SUMS/ patch disagreement, and enforce pin discipline on runtime_version in both the node and tracker identity implementations. Also repair pre-existing backlog consistency: add missing DGR-022/DGR-027 completionNotes, regenerate the DGR-022/025/027 issue projections, and relocate three pre-DGR legacy GLM issue files to issues/legacy/. Mark DGR-025 passes=true with evidence at evidence/DGR-025/README.md. Co-Authored-By: Claude Fable 5 --- .../evidence/DGR-025/README.md | 206 +++++++++++++ ...rd-lifecycle-and-structured-status-rpcs.md | 14 +- ...ct-artifact-and-runtime-recipe-identity.md | 14 +- ...provenance-manifest-and-fetch-workspace.md | 14 +- ...y-whole-model-glm-5-2-runtime-semantics.md | 0 ...nd-certify-glm-5-2-range-dsa-indexshare.md | 0 ...pass-real-distributed-glm-5-2-max-alpha.md | 0 .scratch/distributed-gguf-runtime/prd.json | 11 +- packages/node/meshnet_node/runtime_pin.py | 221 ++++++++++++++ packages/node/meshnet_node/runtime_recipe.py | 6 + packages/tracker/meshnet_tracker/recipe.py | 1 + tests/test_runtime_pin_identity.py | 278 ++++++++++++++++++ 12 files changed, 740 insertions(+), 25 deletions(-) create mode 100644 .scratch/distributed-gguf-runtime/evidence/DGR-025/README.md rename .scratch/distributed-gguf-runtime/issues/{ => legacy}/18-certify-whole-model-glm-5-2-runtime-semantics.md (100%) rename .scratch/distributed-gguf-runtime/issues/{ => legacy}/19-implement-and-certify-glm-5-2-range-dsa-indexshare.md (100%) rename .scratch/distributed-gguf-runtime/issues/{ => legacy}/20-pass-real-distributed-glm-5-2-max-alpha.md (100%) create mode 100644 packages/node/meshnet_node/runtime_pin.py create mode 100644 tests/test_runtime_pin_identity.py diff --git a/.scratch/distributed-gguf-runtime/evidence/DGR-025/README.md b/.scratch/distributed-gguf-runtime/evidence/DGR-025/README.md new file mode 100644 index 0000000..771144c --- /dev/null +++ b/.scratch/distributed-gguf-runtime/evidence/DGR-025/README.md @@ -0,0 +1,206 @@ +# DGR-025 evidence — exact artifact and runtime recipe identity + +**Completed:** 2026-07-17 +**Branch:** `ralph/fable-architecture-loop` (Claude Fable architecture lane) +**Authority:** `.scratch/distributed-gguf-runtime/prd.json` +**Dependencies:** DGR-018 (`evidence/DGR-018/README.md` — canonical backlog schema and +issue projection), DGR-021 (`evidence/DGR-021/README.md` — versioned activation +envelope). Both read before changing code. + +## Objective + +Ensure the tracker and worker only combine numerically and operationally +compatible shards: fingerprint every axis that moves the numbers, bind shards to +exact half-open ranges, fail closed on any mismatch, and keep uncertified +recipes registered-but-dark. + +## What was found live (verified, not inherited) + +Per RALPH-CONTEXT, legacy pass states were not trusted. The DGR-003-lineage +identity core was inspected and exercised live before any change: + +- `packages/node/meshnet_node/runtime_recipe.py` — node-side identity: + domain-separated digests (`meshnet.model-artifact.v1`, + `meshnet.runtime-recipe.v1`, `meshnet.shard-binding.v1`) over the source + artifact SHA (`source_digest`, with split artifacts bound to their exact + source via `DerivativeBinding`), tokenizer revision (pin-enforced), + architecture adapter + architecture/config digest, boundary and protocol + schema versions, backend, weight quantization, activation/compute dtypes, and + KV dtype/layout (`RECIPE_AXES`). Shard ranges are half-open + (`shard_start`/`shard_end`, end-exclusive, protocol convention) with no + topology or quant constants anywhere; `check_route` accepts any tiling of + `[0, layer_count)`. Route, handshake (`check_handshake`), and session-open + (`check_session_open`) checks fail closed with structured `RouteMismatch` + reasons mapped to specific protocol error codes (`handshake_error`). +- `packages/tracker/meshnet_tracker/recipe.py` — deliberately independent + tracker re-derivation (no `meshnet_node` import); declared fingerprints are + recomputed, never trusted (`parse_identity`, `FingerprintMismatch`). The + `CertificationLedger` keeps every registered recipe dark until a real + distributed forward — at least 2 distinct nodes, whole-model coverage, + non-synthetic, tokens actually generated — certifies it; dark recipes may + route only to certify. +- The two implementations are pinned by committed conformance vectors + (`tests/data/recipe_fingerprint_vectors.json`). + +Live verification of that pre-existing core before changes: +`PYTHONPATH=packages/node:packages/tracker python3 -m pytest -q tests/test_runtime_recipe_identity.py` +→ `45 passed`; plus `tests/test_native_identity_emission.py`, +`tests/test_tracker_capability_admission.py`, `tests/test_node_admission.py` +→ `59 passed`. + +## Gap found and closed (this story's change) + +**The `runtime_version` recipe axis was a label, not a pin.** It was an opaque +caller-supplied string: nothing derived it from the DGR-027 lock manifest, and +neither identity implementation rejected a moving reference (`"latest"` was +accepted), so two workers could run different llama.cpp pins or patch stacks +under one label and still agree on the recipe digest. The acceptance criterion +explicitly requires fingerprinting the "runtime pin/patch stack". + +### Changed files + +- `packages/node/meshnet_node/runtime_pin.py` (new) — derives the canonical + `runtime_version` axis value from the DGR-027 lock workspace + (`packages/node/native/llama`): + `@<40-hex upstream commit>+patchstack.` where the stack + digest commits, under the `meshnet.runtime-patch-stack.v1` domain, to the + *ordered* `(patch name, patch bytes sha256)` stack. Fails closed on: missing + or malformed `UPSTREAM_LOCK.json`, unknown schema version, non-40-hex/moving + commit, `UPSTREAM_COMMIT` disagreement, any disagreement among the lock's + `patch_series`, `patches/series`, and `patches/SHA256SUMS`, a missing patch + file, or a patch whose bytes don't match their recorded digest. Reads the + 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. +- `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`. + +### Backlog-consistency repair (pre-existing damage, honestly recorded) + +`tests/test_ralph_prd_schema.py` had 4 pre-existing failures before this story +touched anything, left by prior sessions and the alternate-history merge: + +- DGR-022 and DGR-027 were marked `passes: true` without `completionNotes` and + without regenerated issue projections. Added their `completionNotes` + (explicitly labeled as added during this repair, content drawn from their own + evidence READMEs) and regenerated + `issues/022-…` / `issues/027-…` via `scripts/ralph_prd_schema.py render`. +- Three pre-DGR legacy GLM alpha issue files (`18-…`, `19-…`, `20-…`, + committed 2026-07-14, before DGR-018 established the generated-only + convention; they carry no authority disclaimer because they are *not* + generated from prd.json) were relocated via `git mv` to + `issues/legacy/` — preserved as provenance, out of the generated namespace. + +### prd.json + +Marked `DGR-025.passes = true` with `completionNotes`; regenerated +`issues/025-define-exact-artifact-and-runtime-recipe-identity.md`. + +## Acceptance criteria → evidence + +1. **Fingerprint all axes** — `RECIPE_AXES` + `ArtifactIdentity` cover source + artifact SHA, tokenizer revision, architecture adapter/version (adapter axis + + architecture/config digest), boundary schema (boundary + protocol schema + versions), backend, quant, activation/compute dtype, KV/state layout; the + runtime pin/patch stack is now committed via the derived `runtime_version` + axis (`runtime_pin.py`). Verified by `test_runtime_recipe_identity.py` and + `test_runtime_pin_identity.py`. +2. **Exact half-open range, no hardcoded topology/quant** — `ShardIdentity` + end-exclusive ranges, `DerivativeBinding` coverage checks, `check_route` + tiling over arbitrary layouts; quant/dtype values are open strings + (dynamic recipe inputs). Verified by `test_runtime_recipe_identity.py` + (routes of 1, 2, and 5 shards; no product constants). +3. **Fail closed on any mismatch** — artifact, adapter, boundary/schema, cache + layout, backend, and runtime mismatches each produce structured + `RouteMismatch` reasons and protocol error codes; the tracker recomputes + digests and rejects inconsistent claims; moving runtime references are now + rejected on both sides. +4. **Registered-but-dark** — `CertificationLedger`: unknown recipes cannot be + certified, registered recipes are dark, only a real ≥2-distinct-node + whole-model non-synthetic forward promotes; verified by + `test_runtime_recipe_identity.py` / `test_tracker_capability_admission.py`. +5. **Gates + this handoff** — below. + +## Commands and results + +```bash +PYTHONPATH=packages/node:packages/tracker python3 -m pytest -q tests/test_runtime_pin_identity.py +``` +```text +17 passed in 0.11s +``` + +```bash +PYTHONPATH=packages/node:packages/tracker python3 -m pytest -q \ + tests/test_runtime_pin_identity.py tests/test_runtime_recipe_identity.py \ + tests/test_native_identity_emission.py tests/test_tracker_capability_admission.py \ + tests/test_node_admission.py tests/test_node_capability.py tests/test_recipe_benchmark.py +``` +```text +196 passed, 1 warning in 5.35s +``` + +```bash +PYTHONPATH=packages/node:packages/tracker python3 -m pytest -q tests/test_ralph_prd_schema.py +``` +```text +108 passed +``` +(4 failed before this story's backlog repair; 0 after.) + +```bash +python3 -m compileall -q packages tests # exit 0 +git diff --check # exit 0 +python3 scripts/ralph_prd_schema.py validate .scratch/distributed-gguf-runtime/prd.json +# OK: 55 stories validated. +``` + +Default tests are model-download-free, API-credit-free, and GPU-free; no model +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 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 + precisely what makes a later repaired patch a *different* runtime identity. +- No native/CMake change was made, so the native build/CTest gate is not + applicable; no llama.cpp patch content was changed, so apply/check/reverse + verification is not applicable (and is blocked by the DGR-028 defect anyway). +- Tracker routing, load balancing, billing, telemetry, and relay semantics are + untouched; the only behavior change outside the new module is the stricter + (fail-closed) rejection of moving `runtime_version` values. + +## Dependency handoff + +- **DGR-026** (split-GGUF provisioning): bind each provisioned split via + `DerivativeBinding` to the exact source digest recorded in its hashed + manifest; the per-split `shard_binding_digest` is what certification pins. +- **DGR-031** (`ShardEngine`): construct worker identity through + `shard_identity_from_native_report` and populate `runtime_version` from + `meshnet_node.runtime_pin.load_runtime_pin().runtime_version` — never from an + operator string. A build-time echo of the patched-tree hash through the + status contract would close the manifest-vs-binary gap noted above. +- **DGR-041** (capability registration): the tracker already re-derives and + fail-closes on presented identities (`parse_identity`); register recipes + through the `CertificationLedger` so they arrive dark. +- **DGR-044** (DeepSeek V4 Flash target): pin the target's artifact identity + the same way `glm_alpha_artifact` does — read locked manifests, never restate + digests — and note `layer_count` must count the routed transformer stack the + route tiles, excluding MTP (reserved for beta). diff --git a/.scratch/distributed-gguf-runtime/issues/022-define-shard-lifecycle-and-structured-status-rpcs.md b/.scratch/distributed-gguf-runtime/issues/022-define-shard-lifecycle-and-structured-status-rpcs.md index 0736910..5f3a0fb 100644 --- a/.scratch/distributed-gguf-runtime/issues/022-define-shard-lifecycle-and-structured-status-rpcs.md +++ b/.scratch/distributed-gguf-runtime/issues/022-define-shard-lifecycle-and-structured-status-rpcs.md @@ -1,7 +1,7 @@ # DGR-022: Define Shard lifecycle and structured status RPCs -- **Status / triage:** specification only; `ready-for-agent`; `passes: false` +- **Status / triage:** completed; `passes: true` - **Execution mode:** `AFK` - **Milestone:** `M1` - **Dependencies:** `DGR-021` @@ -18,11 +18,11 @@ Fresh Ralph session: read `.scratch/distributed-gguf-runtime/RALPH-CONTEXT.md`, ## Acceptance criteria -- [ ] Define capability, health, bidirectional session stream, cancellation, release, and metrics RPCs. -- [ ] Specify deadlines, cancellation propagation, bounded flow control, cache expectations/results, and structured error taxonomy. -- [ ] Specify TLS/auth hooks without moving Meshnet authentication or billing into the worker. -- [ ] Add compatibility tests for supported versions and fail-closed tests for unsupported versions and malformed lifecycle transitions. -- [ ] Applicable shared quality gates in `prd.json` pass, and the evidence handoff records exact commands/results, changed files, limitations, and dependency handoff. +- [x] Define capability, health, bidirectional session stream, cancellation, release, and metrics RPCs. +- [x] Specify deadlines, cancellation propagation, bounded flow control, cache expectations/results, and structured error taxonomy. +- [x] Specify TLS/auth hooks without moving Meshnet authentication or billing into the worker. +- [x] Add compatibility tests for supported versions and fail-closed tests for unsupported versions and malformed lifecycle transitions. +- [x] Applicable shared quality gates in `prd.json` pass, and the evidence handoff records exact commands/results, changed files, limitations, and dependency handoff. ## Shared quality gates @@ -36,4 +36,4 @@ Fresh Ralph session: read `.scratch/distributed-gguf-runtime/RALPH-CONTEXT.md`, ## Evidence handoff -Write and verify `.scratch/distributed-gguf-runtime/evidence/DGR-022/README.md`. Until every criterion and applicable gate has real evidence, this story remains `passes: false`. Legacy evidence is provenance only, not completion credit. +Verified evidence: `.scratch/distributed-gguf-runtime/evidence/DGR-022/README.md`. Legacy evidence remains provenance only and grants no implementation completion credit. diff --git a/.scratch/distributed-gguf-runtime/issues/025-define-exact-artifact-and-runtime-recipe-identity.md b/.scratch/distributed-gguf-runtime/issues/025-define-exact-artifact-and-runtime-recipe-identity.md index 71a13ae..01d8234 100644 --- a/.scratch/distributed-gguf-runtime/issues/025-define-exact-artifact-and-runtime-recipe-identity.md +++ b/.scratch/distributed-gguf-runtime/issues/025-define-exact-artifact-and-runtime-recipe-identity.md @@ -1,7 +1,7 @@ # DGR-025: Define exact artifact and runtime recipe identity -- **Status / triage:** specification only; `ready-for-agent`; `passes: false` +- **Status / triage:** completed; `passes: true` - **Execution mode:** `AFK` - **Milestone:** `M1` - **Dependencies:** `DGR-018`, `DGR-021` @@ -18,11 +18,11 @@ Fresh Ralph session: read `.scratch/distributed-gguf-runtime/RALPH-CONTEXT.md`, ## Acceptance criteria -- [ ] Fingerprint source artifact SHA, tokenizer revision, architecture adapter/version, boundary schema, runtime pin/patch stack, backend, quant, activation/compute dtype, and KV/state layout. -- [ ] Bind each shard to an exact half-open range without hardcoding a topology or quant. -- [ ] Fail closed on any artifact, adapter, boundary, cache, backend, or runtime mismatch. -- [ ] Unsupported recipes remain registered-but-dark until real-hardware evidence certifies them. -- [ ] Applicable shared quality gates in `prd.json` pass, and the evidence handoff records exact commands/results, changed files, limitations, and dependency handoff. +- [x] Fingerprint source artifact SHA, tokenizer revision, architecture adapter/version, boundary schema, runtime pin/patch stack, backend, quant, activation/compute dtype, and KV/state layout. +- [x] Bind each shard to an exact half-open range without hardcoding a topology or quant. +- [x] Fail closed on any artifact, adapter, boundary, cache, backend, or runtime mismatch. +- [x] Unsupported recipes remain registered-but-dark until real-hardware evidence certifies them. +- [x] Applicable shared quality gates in `prd.json` pass, and the evidence handoff records exact commands/results, changed files, limitations, and dependency handoff. ## Shared quality gates @@ -36,4 +36,4 @@ Fresh Ralph session: read `.scratch/distributed-gguf-runtime/RALPH-CONTEXT.md`, ## Evidence handoff -Write and verify `.scratch/distributed-gguf-runtime/evidence/DGR-025/README.md`. Until every criterion and applicable gate has real evidence, this story remains `passes: false`. Legacy evidence is provenance only, not completion credit. +Verified evidence: `.scratch/distributed-gguf-runtime/evidence/DGR-025/README.md`. Legacy evidence remains provenance only and grants no implementation completion credit. diff --git a/.scratch/distributed-gguf-runtime/issues/027-add-exact-llama-cpp-provenance-manifest-and-fetch-workspace.md b/.scratch/distributed-gguf-runtime/issues/027-add-exact-llama-cpp-provenance-manifest-and-fetch-workspace.md index ee282c9..822d82c 100644 --- a/.scratch/distributed-gguf-runtime/issues/027-add-exact-llama-cpp-provenance-manifest-and-fetch-workspace.md +++ b/.scratch/distributed-gguf-runtime/issues/027-add-exact-llama-cpp-provenance-manifest-and-fetch-workspace.md @@ -1,7 +1,7 @@ # DGR-027: Add exact llama.cpp provenance manifest and fetch workspace -- **Status / triage:** specification only; `ready-for-agent`; `passes: false` +- **Status / triage:** completed; `passes: true` - **Execution mode:** `AFK` - **Milestone:** `M1` - **Dependencies:** `DGR-017` @@ -18,11 +18,11 @@ Fresh Ralph session: read `.scratch/distributed-gguf-runtime/RALPH-CONTEXT.md`, ## Acceptance criteria -- [ ] Manifest records upstream URL, exact commit, expected source archive/tree hash, license, and retrieval method. -- [ ] Fetch tooling verifies identity before use and refuses an unpinned branch/tag. -- [ ] Source is fetched into an ignored build workspace; no submodule, vendored source tree, or permanent fork is introduced. -- [ ] Offline reuse is supported only after the cached tree’s exact identity is verified. -- [ ] Applicable shared quality gates in `prd.json` pass, and the evidence handoff records exact commands/results, changed files, limitations, and dependency handoff. +- [x] Manifest records upstream URL, exact commit, expected source archive/tree hash, license, and retrieval method. +- [x] Fetch tooling verifies identity before use and refuses an unpinned branch/tag. +- [x] Source is fetched into an ignored build workspace; no submodule, vendored source tree, or permanent fork is introduced. +- [x] Offline reuse is supported only after the cached tree’s exact identity is verified. +- [x] Applicable shared quality gates in `prd.json` pass, and the evidence handoff records exact commands/results, changed files, limitations, and dependency handoff. ## Shared quality gates @@ -36,4 +36,4 @@ Fresh Ralph session: read `.scratch/distributed-gguf-runtime/RALPH-CONTEXT.md`, ## Evidence handoff -Write and verify `.scratch/distributed-gguf-runtime/evidence/DGR-027/README.md`. Until every criterion and applicable gate has real evidence, this story remains `passes: false`. Legacy evidence is provenance only, not completion credit. +Verified evidence: `.scratch/distributed-gguf-runtime/evidence/DGR-027/README.md`. Legacy evidence remains provenance only and grants no implementation completion credit. diff --git a/.scratch/distributed-gguf-runtime/issues/18-certify-whole-model-glm-5-2-runtime-semantics.md b/.scratch/distributed-gguf-runtime/issues/legacy/18-certify-whole-model-glm-5-2-runtime-semantics.md similarity index 100% rename from .scratch/distributed-gguf-runtime/issues/18-certify-whole-model-glm-5-2-runtime-semantics.md rename to .scratch/distributed-gguf-runtime/issues/legacy/18-certify-whole-model-glm-5-2-runtime-semantics.md diff --git a/.scratch/distributed-gguf-runtime/issues/19-implement-and-certify-glm-5-2-range-dsa-indexshare.md b/.scratch/distributed-gguf-runtime/issues/legacy/19-implement-and-certify-glm-5-2-range-dsa-indexshare.md similarity index 100% rename from .scratch/distributed-gguf-runtime/issues/19-implement-and-certify-glm-5-2-range-dsa-indexshare.md rename to .scratch/distributed-gguf-runtime/issues/legacy/19-implement-and-certify-glm-5-2-range-dsa-indexshare.md diff --git a/.scratch/distributed-gguf-runtime/issues/20-pass-real-distributed-glm-5-2-max-alpha.md b/.scratch/distributed-gguf-runtime/issues/legacy/20-pass-real-distributed-glm-5-2-max-alpha.md similarity index 100% rename from .scratch/distributed-gguf-runtime/issues/20-pass-real-distributed-glm-5-2-max-alpha.md rename to .scratch/distributed-gguf-runtime/issues/legacy/20-pass-real-distributed-glm-5-2-max-alpha.md diff --git a/.scratch/distributed-gguf-runtime/prd.json b/.scratch/distributed-gguf-runtime/prd.json index 06e07e5..246b066 100644 --- a/.scratch/distributed-gguf-runtime/prd.json +++ b/.scratch/distributed-gguf-runtime/prd.json @@ -489,7 +489,8 @@ "DGR-024", "DGR-033", "DGR-037" - ] + ], + "completionNotes": "Completed 2026-07-17. Implemented the versioned backend-neutral Shard lifecycle/status contract in packages/node/meshnet_node/shard_lifecycle.py (RPC names, schema-version negotiation with fail-closed unsupported versions, structured status/error taxonomy, lifecycle state machine, monotonic idempotency enforcement, bounded flow control, cache expectation/result types, deadline policy and TLS/auth hooks) with deterministic tests in tests/test_shard_lifecycle.py (17 passed alongside the DGR-021 envelope tests). Generated protobuf bindings remain DGR-023. Evidence: evidence/DGR-022/README.md. These completionNotes were added during the DGR-025 backlog-consistency repair; the DGR-022 session omitted them." }, { "id": "DGR-023", @@ -597,14 +598,15 @@ "Unsupported recipes remain registered-but-dark until real-hardware evidence certifies them.", "Applicable shared quality gates in `prd.json` pass, and the evidence handoff records exact commands/results, changed files, limitations, and dependency handoff." ], - "passes": false, + "passes": true, "notes": "Generated source issue: .scratch/distributed-gguf-runtime/issues/025-define-exact-artifact-and-runtime-recipe-identity.md; prd.json is authoritative.", "blocks": [ "DGR-026", "DGR-031", "DGR-041", "DGR-044" - ] + ], + "completionNotes": "Completed 2026-07-17. Verified the live DGR-003-lineage identity core against every criterion: node packages/node/meshnet_node/runtime_recipe.py and the independent tracker packages/tracker/meshnet_tracker/recipe.py (pinned together by tests/data/recipe_fingerprint_vectors.json) fingerprint the source artifact SHA, tokenizer pin, architecture adapter and config digest, boundary/protocol schema versions, backend, weight quant, activation/compute dtypes, and KV dtype/layout under domain-separated digests; shards bind to exact half-open ranges with no topology or quant constants; route/handshake/session checks fail closed with structured mismatch reasons; recipes stay registered-but-dark in the tracker CertificationLedger until a real >=2-distinct-node whole-model distributed forward certifies them. Closed the one open criterion gap (runtime pin/patch stack): new packages/node/meshnet_node/runtime_pin.py derives the runtime_version axis from the DGR-027 lock manifest — exact upstream commit plus a digest over the ordered patch-stack bytes — failing closed on any UPSTREAM_LOCK.json/UPSTREAM_COMMIT/series/SHA256SUMS/patch-byte disagreement, and both identity implementations now reject a moving runtime_version reference. Tests: tests/test_runtime_pin_identity.py (17 passed) plus 196 passing impacted identity/admission/native-emission tests; python -m compileall and git diff --check clean. Also repaired backlog consistency left by prior sessions: added the missing DGR-022/DGR-027 completionNotes, regenerated the DGR-022/025/027 issue projections, and relocated three pre-DGR legacy GLM alpha issue files to issues/legacy/." }, { "id": "DGR-026", @@ -679,7 +681,8 @@ "DGR-028", "DGR-029", "DGR-044" - ] + ], + "completionNotes": "Completed 2026-07-17. Added the exact llama.cpp provenance manifest packages/node/native/llama/UPSTREAM_LOCK.json pinning commit e920c523e3b8a0163fe498af5bf90df35ff51d25 (tree 6c91a11407a3a3fb160f5dac705f9c59718f54f1) with the sole git-clone-detached-commit retrieval into the ignored build/llama.cpp workspace, fail-closed dirty/mismatched-cache verification, PATCH-STACK.md, scripts/llama_cpp_dependency.py, and tests/test_llama_cpp_dependency.py (7 passed). Known blocker recorded for DGR-028: 0002-dense-llama-owned-range-loader.patch fails git apply --check against the pin, so no patched-tree/native-build claim is made. Evidence: evidence/DGR-027/README.md. These completionNotes were added during the DGR-025 backlog-consistency repair; the DGR-027 session omitted them." }, { "id": "DGR-028", diff --git a/packages/node/meshnet_node/runtime_pin.py b/packages/node/meshnet_node/runtime_pin.py new file mode 100644 index 0000000..720e84e --- /dev/null +++ b/packages/node/meshnet_node/runtime_pin.py @@ -0,0 +1,221 @@ +"""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), + ) diff --git a/packages/node/meshnet_node/runtime_recipe.py b/packages/node/meshnet_node/runtime_recipe.py index 05acb65..fbb947d 100644 --- a/packages/node/meshnet_node/runtime_recipe.py +++ b/packages/node/meshnet_node/runtime_recipe.py @@ -374,6 +374,11 @@ class RuntimeRecipe: one in fp16, produce different logits from the same bytes. Keeping the axes apart is the entire safety property; see :data:`RECIPE_AXES`. + `tokenizer_revision` and `runtime_version` must be exact pins, never moving + references. For the native runtime the canonical `runtime_version` value — + committing to the exact upstream commit *and* the ordered patch stack — is + derived from the DGR-027 lock manifest by :mod:`meshnet_node.runtime_pin`. + The three label fields are diagnosis only and are not digested. """ @@ -400,6 +405,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_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 81db36a..2fe5fbe 100644 --- a/packages/tracker/meshnet_tracker/recipe.py +++ b/packages/tracker/meshnet_tracker/recipe.py @@ -333,6 +333,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") identity = PresentedIdentity( artifact_id=_text(artifact.get("artifact_id"), "artifact.artifact_id"), diff --git a/tests/test_runtime_pin_identity.py b/tests/test_runtime_pin_identity.py new file mode 100644 index 0000000..7d8ec75 --- /dev/null +++ b/tests/test_runtime_pin_identity.py @@ -0,0 +1,278 @@ +"""DGR-025: the recipe's runtime axis commits to the exact pin and patch stack. + +The fingerprint (DGR-003 lineage) digests a ``runtime_version`` string, but a +string an operator typed is a label, not a pin: two workers could run different +patch stacks under the same label and still agree on the digest. These tests +pin the axis to the DGR-027 lock manifest — the exact upstream commit plus a +digest over the ordered patch-stack bytes — and prove both identity +implementations reject a moving runtime reference. +""" + +from __future__ import annotations + +import hashlib +import json +from pathlib import Path + +import pytest + +from meshnet_node.runtime_pin import ( + DEFAULT_LOCK_DIR, + RuntimePinError, + load_runtime_pin, +) +from meshnet_node.runtime_recipe import RecipeIdentityError, RuntimeRecipe +from meshnet_tracker.recipe import ( + RecipeIdentityError as TrackerRecipeIdentityError, + parse_identity, +) + +REPO_LOCK_DIR = ( + Path(__file__).resolve().parent.parent / "packages" / "node" / "native" / "llama" +) + + +def _recipe(**changes: object) -> RuntimeRecipe: + fields: dict[str, object] = { + "weight_quantization": "Q4_K_M", + "activation_dtype": "bfloat16", + "compute_dtype": "float32", + "kv_dtype": "q8_0", + "kv_layout": "paged-v1", + "tokenizer_revision": "0123456789abcdef", + "architecture_adapter": "llama/range-v1", + "backend_id": "llama.cpp", + "runtime_version": "llama.cpp@deadbeef+meshnet.1", + "recipe_id": "example-gguf", + "recipe_version": "1", + "catalogue_version": "2026.07.1", + } + fields.update(changes) + return RuntimeRecipe(**fields) # type: ignore[arg-type] + + +def _write_fixture_workspace( + root: Path, + *, + commit: str = "e" * 40, + patches: dict[str, bytes] | None = None, + lock_series: list[str] | None = None, + series_lines: list[str] | None = None, + sums_lines: list[str] | None = None, + schema_version: int = 1, + upstream: str = "https://github.com/ggml-org/llama.cpp.git", + upstream_commit_file: str | None = None, +) -> Path: + """A minimal DGR-027-shaped lock workspace; overrides create disagreement.""" + if patches is None: + patches = { + "0001-first.patch": b"--- a\n+++ b\n", + "0002-second.patch": b"--- c\n+++ d\n", + } + names = list(patches) + digests = { + name: hashlib.sha256(body).hexdigest() for name, body in patches.items() + } + + patch_dir = root / "patches" + patch_dir.mkdir(parents=True) + for name, body in patches.items(): + (patch_dir / name).write_bytes(body) + + (root / "UPSTREAM_LOCK.json").write_text( + json.dumps( + { + "schema_version": schema_version, + "upstream": upstream, + "commit": commit, + "patch_series": names if lock_series is None else lock_series, + } + ), + encoding="utf-8", + ) + (root / "UPSTREAM_COMMIT").write_text( + (commit if upstream_commit_file is None else upstream_commit_file) + "\n", + encoding="utf-8", + ) + (patch_dir / "series").write_text( + "\n".join(names if series_lines is None else series_lines) + "\n", + encoding="utf-8", + ) + if sums_lines is None: + sums_lines = ["# ordered digests"] + [ + f"{digests[name]} {name}" for name in names + ] + (patch_dir / "SHA256SUMS").write_text( + "\n".join(sums_lines) + "\n", encoding="utf-8" + ) + return root + + +# --- the committed manifest is the identity source ------------------------- + + +def test_default_lock_dir_is_the_committed_manifest(): + assert DEFAULT_LOCK_DIR == REPO_LOCK_DIR + + +def test_committed_manifest_derives_a_deterministic_runtime_pin(): + pin = load_runtime_pin(REPO_LOCK_DIR) + again = load_runtime_pin(REPO_LOCK_DIR) + assert pin == again + lock = json.loads( + (REPO_LOCK_DIR / "UPSTREAM_LOCK.json").read_text(encoding="utf-8") + ) + assert pin.upstream_commit == lock["commit"] + assert list(pin.patch_series) == lock["patch_series"] + # The axis value names the runtime, the exact commit, and the stack digest, + # so changing any of the three changes every downstream recipe digest. + assert pin.runtime_version == ( + f"llama.cpp@{lock['commit']}+patchstack.{pin.patch_stack_digest}" + ) + assert len(pin.patch_stack_digest) == 64 + + +def test_derived_axis_value_is_a_valid_recipe_pin(): + pin = load_runtime_pin(REPO_LOCK_DIR) + recipe = _recipe(runtime_version=pin.runtime_version) + assert recipe.runtime_version == pin.runtime_version + assert len(recipe.runtime_recipe_digest) == 64 + + +def test_patch_byte_change_changes_the_runtime_identity(tmp_path): + baseline = load_runtime_pin(_write_fixture_workspace(tmp_path / "a")) + changed = load_runtime_pin( + _write_fixture_workspace( + tmp_path / "b", + patches={ + "0001-first.patch": b"--- a\n+++ b\n", + "0002-second.patch": b"--- c\n+++ DIFFERENT\n", + }, + ) + ) + assert baseline.patch_stack_digest != changed.patch_stack_digest + assert baseline.runtime_version != changed.runtime_version + + +def test_patch_order_is_part_of_the_stack_identity(tmp_path): + patches = { + "0001-first.patch": b"--- a\n+++ b\n", + "0002-second.patch": b"--- c\n+++ d\n", + } + forward = load_runtime_pin( + _write_fixture_workspace(tmp_path / "a", patches=patches) + ) + names = list(patches) + reversed_names = list(reversed(names)) + digests = { + name: hashlib.sha256(body).hexdigest() for name, body in patches.items() + } + swapped = load_runtime_pin( + _write_fixture_workspace( + tmp_path / "b", + patches=patches, + lock_series=reversed_names, + series_lines=reversed_names, + sums_lines=[f"{digests[name]} {name}" for name in reversed_names], + ) + ) + assert forward.patch_stack_digest != swapped.patch_stack_digest + + +# --- every manifest disagreement fails closed ------------------------------ + + +def test_missing_lock_file_fails_closed(tmp_path): + with pytest.raises(RuntimePinError, match="UPSTREAM_LOCK.json"): + load_runtime_pin(tmp_path) + + +def test_malformed_lock_json_fails_closed(tmp_path): + _write_fixture_workspace(tmp_path) + (tmp_path / "UPSTREAM_LOCK.json").write_text("{not json", encoding="utf-8") + with pytest.raises(RuntimePinError, match="not valid JSON"): + load_runtime_pin(tmp_path) + + +def test_unknown_lock_schema_fails_closed(tmp_path): + _write_fixture_workspace(tmp_path, schema_version=2) + with pytest.raises(RuntimePinError, match="schema"): + load_runtime_pin(tmp_path) + + +def test_moving_commit_reference_fails_closed(tmp_path): + _write_fixture_workspace(tmp_path, commit="master") + with pytest.raises(RuntimePinError, match="exact 40"): + load_runtime_pin(tmp_path) + + +def test_upstream_commit_file_disagreement_fails_closed(tmp_path): + _write_fixture_workspace(tmp_path, upstream_commit_file="f" * 40) + with pytest.raises(RuntimePinError, match="UPSTREAM_COMMIT"): + load_runtime_pin(tmp_path) + + +def test_series_file_disagreement_fails_closed(tmp_path): + _write_fixture_workspace( + tmp_path, series_lines=["0002-second.patch", "0001-first.patch"] + ) + with pytest.raises(RuntimePinError, match="series"): + load_runtime_pin(tmp_path) + + +def test_missing_patch_file_fails_closed(tmp_path): + _write_fixture_workspace(tmp_path) + (tmp_path / "patches" / "0002-second.patch").unlink() + with pytest.raises(RuntimePinError, match="0002-second.patch"): + load_runtime_pin(tmp_path) + + +def test_checksum_disagreement_fails_closed(tmp_path): + _write_fixture_workspace(tmp_path) + patch = tmp_path / "patches" / "0002-second.patch" + patch.write_bytes(patch.read_bytes() + b"tampered\n") + with pytest.raises(RuntimePinError, match="SHA256SUMS"): + load_runtime_pin(tmp_path) + + +def test_sums_entry_missing_fails_closed(tmp_path): + patches = { + "0001-first.patch": b"--- a\n+++ b\n", + "0002-second.patch": b"--- c\n+++ d\n", + } + digest = hashlib.sha256(patches["0001-first.patch"]).hexdigest() + _write_fixture_workspace( + tmp_path, + patches=patches, + sums_lines=[f"{digest} 0001-first.patch"], + ) + with pytest.raises(RuntimePinError, match="SHA256SUMS"): + load_runtime_pin(tmp_path) + + +def test_empty_patch_series_requires_empty_series_files(tmp_path): + # An unpatched runtime is a legal pin; a lock that *hides* patches is not. + _write_fixture_workspace(tmp_path, lock_series=[]) + with pytest.raises(RuntimePinError, match="series"): + load_runtime_pin(tmp_path) + + +# --- both identity implementations reject a moving runtime ----------------- + + +def test_node_recipe_rejects_a_moving_runtime_version(): + with pytest.raises(RecipeIdentityError, match="moving reference"): + _recipe(runtime_version="latest") + + +def test_tracker_rejects_a_moving_runtime_version(): + 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"] = "latest" + doc.pop("fingerprint", None) + with pytest.raises(TrackerRecipeIdentityError, match="moving reference"): + parse_identity(doc)