fix: harden DGR-017 contract continuity

This commit is contained in:
Dobromir Popov
2026-07-14 01:10:33 +03:00
parent ad2d17541c
commit 7364ed6731
4 changed files with 81 additions and 30 deletions

View File

@@ -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` - 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` - 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 **Contract continuity is fail-closed.** The documents `contract_sha256` detects
tamper-proofing. Nothing checked into a repo can stop an agent that rewrites the accidental edits, and the approved v1 digest is pinned independently in code. A caller
threshold *and* re-seals the digest — `test_resealing_a_mutated_contract_changes_its_digest` that changes a threshold and re-seals it under `glm-5.2-max-alpha/v1` is rejected by
asserts that path openly. What the seal removes is the *silent* mutation: the digest `test_resealing_a_mutated_v1_contract_is_rejected`; amendments require a new supported
moves, and the change becomes a visible diff on a file whose entire purpose is to not contract identity under human review. Parsed nested state is recursively immutable,
change, still claiming `contract_id: glm-5.2-max-alpha/v1`. Reviewers, not hashes, are so thresholds cannot change between validation and use; `to_dict()` returns an isolated
the enforcement; the hash makes sure there is something to review. copy rather than exposing the validated object.
## 6. Upstream status — the gating risk for DGR-004/DGR-018 ## 6. Upstream status — the gating risk for DGR-004/DGR-018

View File

@@ -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 -> 1 passed, repeated 5/5 in isolation
$VP -m pytest -q # integrated rerun $VP -m pytest -q # integrated rerun
-> 852 passed, 13 skipped in 253.30s (0:04:13) -> 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)

View File

@@ -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 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 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 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 was always the goal". So the document carries ``contract_sha256`` over its canonical content, while the
canonical content, and :func:`load_alpha_contract` recomputes it on every load. An approved v1 digest is pinned independently in code. :func:`load_alpha_contract`
agent that edits a threshold and forgets the digest is rejected; an agent that recomputes the self-digest and then requires that trusted pre-execution digest.
edits both has left a diff on a file whose whole purpose is to not change. 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 The parsed contract recursively freezes nested mappings and sequences. Thresholds
determined rewrite of the file and the digest together — nothing in-repo can. What therefore cannot change between verification and use, and :meth:`AlphaContract.to_dict`
it does is remove the possibility of a silent one, which is the failure mode that returns an isolated mutable copy for diagnostics and tests.
actually happens: a number nudged mid-run, with no reviewer ever seeing that it
moved.
""" """
from __future__ import annotations from __future__ import annotations
@@ -25,6 +24,7 @@ import re
from dataclasses import dataclass from dataclasses import dataclass
from importlib.resources import files from importlib.resources import files
from pathlib import Path from pathlib import Path
from types import MappingProxyType
from typing import Any, Mapping from typing import Any, Mapping
from .manifest import ( from .manifest import (
@@ -39,6 +39,7 @@ from .manifest import (
ALPHA_CONTRACT_SCHEMA_VERSION = 1 ALPHA_CONTRACT_SCHEMA_VERSION = 1
ALPHA_CONTRACT_VERSION = 1 ALPHA_CONTRACT_VERSION = 1
ALPHA_CONTRACT_ID = "glm-5.2-max-alpha/v1" ALPHA_CONTRACT_ID = "glm-5.2-max-alpha/v1"
ALPHA_CONTRACT_V1_SHA256 = "aab23220280c053a3c14ff559df3cb5c9e1bf7f0f7188c6519e2e9d9ad036ed9"
_CONTRACT_RESOURCE = "alpha-contract.json" _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: def compute_contract_digest(document: Mapping[str, Any]) -> str:
"""SHA-256 over the canonical contract content.""" """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) @dataclass(frozen=True)
@@ -107,7 +124,7 @@ class AlphaContract:
return block[key] return block[key]
def to_dict(self) -> dict: def to_dict(self) -> dict:
return dict(self.raw) return _thaw_json(self.raw)
def parse_alpha_contract(data: Any, source: str = "<memory>") -> AlphaContract: def parse_alpha_contract(data: Any, source: str = "<memory>") -> AlphaContract:
@@ -229,18 +246,28 @@ def parse_alpha_contract(data: Any, source: str = "<memory>") -> AlphaContract:
if not isinstance(amendment_policy, str) or not amendment_policy.strip(): if not isinstance(amendment_policy, str) or not amendment_policy.strip():
raise AlphaContractError(f"{source} must state its amendment policy") 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( return AlphaContract(
schema_version=schema_version, schema_version=schema_version,
contract_version=contract_version, contract_version=contract_version,
contract_id=contract_id, contract_id=contract_id,
locked_at=str(data["locked_at"]), locked_at=str(data["locked_at"]),
locked_by=str(data["locked_by"]), locked_by=str(data["locked_by"]),
target=target, target=frozen["target"],
sections={name: data[name] for name in REQUIRED_SECTIONS}, sections=MappingProxyType({name: frozen[name] for name in REQUIRED_SECTIONS}),
verdicts=tuple(verdicts), verdicts=tuple(verdicts),
amendment_policy=amendment_policy, amendment_policy=amendment_policy,
digest=declared, digest=declared,
raw=data, raw=frozen,
source=source, source=source,
) )

View File

@@ -80,7 +80,7 @@ def snapshot_doc(snapshot):
@pytest.fixture @pytest.fixture
def contract_doc(contract): 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("performance", "quality_pass_with_speed_fail_verdict") == "stop"
assert contract.threshold("reliability", "synthetic_workers_satisfy_alpha") is False 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): 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) parse_alpha_contract(contract_doc)
def test_resealing_a_mutated_contract_changes_its_digest(contract, contract_doc): def test_resealing_a_mutated_v1_contract_is_rejected(contract, contract_doc):
"""Tamper-evidence, not tamper-proofing: a rewrite is legal but never silent."""
contract_doc["performance"]["min_median_decode_tokens_per_second"] = 0.05 contract_doc["performance"]["min_median_decode_tokens_per_second"] = 0.05
resealed = seal_contract(contract_doc) resealed = seal_contract(contract_doc)
# It parses — nothing in-repo can stop a determined rewrite of file plus digest. with pytest.raises(AlphaContractError, match="trusted pre-execution digest"):
reparsed = parse_alpha_contract(resealed) 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. def test_parsed_contract_nested_state_is_immutable(contract):
assert reparsed.digest != contract.digest 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): def test_an_unknown_threshold_cannot_be_invented_at_read_time(contract):