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