fix: bind recipe identity to certified artifact bytes (DGR-025)

Append +artifact.<sha256> to the llama.cpp runtime axis, computed from the
exact bytes read by attest_loaded_runtime, so a differently-built shared
object with copied lock values can no longer forge a certified runtime
identity. Node/tracker parsers require the suffix; new test proves a
byte-identical-lock but different-binary artifact produces a different
recipe fingerprint. Regenerates conformance vectors accordingly.

105 passed in tests/test_native_identity_emission.py,
tests/test_runtime_pin_identity.py, tests/test_runtime_recipe_identity.py.
This commit is contained in:
Dobromir Popov
2026-07-21 13:22:02 +03:00
parent 902ecde363
commit 03e97ca31a
10 changed files with 1277 additions and 69 deletions

View File

@@ -1,8 +1,8 @@
# DGR-025 evidence — exact artifact and runtime recipe identity # DGR-025 evidence — exact artifact and runtime recipe identity
**Completed:** 2026-07-17 **Status:** in progress — controller gates pass; final independent P0/P1 re-review is pending.
**Branch:** `ralph/fable-architecture-loop` (Claude Fable architecture lane) **Branch:** fixed detached Claude Fable provider lane
**Authority:** `.scratch/distributed-gguf-runtime/prd.json` **Authority:** live Gitea #9; the local PRD is a secondary projection.
**Dependencies:** DGR-018 (`evidence/DGR-018/README.md` — canonical backlog schema and **Dependencies:** DGR-018 (`evidence/DGR-018/README.md` — canonical backlog schema and
issue projection), DGR-021 (`evidence/DGR-021/README.md` — versioned activation issue projection), DGR-021 (`evidence/DGR-021/README.md` — versioned activation
envelope). Both read before changing code. envelope). Both read before changing code.
@@ -208,3 +208,240 @@ artifact was touched and nothing was written under `/home`.
the same way `glm_alpha_artifact` does — read locked manifests, never restate the same way `glm_alpha_artifact` does — read locked manifests, never restate
digests — and note `layer_count` must count the routed transformer stack the digests — and note `layer_count` must count the routed transformer stack the
route tiles, excluding MTP (reserved for beta). route tiles, excluding MTP (reserved for beta).
## Reopened P1 repair — 2026-07-18
The earlier evidence above is provenance only. Its stated limitation — that
the identity seam could not attest the executing runtime — was reproduced in
late review, along with the tokenizer-label weakness. This repair replaces
both claims at the production identity boundary.
### Changed files
- `packages/node/meshnet_node/runtime_recipe.py` — replaces the moving-ref
denylist with the sole valid `tokenizer.v1:<sha256>` form, derived from an
ordered map of named tokenizer/config byte digests. A label, tag, branch, or
symbolic ref cannot be a valid identity.
- `packages/tracker/meshnet_tracker/recipe.py` — independent tracker
derivation and validation of the same tokenizer byte identity; it does not
import node code.
- `packages/node/meshnet_node/runtime_pin.py` — adds patched source-tree and
numerically relevant build-recipe digest to the lock-derived runtime pin.
- `packages/node/meshnet_node/native_backend.py`
`NativeLoadedArtifactReport` now requires an executing-runtime attestation:
runtime/source-tree/patch-stack/build-recipe digests and boundary/protocol
ABI versions. `shard_identity_from_native_report` compares every field to
the lock/build-derived expectation before emitting an identity.
- `scripts/gen_recipe_fingerprint_vectors.py` and
`tests/data/recipe_fingerprint_vectors.json` — regenerate canonical vectors
for the strengthened wire contract.
- `tests/test_runtime_pin_identity.py`,
`tests/test_runtime_recipe_identity.py`, and
`tests/test_native_identity_emission.py` — cover mutable labels including
`origin/main`, `stable`, `release`, a tag, and `HEAD`; independent node and
tracker validation; distinct byte sets under one label; one-byte fingerprint
change; build-recipe change; and each executing-runtime attestation mismatch.
### Verification
```bash
PYTHONPATH=packages/node:packages/tracker python3 scripts/gen_recipe_fingerprint_vectors.py
PYTHONPATH=packages/node:packages/tracker python3 -m pytest -q \
tests/test_runtime_pin_identity.py tests/test_native_identity_emission.py \
tests/test_runtime_recipe_identity.py
```
```text
92 passed
```
```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_node_admission.py \
tests/test_node_capability.py tests/test_recipe_benchmark.py
```
```text
188 passed, 1 pre-existing pytest thread warning
```
`python3 scripts/ralph_prd_schema.py validate
.scratch/distributed-gguf-runtime/prd.json`, `python3 -m compileall -q packages
tests`, and `git diff --check` each exit 0. The broader PRD pytest projection
suite has two unrelated existing DGR-023 failures: its `passes: true` entry has
no completion notes and its generated issue file is stale. The socket-backed
subset of `test_tracker_capability_admission.py` is additionally un-runnable in
this sandbox (`PermissionError: [Errno 1] Operation not permitted` creating an
AF_INET socket); its deterministic non-socket identity coverage is included in
the passing runs above.
### Remaining boundary (superseded 2026-07-18, same day — see below)
The attestation at this point was a native runtime report *contract*: a
Python dataclass the worker was trusted to populate. Late review reproduced
the obvious hole — `load_runtime_pin()` is world-readable, so any operator
could copy the lock's values into the dataclass and pass every comparison.
The section below closes that hole.
## Executing-artifact evidence binding — 2026-07-18 (this repair)
The executing native runtime's identity must not be forgeable by copying
repository lock values into a Python self-report. Attestation values are now
accepted only when *extracted from the native artifact itself*, through two
channels that must agree, and the seam fails closed until such native
evidence exists.
### The boundary
`meshnet_node.native_backend` now defines the attestation extraction
contract:
- **Static channel** — the artifact's bytes must embed exactly one
NUL-terminated `MESHNET-RUNTIME-ATTESTATION.v1:<canonical json>` marker.
The canonical payload (`attestation_payload` /
`expected_attestation_payload`) commits to runtime name, upstream commit,
patched tree, ordered patch-stack digest, build-recipe digest, and
boundary/protocol ABI versions; the DGR-027 CMake ABI-marker lane is where
a real native build bakes it in from the lock at configure time.
- **Dynamic channel** — the artifact must actually `dlopen`, and its exported
`llama_meshnet_runtime_attestation` symbol must return byte-identically the
embedded marker. A marker pasted into a plain file is not an executing
runtime.
- **Evidence capability** — `attest_loaded_runtime(artifact_path)` is the
only mint for `NativeArtifactEvidence` (module-private token). The evidence
records the artifact path, a sha256 over the artifact bytes
(`binary_digest`), and a sha256 over the extracted payload
(`payload_digest`). `NativeRuntimeAttestation` requires the evidence and
re-derives the canonical payload from its own field values on
construction: if the digest disagrees, construction fails — so
`dataclasses.replace`-style laundering of a mismatched runtime with copied
lock values also fails.
- `shard_identity_from_native_report` is unchanged downstream: it still
compares every attested field to the lock/build-derived expectation and
the `runtime_version` axis stays lock-derived, so the committed
conformance vectors are unchanged by this repair (regenerated and
byte-stable).
Fail-closed consequence: in a workspace with no built native artifact (this
one — the DGR-028 patch defect still blocks a native build), no attestation
and therefore no native identity can exist at all.
### Changed files
- `packages/node/meshnet_node/native_backend.py` — marker/symbol contract,
canonical payload encoding, `NativeArtifactEvidence` (token-guarded),
evidence-bound `NativeRuntimeAttestation`, `attest_loaded_runtime`
extractor with strict payload parsing (exact key set, types, canonical
re-encoding).
- `tests/test_native_identity_emission.py` — rewritten around real compiled
fixture artifacts: tests build tiny genuine/forged shared objects with
`cc -shared` at test time (skipped cleanly if no C compiler; one is
present here) and prove copied lock values alone cannot pass anywhere.
### Behavior tests proving copied lock values cannot pass
- Bare `NativeRuntimeAttestation(**lock_values)` (the pre-repair forgery) is
unconstructible; `evidence=None` and hand-authored/`object()`-token
`NativeArtifactEvidence` each raise.
- The true marker bytes written into a plain file fail (`not a loadable`).
- A loadable artifact with no marker, with conflicting markers, without the
exported symbol, whose symbol disagrees with its marker, or whose payload
is non-canonical (wrong keys, or right keys re-encoded with whitespace)
each fail closed.
- A self-consistent artifact built from the *wrong* values attests, then
fails identity emission per-field (runtime name, upstream commit, patched
tree, patch stack, build recipe, boundary/protocol ABI), and
`dataclasses.replace`-ing it with the lock's true values fails the
evidence binding (`edited after extraction`).
- The genuine path: an artifact embedding
`expected_attestation_payload(load_runtime_pin())` attests, emits the
lock-derived identity, and its evidence `binary_digest` equals the sha256
of the artifact bytes.
### Verification (all in this worktree, 2026-07-18)
```bash
PYTHONPATH=packages/node:packages/tracker python3 -m pytest -q \
tests/test_native_identity_emission.py tests/test_runtime_pin_identity.py \
tests/test_runtime_recipe_identity.py
```
```text
104 passed
```
```bash
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
96 passed, 1 pre-existing pytest thread warning
```
```bash
PYTHONPATH=packages/node:packages/tracker python3 -m pytest -q \
tests/test_tracker_capability_admission.py
```
```text
34 passed # socket-backed subset ran in this session's sandbox
```
`PYTHONPATH=packages/node:packages/tracker python3
scripts/gen_recipe_fingerprint_vectors.py` reproduces the committed vectors
byte-for-byte; `python3 -m compileall -q packages tests` and
`git diff --check` each exit 0.
A controller full-suite run (`python3 -m pytest -q`) was also executed and is
not represented as green: `13 failed, 1104 passed, 22 skipped, 2 warnings`.
The failures are outside the DGR-025 changed paths: unavailable optional
`zstandard`/`langchain_openai` dependencies, unrelated billing/dynamic-routing/
tracker expectations, and the already recorded stale DGR-023 local projection.
The exact DGR-025 identity suites and broader admission coverage remain green as
recorded above.
### Remaining boundary
What is now proven: no identity can be constructed, registered, admitted, or
certified without evidence extracted from an actual loadable native artifact
that both embeds and reports the attestation, and the extracted values cannot
be edited afterward. What is deliberately not claimed: a cross-compiler
bit-reproducible binary SHA, defense against an adversary who *builds* a
native artifact that embeds lock-true values while lying about its source
(a categorically higher bar than authoring a Python dict), an OS-level swap
of the artifact file between the byte read and the `dlopen` (documented
residual race), or in-process tampering below Python semantics. Real
distributed certification (the registered-but-dark ledger) remains the final
backstop behind this boundary; the DGR-028+ native build lane must embed the
marker via the reserved CMake ABI-marker hook.
## Executing-byte identity repair — 2026-07-18 controller follow-up
A later controller review rejected the preceding remaining-boundary claim as
insufficient for DGR-025: a separately built loadable shared object could copy
all public lock values into both marker channels and receive the same
`runtime_version` as a certified artifact. The repair now appends
`+artifact.<sha256>` to the llama.cpp runtime axis, where the digest is computed
from the exact bytes read by `attest_loaded_runtime`. Node and tracker parsers
independently require this suffix. Consequently, copying lock values into a
different loadable artifact produces a different recipe fingerprint; only the
same artifact bytes can retain the same identity, and every new binary remains
dark until certified.
`test_copying_public_lock_values_cannot_forge_the_certified_runtime_identity`
builds a second loadable artifact with byte-identical lock attestation but
different executable bytes, and proves both its `runtime_version` and recipe
digest differ from the accepted artifact. Conformance vectors were regenerated
for the strengthened wire identity.
Controller verification:
```text
python3 scripts/gen_recipe_fingerprint_vectors.py
python3 -m pytest -q tests/test_native_identity_emission.py \
tests/test_runtime_pin_identity.py tests/test_runtime_recipe_identity.py
# 105 passed in 0.52s
python3 -m compileall -q packages/node/meshnet_node \
packages/tracker/meshnet_tracker tests scripts/gen_recipe_fingerprint_vectors.py
# exit 0
git diff --check
# exit 0
```

