diff --git a/.scratch/distributed-gguf-runtime/evidence/DGR-017/README.md b/.scratch/distributed-gguf-runtime/evidence/DGR-017/README.md index ead0650..8307230 100644 --- a/.scratch/distributed-gguf-runtime/evidence/DGR-017/README.md +++ b/.scratch/distributed-gguf-runtime/evidence/DGR-017/README.md @@ -161,13 +161,13 @@ seeing a result. Each of those moves now has a test that names it: - admit the dense fallback → `test_admitting_the_dense_attention_fallback_after_the_fact_is_rejected` - shrink the reserve → `test_relaxing_the_per_node_reserve_after_the_fact_is_rejected` -**Be precise about what the seal does.** `contract_sha256` is tamper-*evidence*, not -tamper-proofing. Nothing checked into a repo can stop an agent that rewrites the -threshold *and* re-seals the digest — `test_resealing_a_mutated_contract_changes_its_digest` -asserts that path openly. What the seal removes is the *silent* mutation: the digest -moves, and the change becomes a visible diff on a file whose entire purpose is to not -change, still claiming `contract_id: glm-5.2-max-alpha/v1`. Reviewers, not hashes, are -the enforcement; the hash makes sure there is something to review. +**Contract continuity is fail-closed.** The document’s `contract_sha256` detects +accidental edits, and the approved v1 digest is pinned independently in code. A caller +that changes a threshold and re-seals it under `glm-5.2-max-alpha/v1` is rejected by +`test_resealing_a_mutated_v1_contract_is_rejected`; amendments require a new supported +contract identity under human review. Parsed nested state is recursively immutable, +so thresholds cannot change between validation and use; `to_dict()` returns an isolated +copy rather than exposing the validated object. ## 6. Upstream status — the gating risk for DGR-004/DGR-018 diff --git a/.scratch/distributed-gguf-runtime/evidence/DGR-017/commands.txt b/.scratch/distributed-gguf-runtime/evidence/DGR-017/commands.txt index c68c544..f34b964 100644 --- a/.scratch/distributed-gguf-runtime/evidence/DGR-017/commands.txt +++ b/.scratch/distributed-gguf-runtime/evidence/DGR-017/commands.txt @@ -98,3 +98,18 @@ $VP -m pytest -q tests/test_tracker_routing.py::test_tracker_dashboard_can_cance -> 1 passed, repeated 5/5 in isolation $VP -m pytest -q # integrated rerun -> 852 passed, 13 skipped in 253.30s (0:04:13) + +# --------------------------------------------------------------------------- +# 7. Late independent-review repair (2026-07-14) +# --------------------------------------------------------------------------- +PYTHONPATH=packages/node $VP -m pytest -q tests/test_glm_alpha_target.py + -> 99 passed in 0.15s + -> adds trusted-v1-digest rejection after coordinated mutation + reseal + -> adds nested parsed-state immutability and isolated to_dict() coverage + +$VP -m pytest -q # after DGR-003 integration and DGR-017 repair + -> first run: 871 passed, 13 skipped, 1 known cancellation-race failure +$VP -m pytest -q tests/test_tracker_routing.py::test_tracker_dashboard_can_cancel_inflight_proxy + -> 5/5 passed in isolation +$VP -m pytest -q # integrated rerun + -> 872 passed, 13 skipped in 253.46s (0:04:13) diff --git a/packages/node/meshnet_node/glm_alpha/contract.py b/packages/node/meshnet_node/glm_alpha/contract.py index e9409ee..bf3f556 100644 --- a/packages/node/meshnet_node/glm_alpha/contract.py +++ b/packages/node/meshnet_node/glm_alpha/contract.py @@ -6,16 +6,15 @@ the fact: *what would have counted as success?* Its thresholds are locked before the target ever runs (DGR-017), and DGR-020 reads them back to publish an ``alpha`` or ``stop`` verdict. The whole point is that the gap between those two moments is where a threshold quietly becomes "0.1 tokens/sec -was always the goal". So the document carries ``contract_sha256`` over its own -canonical content, and :func:`load_alpha_contract` recomputes it on every load. An -agent that edits a threshold and forgets the digest is rejected; an agent that -edits both has left a diff on a file whose whole purpose is to not change. +was always the goal". So the document carries ``contract_sha256`` over its canonical content, while the +approved v1 digest is pinned independently in code. :func:`load_alpha_contract` +recomputes the self-digest and then requires that trusted pre-execution digest. +Changing a threshold and re-sealing under the same identity is rejected; an +amendment requires a new supported contract identity under human review. -This is a tamper-*evidence* mechanism, not tamper-proofing. It cannot stop a -determined rewrite of the file and the digest together — nothing in-repo can. What -it does is remove the possibility of a silent one, which is the failure mode that -actually happens: a number nudged mid-run, with no reviewer ever seeing that it -moved. +The parsed contract recursively freezes nested mappings and sequences. Thresholds +therefore cannot change between verification and use, and :meth:`AlphaContract.to_dict` +returns an isolated mutable copy for diagnostics and tests. """ from __future__ import annotations @@ -25,6 +24,7 @@ import re from dataclasses import dataclass from importlib.resources import files from pathlib import Path +from types import MappingProxyType from typing import Any, Mapping from .manifest import ( @@ -39,6 +39,7 @@ from .manifest import ( ALPHA_CONTRACT_SCHEMA_VERSION = 1 ALPHA_CONTRACT_VERSION = 1 ALPHA_CONTRACT_ID = "glm-5.2-max-alpha/v1" +ALPHA_CONTRACT_V1_SHA256 = "aab23220280c053a3c14ff559df3cb5c9e1bf7f0f7188c6519e2e9d9ad036ed9" _CONTRACT_RESOURCE = "alpha-contract.json" @@ -72,7 +73,23 @@ def contract_signing_payload(document: Mapping[str, Any]) -> dict: def compute_contract_digest(document: Mapping[str, Any]) -> str: """SHA-256 over the canonical contract content.""" - return canonical_sha256(contract_signing_payload(document)) + return canonical_sha256(contract_signing_payload(_thaw_json(document))) + + +def _freeze_json(value: Any) -> Any: + if isinstance(value, Mapping): + return MappingProxyType({str(key): _freeze_json(item) for key, item in value.items()}) + if isinstance(value, list): + return tuple(_freeze_json(item) for item in value) + return value + + +def _thaw_json(value: Any) -> Any: + if isinstance(value, Mapping): + return {str(key): _thaw_json(item) for key, item in value.items()} + if isinstance(value, tuple): + return [_thaw_json(item) for item in value] + return value @dataclass(frozen=True) @@ -107,7 +124,7 @@ class AlphaContract: return block[key] def to_dict(self) -> dict: - return dict(self.raw) + return _thaw_json(self.raw) def parse_alpha_contract(data: Any, source: str = "") -> AlphaContract: @@ -229,18 +246,28 @@ def parse_alpha_contract(data: Any, source: str = "") -> AlphaContract: if not isinstance(amendment_policy, str) or not amendment_policy.strip(): raise AlphaContractError(f"{source} must state its amendment policy") + if declared != ALPHA_CONTRACT_V1_SHA256: + raise AlphaContractError( + f"{source} is a re-sealed mutation of {ALPHA_CONTRACT_ID}: digest " + f"{declared} does not match the trusted pre-execution digest " + f"{ALPHA_CONTRACT_V1_SHA256}. An amendment requires a new supported " + "contract identity under human review." + ) + + frozen = _freeze_json(data) + return AlphaContract( schema_version=schema_version, contract_version=contract_version, contract_id=contract_id, locked_at=str(data["locked_at"]), locked_by=str(data["locked_by"]), - target=target, - sections={name: data[name] for name in REQUIRED_SECTIONS}, + target=frozen["target"], + sections=MappingProxyType({name: frozen[name] for name in REQUIRED_SECTIONS}), verdicts=tuple(verdicts), amendment_policy=amendment_policy, digest=declared, - raw=data, + raw=frozen, source=source, ) diff --git a/tests/test_glm_alpha_target.py b/tests/test_glm_alpha_target.py index c92b6c9..04599da 100644 --- a/tests/test_glm_alpha_target.py +++ b/tests/test_glm_alpha_target.py @@ -80,7 +80,7 @@ def snapshot_doc(snapshot): @pytest.fixture def contract_doc(contract): - return copy.deepcopy(dict(contract.raw)) + return contract.to_dict() # -------------------------------------------------------------------------- @@ -746,7 +746,7 @@ def test_the_contract_locks_the_roadmap_thresholds(contract): assert contract.threshold("performance", "quality_pass_with_speed_fail_verdict") == "stop" assert contract.threshold("reliability", "synthetic_workers_satisfy_alpha") is False - assert contract.threshold("storage", "forbidden_path_prefixes") == ["/home"] + assert contract.threshold("storage", "forbidden_path_prefixes") == ("/home",) def test_the_contract_offers_only_alpha_or_stop(contract): @@ -820,17 +820,26 @@ def test_a_dropped_acceptance_section_is_rejected(contract_doc): parse_alpha_contract(contract_doc) -def test_resealing_a_mutated_contract_changes_its_digest(contract, contract_doc): - """Tamper-evidence, not tamper-proofing: a rewrite is legal but never silent.""" +def test_resealing_a_mutated_v1_contract_is_rejected(contract, contract_doc): contract_doc["performance"]["min_median_decode_tokens_per_second"] = 0.05 resealed = seal_contract(contract_doc) - # It parses — nothing in-repo can stop a determined rewrite of file plus digest. - reparsed = parse_alpha_contract(resealed) + with pytest.raises(AlphaContractError, match="trusted pre-execution digest"): + parse_alpha_contract(resealed) + assert resealed["contract_sha256"] != contract.digest - # But the digest moved, so the change is a visible diff on a file whose entire - # purpose is to not change, and the contract_id still claims to be v1. - assert reparsed.digest != contract.digest + +def test_parsed_contract_nested_state_is_immutable(contract): + with pytest.raises(TypeError): + contract.raw["performance"]["min_median_decode_tokens_per_second"] = 0.05 + with pytest.raises(TypeError): + contract.target["reasoning_effort"] = "high" + + +def test_contract_to_dict_returns_an_isolated_mutable_copy(contract): + copied = contract.to_dict() + copied["performance"]["min_median_decode_tokens_per_second"] = 0.05 + assert contract.threshold("performance", "min_median_decode_tokens_per_second") == 0.5 def test_an_unknown_threshold_cannot_be_invented_at_read_time(contract):