Merge DGR-025 (certified-artifact-byte recipe identity) from ralph-fable-loop lane
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
# 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`
|
||||
**Status:** in progress — controller gates pass; final independent P0/P1 re-review is pending.
|
||||
**Branch:** fixed detached Claude Fable provider lane
|
||||
**Authority:** live Gitea #9; the local PRD is a secondary projection.
|
||||
**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.
|
||||
@@ -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
|
||||
digests — and note `layer_count` must count the routed transformer stack the
|
||||
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
|
||||
```
|
||||
|
||||
@@ -9,10 +9,15 @@ authoritative immutable GGUF artifact pin and must remain identity-free.
|
||||
|
||||
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 .runtime_pin import load_runtime_pin
|
||||
from .runtime_pin import DEFAULT_LOCK_DIR, RuntimePin, load_runtime_pin
|
||||
from .runtime_recipe import (
|
||||
ArtifactIdentity,
|
||||
DerivativeBinding,
|
||||
@@ -23,6 +28,323 @@ from .runtime_recipe import (
|
||||
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)
|
||||
class NativeLoadedArtifactReport:
|
||||
@@ -32,6 +354,10 @@ class NativeLoadedArtifactReport:
|
||||
parsed GGUF metadata while the model is live. Byte counts are operational
|
||||
evidence rather than compatibility axes, but keeping them beside the range
|
||||
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
|
||||
@@ -42,6 +368,7 @@ class NativeLoadedArtifactReport:
|
||||
architecture: str
|
||||
architecture_digest: str
|
||||
layer_count: int
|
||||
runtime_attestation: NativeRuntimeAttestation
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
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")
|
||||
if min(self.mapped_bytes, self.resident_bytes, self.registered_bytes) < 0:
|
||||
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)
|
||||
@@ -82,7 +413,12 @@ class NativeNumericalRecipe:
|
||||
|
||||
@dataclass(frozen=True)
|
||||
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
|
||||
artifact_pin: ImmutableArtifactPin
|
||||
@@ -90,8 +426,72 @@ class NativeIdentityInputs:
|
||||
numerical_recipe: NativeNumericalRecipe
|
||||
|
||||
|
||||
def shard_identity_from_native_report(inputs: NativeIdentityInputs) -> ShardIdentity:
|
||||
"""Derive identity only from the native report and immutable pinned inputs."""
|
||||
def _require_attested_runtime(
|
||||
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
|
||||
pin = inputs.artifact_pin
|
||||
recipe = inputs.numerical_recipe
|
||||
@@ -99,7 +499,17 @@ def shard_identity_from_native_report(inputs: NativeIdentityInputs) -> ShardIden
|
||||
raise RecipeIdentityError(
|
||||
"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_id=pin.artifact_id,
|
||||
revision=pin.revision,
|
||||
|
||||
@@ -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
|
||||
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
|
||||
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,
|
||||
40-character upstream commit, a digest over the ordered patch-stack bytes, and
|
||||
a digest over the numerically relevant build recipe. A different upstream pin,
|
||||
a reordered stack, a single changed patch byte, or a changed build flag each
|
||||
produce a different axis value, which produces a different recipe digest,
|
||||
which partitions the route — exactly the fail-closed behavior DGR-025 asks
|
||||
for.
|
||||
for. The pin also records the patched source tree's git tree id, which the
|
||||
executing runtime's attestation is compared against
|
||||
(:mod:`meshnet_node.native_backend`).
|
||||
|
||||
Every consistency check here fails closed. The manifest keeps three records of
|
||||
the stack — ``UPSTREAM_LOCK.json``'s ``patch_series``, ``patches/series``, and
|
||||
@@ -36,6 +39,7 @@ from pathlib import Path
|
||||
# Domain separation, matching the runtime_recipe digest convention: a patch
|
||||
# stack digest can never be confused with an artifact or recipe digest.
|
||||
PATCH_STACK_DIGEST_DOMAIN = "meshnet.runtime-patch-stack.v1"
|
||||
BUILD_RECIPE_DIGEST_DOMAIN = "meshnet.runtime-build-recipe.v1"
|
||||
|
||||
# The UPSTREAM_LOCK.json layout this reader understands (DGR-027 schema).
|
||||
RUNTIME_PIN_SCHEMA_VERSION = 1
|
||||
@@ -58,14 +62,42 @@ def _canonical_sha256(value: object) -> str:
|
||||
return hashlib.sha256(payload.encode("utf-8")).hexdigest()
|
||||
|
||||
|
||||
def build_recipe_digest(build: object) -> str:
|
||||
"""A digest over the numerically relevant build recipe.
|
||||
|
||||
The lock's ``build`` object records what the runtime is compiled *as* —
|
||||
the configure flags, standards, and targets that select kernels and
|
||||
numeric behavior. Two binaries built from one patched tree with different
|
||||
build recipes can disagree numerically, so the recipe is part of runtime
|
||||
identity. This is deliberately a digest over the *recorded recipe*, not a
|
||||
compiler-specific binary SHA: reproducible-binary attestation is not
|
||||
claimed here.
|
||||
"""
|
||||
if not isinstance(build, dict) or not build:
|
||||
raise RuntimePinError(
|
||||
"the lock's 'build' section must be a non-empty JSON object; a "
|
||||
"runtime with an unstated build recipe has an unknowable identity"
|
||||
)
|
||||
return _canonical_sha256(
|
||||
{"domain": BUILD_RECIPE_DIGEST_DOMAIN, "body": build}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class RuntimePin:
|
||||
"""One exact runtime: 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
|
||||
upstream_commit: str
|
||||
patched_tree: str
|
||||
patch_series: tuple[str, ...]
|
||||
patch_digests: tuple[str, ...]
|
||||
build_recipe_digest: str
|
||||
|
||||
@property
|
||||
def patch_stack_digest(self) -> str:
|
||||
@@ -89,10 +121,16 @@ class RuntimePin:
|
||||
|
||||
@property
|
||||
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 (
|
||||
f"{self.runtime_name}@{self.upstream_commit}"
|
||||
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"
|
||||
)
|
||||
|
||||
patched_tree = lock.get("patched_tree")
|
||||
if not isinstance(patched_tree, str) or not _HEX40.match(patched_tree):
|
||||
raise RuntimePinError(
|
||||
"UPSTREAM_LOCK.json patched_tree must be the exact 40-character git "
|
||||
"tree object id of the source tree after the patch stack is applied"
|
||||
)
|
||||
|
||||
build_digest = build_recipe_digest(lock.get("build"))
|
||||
|
||||
commit_file = _read_text(lock_dir / "UPSTREAM_COMMIT", "UPSTREAM_COMMIT")
|
||||
recorded = commit_file.strip().splitlines()[0].strip() if commit_file.strip() else ""
|
||||
if recorded != commit:
|
||||
@@ -216,6 +263,8 @@ def load_runtime_pin(lock_dir: Path = DEFAULT_LOCK_DIR) -> RuntimePin:
|
||||
return RuntimePin(
|
||||
runtime_name=runtime_name,
|
||||
upstream_commit=commit,
|
||||
patched_tree=patched_tree,
|
||||
patch_series=tuple(lock_series),
|
||||
patch_digests=tuple(digests),
|
||||
build_recipe_digest=build_digest,
|
||||
)
|
||||
|
||||
@@ -73,6 +73,7 @@ RECIPE_IDENTITY_SCHEMA_VERSION = 1
|
||||
ARTIFACT_DIGEST_DOMAIN = "meshnet.model-artifact.v1"
|
||||
RECIPE_DIGEST_DOMAIN = "meshnet.runtime-recipe.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
|
||||
# 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}$")
|
||||
_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"})
|
||||
|
||||
# A revision that can move is not a pin. DGR-017 learned this on the artifact;
|
||||
# it is just as true of a tokenizer.
|
||||
# The one shape a tokenizer identity may take: a digest over the tokenizer's
|
||||
# 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"})
|
||||
|
||||
|
||||
@@ -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):
|
||||
raise RecipeIdentityError(
|
||||
"'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
|
||||
|
||||
@@ -389,10 +443,13 @@ 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`.
|
||||
`tokenizer_revision` must be a content-addressed tokenizer identity
|
||||
(:func:`tokenizer_identity`) — a digest over the tokenizer's actual bytes,
|
||||
never a repository label that merely points at bytes. `runtime_version`
|
||||
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.
|
||||
"""
|
||||
@@ -419,7 +476,7 @@ class RuntimeRecipe:
|
||||
_require_int(value, f"recipe.{axis}", 1)
|
||||
else:
|
||||
_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_text(self.recipe_id, "recipe.recipe_id")
|
||||
_require_text(self.recipe_version, "recipe.recipe_version")
|
||||
|
||||
@@ -43,6 +43,7 @@ RECIPE_IDENTITY_SCHEMA_VERSION = 1
|
||||
ARTIFACT_DIGEST_DOMAIN = "meshnet.model-artifact.v1"
|
||||
RECIPE_DIGEST_DOMAIN = "meshnet.runtime-recipe.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
|
||||
# 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}$")
|
||||
_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"})
|
||||
_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):
|
||||
"""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()
|
||||
if backend in _LLAMA_CPP_BACKEND_IDS and not _LLAMA_CPP_RUNTIME_PIN.fullmatch(text):
|
||||
raise RecipeIdentityError(
|
||||
"'recipe.runtime_version' for llama.cpp must bind a 40-hex commit "
|
||||
"and a 64-hex ordered patch-stack digest"
|
||||
"'recipe.runtime_version' for llama.cpp must bind a 40-hex commit, "
|
||||
"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
|
||||
|
||||
@@ -347,7 +391,7 @@ def parse_identity(data: Any) -> PresentedIdentity:
|
||||
axes[axis] = _integer(value, f"recipe.{axis}", 1)
|
||||
else:
|
||||
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"])
|
||||
|
||||
identity = PresentedIdentity(
|
||||
|
||||
@@ -34,11 +34,25 @@ from meshnet_node.runtime_recipe import ( # noqa: E402
|
||||
DerivativeBinding,
|
||||
RuntimeRecipe,
|
||||
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"
|
||||
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(
|
||||
weight_quantization="Q4_K_M",
|
||||
@@ -46,10 +60,13 @@ _RECIPE = RuntimeRecipe(
|
||||
compute_dtype="float32",
|
||||
kv_dtype="q8_0",
|
||||
kv_layout="paged-v1",
|
||||
tokenizer_revision="0123456789abcdef",
|
||||
tokenizer_revision=_TOKENIZER_IDENTITY,
|
||||
architecture_adapter="llama/range-v1",
|
||||
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_version="1",
|
||||
catalogue_version="2026.07.1",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"schema_version": 2,
|
||||
"vectors": [
|
||||
{
|
||||
"description": "An undivided artifact: content digest is the source digest.",
|
||||
@@ -8,9 +8,9 @@
|
||||
"model_artifact_digest": "8a0f43d6aa49d77834bdb47bcae9f42c886b7ccfe0ac014932b2a2b38697a47b",
|
||||
"recipe_id": "example-gguf",
|
||||
"recipe_version": "1",
|
||||
"runtime_recipe_digest": "63001e0efeada5b97f2f3562562dc0fd2d9bd8904dc6b7b99a71d98f3e938bd0"
|
||||
"runtime_recipe_digest": "73133ee50866da5b26e94a39e3e10865c41651d0b1fd1655ceae09d2234cb8eb"
|
||||
},
|
||||
"fingerprint_proto_hex": "0a40386130663433643661613439643737383334626462343762636165396634326338383662376363666530616330313439333262326132623338363937613437621240363330303165306566656164613562393766326633353632353632646330666432643962643839303464633662376239396137316439386633653933386264301a0c6578616d706c652d676775662201312a09323032362e30372e31",
|
||||
"fingerprint_proto_hex": "0a40386130663433643661613439643737383334626462343762636165396634326338383662376363666530616330313439333262326132623338363937613437621240373331333365653530383636646135623236653934613339653365313038363563343136353164306231666431363535636561653039643232333463623865621a0c6578616d706c652d676775662201312a09323032362e30372e31",
|
||||
"identity": {
|
||||
"artifact": {
|
||||
"architecture": "dense-llama",
|
||||
@@ -26,7 +26,7 @@
|
||||
"model_artifact_digest": "8a0f43d6aa49d77834bdb47bcae9f42c886b7ccfe0ac014932b2a2b38697a47b",
|
||||
"recipe_id": "example-gguf",
|
||||
"recipe_version": "1",
|
||||
"runtime_recipe_digest": "63001e0efeada5b97f2f3562562dc0fd2d9bd8904dc6b7b99a71d98f3e938bd0"
|
||||
"runtime_recipe_digest": "73133ee50866da5b26e94a39e3e10865c41651d0b1fd1655ceae09d2234cb8eb"
|
||||
},
|
||||
"recipe": {
|
||||
"activation_dtype": "bfloat16",
|
||||
@@ -40,8 +40,8 @@
|
||||
"protocol_schema_version": 1,
|
||||
"recipe_id": "example-gguf",
|
||||
"recipe_version": "1",
|
||||
"runtime_version": "llama.cpp@dddddddddddddddddddddddddddddddddddddddd+patchstack.eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
|
||||
"tokenizer_revision": "0123456789abcdef",
|
||||
"runtime_version": "llama.cpp@dddddddddddddddddddddddddddddddddddddddd+patchstack.eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee+build.ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff+artifact.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
"tokenizer_revision": "tokenizer.v1:2d4e25eb9dd1a5e6ecef2fb36c0cb49a6cbe61161c807ea07a62ba268e9fb665",
|
||||
"weight_quantization": "Q4_K_M"
|
||||
},
|
||||
"schema_version": 1,
|
||||
@@ -58,9 +58,9 @@
|
||||
"model_artifact_digest": "8a0f43d6aa49d77834bdb47bcae9f42c886b7ccfe0ac014932b2a2b38697a47b",
|
||||
"recipe_id": "example-gguf",
|
||||
"recipe_version": "1",
|
||||
"runtime_recipe_digest": "63001e0efeada5b97f2f3562562dc0fd2d9bd8904dc6b7b99a71d98f3e938bd0"
|
||||
"runtime_recipe_digest": "73133ee50866da5b26e94a39e3e10865c41651d0b1fd1655ceae09d2234cb8eb"
|
||||
},
|
||||
"fingerprint_proto_hex": "0a40386130663433643661613439643737383334626462343762636165396634326338383662376363666530616330313439333262326132623338363937613437621240363330303165306566656164613562393766326633353632353632646330666432643962643839303464633662376239396137316439386633653933386264301a0c6578616d706c652d676775662201312a09323032362e30372e31",
|
||||
"fingerprint_proto_hex": "0a40386130663433643661613439643737383334626462343762636165396634326338383662376363666530616330313439333262326132623338363937613437621240373331333365653530383636646135623236653934613339653365313038363563343136353164306231666431363535636561653039643232333463623865621a0c6578616d706c652d676775662201312a09323032362e30372e31",
|
||||
"identity": {
|
||||
"artifact": {
|
||||
"architecture": "dense-llama",
|
||||
@@ -80,7 +80,7 @@
|
||||
"model_artifact_digest": "8a0f43d6aa49d77834bdb47bcae9f42c886b7ccfe0ac014932b2a2b38697a47b",
|
||||
"recipe_id": "example-gguf",
|
||||
"recipe_version": "1",
|
||||
"runtime_recipe_digest": "63001e0efeada5b97f2f3562562dc0fd2d9bd8904dc6b7b99a71d98f3e938bd0"
|
||||
"runtime_recipe_digest": "73133ee50866da5b26e94a39e3e10865c41651d0b1fd1655ceae09d2234cb8eb"
|
||||
},
|
||||
"recipe": {
|
||||
"activation_dtype": "bfloat16",
|
||||
@@ -94,8 +94,8 @@
|
||||
"protocol_schema_version": 1,
|
||||
"recipe_id": "example-gguf",
|
||||
"recipe_version": "1",
|
||||
"runtime_version": "llama.cpp@dddddddddddddddddddddddddddddddddddddddd+patchstack.eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
|
||||
"tokenizer_revision": "0123456789abcdef",
|
||||
"runtime_version": "llama.cpp@dddddddddddddddddddddddddddddddddddddddd+patchstack.eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee+build.ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff+artifact.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
"tokenizer_revision": "tokenizer.v1:2d4e25eb9dd1a5e6ecef2fb36c0cb49a6cbe61161c807ea07a62ba268e9fb665",
|
||||
"weight_quantization": "Q4_K_M"
|
||||
},
|
||||
"schema_version": 1,
|
||||
|
||||
@@ -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
|
||||
|
||||
import dataclasses
|
||||
import hashlib
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
import pytest
|
||||
|
||||
from meshnet_node.doctor import DoctorSelection, validate_loaded_backend
|
||||
from meshnet_node.native_backend import (
|
||||
ATTESTATION_MARKER_PREFIX,
|
||||
ATTESTATION_SYMBOL,
|
||||
ImmutableArtifactPin,
|
||||
NativeArtifactEvidence,
|
||||
NativeIdentityInputs,
|
||||
NativeLoadedArtifactReport,
|
||||
NativeNumericalRecipe,
|
||||
NativeRuntimeAttestation,
|
||||
NativeSessionRejected,
|
||||
NativeWorkerBackendAdapter,
|
||||
attest_loaded_runtime,
|
||||
attestation_payload,
|
||||
expected_attestation_payload,
|
||||
shard_identity_from_native_report,
|
||||
)
|
||||
from meshnet_node.native_protocol import SCHEMA_VERSION, pb
|
||||
from meshnet_node.runtime_pin import load_runtime_pin
|
||||
from meshnet_node.recipe_manifest import parse_recipe_manifest
|
||||
from meshnet_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
|
||||
|
||||
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:
|
||||
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(
|
||||
owned_start_layer=2,
|
||||
owned_end_layer=6,
|
||||
@@ -34,6 +141,7 @@ def _inputs(**changes: object) -> NativeIdentityInputs:
|
||||
architecture="llama",
|
||||
architecture_digest=_digest("a"),
|
||||
layer_count=8,
|
||||
runtime_attestation=attestation,
|
||||
)
|
||||
recipe = NativeNumericalRecipe(
|
||||
weight_quantization="Q4_K_M",
|
||||
@@ -54,13 +162,21 @@ def _inputs(**changes: object) -> NativeIdentityInputs:
|
||||
revision="0123456789abcdef",
|
||||
content_digest=_digest("b"),
|
||||
),
|
||||
"tokenizer_revision": "abcdef0123456789",
|
||||
"tokenizer_revision": tokenizer_identity(
|
||||
{"tokenizer.json": b'{"vocab":{"a":0}}\n'}
|
||||
),
|
||||
"numerical_recipe": recipe,
|
||||
}
|
||||
values.update(changes)
|
||||
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:
|
||||
identity = adapter.identity
|
||||
fields: dict[str, object] = {
|
||||
@@ -78,26 +194,217 @@ def _open(adapter: NativeWorkerBackendAdapter, **changes: object) -> pb.SessionO
|
||||
return pb.SessionOpen(**fields) # type: ignore[arg-type]
|
||||
|
||||
|
||||
def test_native_identity_uses_loaded_report_not_a_caller_range():
|
||||
identity = shard_identity_from_native_report(_inputs())
|
||||
# --- copied lock values alone must never pass ------------------------------
|
||||
|
||||
|
||||
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.artifact.architecture == "llama"
|
||||
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"):
|
||||
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"):
|
||||
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():
|
||||
adapter = NativeWorkerBackendAdapter(_inputs())
|
||||
def test_native_worker_rejects_bad_session_open_before_session_acceptance(
|
||||
genuine_attestation,
|
||||
):
|
||||
adapter = NativeWorkerBackendAdapter(_inputs(genuine_attestation))
|
||||
accepted = adapter.on_session_open(
|
||||
_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
|
||||
|
||||
|
||||
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(
|
||||
{"schema_version": 1, "catalogue_version": "2026.07.1", "recipes": [
|
||||
{"id": "native", "version": "1", "backend_id": "llama-cpp"}
|
||||
]}
|
||||
)
|
||||
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.
|
||||
native.hidden_size = 8
|
||||
native.is_head = False
|
||||
|
||||
@@ -21,15 +21,21 @@ from meshnet_node.runtime_pin import (
|
||||
RuntimePinError,
|
||||
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 (
|
||||
RecipeIdentityError as TrackerRecipeIdentityError,
|
||||
parse_identity,
|
||||
tokenizer_identity as tracker_tokenizer_identity,
|
||||
)
|
||||
|
||||
REPO_LOCK_DIR = (
|
||||
Path(__file__).resolve().parent.parent / "packages" / "node" / "native" / "llama"
|
||||
)
|
||||
TOKENIZER = tokenizer_identity({"tokenizer.json": b'{"vocab":{"a":0}}\n'})
|
||||
|
||||
|
||||
def _recipe(**changes: object) -> RuntimeRecipe:
|
||||
@@ -39,10 +45,14 @@ def _recipe(**changes: object) -> RuntimeRecipe:
|
||||
"compute_dtype": "float32",
|
||||
"kv_dtype": "q8_0",
|
||||
"kv_layout": "paged-v1",
|
||||
"tokenizer_revision": "0123456789abcdef",
|
||||
"tokenizer_revision": TOKENIZER,
|
||||
"architecture_adapter": "llama/range-v1",
|
||||
"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_version": "1",
|
||||
"catalogue_version": "2026.07.1",
|
||||
@@ -62,6 +72,8 @@ def _write_fixture_workspace(
|
||||
schema_version: int = 1,
|
||||
upstream: str = "https://github.com/ggml-org/llama.cpp.git",
|
||||
upstream_commit_file: str | None = None,
|
||||
patched_tree: str = "a" * 40,
|
||||
build: dict[str, object] | None = None,
|
||||
) -> Path:
|
||||
"""A minimal DGR-027-shaped lock workspace; overrides create disagreement."""
|
||||
if patches is None:
|
||||
@@ -85,6 +97,8 @@ def _write_fixture_workspace(
|
||||
"schema_version": schema_version,
|
||||
"upstream": upstream,
|
||||
"commit": commit,
|
||||
"patched_tree": patched_tree,
|
||||
"build": build or {"configure_flags": ["-DTEST=ON"]},
|
||||
"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 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.
|
||||
# The axis value names the runtime, exact commit, stack, and build recipe.
|
||||
assert pin.runtime_version == (
|
||||
f"llama.cpp@{lock['commit']}+patchstack.{pin.patch_stack_digest}"
|
||||
f"+build.{pin.build_recipe_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
|
||||
runtime_version = pin.runtime_version + "+artifact." + "a" * 64
|
||||
recipe = _recipe(runtime_version=runtime_version)
|
||||
assert recipe.runtime_version == runtime_version
|
||||
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
|
||||
|
||||
|
||||
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 ------------------------------
|
||||
|
||||
|
||||
@@ -257,6 +282,53 @@ def test_empty_patch_series_requires_empty_series_files(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 -----------------
|
||||
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ from meshnet_node.runtime_recipe import (
|
||||
check_session_open,
|
||||
check_route,
|
||||
handshake_error,
|
||||
tokenizer_identity,
|
||||
)
|
||||
from meshnet_tracker.capability import (
|
||||
POLICY_COMPAT,
|
||||
@@ -49,6 +50,7 @@ from meshnet_tracker.recipe import (
|
||||
)
|
||||
|
||||
VECTORS = Path(__file__).parent / "data" / "recipe_fingerprint_vectors.json"
|
||||
TOKENIZER = tokenizer_identity({"tokenizer.json": b'{"vocab":{"a":0}}\n'})
|
||||
|
||||
|
||||
def _digest(char: str) -> str:
|
||||
@@ -62,10 +64,14 @@ def _recipe(**changes: object) -> RuntimeRecipe:
|
||||
"compute_dtype": "float32",
|
||||
"kv_dtype": "q8_0",
|
||||
"kv_layout": "paged-v1",
|
||||
"tokenizer_revision": "0123456789abcdef",
|
||||
"tokenizer_revision": TOKENIZER,
|
||||
"architecture_adapter": "llama/range-v1",
|
||||
"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_version": "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"),
|
||||
("kv_dtype", "float16"),
|
||||
("kv_layout", "contiguous-v2"),
|
||||
("tokenizer_revision", "fedcba9876543210"),
|
||||
(
|
||||
"tokenizer_revision",
|
||||
tokenizer_identity({"tokenizer.json": b'{"vocab":{"b":0}}\n'}),
|
||||
),
|
||||
("architecture_adapter", "llama/range-v2"),
|
||||
("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),
|
||||
("protocol_schema_version", 2),
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user