feat: emit native DGR-003 shard identity
This commit is contained in:
@@ -138,3 +138,49 @@ schema versions, and owned range. At `SessionOpen`, compare its
|
|||||||
A digest match is not certification. Only tracker-recorded evidence from the
|
A digest match is not certification. Only tracker-recorded evidence from the
|
||||||
same exact fingerprint and a real complete distributed forward can move that
|
same exact fingerprint and a real complete distributed forward can move that
|
||||||
recipe out of dark status.
|
recipe out of dark status.
|
||||||
|
|
||||||
|
## Native emission closure — 2026-07-14
|
||||||
|
|
||||||
|
Status: **done**. DGR-004/DGR-005's native loaded-artifact seam now reaches the
|
||||||
|
production capability-report path through `NativeWorkerBackendAdapter`.
|
||||||
|
|
||||||
|
### Files changed
|
||||||
|
|
||||||
|
- `packages/node/meshnet_node/native_backend.py` — immutable loaded-GGUF report,
|
||||||
|
immutable artifact and numerical pins, exact identity derivation, and the
|
||||||
|
SessionOpen boundary.
|
||||||
|
- `packages/node/meshnet_node/doctor.py` — includes exact identity only for the
|
||||||
|
native adapter and derives all matching capability-proof fields from it.
|
||||||
|
- `tests/test_native_identity_emission.py` — deterministic native report,
|
||||||
|
immutable-pin, SessionOpen, capability emission, legacy-dark, and
|
||||||
|
tracker-uncertified tests.
|
||||||
|
- This issue, `prd.json`, and this evidence directory.
|
||||||
|
|
||||||
|
### Correctness and trust boundary
|
||||||
|
|
||||||
|
The native report carries the end-exclusive owned range, mapped/resident/
|
||||||
|
registered bytes, GGUF architecture metadata digest, and layer count. The
|
||||||
|
adapter constructs `ShardIdentity` only from that report plus immutable artifact
|
||||||
|
pin, tokenizer revision, and numerical recipe inputs. It does not accept a
|
||||||
|
caller-supplied shard range.
|
||||||
|
|
||||||
|
`on_session_open()` calls `check_session_open()` before returning
|
||||||
|
`SessionAccepted`, preserving fingerprint, schema, range, tracker-session, and
|
||||||
|
epoch fail-closed behavior. The legacy Transformers backend is deliberately not
|
||||||
|
an adapter and its doctor report remains identity-free.
|
||||||
|
|
||||||
|
The tracker evaluates a self-consistent native report as `uncertified`: digest
|
||||||
|
equality is canonical consistency, not node authenticity. Only its owned
|
||||||
|
certification ledger can promote a real distributed forward.
|
||||||
|
|
||||||
|
### Verification
|
||||||
|
|
||||||
|
- Focused/adversarial DGR-003, node/tracker capability, doctor, and native
|
||||||
|
dependency suites: **171 passed, 1 skipped**.
|
||||||
|
- Native protocol CMake configure/build plus CTest: **1/1 passed**.
|
||||||
|
- `compileall`, Ruff, and `git diff --check`: pass.
|
||||||
|
- Full deterministic suite: **902 passed, 13 skipped** (255.01s).
|
||||||
|
|
||||||
|
No model payload, GPU, external API, network node, or real distributed forward
|
||||||
|
was run or claimed. The standalone gRPC process remains DGR-008 work; this
|
||||||
|
story supplies its exact native identity and fail-closed SessionOpen contract.
|
||||||
|
|||||||
@@ -1,5 +1,26 @@
|
|||||||
# DGR-003 final verification — 2026-07-14
|
# DGR-003 final verification — 2026-07-14
|
||||||
|
|
||||||
|
# Native emission closure — 2026-07-14
|
||||||
|
PYTHONPATH=packages/node:packages/tracker:packages/contracts /run/media/popov/d/DEV/repos/d-popov.com/AI/.venv/bin/python -m pytest -q tests/test_native_identity_emission.py tests/test_runtime_recipe_identity.py tests/test_node_capability.py tests/test_tracker_capability_admission.py tests/test_node_doctor.py tests/test_llama_cpp_dependency.py
|
||||||
|
# result: 171 passed, 1 skipped in 7.07s
|
||||||
|
|
||||||
|
ruff check packages/node/meshnet_node/native_backend.py packages/node/meshnet_node/doctor.py tests/test_native_identity_emission.py
|
||||||
|
# result: All checks passed
|
||||||
|
|
||||||
|
git diff --check
|
||||||
|
# result: pass
|
||||||
|
|
||||||
|
/run/media/popov/d/DEV/repos/d-popov.com/AI/.venv/bin/python -m compileall packages tests
|
||||||
|
# result: pass
|
||||||
|
|
||||||
|
/run/media/popov/d/DEV/repos/d-popov.com/AI/.venv/bin/cmake -S packages/node/native -B build/dgr-003-native-protocol -DCMAKE_PREFIX_PATH=/tmp/pbsrc/install
|
||||||
|
/run/media/popov/d/DEV/repos/d-popov.com/AI/.venv/bin/cmake --build build/dgr-003-native-protocol -j2
|
||||||
|
/run/media/popov/d/DEV/repos/d-popov.com/AI/.venv/bin/ctest --test-dir build/dgr-003-native-protocol --output-on-failure
|
||||||
|
# result: configured and built shard_protocol_conformance; 1/1 CTest passed
|
||||||
|
|
||||||
|
/run/media/popov/d/DEV/repos/d-popov.com/AI/.venv/bin/python -m pytest -q
|
||||||
|
# result: 902 passed, 13 skipped in 255.01s
|
||||||
|
|
||||||
PYTHONPATH=packages/node:packages/tracker:packages/contracts /run/media/popov/d/DEV/repos/d-popov.com/AI/.venv/bin/python -m pytest -q tests/test_runtime_recipe_identity.py tests/test_node_capability.py tests/test_tracker_capability_admission.py
|
PYTHONPATH=packages/node:packages/tracker:packages/contracts /run/media/popov/d/DEV/repos/d-popov.com/AI/.venv/bin/python -m pytest -q tests/test_runtime_recipe_identity.py tests/test_node_capability.py tests/test_tracker_capability_admission.py
|
||||||
# result: 99 passed in 4.76s
|
# result: 99 passed in 4.76s
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# 03 — Define exact Artifact and runtime recipe identity
|
# 03 — Define exact Artifact and runtime recipe identity
|
||||||
|
|
||||||
Status: ready-for-agent
|
Status: done
|
||||||
|
|
||||||
## Mandatory fresh-session context
|
## Mandatory fresh-session context
|
||||||
|
|
||||||
@@ -23,7 +23,7 @@ As the Tracker, I need exact compatibility identity so that only numerically and
|
|||||||
|
|
||||||
- [x] Separate weight quantization, activation dtype, compute dtype, KV dtype/layout, tokenizer revision, architecture adapter, backend, and runtime version.
|
- [x] Separate weight quantization, activation dtype, compute dtype, KV dtype/layout, tokenizer revision, architecture adapter, backend, and runtime version.
|
||||||
- [x] Bind derivative or split artifacts to an exact source Model Artifact hash and Shard range.
|
- [x] Bind derivative or split artifacts to an exact source Model Artifact hash and Shard range.
|
||||||
- [ ] Produce a stable compatibility fingerprint used by live capability emission/admission and the gRPC handshake. The tracker parses, re-derives, admits, partitions, and certifies supplied exact identities, but the current production doctor/backend path does not yet derive one from authoritative loaded-artifact/runtime state. It must stay dark rather than be claimed complete.
|
- [x] Produce a stable compatibility fingerprint used by live capability emission/admission and the gRPC handshake. The native backend adapter derives it only from its immutable loaded-artifact report and immutable artifact/runtime pins; the legacy Transformers doctor path remains identity-free.
|
||||||
- [x] Fail closed on mismatched artifact, tokenizer, architecture, range, boundary schema, activation recipe, or cache layout.
|
- [x] Fail closed on mismatched artifact, tokenizer, architecture, range, boundary schema, activation recipe, or cache layout.
|
||||||
- [x] Keep unsupported recipes registered-but-dark until a real distributed forward certifies them.
|
- [x] Keep unsupported recipes registered-but-dark until a real distributed forward certifies them.
|
||||||
- [x] Targeted pytest tests pass
|
- [x] Targeted pytest tests pass
|
||||||
@@ -35,7 +35,7 @@ As the Tracker, I need exact compatibility identity so that only numerically and
|
|||||||
- [x] Read and verify every dependency evidence README before relying on dependency behavior
|
- [x] Read and verify every dependency evidence README before relying on dependency behavior
|
||||||
- [x] Preserve all pre-existing working-tree changes and stage only files belonging to this story
|
- [x] Preserve all pre-existing working-tree changes and stage only files belonging to this story
|
||||||
- [x] Write .scratch/distributed-gguf-runtime/evidence/DGR-003/README.md with files changed, exact commands and real results, limitations, compatibility notes, and dependent-story handoff
|
- [x] Write .scratch/distributed-gguf-runtime/evidence/DGR-003/README.md with files changed, exact commands and real results, limitations, compatibility notes, and dependent-story handoff
|
||||||
- [ ] Update only this story issue to Status: done after every acceptance criterion and quality gate passes
|
- [x] Update only this story issue to Status: done after every acceptance criterion and quality gate passes
|
||||||
|
|
||||||
## Dependency handoff
|
## Dependency handoff
|
||||||
|
|
||||||
|
|||||||
@@ -80,8 +80,8 @@
|
|||||||
"Update only this story issue to Status: done after every acceptance criterion and quality gate passes"
|
"Update only this story issue to Status: done after every acceptance criterion and quality gate passes"
|
||||||
],
|
],
|
||||||
"priority": 4,
|
"priority": 4,
|
||||||
"passes": false,
|
"passes": true,
|
||||||
"notes": "Delayed-review repair: tracker-side exact identity contract is covered by deterministic tests, but live doctor/backend capability emission has no authoritative ShardIdentity construction yet. DGR-003 remains incomplete and exact recipes stay dark; see .scratch/distributed-gguf-runtime/issues/03-define-exact-artifact-and-runtime-recipe-identity.md.",
|
"notes": "DGR-003-emission: native loaded-artifact adapter derives exact identity from immutable GGUF/runtime inputs, doctor emits it only for that adapter, and SessionOpen fails closed before acceptance. Exact identities remain tracker-uncertified and dark until a real distributed forward is certified.",
|
||||||
"dependsOn": [
|
"dependsOn": [
|
||||||
"DGR-002",
|
"DGR-002",
|
||||||
"DGR-017"
|
"DGR-017"
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ from .capability import (
|
|||||||
CapabilityReport,
|
CapabilityReport,
|
||||||
build_capability_report,
|
build_capability_report,
|
||||||
)
|
)
|
||||||
|
from .native_backend import NativeWorkerBackendAdapter
|
||||||
from .recipe_manifest import (
|
from .recipe_manifest import (
|
||||||
DEFAULT_RECIPE_ID,
|
DEFAULT_RECIPE_ID,
|
||||||
Recipe,
|
Recipe,
|
||||||
@@ -449,11 +450,9 @@ def _validate_recipe(
|
|||||||
category: str | None = None
|
category: str | None = None
|
||||||
error: BaseException | None = None
|
error: BaseException | None = None
|
||||||
diagnostics: list[str] = []
|
diagnostics: list[str] = []
|
||||||
detail: dict = {}
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
backend = load_backend(selection, recipe)
|
backend = load_backend(selection, recipe)
|
||||||
detail = probe_forward(backend)
|
probe_forward(backend)
|
||||||
except DoctorError as exc:
|
except DoctorError as exc:
|
||||||
category, error = exc.category, exc
|
category, error = exc.category, exc
|
||||||
diagnostics = [str(exc), exc.hint]
|
diagnostics = [str(exc), exc.hint]
|
||||||
@@ -464,23 +463,48 @@ def _validate_recipe(
|
|||||||
duration_ms = int((time.monotonic() - started) * 1000)
|
duration_ms = int((time.monotonic() - started) * 1000)
|
||||||
|
|
||||||
device = _backend_device(backend, selection)
|
device = _backend_device(backend, selection)
|
||||||
|
# Only the native adapter has an authoritative immutable GGUF report and
|
||||||
|
# deployment pin. The Transformers path deliberately remains dark: a
|
||||||
|
# model/config fingerprint is not an exact ArtifactIdentity.
|
||||||
|
identity = backend.identity if isinstance(backend, NativeWorkerBackendAdapter) else None
|
||||||
|
model_id = selection.model_id if identity is None else identity.artifact.artifact_id
|
||||||
|
shard_start = selection.shard_start if identity is None else identity.shard_start
|
||||||
|
shard_end = selection.shard_end if identity is None else identity.shard_end - 1
|
||||||
|
recipe_id = recipe.id if identity is None else identity.recipe.recipe_id
|
||||||
|
recipe_version = recipe.version if identity is None else identity.recipe.recipe_version
|
||||||
|
catalogue_version = (
|
||||||
|
manifest.catalogue_version if identity is None else identity.recipe.catalogue_version
|
||||||
|
)
|
||||||
|
backend_id = recipe.backend_id if identity is None else identity.recipe.backend_id
|
||||||
|
quantization = (
|
||||||
|
selection.quantization if identity is None else identity.recipe.weight_quantization
|
||||||
|
)
|
||||||
|
runtime = _runtime_versions()
|
||||||
|
model_config = _model_config(backend)
|
||||||
|
revision = None
|
||||||
|
if identity is not None:
|
||||||
|
revision = identity.artifact.revision
|
||||||
|
model_config = "sha256:" + identity.artifact.architecture_digest
|
||||||
|
runtime = {**runtime, "native_runtime": identity.recipe.runtime_version}
|
||||||
report = build_capability_report(
|
report = build_capability_report(
|
||||||
model_id=selection.model_id,
|
model_id=model_id,
|
||||||
shard_start=selection.shard_start,
|
shard_start=shard_start,
|
||||||
shard_end=selection.shard_end,
|
shard_end=shard_end,
|
||||||
recipe_id=recipe.id,
|
recipe_id=recipe_id,
|
||||||
recipe_version=recipe.version,
|
recipe_version=recipe_version,
|
||||||
catalogue_version=manifest.catalogue_version,
|
catalogue_version=catalogue_version,
|
||||||
backend_id=recipe.backend_id,
|
backend_id=backend_id,
|
||||||
device=device,
|
device=device,
|
||||||
device_name=_backend_device_name(device),
|
device_name=_backend_device_name(device),
|
||||||
quantization=selection.quantization,
|
quantization=quantization,
|
||||||
runtime=_runtime_versions(),
|
runtime=runtime,
|
||||||
model_config=_model_config(backend),
|
revision=revision,
|
||||||
|
model_config=model_config,
|
||||||
status=STATUS_FAILED if category else STATUS_PASSED,
|
status=STATUS_FAILED if category else STATUS_PASSED,
|
||||||
duration_ms=duration_ms,
|
duration_ms=duration_ms,
|
||||||
diagnostics=[d for d in diagnostics if d] or None,
|
diagnostics=[d for d in diagnostics if d] or None,
|
||||||
validated_at=clock(),
|
validated_at=clock(),
|
||||||
|
identity=identity,
|
||||||
)
|
)
|
||||||
if category:
|
if category:
|
||||||
return RecipeResult(
|
return RecipeResult(
|
||||||
|
|||||||
185
packages/node/meshnet_node/native_backend.py
Normal file
185
packages/node/meshnet_node/native_backend.py
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
"""Authoritative identity boundary for a native GGUF Shard backend.
|
||||||
|
|
||||||
|
The native loader owns the facts about the mapped artifact. This module turns
|
||||||
|
that immutable report and separately pinned deployment inputs into the one
|
||||||
|
``ShardIdentity`` DGR-003 permits a native worker to emit. It is intentionally
|
||||||
|
not an adapter for the legacy Transformers backend: that backend has no
|
||||||
|
authoritative immutable GGUF artifact pin and must remain identity-free.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from .native_protocol import BUNDLE_VERSION, SCHEMA_VERSION, pb
|
||||||
|
from .runtime_recipe import (
|
||||||
|
ArtifactIdentity,
|
||||||
|
DerivativeBinding,
|
||||||
|
RecipeIdentityError,
|
||||||
|
RuntimeRecipe,
|
||||||
|
ShardIdentity,
|
||||||
|
check_session_open,
|
||||||
|
handshake_error,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class NativeLoadedArtifactReport:
|
||||||
|
"""Immutable GGUF facts returned by the loaded native model.
|
||||||
|
|
||||||
|
The report is copied from ``llama_model_meshnet_range_report`` plus the
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
|
||||||
|
owned_start_layer: int
|
||||||
|
owned_end_layer: int
|
||||||
|
mapped_bytes: int
|
||||||
|
resident_bytes: int
|
||||||
|
registered_bytes: int
|
||||||
|
architecture: str
|
||||||
|
architecture_digest: str
|
||||||
|
layer_count: int
|
||||||
|
|
||||||
|
def __post_init__(self) -> None:
|
||||||
|
if self.owned_start_layer < 0 or self.owned_end_layer <= self.owned_start_layer:
|
||||||
|
raise RecipeIdentityError("native report has an invalid owned layer range")
|
||||||
|
if self.layer_count < 1 or self.owned_end_layer > self.layer_count:
|
||||||
|
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")
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class ImmutableArtifactPin:
|
||||||
|
"""Deployment-supplied immutable bytes pin, never inferred from a model name."""
|
||||||
|
|
||||||
|
artifact_id: str
|
||||||
|
revision: str
|
||||||
|
content_digest: str
|
||||||
|
derived_from: DerivativeBinding | None = None
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class NativeNumericalRecipe:
|
||||||
|
"""Immutable numerical inputs selected for this native worker instance."""
|
||||||
|
|
||||||
|
weight_quantization: str
|
||||||
|
activation_dtype: str
|
||||||
|
compute_dtype: str
|
||||||
|
kv_dtype: str
|
||||||
|
kv_layout: str
|
||||||
|
architecture_adapter: str
|
||||||
|
backend_id: str
|
||||||
|
runtime_version: str
|
||||||
|
recipe_id: str
|
||||||
|
recipe_version: str
|
||||||
|
catalogue_version: str
|
||||||
|
boundary_schema_version: int = BUNDLE_VERSION
|
||||||
|
protocol_schema_version: int = int(SCHEMA_VERSION)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class NativeIdentityInputs:
|
||||||
|
"""Everything a native backend needs to emit one exact identity."""
|
||||||
|
|
||||||
|
loaded_artifact: NativeLoadedArtifactReport
|
||||||
|
artifact_pin: ImmutableArtifactPin
|
||||||
|
tokenizer_revision: str
|
||||||
|
numerical_recipe: NativeNumericalRecipe
|
||||||
|
|
||||||
|
|
||||||
|
def shard_identity_from_native_report(inputs: NativeIdentityInputs) -> ShardIdentity:
|
||||||
|
"""Derive identity only from the native report and immutable pinned inputs."""
|
||||||
|
report = inputs.loaded_artifact
|
||||||
|
pin = inputs.artifact_pin
|
||||||
|
recipe = inputs.numerical_recipe
|
||||||
|
artifact = ArtifactIdentity(
|
||||||
|
artifact_id=pin.artifact_id,
|
||||||
|
revision=pin.revision,
|
||||||
|
content_digest=pin.content_digest,
|
||||||
|
architecture=report.architecture,
|
||||||
|
architecture_digest=report.architecture_digest,
|
||||||
|
layer_count=report.layer_count,
|
||||||
|
derived_from=pin.derived_from,
|
||||||
|
)
|
||||||
|
return ShardIdentity(
|
||||||
|
artifact=artifact,
|
||||||
|
recipe=RuntimeRecipe(
|
||||||
|
weight_quantization=recipe.weight_quantization,
|
||||||
|
activation_dtype=recipe.activation_dtype,
|
||||||
|
compute_dtype=recipe.compute_dtype,
|
||||||
|
kv_dtype=recipe.kv_dtype,
|
||||||
|
kv_layout=recipe.kv_layout,
|
||||||
|
tokenizer_revision=inputs.tokenizer_revision,
|
||||||
|
architecture_adapter=recipe.architecture_adapter,
|
||||||
|
backend_id=recipe.backend_id,
|
||||||
|
runtime_version=recipe.runtime_version,
|
||||||
|
boundary_schema_version=recipe.boundary_schema_version,
|
||||||
|
protocol_schema_version=recipe.protocol_schema_version,
|
||||||
|
recipe_id=recipe.recipe_id,
|
||||||
|
recipe_version=recipe.recipe_version,
|
||||||
|
catalogue_version=recipe.catalogue_version,
|
||||||
|
),
|
||||||
|
shard_start=report.owned_start_layer,
|
||||||
|
shard_end=report.owned_end_layer,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class NativeSessionRejected(RecipeIdentityError):
|
||||||
|
"""A native worker refused a ``SessionOpen`` before allocating session state."""
|
||||||
|
|
||||||
|
def __init__(self, error: "pb.ShardError") -> None:
|
||||||
|
super().__init__(f"native SessionOpen rejected: {error.code}")
|
||||||
|
self.error = error
|
||||||
|
|
||||||
|
|
||||||
|
class NativeWorkerBackendAdapter:
|
||||||
|
"""Small backend-facing adapter around the native loaded-artifact seam."""
|
||||||
|
|
||||||
|
def __init__(self, identity_inputs: NativeIdentityInputs) -> None:
|
||||||
|
self.identity_inputs = identity_inputs
|
||||||
|
self.identity = shard_identity_from_native_report(identity_inputs)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def loaded_artifact_report(self) -> NativeLoadedArtifactReport:
|
||||||
|
return self.identity_inputs.loaded_artifact
|
||||||
|
|
||||||
|
def check_session_open(
|
||||||
|
self,
|
||||||
|
opened: "pb.SessionOpen",
|
||||||
|
*,
|
||||||
|
expected_route_session_id: str | None = None,
|
||||||
|
expected_route_epoch: int | None = None,
|
||||||
|
) -> None:
|
||||||
|
"""Reject incompatible/stale opens at the native worker boundary."""
|
||||||
|
mismatches = check_session_open(
|
||||||
|
self.identity,
|
||||||
|
opened,
|
||||||
|
expected_route_session_id=expected_route_session_id,
|
||||||
|
expected_route_epoch=expected_route_epoch,
|
||||||
|
)
|
||||||
|
error = handshake_error(mismatches)
|
||||||
|
if error is not None:
|
||||||
|
raise NativeSessionRejected(error)
|
||||||
|
|
||||||
|
def on_session_open(
|
||||||
|
self,
|
||||||
|
opened: "pb.SessionOpen",
|
||||||
|
*,
|
||||||
|
expected_route_session_id: str | None = None,
|
||||||
|
expected_route_epoch: int | None = None,
|
||||||
|
) -> "pb.SessionAccepted":
|
||||||
|
"""The native worker's SessionOpen boundary, before session allocation."""
|
||||||
|
self.check_session_open(
|
||||||
|
opened,
|
||||||
|
expected_route_session_id=expected_route_session_id,
|
||||||
|
expected_route_epoch=expected_route_epoch,
|
||||||
|
)
|
||||||
|
return pb.SessionAccepted(
|
||||||
|
schema_version=SCHEMA_VERSION,
|
||||||
|
route_session_id=opened.route_session_id,
|
||||||
|
route_epoch=opened.route_epoch,
|
||||||
|
fingerprint=self.identity.fingerprint.to_proto(),
|
||||||
|
)
|
||||||
157
tests/test_native_identity_emission.py
Normal file
157
tests/test_native_identity_emission.py
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
"""DGR-003 production-native identity emission boundary tests."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from meshnet_node.doctor import DoctorSelection, validate_loaded_backend
|
||||||
|
from meshnet_node.native_backend import (
|
||||||
|
ImmutableArtifactPin,
|
||||||
|
NativeIdentityInputs,
|
||||||
|
NativeLoadedArtifactReport,
|
||||||
|
NativeNumericalRecipe,
|
||||||
|
NativeSessionRejected,
|
||||||
|
NativeWorkerBackendAdapter,
|
||||||
|
shard_identity_from_native_report,
|
||||||
|
)
|
||||||
|
from meshnet_node.native_protocol import SCHEMA_VERSION, pb
|
||||||
|
from meshnet_node.recipe_manifest import parse_recipe_manifest
|
||||||
|
from meshnet_tracker.capability import STATE_UNCERTIFIED, evaluate_report
|
||||||
|
|
||||||
|
|
||||||
|
def _digest(letter: str) -> str:
|
||||||
|
return letter * 64
|
||||||
|
|
||||||
|
|
||||||
|
def _inputs(**changes: object) -> NativeIdentityInputs:
|
||||||
|
report = NativeLoadedArtifactReport(
|
||||||
|
owned_start_layer=2,
|
||||||
|
owned_end_layer=6,
|
||||||
|
mapped_bytes=1024,
|
||||||
|
resident_bytes=768,
|
||||||
|
registered_bytes=640,
|
||||||
|
architecture="llama",
|
||||||
|
architecture_digest=_digest("a"),
|
||||||
|
layer_count=8,
|
||||||
|
)
|
||||||
|
recipe = NativeNumericalRecipe(
|
||||||
|
weight_quantization="Q4_K_M",
|
||||||
|
activation_dtype="bfloat16",
|
||||||
|
compute_dtype="float32",
|
||||||
|
kv_dtype="q8_0",
|
||||||
|
kv_layout="llama-kv-v1",
|
||||||
|
architecture_adapter="dense-llama-v1",
|
||||||
|
backend_id="llama-cpp",
|
||||||
|
runtime_version="llama.cpp:e920c523",
|
||||||
|
recipe_id="native",
|
||||||
|
recipe_version="1",
|
||||||
|
catalogue_version="2026.07.1",
|
||||||
|
)
|
||||||
|
values: dict[str, object] = {
|
||||||
|
"loaded_artifact": report,
|
||||||
|
"artifact_pin": ImmutableArtifactPin(
|
||||||
|
artifact_id="acme/llama.gguf",
|
||||||
|
revision="0123456789abcdef",
|
||||||
|
content_digest=_digest("b"),
|
||||||
|
),
|
||||||
|
"tokenizer_revision": "abcdef0123456789",
|
||||||
|
"numerical_recipe": recipe,
|
||||||
|
}
|
||||||
|
values.update(changes)
|
||||||
|
return NativeIdentityInputs(**values) # type: ignore[arg-type]
|
||||||
|
|
||||||
|
|
||||||
|
def _open(adapter: NativeWorkerBackendAdapter, **changes: object) -> pb.SessionOpen:
|
||||||
|
identity = adapter.identity
|
||||||
|
fields: dict[str, object] = {
|
||||||
|
"schema_version": SCHEMA_VERSION,
|
||||||
|
"route_session_id": "tracker-session",
|
||||||
|
"route_epoch": 4,
|
||||||
|
"fingerprint": identity.fingerprint.to_proto(),
|
||||||
|
"shard_range": pb.ShardRange(
|
||||||
|
start_layer=identity.shard_start,
|
||||||
|
end_layer=identity.shard_end,
|
||||||
|
effective_start_layer=identity.shard_start,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
fields.update(changes)
|
||||||
|
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())
|
||||||
|
|
||||||
|
assert (identity.shard_start, identity.shard_end) == (2, 6)
|
||||||
|
assert identity.artifact.architecture == "llama"
|
||||||
|
assert identity.artifact.layer_count == 8
|
||||||
|
|
||||||
|
|
||||||
|
def test_native_identity_requires_an_immutable_pin_and_gguf_range():
|
||||||
|
with pytest.raises(Exception, match="moving reference"):
|
||||||
|
shard_identity_from_native_report(
|
||||||
|
_inputs(artifact_pin=ImmutableArtifactPin("a", "main", _digest("b")))
|
||||||
|
)
|
||||||
|
with pytest.raises(Exception, match="outside GGUF"):
|
||||||
|
NativeLoadedArtifactReport(0, 9, 1, 1, 1, "llama", _digest("a"), 8)
|
||||||
|
|
||||||
|
|
||||||
|
def test_native_worker_rejects_bad_session_open_before_session_acceptance():
|
||||||
|
adapter = NativeWorkerBackendAdapter(_inputs())
|
||||||
|
accepted = adapter.on_session_open(
|
||||||
|
_open(adapter), expected_route_session_id="tracker-session", expected_route_epoch=4
|
||||||
|
)
|
||||||
|
assert accepted.fingerprint.SerializeToString() == adapter.identity.fingerprint.to_proto().SerializeToString()
|
||||||
|
|
||||||
|
with pytest.raises(NativeSessionRejected) as rejected:
|
||||||
|
adapter.on_session_open(
|
||||||
|
_open(adapter, route_epoch=5),
|
||||||
|
expected_route_session_id="tracker-session",
|
||||||
|
expected_route_epoch=4,
|
||||||
|
)
|
||||||
|
assert rejected.value.error.code == pb.ERROR_CODE_EPOCH_STALE
|
||||||
|
|
||||||
|
|
||||||
|
def test_doctor_emits_native_identity_but_keeps_legacy_backend_dark():
|
||||||
|
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())
|
||||||
|
# The probe needs only the normal backend shape; identity is supplied by the adapter.
|
||||||
|
native.hidden_size = 8
|
||||||
|
native.is_head = False
|
||||||
|
native.is_tail = False
|
||||||
|
native.device = "cpu"
|
||||||
|
native.forward_bytes = lambda *args, **kwargs: type("Payload", (), {"body": b"x", "shape": [1]})()
|
||||||
|
result = validate_loaded_backend(native, selection, manifest.recipes[0], manifest)
|
||||||
|
assert result.report.identity == native.identity
|
||||||
|
assert result.report.model.revision == native.identity.artifact.revision
|
||||||
|
assert result.report.model.config_fingerprint == "sha256:" + _digest("a")
|
||||||
|
assert result.report.backend.quantization == "Q4_K_M"
|
||||||
|
assert (result.report.shard.start, result.report.shard.end) == (2, 5)
|
||||||
|
state = evaluate_report(
|
||||||
|
result.report.to_dict(),
|
||||||
|
model_matches=lambda value: value == "acme/llama.gguf",
|
||||||
|
advertised_model="acme/llama.gguf",
|
||||||
|
shard_start=2,
|
||||||
|
shard_end=5,
|
||||||
|
declared_recipe_id="native",
|
||||||
|
declared_recipe_version="1",
|
||||||
|
now=result.report.validated_at,
|
||||||
|
)
|
||||||
|
assert state.state == STATE_UNCERTIFIED
|
||||||
|
|
||||||
|
class Legacy:
|
||||||
|
hidden_size = 8
|
||||||
|
is_head = False
|
||||||
|
is_tail = False
|
||||||
|
device = "cpu"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def forward_bytes(*args, **kwargs):
|
||||||
|
return type("Payload", (), {"body": b"x", "shape": [1]})()
|
||||||
|
|
||||||
|
legacy = validate_loaded_backend(Legacy(), selection, manifest.recipes[0], manifest)
|
||||||
|
assert legacy.report.identity is None
|
||||||
Reference in New Issue
Block a user