View File

@@ -9,10 +9,15 @@ authoritative immutable GGUF artifact pin and must remain identity-free.
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass import ctypes
import hashlib
import json
import re
from dataclasses import dataclass, field
from pathlib import Path
from .native_protocol import BUNDLE_VERSION, SCHEMA_VERSION, pb from .native_protocol import BUNDLE_VERSION, SCHEMA_VERSION, pb
from .runtime_pin import load_runtime_pin from .runtime_pin import DEFAULT_LOCK_DIR, RuntimePin, load_runtime_pin
from .runtime_recipe import ( from .runtime_recipe import (
ArtifactIdentity, ArtifactIdentity,
DerivativeBinding, DerivativeBinding,
@@ -23,6 +28,323 @@ from .runtime_recipe import (
handshake_error, handshake_error,
) )
_HEX40 = re.compile(r"^[0-9a-f]{40}$")
_HEX64 = re.compile(r"^[0-9a-f]{64}$")
# The executing-runtime attestation contract.
#
# The repository lock is world-readable, so a Python object holding
# lock-shaped values proves nothing about the runtime that will execute:
# copying `load_runtime_pin()` into a self-report is exactly the forgery
# DGR-025 forbids. Attestation values are therefore accepted only when
# *extracted from the native artifact itself*, through two channels that must
# agree:
#
# 1. static — the artifact's bytes embed exactly one
# ``MESHNET-RUNTIME-ATTESTATION.v1:<canonical json>`` marker (NUL
# terminated). The DGR-027 CMake ABI-marker lane is where the native
# build bakes it in from the lock at configure time.
# 2. dynamic — the artifact must actually dlopen, and its exported
# ``llama_meshnet_runtime_attestation`` symbol must return that same
# marker. A marker pasted into a plain file is not an executing runtime.
#
# What this cannot prove: a cross-compiler bit-reproducible binary SHA, or
# that an adversary did not *build* a native artifact that embeds lock-true
# values while lying about its source. Manufacturing a lying native build is
# a categorically higher bar than authoring a Python dict, and real
# distributed certification (DGR-025's registered-but-dark ledger) remains
# the final backstop behind this boundary.
ATTESTATION_MARKER_PREFIX = b"MESHNET-RUNTIME-ATTESTATION.v1:"
ATTESTATION_SYMBOL = "llama_meshnet_runtime_attestation"
_ATTESTATION_STR_FIELDS = (
"runtime_name",
"upstream_commit",
"patched_tree",
"patch_stack_digest",
"build_recipe_digest",
)
_ATTESTATION_INT_FIELDS = ("boundary_schema_version", "protocol_schema_version")
# Module-private capability: evidence can only be minted where an artifact
# was actually read, scanned, loaded, and queried.
_EVIDENCE_TOKEN = object()
def attestation_payload(
*,
runtime_name: str,
upstream_commit: str,
patched_tree: str,
patch_stack_digest: str,
build_recipe_digest: str,
boundary_schema_version: int,
protocol_schema_version: int,
) -> bytes:
"""The canonical marker payload for one exact runtime.
This single encoding is shared by the build lane that embeds the marker,
the extractor that parses it, and the binding check that ties attestation
fields to the extracted evidence — so there is exactly one byte string a
given runtime identity can legitimately embed.
"""
return json.dumps(
{
"runtime_name": runtime_name,
"upstream_commit": upstream_commit,
"patched_tree": patched_tree,
"patch_stack_digest": patch_stack_digest,
"build_recipe_digest": build_recipe_digest,
"boundary_schema_version": boundary_schema_version,
"protocol_schema_version": protocol_schema_version,
},
sort_keys=True,
separators=(",", ":"),
ensure_ascii=False,
).encode("utf-8")
def expected_attestation_payload(
pin: RuntimePin,
*,
boundary_schema_version: int = BUNDLE_VERSION,
protocol_schema_version: int = int(SCHEMA_VERSION),
) -> bytes:
"""The marker payload a native build of this lock workspace must embed."""
return attestation_payload(
runtime_name=pin.runtime_name,
upstream_commit=pin.upstream_commit,
patched_tree=pin.patched_tree,
patch_stack_digest=pin.patch_stack_digest,
build_recipe_digest=pin.build_recipe_digest,
boundary_schema_version=boundary_schema_version,
protocol_schema_version=protocol_schema_version,
)
@dataclass(frozen=True)
class NativeArtifactEvidence:
"""Proof that attestation values came out of a loadable native artifact.
``binary_digest`` pins *which* artifact bytes were attested;
``payload_digest`` pins *what* those bytes attested, and is re-derived
from the attestation's own fields on construction so the values cannot be
edited after extraction (``dataclasses.replace`` laundering fails).
Only :func:`attest_loaded_runtime` can mint this object.
"""
artifact_path: str
binary_digest: str
payload_digest: str
_token: object = field(default=None, repr=False, compare=False)
def __post_init__(self) -> None:
if self._token is not _EVIDENCE_TOKEN:
raise RecipeIdentityError(
"native artifact evidence can only be minted by "
"attest_loaded_runtime() from an actually loaded native "
"artifact; it cannot be authored from repository lock values"
)
if not isinstance(self.artifact_path, str) or not self.artifact_path:
raise RecipeIdentityError("native artifact evidence must name the artifact")
for field_name in ("binary_digest", "payload_digest"):
value = getattr(self, field_name)
if not isinstance(value, str) or not _HEX64.fullmatch(value):
raise RecipeIdentityError(
f"native artifact evidence {field_name!r} must be a 64-hex sha256"
)
@dataclass(frozen=True)
class NativeRuntimeAttestation:
"""What the *executing* runtime reports about itself, at load time.
The repository lock says what the runtime is supposed to be; this says
what the loaded runtime *is* — the source tree it was built from, the
patch stack compiled into it, the numerically relevant build recipe, and
the boundary/protocol schema (ABI) it speaks. The values are never
accepted from a caller: they must arrive bound to
:class:`NativeArtifactEvidence`, which only
:func:`attest_loaded_runtime` can produce by reading, loading, and
querying the native artifact itself. Identity construction then compares
them to the lock/build-derived expectation and refuses on any difference,
so a worker cannot serve a lock it is not actually running — and cannot
fake one by copying the lock into a Python self-report.
Deliberately *not* attested: a compiler-specific binary SHA. Binding the
recorded build recipe is honest about what the manifest can prove;
bit-reproducible binary attestation is not claimed.
"""
runtime_name: str
upstream_commit: str
patched_tree: str
patch_stack_digest: str
build_recipe_digest: str
boundary_schema_version: int
protocol_schema_version: int
evidence: NativeArtifactEvidence
def __post_init__(self) -> None:
if not isinstance(self.evidence, NativeArtifactEvidence):
raise RecipeIdentityError(
"runtime attestation values must be extracted from the loaded "
"native artifact via attest_loaded_runtime(); a Python "
"self-report carrying copied lock values is not an attestation"
)
if not isinstance(self.runtime_name, str) or not self.runtime_name.strip():
raise RecipeIdentityError(
"runtime attestation must name the executing runtime"
)
for field_name, pattern, what in (
("upstream_commit", _HEX40, "40-hex upstream commit"),
("patched_tree", _HEX40, "40-hex patched source tree id"),
("patch_stack_digest", _HEX64, "64-hex patch-stack digest"),
("build_recipe_digest", _HEX64, "64-hex build-recipe digest"),
):
value = getattr(self, field_name)
if not isinstance(value, str) or not pattern.fullmatch(value):
raise RecipeIdentityError(
f"runtime attestation {field_name!r} must be an exact {what}"
)
for field_name in _ATTESTATION_INT_FIELDS:
value = getattr(self, field_name)
if isinstance(value, bool) or not isinstance(value, int) or value < 1:
raise RecipeIdentityError(
f"runtime attestation {field_name!r} must be a positive integer"
)
payload = attestation_payload(
**{name: getattr(self, name) for name in _ATTESTATION_STR_FIELDS},
**{name: getattr(self, name) for name in _ATTESTATION_INT_FIELDS},
)
if hashlib.sha256(payload).hexdigest() != self.evidence.payload_digest:
raise RecipeIdentityError(
"runtime attestation fields do not match the attestation "
"extracted from the native artifact; refusing values edited "
"after extraction"
)
def _parse_attestation_payload(payload: bytes) -> dict[str, object]:
"""Strictly parse one embedded marker payload, or refuse."""
try:
doc = json.loads(payload.decode("utf-8"))
except (UnicodeDecodeError, json.JSONDecodeError) as exc:
raise RecipeIdentityError(
f"embedded runtime attestation marker is not valid JSON: {exc}"
) from exc
expected_keys = set(_ATTESTATION_STR_FIELDS) | set(_ATTESTATION_INT_FIELDS)
if not isinstance(doc, dict) or set(doc) != expected_keys:
raise RecipeIdentityError(
"embedded runtime attestation marker must record exactly the "
"attestation fields"
)
for name in _ATTESTATION_STR_FIELDS:
if not isinstance(doc[name], str):
raise RecipeIdentityError(
f"embedded runtime attestation field {name!r} must be a string"
)
for name in _ATTESTATION_INT_FIELDS:
if isinstance(doc[name], bool) or not isinstance(doc[name], int):
raise RecipeIdentityError(
f"embedded runtime attestation field {name!r} must be an integer"
)
if attestation_payload(**doc) != payload: # type: ignore[arg-type]
raise RecipeIdentityError(
"embedded runtime attestation marker is not in canonical form"
)
return doc
def attest_loaded_runtime(artifact_path: Path | str) -> NativeRuntimeAttestation:
"""Extract the executing runtime's attestation from its native artifact.
Fails closed when the artifact is missing or empty, embeds no attestation
marker (a runtime built without the attestation lane cannot prove what it
is), embeds conflicting markers, is not a loadable shared object, does
not export :data:`ATTESTATION_SYMBOL`, or reports through that symbol
anything other than the embedded marker.
The returned attestation is bound to the artifact by its byte digest and
to the extracted values by the payload digest. Loading the artifact does
execute its initializers — this is the same artifact the worker is about
to run inference with, so that adds no new execution. An OS-level swap
of the file between the byte read and the dlopen is a documented
residual race; distributed certification remains the final backstop.
"""
path = Path(artifact_path)
try:
data = path.read_bytes()
except FileNotFoundError:
raise RecipeIdentityError(
f"native runtime artifact not found at {path}; without the built "
"native runtime there is no executing identity to attest"
) from None
except OSError as exc:
raise RecipeIdentityError(
f"native runtime artifact at {path} is unreadable: {exc}"
) from exc
if not data:
raise RecipeIdentityError(f"native runtime artifact at {path} is empty")
payloads: list[bytes] = []
cursor = 0
while (start := data.find(ATTESTATION_MARKER_PREFIX, cursor)) >= 0:
end = data.find(b"\x00", start)
if end < 0:
raise RecipeIdentityError(
"embedded runtime attestation marker is not NUL-terminated"
)
payloads.append(data[start + len(ATTESTATION_MARKER_PREFIX) : end])
cursor = end
if not payloads:
raise RecipeIdentityError(
f"native runtime artifact at {path} embeds no runtime attestation "
"marker; a runtime built without the attestation lane cannot "
"prove what it is"
)
if len(set(payloads)) != 1:
raise RecipeIdentityError(
f"native runtime artifact at {path} embeds conflicting runtime "
"attestation markers"
)
payload = payloads[0]
doc = _parse_attestation_payload(payload)
try:
library = ctypes.CDLL(str(path), mode=ctypes.RTLD_LOCAL)
except OSError as exc:
raise RecipeIdentityError(
f"native runtime artifact at {path} is not a loadable native "
"artifact; an attestation marker copied into a plain file is not "
"an executing runtime"
) from exc
try:
symbol = getattr(library, ATTESTATION_SYMBOL)
except AttributeError:
raise RecipeIdentityError(
f"native runtime artifact at {path} does not export "
f"{ATTESTATION_SYMBOL}; the loaded runtime itself must report "
"its attestation"
) from None
symbol.restype = ctypes.c_char_p
symbol.argtypes = []
reported = symbol()
if reported != ATTESTATION_MARKER_PREFIX + payload:
raise RecipeIdentityError(
f"the runtime loaded from {path} reports a different attestation "
"than its artifact embeds; refusing an artifact that disagrees "
"with itself"
)
evidence = NativeArtifactEvidence(
artifact_path=str(path),
binary_digest=hashlib.sha256(data).hexdigest(),
payload_digest=hashlib.sha256(payload).hexdigest(),
_token=_EVIDENCE_TOKEN,
)
return NativeRuntimeAttestation(evidence=evidence, **doc) # type: ignore[arg-type]
@dataclass(frozen=True) @dataclass(frozen=True)
class NativeLoadedArtifactReport: class NativeLoadedArtifactReport:
@@ -32,6 +354,10 @@ class NativeLoadedArtifactReport:
parsed GGUF metadata while the model is live. Byte counts are operational parsed GGUF metadata while the model is live. Byte counts are operational
evidence rather than compatibility axes, but keeping them beside the range evidence rather than compatibility axes, but keeping them beside the range
prevents a caller from substituting an unverified range declaration. prevents a caller from substituting an unverified range declaration.
``runtime_attestation`` must be the evidence-bound attestation extracted
from the loaded native artifact by :func:`attest_loaded_runtime`; a
report without one cannot be turned into an identity at all, and one
cannot exist without an actual native artifact to extract it from.
""" """
owned_start_layer: int owned_start_layer: int
@@ -42,6 +368,7 @@ class NativeLoadedArtifactReport:
architecture: str architecture: str
architecture_digest: str architecture_digest: str
layer_count: int layer_count: int
runtime_attestation: NativeRuntimeAttestation
def __post_init__(self) -> None: def __post_init__(self) -> None:
if self.owned_start_layer < 0 or self.owned_end_layer <= self.owned_start_layer: if self.owned_start_layer < 0 or self.owned_end_layer <= self.owned_start_layer:
@@ -50,6 +377,10 @@ class NativeLoadedArtifactReport:
raise RecipeIdentityError("native report range is outside GGUF layer metadata") raise RecipeIdentityError("native report range is outside GGUF layer metadata")
if min(self.mapped_bytes, self.resident_bytes, self.registered_bytes) < 0: if min(self.mapped_bytes, self.resident_bytes, self.registered_bytes) < 0:
raise RecipeIdentityError("native report byte counts must be non-negative") raise RecipeIdentityError("native report byte counts must be non-negative")
if not isinstance(self.runtime_attestation, NativeRuntimeAttestation):
raise RecipeIdentityError(
"native report must carry the executing runtime's attestation"
)
@dataclass(frozen=True) @dataclass(frozen=True)
@@ -82,7 +413,12 @@ class NativeNumericalRecipe:
@dataclass(frozen=True) @dataclass(frozen=True)
class NativeIdentityInputs: class NativeIdentityInputs:
"""Everything a native backend needs to emit one exact identity.""" """Everything a native backend needs to emit one exact identity.
``tokenizer_revision`` must be the content-addressed identity computed by
:func:`meshnet_node.runtime_recipe.tokenizer_identity` over the loaded
tokenizer/config bytes; identity construction rejects anything else.
"""
loaded_artifact: NativeLoadedArtifactReport loaded_artifact: NativeLoadedArtifactReport
artifact_pin: ImmutableArtifactPin artifact_pin: ImmutableArtifactPin
@@ -90,8 +426,72 @@ class NativeIdentityInputs:
numerical_recipe: NativeNumericalRecipe numerical_recipe: NativeNumericalRecipe
def shard_identity_from_native_report(inputs: NativeIdentityInputs) -> ShardIdentity: def _require_attested_runtime(
"""Derive identity only from the native report and immutable pinned inputs.""" attested: NativeRuntimeAttestation,
expected: RuntimePin,
recipe: NativeNumericalRecipe,
) -> None:
"""Fail closed unless the executing runtime is the locked, built runtime.
Every comparison is exact and every difference is separately fatal: an
attestation that agrees on the commit but not the patch stack (or the
patched tree, or the build recipe, or the ABI) is a different runtime
wearing the lock's name, and letting it emit the lock's identity is
exactly the substitution DGR-025 exists to prevent.
"""
for what, got, want in (
("runtime name", attested.runtime_name, expected.runtime_name),
("upstream commit", attested.upstream_commit, expected.upstream_commit),
("patched source tree", attested.patched_tree, expected.patched_tree),
(
"ordered patch stack",
attested.patch_stack_digest,
expected.patch_stack_digest,
),
(
"build recipe",
attested.build_recipe_digest,
expected.build_recipe_digest,
),
):
if got != want:
raise RecipeIdentityError(
f"the executing runtime's attested {what} does not match the "
"lock/build-derived expectation; refusing to emit an identity "
"for a runtime this node is not provably running"
)
for what, got, want in (
(
"boundary schema",
attested.boundary_schema_version,
recipe.boundary_schema_version,
),
(
"protocol schema",
attested.protocol_schema_version,
recipe.protocol_schema_version,
),
):
if got != want:
raise RecipeIdentityError(
f"the executing runtime's attested {what} version ({got}) does "
f"not match the recipe's ({want}); an ABI the runtime does not "
"actually speak cannot be part of its identity"
)
def shard_identity_from_native_report(
inputs: NativeIdentityInputs,
*,
lock_dir: Path = DEFAULT_LOCK_DIR,
) -> ShardIdentity:
"""Derive identity only from the native report and immutable pinned inputs.
The ``runtime_version`` axis is never accepted from a caller: it is derived
from the committed lock workspace, and the loaded runtime's attestation
must match that lock/build-derived expectation exactly — otherwise this
raises and no identity exists to register, admit, or certify.
"""
report = inputs.loaded_artifact report = inputs.loaded_artifact
pin = inputs.artifact_pin pin = inputs.artifact_pin
recipe = inputs.numerical_recipe recipe = inputs.numerical_recipe
@@ -99,7 +499,17 @@ def shard_identity_from_native_report(inputs: NativeIdentityInputs) -> ShardIden
raise RecipeIdentityError( raise RecipeIdentityError(
"native llama.cpp identity requires backend_id 'llama.cpp' or 'llama-cpp'" "native llama.cpp identity requires backend_id 'llama.cpp' or 'llama-cpp'"
) )
runtime_version = load_runtime_pin().runtime_version runtime_pin = load_runtime_pin(lock_dir)
_require_attested_runtime(report.runtime_attestation, runtime_pin, recipe)
# The lock-derived prefix identifies the intended source/patch/build
# recipe. The executing artifact digest identifies the bytes that actually
# supplied the attestation. Without this suffix, any independently built
# shared object could copy the public lock values into its marker and claim
# the exact same compatibility identity as the certified artifact.
runtime_version = (
f"{runtime_pin.runtime_version}"
f"+artifact.{report.runtime_attestation.evidence.binary_digest}"
)
artifact = ArtifactIdentity( artifact = ArtifactIdentity(
artifact_id=pin.artifact_id, artifact_id=pin.artifact_id,
revision=pin.revision, revision=pin.revision,

View File

@@ -8,13 +8,16 @@ still hash to the same recipe. The DGR-027 lock manifest
and the ordered patch stack the native runtime is built from, so the axis and the ordered patch stack the native runtime is built from, so the axis
value is *derived* from that manifest, never asserted. value is *derived* from that manifest, never asserted.
The derived value has three load-bearing parts, and each is separately fatal The derived value has four load-bearing parts, and each is separately fatal
to compatibility: the runtime name (from the upstream URL), the exact to compatibility: the runtime name (from the upstream URL), the exact
40-character upstream commit, and a digest over the ordered patch-stack bytes. 40-character upstream commit, a digest over the ordered patch-stack bytes, and
A different upstream pin, a reordered stack, or a single changed patch byte a digest over the numerically relevant build recipe. A different upstream pin,
each produce a different axis value, which produces a different recipe digest, a reordered stack, a single changed patch byte, or a changed build flag each
produce a different axis value, which produces a different recipe digest,
which partitions the route — exactly the fail-closed behavior DGR-025 asks which partitions the route — exactly the fail-closed behavior DGR-025 asks
for. for. The pin also records the patched source tree's git tree id, which the
executing runtime's attestation is compared against
(:mod:`meshnet_node.native_backend`).
Every consistency check here fails closed. The manifest keeps three records of Every consistency check here fails closed. The manifest keeps three records of
the stack — ``UPSTREAM_LOCK.json``'s ``patch_series``, ``patches/series``, and the stack — ``UPSTREAM_LOCK.json``'s ``patch_series``, ``patches/series``, and
@@ -36,6 +39,7 @@ from pathlib import Path
# Domain separation, matching the runtime_recipe digest convention: a patch # Domain separation, matching the runtime_recipe digest convention: a patch
# stack digest can never be confused with an artifact or recipe digest. # stack digest can never be confused with an artifact or recipe digest.
PATCH_STACK_DIGEST_DOMAIN = "meshnet.runtime-patch-stack.v1" PATCH_STACK_DIGEST_DOMAIN = "meshnet.runtime-patch-stack.v1"
BUILD_RECIPE_DIGEST_DOMAIN = "meshnet.runtime-build-recipe.v1"
# The UPSTREAM_LOCK.json layout this reader understands (DGR-027 schema). # The UPSTREAM_LOCK.json layout this reader understands (DGR-027 schema).
RUNTIME_PIN_SCHEMA_VERSION = 1 RUNTIME_PIN_SCHEMA_VERSION = 1
@@ -58,14 +62,42 @@ def _canonical_sha256(value: object) -> str:
return hashlib.sha256(payload.encode("utf-8")).hexdigest() return hashlib.sha256(payload.encode("utf-8")).hexdigest()
def build_recipe_digest(build: object) -> str:
"""A digest over the numerically relevant build recipe.
The lock's ``build`` object records what the runtime is compiled *as* —
the configure flags, standards, and targets that select kernels and
numeric behavior. Two binaries built from one patched tree with different
build recipes can disagree numerically, so the recipe is part of runtime
identity. This is deliberately a digest over the *recorded recipe*, not a
compiler-specific binary SHA: reproducible-binary attestation is not
claimed here.
"""
if not isinstance(build, dict) or not build:
raise RuntimePinError(
"the lock's 'build' section must be a non-empty JSON object; a "
"runtime with an unstated build recipe has an unknowable identity"
)
return _canonical_sha256(
{"domain": BUILD_RECIPE_DIGEST_DOMAIN, "body": build}
)
@dataclass(frozen=True) @dataclass(frozen=True)
class RuntimePin: class RuntimePin:
"""One exact runtime: a name, an upstream commit, and an ordered patch stack.""" """One exact runtime: name, upstream commit, patched tree, patch stack, build.
``patched_tree`` is the git tree object id of the source tree *after* the
ordered patch stack is applied — what the runtime was actually compiled
from, as distinct from the upstream commit it started from.
"""
runtime_name: str runtime_name: str
upstream_commit: str upstream_commit: str
patched_tree: str
patch_series: tuple[str, ...] patch_series: tuple[str, ...]
patch_digests: tuple[str, ...] patch_digests: tuple[str, ...]
build_recipe_digest: str
@property @property
def patch_stack_digest(self) -> str: def patch_stack_digest(self) -> str:
@@ -89,10 +121,16 @@ class RuntimePin:
@property @property
def runtime_version(self) -> str: def runtime_version(self) -> str:
"""The exact ``runtime_version`` recipe axis value for this pin.""" """The exact ``runtime_version`` recipe axis value for this pin.
Commits to the runtime name, the exact upstream commit, the ordered
patch stack, and the numerically relevant build recipe — each
separately fatal to compatibility.
"""
return ( return (
f"{self.runtime_name}@{self.upstream_commit}" f"{self.runtime_name}@{self.upstream_commit}"
f"+patchstack.{self.patch_stack_digest}" f"+patchstack.{self.patch_stack_digest}"
f"+build.{self.build_recipe_digest}"
) )
@@ -166,6 +204,15 @@ def load_runtime_pin(lock_dir: Path = DEFAULT_LOCK_DIR) -> RuntimePin:
"hexadecimal object id; a moving reference is not a pin" "hexadecimal object id; a moving reference is not a pin"
) )
patched_tree = lock.get("patched_tree")
if not isinstance(patched_tree, str) or not _HEX40.match(patched_tree):
raise RuntimePinError(
"UPSTREAM_LOCK.json patched_tree must be the exact 40-character git "
"tree object id of the source tree after the patch stack is applied"
)
build_digest = build_recipe_digest(lock.get("build"))
commit_file = _read_text(lock_dir / "UPSTREAM_COMMIT", "UPSTREAM_COMMIT") commit_file = _read_text(lock_dir / "UPSTREAM_COMMIT", "UPSTREAM_COMMIT")
recorded = commit_file.strip().splitlines()[0].strip() if commit_file.strip() else "" recorded = commit_file.strip().splitlines()[0].strip() if commit_file.strip() else ""
if recorded != commit: if recorded != commit:
@@ -216,6 +263,8 @@ def load_runtime_pin(lock_dir: Path = DEFAULT_LOCK_DIR) -> RuntimePin:
return RuntimePin( return RuntimePin(
runtime_name=runtime_name, runtime_name=runtime_name,
upstream_commit=commit, upstream_commit=commit,
patched_tree=patched_tree,
patch_series=tuple(lock_series), patch_series=tuple(lock_series),
patch_digests=tuple(digests), patch_digests=tuple(digests),
build_recipe_digest=build_digest,
) )

View File

@@ -73,6 +73,7 @@ RECIPE_IDENTITY_SCHEMA_VERSION = 1
ARTIFACT_DIGEST_DOMAIN = "meshnet.model-artifact.v1" ARTIFACT_DIGEST_DOMAIN = "meshnet.model-artifact.v1"
RECIPE_DIGEST_DOMAIN = "meshnet.runtime-recipe.v1" RECIPE_DIGEST_DOMAIN = "meshnet.runtime-recipe.v1"
SHARD_BINDING_DIGEST_DOMAIN = "meshnet.shard-binding.v1" SHARD_BINDING_DIGEST_DOMAIN = "meshnet.shard-binding.v1"
TOKENIZER_DIGEST_DOMAIN = "meshnet.tokenizer-identity.v1"
# The axes of a runtime recipe. Every one of these changes the numbers a Shard # 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 # produces, so every one of them is part of identity and none of them may be
@@ -126,12 +127,22 @@ _AXIS_MISMATCH: Mapping[str, str] = {
_HEX64 = re.compile(r"^[0-9a-f]{64}$") _HEX64 = re.compile(r"^[0-9a-f]{64}$")
_LLAMA_CPP_RUNTIME_PIN = re.compile( _LLAMA_CPP_RUNTIME_PIN = re.compile(
r"^llama\.cpp@[0-9a-f]{40}\+patchstack\.[0-9a-f]{64}$" r"^llama\.cpp@[0-9a-f]{40}\+patchstack\.[0-9a-f]{64}"
r"\+build\.[0-9a-f]{64}\+artifact\.[0-9a-f]{64}$"
) )
_LLAMA_CPP_BACKEND_IDS = frozenset({"llama.cpp", "llama-cpp"}) _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; # The one shape a tokenizer identity may take: a digest over the tokenizer's
# it is just as true of a tokenizer. # actual bytes (see `tokenizer_identity`). Any *label* — `origin/main`,
# `stable`, `release`, a tag, a symbolic ref — names a mutable pointer, and a
# denylist of known-mutable names can never enumerate them all. So the check is
# inverted: instead of rejecting labels we recognize as moving, accept only a
# value that could not be a label in the first place.
_TOKENIZER_IDENTITY = re.compile(r"^tokenizer\.v1:[0-9a-f]{64}$")
# A revision that can move is not a pin. DGR-017 learned this on the artifact.
# Used for diagnosis-only fields (`artifact.revision`); the digested tokenizer
# axis requires the strictly stronger `_TOKENIZER_IDENTITY` form.
_MOVING_REFS = frozenset({"main", "master", "head", "latest", "dev", "trunk"}) _MOVING_REFS = frozenset({"main", "master", "head", "latest", "dev", "trunk"})
@@ -204,7 +215,50 @@ def _require_runtime_pin(value: Any, backend_id: Any) -> str:
if backend in _LLAMA_CPP_BACKEND_IDS and not _LLAMA_CPP_RUNTIME_PIN.fullmatch(text): if backend in _LLAMA_CPP_BACKEND_IDS and not _LLAMA_CPP_RUNTIME_PIN.fullmatch(text):
raise RecipeIdentityError( raise RecipeIdentityError(
"'recipe.runtime_version' for llama.cpp must be " "'recipe.runtime_version' for llama.cpp must be "
"'llama.cpp@<40-hex commit>+patchstack.<64-hex digest>'" "'llama.cpp@<40-hex commit>+patchstack.<64-hex digest>"
"+build.<64-hex digest>+artifact.<64-hex digest>'"
)
return text
def tokenizer_identity(files: Mapping[str, bytes]) -> str:
"""The content-addressed identity of a tokenizer: a digest over its bytes.
`files` maps each numerically relevant tokenizer/config file name — for a
GGUF, the embedded tokenizer metadata blob; for a safetensors deployment,
`tokenizer.json`, `tokenizer_config.json`, `special_tokens_map.json` — to
that file's exact bytes. The identity commits to each name and each byte
set, so two tokenizers published under one label differ, and a one-byte
edit is a different tokenizer.
"""
if not isinstance(files, Mapping) or not files:
raise RecipeIdentityError(
"tokenizer identity requires at least one named tokenizer/config "
"byte set; an identity over nothing pins nothing"
)
digests: dict[str, str] = {}
for name, body in files.items():
if not isinstance(name, str) or not name.strip():
raise RecipeIdentityError(
"tokenizer identity file names must be non-empty strings"
)
if not isinstance(body, (bytes, bytearray)):
raise RecipeIdentityError(
f"tokenizer identity for {name!r} requires the file's bytes, "
"not a path or label"
)
digests[name] = hashlib.sha256(bytes(body)).hexdigest()
return "tokenizer.v1:" + _digest(TOKENIZER_DIGEST_DOMAIN, {"files": digests})
def _require_tokenizer_identity(value: Any, what: str) -> str:
text = _require_text(value, what)
if not _TOKENIZER_IDENTITY.fullmatch(text):
raise RecipeIdentityError(
f"{what!r} must be a content-addressed tokenizer identity "
"'tokenizer.v1:<64-hex digest>' derived from the tokenizer's bytes "
"(tokenizer_identity); a repository label, tag, branch, or symbolic "
"ref names a mutable pointer, not the bytes it currently resolves to"
) )
return text return text
@@ -389,10 +443,13 @@ class RuntimeRecipe:
one in fp16, produce different logits from the same bytes. Keeping the axes one in fp16, produce different logits from the same bytes. Keeping the axes
apart is the entire safety property; see :data:`RECIPE_AXES`. apart is the entire safety property; see :data:`RECIPE_AXES`.
`tokenizer_revision` and `runtime_version` must be exact pins, never moving `tokenizer_revision` must be a content-addressed tokenizer identity
references. For the native runtime the canonical `runtime_version` value — (:func:`tokenizer_identity`) — a digest over the tokenizer's actual bytes,
committing to the exact upstream commit *and* the ordered patch stack — is never a repository label that merely points at bytes. `runtime_version`
derived from the DGR-027 lock manifest by :mod:`meshnet_node.runtime_pin`. must be an exact pin; for the native runtime the canonical value —
committing to the exact upstream commit, the ordered patch stack, *and*
the numerically relevant build recipe — 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. The three label fields are diagnosis only and are not digested.
""" """
@@ -419,7 +476,7 @@ class RuntimeRecipe:
_require_int(value, f"recipe.{axis}", 1) _require_int(value, f"recipe.{axis}", 1)
else: else:
_require_text(value, f"recipe.{axis}") _require_text(value, f"recipe.{axis}")
_require_pin(self.tokenizer_revision, "recipe.tokenizer_revision") _require_tokenizer_identity(self.tokenizer_revision, "recipe.tokenizer_revision")
_require_runtime_pin(self.runtime_version, self.backend_id) _require_runtime_pin(self.runtime_version, self.backend_id)
_require_text(self.recipe_id, "recipe.recipe_id") _require_text(self.recipe_id, "recipe.recipe_id")
_require_text(self.recipe_version, "recipe.recipe_version") _require_text(self.recipe_version, "recipe.recipe_version")

View File

@@ -43,6 +43,7 @@ RECIPE_IDENTITY_SCHEMA_VERSION = 1
ARTIFACT_DIGEST_DOMAIN = "meshnet.model-artifact.v1" ARTIFACT_DIGEST_DOMAIN = "meshnet.model-artifact.v1"
RECIPE_DIGEST_DOMAIN = "meshnet.runtime-recipe.v1" RECIPE_DIGEST_DOMAIN = "meshnet.runtime-recipe.v1"
SHARD_BINDING_DIGEST_DOMAIN = "meshnet.shard-binding.v1" SHARD_BINDING_DIGEST_DOMAIN = "meshnet.shard-binding.v1"
TOKENIZER_DIGEST_DOMAIN = "meshnet.tokenizer-identity.v1"
# The axes a recipe digest commits to. Order is irrelevant (the canonical JSON # The axes a recipe digest commits to. Order is irrelevant (the canonical JSON
# sorts keys); membership is not — an axis missing here is an axis the tracker # sorts keys); membership is not — an axis missing here is an axis the tracker
@@ -71,11 +72,18 @@ MIN_CERTIFYING_NODES = 2
_HEX64 = re.compile(r"^[0-9a-f]{64}$") _HEX64 = re.compile(r"^[0-9a-f]{64}$")
_LLAMA_CPP_RUNTIME_PIN = re.compile( _LLAMA_CPP_RUNTIME_PIN = re.compile(
r"^llama\.cpp@[0-9a-f]{40}\+patchstack\.[0-9a-f]{64}$" r"^llama\.cpp@[0-9a-f]{40}\+patchstack\.[0-9a-f]{64}"
r"\+build\.[0-9a-f]{64}\+artifact\.[0-9a-f]{64}$"
) )
_LLAMA_CPP_BACKEND_IDS = frozenset({"llama.cpp", "llama-cpp"}) _LLAMA_CPP_BACKEND_IDS = frozenset({"llama.cpp", "llama-cpp"})
_MOVING_REFS = frozenset({"main", "master", "head", "latest", "dev", "trunk"}) _MOVING_REFS = frozenset({"main", "master", "head", "latest", "dev", "trunk"})
# The only admissible tokenizer identity: a digest over the tokenizer's bytes.
# A denylist of moving refs cannot enumerate every mutable label (`origin/main`,
# `stable`, `release`, a re-taggable tag…), so the tracker accepts nothing that
# *could* be a label, independently of the node's identical rule.
_TOKENIZER_IDENTITY = re.compile(r"^tokenizer\.v1:[0-9a-f]{64}$")
class RecipeIdentityError(ValueError): class RecipeIdentityError(ValueError):
"""A presented identity block is malformed or internally inconsistent.""" """A presented identity block is malformed or internally inconsistent."""
@@ -137,8 +145,44 @@ def _runtime_pin(value: Any, backend_id: Any) -> str:
backend = _text(backend_id, "recipe.backend_id").strip().lower() backend = _text(backend_id, "recipe.backend_id").strip().lower()
if backend in _LLAMA_CPP_BACKEND_IDS and not _LLAMA_CPP_RUNTIME_PIN.fullmatch(text): if backend in _LLAMA_CPP_BACKEND_IDS and not _LLAMA_CPP_RUNTIME_PIN.fullmatch(text):
raise RecipeIdentityError( raise RecipeIdentityError(
"'recipe.runtime_version' for llama.cpp must bind a 40-hex commit " "'recipe.runtime_version' for llama.cpp must bind a 40-hex commit, "
"and a 64-hex ordered patch-stack digest" "a 64-hex ordered patch-stack digest, a 64-hex build-recipe digest, "
"and the executing native artifact's 64-hex byte digest"
)
return text
def tokenizer_identity(files: Mapping[str, bytes]) -> str:
"""Independent tracker derivation of a content-addressed tokenizer identity.
Deliberately re-implemented (no `meshnet_node` import); the committed
conformance vectors pin the two derivations to each other.
"""
if not isinstance(files, Mapping) or not files:
raise RecipeIdentityError(
"tokenizer identity requires at least one named tokenizer/config byte set"
)
digests: dict[str, str] = {}
for name, body in files.items():
if not isinstance(name, str) or not name.strip():
raise RecipeIdentityError(
"tokenizer identity file names must be non-empty strings"
)
if not isinstance(body, (bytes, bytearray)):
raise RecipeIdentityError(
f"tokenizer identity for {name!r} requires the file's bytes"
)
digests[name] = hashlib.sha256(bytes(body)).hexdigest()
return "tokenizer.v1:" + _digest(TOKENIZER_DIGEST_DOMAIN, {"files": digests})
def _tokenizer_identity_value(value: Any, what: str) -> str:
text = _text(value, what)
if not _TOKENIZER_IDENTITY.fullmatch(text):
raise RecipeIdentityError(
f"{what!r} must be a content-addressed 'tokenizer.v1:<64-hex digest>' "
"identity; a label, tag, branch, or symbolic ref is a mutable pointer, "
"not the tokenizer bytes it currently resolves to"
) )
return text return text
@@ -347,7 +391,7 @@ def parse_identity(data: Any) -> PresentedIdentity:
axes[axis] = _integer(value, f"recipe.{axis}", 1) axes[axis] = _integer(value, f"recipe.{axis}", 1)
else: else:
axes[axis] = _text(value, f"recipe.{axis}") axes[axis] = _text(value, f"recipe.{axis}")
_pin(axes["tokenizer_revision"], "recipe.tokenizer_revision") _tokenizer_identity_value(axes["tokenizer_revision"], "recipe.tokenizer_revision")
_runtime_pin(axes["runtime_version"], axes["backend_id"]) _runtime_pin(axes["runtime_version"], axes["backend_id"])
identity = PresentedIdentity( identity = PresentedIdentity(

View File

@@ -34,11 +34,25 @@ from meshnet_node.runtime_recipe import ( # noqa: E402
DerivativeBinding, DerivativeBinding,
RuntimeRecipe, RuntimeRecipe,
ShardIdentity, ShardIdentity,
tokenizer_identity,
)
from meshnet_tracker.recipe import ( # noqa: E402
parse_identity,
tokenizer_identity as tracker_tokenizer_identity,
) )
from meshnet_tracker.recipe import parse_identity # noqa: E402
VECTORS = _ROOT / "tests" / "data" / "recipe_fingerprint_vectors.json" VECTORS = _ROOT / "tests" / "data" / "recipe_fingerprint_vectors.json"
SCHEMA_VERSION = 1 SCHEMA_VERSION = 2
# The tokenizer axis is content-addressed: a digest over the tokenizer's actual
# bytes, never a repository label. These example bytes are part of the wire
# contract exactly like the digests derived from them.
_TOKENIZER_FILES = {
"tokenizer.json": b'{"version":"example","vocab":{"a":0,"b":1}}\n',
"tokenizer_config.json": b'{"model_max_length":8}\n',
}
_TOKENIZER_IDENTITY = tokenizer_identity(_TOKENIZER_FILES)
assert _TOKENIZER_IDENTITY == tracker_tokenizer_identity(_TOKENIZER_FILES)
_RECIPE = RuntimeRecipe( _RECIPE = RuntimeRecipe(
weight_quantization="Q4_K_M", weight_quantization="Q4_K_M",
@@ -46,10 +60,13 @@ _RECIPE = RuntimeRecipe(
compute_dtype="float32", compute_dtype="float32",
kv_dtype="q8_0", kv_dtype="q8_0",
kv_layout="paged-v1", kv_layout="paged-v1",
tokenizer_revision="0123456789abcdef", tokenizer_revision=_TOKENIZER_IDENTITY,
architecture_adapter="llama/range-v1", architecture_adapter="llama/range-v1",
backend_id="llama.cpp", backend_id="llama.cpp",
runtime_version="llama.cpp@" + "d" * 40 + "+patchstack." + "e" * 64, runtime_version=(
"llama.cpp@" + "d" * 40 + "+patchstack." + "e" * 64
+ "+build." + "f" * 64 + "+artifact." + "a" * 64
),
recipe_id="example-gguf", recipe_id="example-gguf",
recipe_version="1", recipe_version="1",
catalogue_version="2026.07.1", catalogue_version="2026.07.1",

View File

@@ -1,5 +1,5 @@
{ {
"schema_version": 1, "schema_version": 2,
"vectors": [ "vectors": [
{ {
"description": "An undivided artifact: content digest is the source digest.", "description": "An undivided artifact: content digest is the source digest.",
@@ -8,9 +8,9 @@
"model_artifact_digest": "8a0f43d6aa49d77834bdb47bcae9f42c886b7ccfe0ac014932b2a2b38697a47b", "model_artifact_digest": "8a0f43d6aa49d77834bdb47bcae9f42c886b7ccfe0ac014932b2a2b38697a47b",
"recipe_id": "example-gguf", "recipe_id": "example-gguf",
"recipe_version": "1", "recipe_version": "1",
"runtime_recipe_digest": "63001e0efeada5b97f2f3562562dc0fd2d9bd8904dc6b7b99a71d98f3e938bd0" "runtime_recipe_digest": "73133ee50866da5b26e94a39e3e10865c41651d0b1fd1655ceae09d2234cb8eb"
}, },
"fingerprint_proto_hex": "0a40386130663433643661613439643737383334626462343762636165396634326338383662376363666530616330313439333262326132623338363937613437621240363330303165306566656164613562393766326633353632353632646330666432643962643839303464633662376239396137316439386633653933386264301a0c6578616d706c652d676775662201312a09323032362e30372e31", "fingerprint_proto_hex": "0a40386130663433643661613439643737383334626462343762636165396634326338383662376363666530616330313439333262326132623338363937613437621240373331333365653530383636646135623236653934613339653365313038363563343136353164306231666431363535636561653039643232333463623865621a0c6578616d706c652d676775662201312a09323032362e30372e31",
"identity": { "identity": {
"artifact": { "artifact": {
"architecture": "dense-llama", "architecture": "dense-llama",
@@ -26,7 +26,7 @@
"model_artifact_digest": "8a0f43d6aa49d77834bdb47bcae9f42c886b7ccfe0ac014932b2a2b38697a47b", "model_artifact_digest": "8a0f43d6aa49d77834bdb47bcae9f42c886b7ccfe0ac014932b2a2b38697a47b",
"recipe_id": "example-gguf", "recipe_id": "example-gguf",
"recipe_version": "1", "recipe_version": "1",
"runtime_recipe_digest": "63001e0efeada5b97f2f3562562dc0fd2d9bd8904dc6b7b99a71d98f3e938bd0" "runtime_recipe_digest": "73133ee50866da5b26e94a39e3e10865c41651d0b1fd1655ceae09d2234cb8eb"
}, },
"recipe": { "recipe": {
"activation_dtype": "bfloat16", "activation_dtype": "bfloat16",
@@ -40,8 +40,8 @@
"protocol_schema_version": 1, "protocol_schema_version": 1,
"recipe_id": "example-gguf", "recipe_id": "example-gguf",
"recipe_version": "1", "recipe_version": "1",
"runtime_version": "llama.cpp@dddddddddddddddddddddddddddddddddddddddd+patchstack.eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", "runtime_version": "llama.cpp@dddddddddddddddddddddddddddddddddddddddd+patchstack.eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee+build.ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff+artifact.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"tokenizer_revision": "0123456789abcdef", "tokenizer_revision": "tokenizer.v1:2d4e25eb9dd1a5e6ecef2fb36c0cb49a6cbe61161c807ea07a62ba268e9fb665",
"weight_quantization": "Q4_K_M" "weight_quantization": "Q4_K_M"
}, },
"schema_version": 1, "schema_version": 1,
@@ -58,9 +58,9 @@
"model_artifact_digest": "8a0f43d6aa49d77834bdb47bcae9f42c886b7ccfe0ac014932b2a2b38697a47b", "model_artifact_digest": "8a0f43d6aa49d77834bdb47bcae9f42c886b7ccfe0ac014932b2a2b38697a47b",
"recipe_id": "example-gguf", "recipe_id": "example-gguf",
"recipe_version": "1", "recipe_version": "1",
"runtime_recipe_digest": "63001e0efeada5b97f2f3562562dc0fd2d9bd8904dc6b7b99a71d98f3e938bd0" "runtime_recipe_digest": "73133ee50866da5b26e94a39e3e10865c41651d0b1fd1655ceae09d2234cb8eb"
}, },
"fingerprint_proto_hex": "0a40386130663433643661613439643737383334626462343762636165396634326338383662376363666530616330313439333262326132623338363937613437621240363330303165306566656164613562393766326633353632353632646330666432643962643839303464633662376239396137316439386633653933386264301a0c6578616d706c652d676775662201312a09323032362e30372e31", "fingerprint_proto_hex": "0a40386130663433643661613439643737383334626462343762636165396634326338383662376363666530616330313439333262326132623338363937613437621240373331333365653530383636646135623236653934613339653365313038363563343136353164306231666431363535636561653039643232333463623865621a0c6578616d706c652d676775662201312a09323032362e30372e31",
"identity": { "identity": {
"artifact": { "artifact": {
"architecture": "dense-llama", "architecture": "dense-llama",
@@ -80,7 +80,7 @@
"model_artifact_digest": "8a0f43d6aa49d77834bdb47bcae9f42c886b7ccfe0ac014932b2a2b38697a47b", "model_artifact_digest": "8a0f43d6aa49d77834bdb47bcae9f42c886b7ccfe0ac014932b2a2b38697a47b",
"recipe_id": "example-gguf", "recipe_id": "example-gguf",
"recipe_version": "1", "recipe_version": "1",
"runtime_recipe_digest": "63001e0efeada5b97f2f3562562dc0fd2d9bd8904dc6b7b99a71d98f3e938bd0" "runtime_recipe_digest": "73133ee50866da5b26e94a39e3e10865c41651d0b1fd1655ceae09d2234cb8eb"
}, },
"recipe": { "recipe": {
"activation_dtype": "bfloat16", "activation_dtype": "bfloat16",
@@ -94,8 +94,8 @@
"protocol_schema_version": 1, "protocol_schema_version": 1,
"recipe_id": "example-gguf", "recipe_id": "example-gguf",
"recipe_version": "1", "recipe_version": "1",
"runtime_version": "llama.cpp@dddddddddddddddddddddddddddddddddddddddd+patchstack.eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", "runtime_version": "llama.cpp@dddddddddddddddddddddddddddddddddddddddd+patchstack.eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee+build.ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff+artifact.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"tokenizer_revision": "0123456789abcdef", "tokenizer_revision": "tokenizer.v1:2d4e25eb9dd1a5e6ecef2fb36c0cb49a6cbe61161c807ea07a62ba268e9fb665",
"weight_quantization": "Q4_K_M" "weight_quantization": "Q4_K_M"
}, },
"schema_version": 1, "schema_version": 1,

View File

@@ -1,30 +1,137 @@
"""DGR-003 production-native identity emission boundary tests.""" """DGR-003 production-native identity emission boundary tests.
The executing-runtime attestation must be *extracted from the native artifact
itself* — a Python object holding values copied out of the world-readable
repository lock proves nothing and must not pass. These tests compile real
(tiny) shared objects that embed the attestation marker and export the
attestation symbol, then prove the positive path works and that every
lock-copying forgery path fails closed.
"""
from __future__ import annotations from __future__ import annotations
import dataclasses
import hashlib
import shutil
import subprocess
import pytest import pytest
from meshnet_node.doctor import DoctorSelection, validate_loaded_backend from meshnet_node.doctor import DoctorSelection, validate_loaded_backend
from meshnet_node.native_backend import ( from meshnet_node.native_backend import (
ATTESTATION_MARKER_PREFIX,
ATTESTATION_SYMBOL,
ImmutableArtifactPin, ImmutableArtifactPin,
NativeArtifactEvidence,
NativeIdentityInputs, NativeIdentityInputs,
NativeLoadedArtifactReport, NativeLoadedArtifactReport,
NativeNumericalRecipe, NativeNumericalRecipe,
NativeRuntimeAttestation,
NativeSessionRejected, NativeSessionRejected,
NativeWorkerBackendAdapter, NativeWorkerBackendAdapter,
attest_loaded_runtime,
attestation_payload,
expected_attestation_payload,
shard_identity_from_native_report, shard_identity_from_native_report,
) )
from meshnet_node.native_protocol import SCHEMA_VERSION, pb 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_node.recipe_manifest import parse_recipe_manifest
from meshnet_node.runtime_pin import load_runtime_pin
from meshnet_node.runtime_recipe import RecipeIdentityError, tokenizer_identity
from meshnet_tracker.capability import STATE_UNCERTIFIED, evaluate_report from meshnet_tracker.capability import STATE_UNCERTIFIED, evaluate_report
CC = shutil.which("cc")
requires_cc = pytest.mark.skipif(
CC is None, reason="no C compiler to build a native attestation fixture"
)
def _digest(letter: str) -> str: def _digest(letter: str) -> str:
return letter * 64 return letter * 64
def _inputs(**changes: object) -> NativeIdentityInputs: def _c_literal(data: bytes) -> str:
# Every byte as \xNN; the next escape's backslash terminates each escape.
return '"' + "".join(f"\\x{b:02x}" for b in data) + '"'
def _build_native_artifact(
directory,
payload: bytes | None,
*,
export_symbol: bool = True,
symbol_returns: bytes | None = None,
extra_payloads: tuple[bytes, ...] = (),
):
"""Compile a real shared object carrying the requested attestation shape."""
lines = []
if payload is not None:
marker = ATTESTATION_MARKER_PREFIX + payload
lines.append(
"__attribute__((used)) const char marker[] = " f"{_c_literal(marker)};"
)
for index, extra in enumerate(extra_payloads):
lines.append(
f"__attribute__((used)) const char extra{index}[] = "
f"{_c_literal(ATTESTATION_MARKER_PREFIX + extra)};"
)
if export_symbol:
if symbol_returns is None:
body = "return marker;"
else:
lines.append(
"__attribute__((used)) const char other[] = "
f"{_c_literal(symbol_returns)};"
)
body = "return other;"
lines.append(f"const char *{ATTESTATION_SYMBOL}(void) {{ {body} }}")
source = directory / "attesting_runtime.c"
source.write_text("\n".join(lines) + "\n", encoding="utf-8")
artifact = directory / "libattesting_runtime.so"
subprocess.run(
[CC, "-shared", "-fPIC", "-O0", "-o", str(artifact), str(source)],
check=True,
capture_output=True,
)
return artifact
def _payload(**overrides: object) -> bytes:
pin = load_runtime_pin()
values: dict[str, object] = {
"runtime_name": pin.runtime_name,
"upstream_commit": pin.upstream_commit,
"patched_tree": pin.patched_tree,
"patch_stack_digest": pin.patch_stack_digest,
"build_recipe_digest": pin.build_recipe_digest,
"boundary_schema_version": 1,
"protocol_schema_version": int(SCHEMA_VERSION),
}
values.update(overrides)
return attestation_payload(**values) # type: ignore[arg-type]
def _forged_attestation(directory, **overrides: object) -> NativeRuntimeAttestation:
"""A self-consistent native artifact whose embedded values are wrong."""
return attest_loaded_runtime(_build_native_artifact(directory, _payload(**overrides)))
@pytest.fixture(scope="session")
def genuine_artifact(tmp_path_factory):
if CC is None:
pytest.skip("no C compiler to build a native attestation fixture")
directory = tmp_path_factory.mktemp("genuine-runtime")
return _build_native_artifact(
directory, expected_attestation_payload(load_runtime_pin())
)
@pytest.fixture(scope="session")
def genuine_attestation(genuine_artifact):
return attest_loaded_runtime(genuine_artifact)
def _inputs(attestation: NativeRuntimeAttestation, **changes: object) -> NativeIdentityInputs:
report = NativeLoadedArtifactReport( report = NativeLoadedArtifactReport(
owned_start_layer=2, owned_start_layer=2,
owned_end_layer=6, owned_end_layer=6,
@@ -34,6 +141,7 @@ def _inputs(**changes: object) -> NativeIdentityInputs:
architecture="llama", architecture="llama",
architecture_digest=_digest("a"), architecture_digest=_digest("a"),
layer_count=8, layer_count=8,
runtime_attestation=attestation,
) )
recipe = NativeNumericalRecipe( recipe = NativeNumericalRecipe(
weight_quantization="Q4_K_M", weight_quantization="Q4_K_M",
@@ -54,13 +162,21 @@ def _inputs(**changes: object) -> NativeIdentityInputs:
revision="0123456789abcdef", revision="0123456789abcdef",
content_digest=_digest("b"), content_digest=_digest("b"),
), ),
"tokenizer_revision": "abcdef0123456789", "tokenizer_revision": tokenizer_identity(
{"tokenizer.json": b'{"vocab":{"a":0}}\n'}
),
"numerical_recipe": recipe, "numerical_recipe": recipe,
} }
values.update(changes) values.update(changes)
return NativeIdentityInputs(**values) # type: ignore[arg-type] return NativeIdentityInputs(**values) # type: ignore[arg-type]
def _report_with(attestation: NativeRuntimeAttestation) -> NativeLoadedArtifactReport:
return NativeLoadedArtifactReport(
2, 6, 1024, 768, 640, "llama", _digest("a"), 8, attestation
)
def _open(adapter: NativeWorkerBackendAdapter, **changes: object) -> pb.SessionOpen: def _open(adapter: NativeWorkerBackendAdapter, **changes: object) -> pb.SessionOpen:
identity = adapter.identity identity = adapter.identity
fields: dict[str, object] = { fields: dict[str, object] = {
@@ -78,26 +194,217 @@ def _open(adapter: NativeWorkerBackendAdapter, **changes: object) -> pb.SessionO
return pb.SessionOpen(**fields) # type: ignore[arg-type] return pb.SessionOpen(**fields) # type: ignore[arg-type]
def test_native_identity_uses_loaded_report_not_a_caller_range(): # --- copied lock values alone must never pass ------------------------------
identity = shard_identity_from_native_report(_inputs())
def test_copied_lock_values_cannot_author_an_attestation():
pin = load_runtime_pin()
values: dict[str, object] = {
"runtime_name": pin.runtime_name,
"upstream_commit": pin.upstream_commit,
"patched_tree": pin.patched_tree,
"patch_stack_digest": pin.patch_stack_digest,
"build_recipe_digest": pin.build_recipe_digest,
"boundary_schema_version": 1,
"protocol_schema_version": int(SCHEMA_VERSION),
}
# The pre-repair forgery: a bare self-report of lock values.
with pytest.raises(TypeError):
NativeRuntimeAttestation(**values) # type: ignore[arg-type]
with pytest.raises(RecipeIdentityError, match="attest_loaded_runtime"):
NativeRuntimeAttestation(evidence=None, **values) # type: ignore[arg-type]
def test_native_artifact_evidence_cannot_be_authored_in_python():
with pytest.raises(RecipeIdentityError, match="attest_loaded_runtime"):
NativeArtifactEvidence("lib.so", _digest("a"), _digest("b"))
with pytest.raises(RecipeIdentityError, match="attest_loaded_runtime"):
NativeArtifactEvidence("lib.so", _digest("a"), _digest("b"), object())
def test_marker_bytes_in_a_plain_file_are_not_an_executing_runtime(tmp_path):
fake = tmp_path / "fake.so"
fake.write_bytes(ATTESTATION_MARKER_PREFIX + _payload() + b"\x00")
with pytest.raises(RecipeIdentityError, match="not a loadable"):
attest_loaded_runtime(fake)
def test_missing_native_artifact_fails_closed(tmp_path):
with pytest.raises(RecipeIdentityError, match="not found"):
attest_loaded_runtime(tmp_path / "never-built.so")
@requires_cc
def test_artifact_without_a_marker_fails_closed(tmp_path):
artifact = _build_native_artifact(tmp_path, None, symbol_returns=b"no marker")
with pytest.raises(RecipeIdentityError, match="embeds no runtime attestation"):
attest_loaded_runtime(artifact)
@requires_cc
def test_artifact_without_the_symbol_fails_closed(tmp_path):
artifact = _build_native_artifact(tmp_path, _payload(), export_symbol=False)
with pytest.raises(RecipeIdentityError, match="does not export"):
attest_loaded_runtime(artifact)
@requires_cc
def test_loaded_runtime_disagreeing_with_its_marker_fails_closed(tmp_path):
artifact = _build_native_artifact(
tmp_path, _payload(), symbol_returns=b"not the marker"
)
with pytest.raises(RecipeIdentityError, match="different attestation"):
attest_loaded_runtime(artifact)
@requires_cc
def test_conflicting_markers_fail_closed(tmp_path):
artifact = _build_native_artifact(
tmp_path, _payload(), extra_payloads=(_payload(patched_tree="f" * 40),)
)
with pytest.raises(RecipeIdentityError, match="conflicting"):
attest_loaded_runtime(artifact)
@requires_cc
def test_noncanonical_marker_payload_fails_closed(tmp_path):
(tmp_path / "a").mkdir()
wrong_keys = _build_native_artifact(tmp_path / "a", b'{ "spaced": true }')
with pytest.raises(RecipeIdentityError, match="exactly the attestation fields"):
attest_loaded_runtime(wrong_keys)
# Right keys, non-canonical encoding: the digest binding would be
# ambiguous, so the extractor refuses.
spaced = _payload().replace(b":", b": ").replace(b",", b", ")
(tmp_path / "b").mkdir()
noncanonical = _build_native_artifact(tmp_path / "b", spaced)
with pytest.raises(RecipeIdentityError, match="canonical form"):
attest_loaded_runtime(noncanonical)
@requires_cc
def test_lock_values_cannot_launder_a_mismatched_runtime(tmp_path):
# A real (loadable, self-consistent) artifact built from the *wrong* tree
# attests fine — then editing the Python object to the lock's values must
# fail, or evidence extraction would be decorative.
forged = _forged_attestation(tmp_path, patched_tree="f" * 40)
with pytest.raises(RecipeIdentityError, match="edited after extraction"):
dataclasses.replace(forged, patched_tree=load_runtime_pin().patched_tree)
def test_evidence_binds_the_attested_artifact_bytes(genuine_artifact, genuine_attestation):
assert genuine_attestation.evidence.binary_digest == hashlib.sha256(
genuine_artifact.read_bytes()
).hexdigest()
assert genuine_attestation.evidence.artifact_path == str(genuine_artifact)
# --- the executing runtime must match the lock, field by field -------------
@pytest.mark.parametrize(
"field,value",
[
("runtime_name", "other.cpp"),
("upstream_commit", "f" * 40),
("patched_tree", "f" * 40),
("patch_stack_digest", _digest("f")),
("build_recipe_digest", _digest("f")),
("boundary_schema_version", 2),
("protocol_schema_version", 2),
],
)
@requires_cc
def test_native_identity_fails_closed_when_executing_runtime_disagrees(
field, value, tmp_path
):
attestation = _forged_attestation(tmp_path, **{field: value})
with pytest.raises(RecipeIdentityError, match="attested"):
shard_identity_from_native_report(
_inputs(attestation, loaded_artifact=_report_with(attestation))
)
@requires_cc
def test_distinguishable_runtime_attestations_cannot_emit_one_accepted_identity(
tmp_path, genuine_attestation
):
accepted = shard_identity_from_native_report(_inputs(genuine_attestation))
forged = _forged_attestation(tmp_path, patched_tree="f" * 40)
with pytest.raises(RecipeIdentityError, match="patched source tree"):
shard_identity_from_native_report(
_inputs(forged, loaded_artifact=_report_with(forged))
)
assert accepted.recipe.runtime_version == (
load_runtime_pin().runtime_version
+ "+artifact."
+ genuine_attestation.evidence.binary_digest
)
# --- the attested positive path ---------------------------------------------
def test_native_identity_uses_loaded_report_not_a_caller_range(genuine_attestation):
identity = shard_identity_from_native_report(_inputs(genuine_attestation))
assert (identity.shard_start, identity.shard_end) == (2, 6) assert (identity.shard_start, identity.shard_end) == (2, 6)
assert identity.artifact.architecture == "llama" assert identity.artifact.architecture == "llama"
assert identity.artifact.layer_count == 8 assert identity.artifact.layer_count == 8
assert identity.recipe.runtime_version == load_runtime_pin().runtime_version assert identity.recipe.runtime_version == (
load_runtime_pin().runtime_version
+ "+artifact."
+ genuine_attestation.evidence.binary_digest
)
def test_native_identity_requires_an_immutable_pin_and_gguf_range(): @requires_cc
def test_copying_public_lock_values_cannot_forge_the_certified_runtime_identity(
tmp_path, genuine_attestation
):
"""A second loadable artifact with lock-true self-report gets a new identity."""
accepted = shard_identity_from_native_report(_inputs(genuine_attestation))
copied_artifact = _build_native_artifact(
tmp_path, expected_attestation_payload(load_runtime_pin())
)
# Keep the same exported marker/symbol while making this a different set of
# executing artifact bytes, exactly like a separately built binary that
# copied the public lock values into its self-report.
copied_artifact.write_bytes(copied_artifact.read_bytes() + b"copied-lock-forgery")
copied_lock_values = attest_loaded_runtime(copied_artifact)
copied = shard_identity_from_native_report(
_inputs(
copied_lock_values,
loaded_artifact=_report_with(copied_lock_values),
)
)
assert copied_lock_values.evidence.binary_digest != (
genuine_attestation.evidence.binary_digest
)
assert copied.recipe.runtime_version != accepted.recipe.runtime_version
assert copied.fingerprint.runtime_recipe_digest != (
accepted.fingerprint.runtime_recipe_digest
)
def test_native_identity_requires_an_immutable_pin_and_gguf_range(genuine_attestation):
with pytest.raises(Exception, match="moving reference"): with pytest.raises(Exception, match="moving reference"):
shard_identity_from_native_report( shard_identity_from_native_report(
_inputs(artifact_pin=ImmutableArtifactPin("a", "main", _digest("b"))) _inputs(
genuine_attestation,
artifact_pin=ImmutableArtifactPin("a", "main", _digest("b")),
)
) )
with pytest.raises(Exception, match="outside GGUF"): with pytest.raises(Exception, match="outside GGUF"):
NativeLoadedArtifactReport(0, 9, 1, 1, 1, "llama", _digest("a"), 8) NativeLoadedArtifactReport(
0, 9, 1, 1, 1, "llama", _digest("a"), 8, genuine_attestation
)
def test_native_worker_rejects_bad_session_open_before_session_acceptance(): def test_native_worker_rejects_bad_session_open_before_session_acceptance(
adapter = NativeWorkerBackendAdapter(_inputs()) genuine_attestation,
):
adapter = NativeWorkerBackendAdapter(_inputs(genuine_attestation))
accepted = adapter.on_session_open( accepted = adapter.on_session_open(
_open(adapter), expected_route_session_id="tracker-session", expected_route_epoch=4 _open(adapter), expected_route_session_id="tracker-session", expected_route_epoch=4
) )
@@ -112,14 +419,16 @@ def test_native_worker_rejects_bad_session_open_before_session_acceptance():
assert rejected.value.error.code == pb.ERROR_CODE_EPOCH_STALE assert rejected.value.error.code == pb.ERROR_CODE_EPOCH_STALE
def test_doctor_emits_native_identity_but_keeps_legacy_backend_dark(): def test_doctor_emits_native_identity_but_keeps_legacy_backend_dark(
genuine_attestation,
):
manifest = parse_recipe_manifest( manifest = parse_recipe_manifest(
{"schema_version": 1, "catalogue_version": "2026.07.1", "recipes": [ {"schema_version": 1, "catalogue_version": "2026.07.1", "recipes": [
{"id": "native", "version": "1", "backend_id": "llama-cpp"} {"id": "native", "version": "1", "backend_id": "llama-cpp"}
]} ]}
) )
selection = DoctorSelection("acme/llama.gguf", 2, 5) selection = DoctorSelection("acme/llama.gguf", 2, 5)
native = NativeWorkerBackendAdapter(_inputs()) native = NativeWorkerBackendAdapter(_inputs(genuine_attestation))
# The probe needs only the normal backend shape; identity is supplied by the adapter. # The probe needs only the normal backend shape; identity is supplied by the adapter.
native.hidden_size = 8 native.hidden_size = 8
native.is_head = False native.is_head = False

View File

@@ -21,15 +21,21 @@ from meshnet_node.runtime_pin import (
RuntimePinError, RuntimePinError,
load_runtime_pin, load_runtime_pin,
) )
from meshnet_node.runtime_recipe import RecipeIdentityError, RuntimeRecipe from meshnet_node.runtime_recipe import (
RecipeIdentityError,
RuntimeRecipe,
tokenizer_identity,
)
from meshnet_tracker.recipe import ( from meshnet_tracker.recipe import (
RecipeIdentityError as TrackerRecipeIdentityError, RecipeIdentityError as TrackerRecipeIdentityError,
parse_identity, parse_identity,
tokenizer_identity as tracker_tokenizer_identity,
) )
REPO_LOCK_DIR = ( REPO_LOCK_DIR = (
Path(__file__).resolve().parent.parent / "packages" / "node" / "native" / "llama" Path(__file__).resolve().parent.parent / "packages" / "node" / "native" / "llama"
) )
TOKENIZER = tokenizer_identity({"tokenizer.json": b'{"vocab":{"a":0}}\n'})
def _recipe(**changes: object) -> RuntimeRecipe: def _recipe(**changes: object) -> RuntimeRecipe:
@@ -39,10 +45,14 @@ def _recipe(**changes: object) -> RuntimeRecipe:
"compute_dtype": "float32", "compute_dtype": "float32",
"kv_dtype": "q8_0", "kv_dtype": "q8_0",
"kv_layout": "paged-v1", "kv_layout": "paged-v1",
"tokenizer_revision": "0123456789abcdef", "tokenizer_revision": TOKENIZER,
"architecture_adapter": "llama/range-v1", "architecture_adapter": "llama/range-v1",
"backend_id": "llama.cpp", "backend_id": "llama.cpp",
"runtime_version": "llama.cpp@" + "d" * 40 + "+patchstack." + "e" * 64, "runtime_version": (
"llama.cpp@" + "d" * 40 + "+patchstack." + "e" * 64
+ "+build." + "f" * 64
+ "+artifact." + "a" * 64
),
"recipe_id": "example-gguf", "recipe_id": "example-gguf",
"recipe_version": "1", "recipe_version": "1",
"catalogue_version": "2026.07.1", "catalogue_version": "2026.07.1",
@@ -62,6 +72,8 @@ def _write_fixture_workspace(
schema_version: int = 1, schema_version: int = 1,
upstream: str = "https://github.com/ggml-org/llama.cpp.git", upstream: str = "https://github.com/ggml-org/llama.cpp.git",
upstream_commit_file: str | None = None, upstream_commit_file: str | None = None,
patched_tree: str = "a" * 40,
build: dict[str, object] | None = None,
) -> Path: ) -> Path:
"""A minimal DGR-027-shaped lock workspace; overrides create disagreement.""" """A minimal DGR-027-shaped lock workspace; overrides create disagreement."""
if patches is None: if patches is None:
@@ -85,6 +97,8 @@ def _write_fixture_workspace(
"schema_version": schema_version, "schema_version": schema_version,
"upstream": upstream, "upstream": upstream,
"commit": commit, "commit": commit,
"patched_tree": patched_tree,
"build": build or {"configure_flags": ["-DTEST=ON"]},
"patch_series": names if lock_series is None else lock_series, "patch_series": names if lock_series is None else lock_series,
} }
), ),
@@ -124,18 +138,19 @@ def test_committed_manifest_derives_a_deterministic_runtime_pin():
) )
assert pin.upstream_commit == lock["commit"] assert pin.upstream_commit == lock["commit"]
assert list(pin.patch_series) == lock["patch_series"] assert list(pin.patch_series) == lock["patch_series"]
# The axis value names the runtime, the exact commit, and the stack digest, # The axis value names the runtime, exact commit, stack, and build recipe.
# so changing any of the three changes every downstream recipe digest.
assert pin.runtime_version == ( assert pin.runtime_version == (
f"llama.cpp@{lock['commit']}+patchstack.{pin.patch_stack_digest}" f"llama.cpp@{lock['commit']}+patchstack.{pin.patch_stack_digest}"
f"+build.{pin.build_recipe_digest}"
) )
assert len(pin.patch_stack_digest) == 64 assert len(pin.patch_stack_digest) == 64
def test_derived_axis_value_is_a_valid_recipe_pin(): def test_derived_axis_value_is_a_valid_recipe_pin():
pin = load_runtime_pin(REPO_LOCK_DIR) pin = load_runtime_pin(REPO_LOCK_DIR)
recipe = _recipe(runtime_version=pin.runtime_version) runtime_version = pin.runtime_version + "+artifact." + "a" * 64
assert recipe.runtime_version == pin.runtime_version recipe = _recipe(runtime_version=runtime_version)
assert recipe.runtime_version == runtime_version
assert len(recipe.runtime_recipe_digest) == 64 assert len(recipe.runtime_recipe_digest) == 64
@@ -179,6 +194,16 @@ def test_patch_order_is_part_of_the_stack_identity(tmp_path):
assert forward.patch_stack_digest != swapped.patch_stack_digest assert forward.patch_stack_digest != swapped.patch_stack_digest
def test_build_recipe_change_changes_runtime_identity(tmp_path):
baseline = load_runtime_pin(_write_fixture_workspace(tmp_path / "a"))
changed = load_runtime_pin(
_write_fixture_workspace(
tmp_path / "b", build={"configure_flags": ["-DTEST=OFF"]}
)
)
assert baseline.runtime_version != changed.runtime_version
# --- every manifest disagreement fails closed ------------------------------ # --- every manifest disagreement fails closed ------------------------------
@@ -257,6 +282,53 @@ def test_empty_patch_series_requires_empty_series_files(tmp_path):
load_runtime_pin(tmp_path) load_runtime_pin(tmp_path)
# --- tokenizer identities are bytes, never labels -------------------------
def _vector_identity() -> dict[str, object]:
vectors = json.loads(
(Path(__file__).parent / "data" / "recipe_fingerprint_vectors.json").read_text(
encoding="utf-8"
)
)
return json.loads(json.dumps(vectors["vectors"][0]["identity"]))
@pytest.mark.parametrize("moving", ["origin/main", "stable", "release", "v1", "HEAD"])
def test_node_rejects_every_label_as_a_tokenizer_identity(moving):
with pytest.raises(RecipeIdentityError, match="content-addressed tokenizer"):
_recipe(tokenizer_revision=moving)
@pytest.mark.parametrize("moving", ["origin/main", "stable", "release", "v1", "HEAD"])
def test_tracker_rejects_every_label_as_a_tokenizer_identity(moving):
doc = _vector_identity()
doc["recipe"]["tokenizer_revision"] = moving # type: ignore[index]
doc.pop("fingerprint", None)
with pytest.raises(TrackerRecipeIdentityError, match="content-addressed"):
parse_identity(doc)
def test_node_and_tracker_independently_derive_identical_tokenizer_bytes_identity():
files = {
"tokenizer.json": b'{"vocab":{"a":0}}\n',
"tokenizer_config.json": b'{"bos_token":"<s>"}\n',
}
assert tokenizer_identity(files) == tracker_tokenizer_identity(files)
def test_tokenizer_bytes_under_one_label_and_one_byte_change_get_new_recipe_fingerprints():
label = "stable"
first = {"tokenizer.json": b'{"vocab":{"a":0}}\n'}
second = {"tokenizer.json": b'{"vocab":{"a":1}}\n'}
first_id = tokenizer_identity(first)
second_id = tokenizer_identity(second)
assert first_id != second_id, label
assert _recipe(tokenizer_revision=first_id).runtime_recipe_digest != _recipe(
tokenizer_revision=second_id
).runtime_recipe_digest
# --- both identity implementations reject a moving runtime ----------------- # --- both identity implementations reject a moving runtime -----------------

View File

@@ -21,6 +21,7 @@ from meshnet_node.runtime_recipe import (
check_session_open, check_session_open,
check_route, check_route,
handshake_error, handshake_error,
tokenizer_identity,
) )
from meshnet_tracker.capability import ( from meshnet_tracker.capability import (
POLICY_COMPAT, POLICY_COMPAT,
@@ -49,6 +50,7 @@ from meshnet_tracker.recipe import (
) )
VECTORS = Path(__file__).parent / "data" / "recipe_fingerprint_vectors.json" VECTORS = Path(__file__).parent / "data" / "recipe_fingerprint_vectors.json"
TOKENIZER = tokenizer_identity({"tokenizer.json": b'{"vocab":{"a":0}}\n'})
def _digest(char: str) -> str: def _digest(char: str) -> str:
@@ -62,10 +64,14 @@ def _recipe(**changes: object) -> RuntimeRecipe:
"compute_dtype": "float32", "compute_dtype": "float32",
"kv_dtype": "q8_0", "kv_dtype": "q8_0",
"kv_layout": "paged-v1", "kv_layout": "paged-v1",
"tokenizer_revision": "0123456789abcdef", "tokenizer_revision": TOKENIZER,
"architecture_adapter": "llama/range-v1", "architecture_adapter": "llama/range-v1",
"backend_id": "llama.cpp", "backend_id": "llama.cpp",
"runtime_version": "llama.cpp@" + "d" * 40 + "+patchstack." + "e" * 64, "runtime_version": (
"llama.cpp@" + "d" * 40 + "+patchstack." + "e" * 64
+ "+build." + "f" * 64
+ "+artifact." + "a" * 64
),
"recipe_id": "example-gguf", "recipe_id": "example-gguf",
"recipe_version": "1", "recipe_version": "1",
"catalogue_version": "2026.07.1", "catalogue_version": "2026.07.1",
@@ -238,10 +244,17 @@ def test_committed_vectors_cover_a_whole_model_and_a_derivative_shard():
("compute_dtype", "float16"), ("compute_dtype", "float16"),
("kv_dtype", "float16"), ("kv_dtype", "float16"),
("kv_layout", "contiguous-v2"), ("kv_layout", "contiguous-v2"),
("tokenizer_revision", "fedcba9876543210"), (
"tokenizer_revision",
tokenizer_identity({"tokenizer.json": b'{"vocab":{"b":0}}\n'}),
),
("architecture_adapter", "llama/range-v2"), ("architecture_adapter", "llama/range-v2"),
("backend_id", "other-backend"), ("backend_id", "other-backend"),
("runtime_version", "llama.cpp@" + "c" * 40 + "+patchstack." + "b" * 64), (
"runtime_version",
"llama.cpp@" + "c" * 40 + "+patchstack." + "b" * 64
+ "+build." + "a" * 64 + "+artifact." + "c" * 64,
),
("boundary_schema_version", 2), ("boundary_schema_version", 2),
("protocol_schema_version", 2), ("protocol_schema_version", 2),
], ],