Build latest book artifacts / build (push) Canceled after 0s
dependency resolution / resolve (3.11) (push) Canceled after 0s
dependency resolution / resolve (3.13) (push) Canceled after 0s
deploy-pages / build (push) Canceled after 0s
deploy-pages / deploy (push) Canceled after 0s
i18n consistency check / check (push) Canceled after 0s
provider adoption tests / test (chapter2/context-compression) (push) Canceled after 0s
provider adoption tests / test (chapter2/prompt-injection) (push) Canceled after 0s
provider adoption tests / test (chapter2/system-hint) (push) Canceled after 0s
provider adoption tests / test (chapter3/log-sanitization) (push) Canceled after 0s
web-search-agent tests / test (push) Canceled after 0s
web-search-agent tests / agentbook (push) Canceled after 0s
136 lines
5.7 KiB
Python
136 lines
5.7 KiB
Python
from __future__ import annotations
|
|
|
|
import hashlib
|
|
import json
|
|
import shutil
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
import run_training_report_audit as audit
|
|
import validate_evidence as validator
|
|
|
|
|
|
def digest(path: Path) -> str:
|
|
return hashlib.sha256(path.read_bytes()).hexdigest()
|
|
|
|
|
|
def write_json(path: Path, value: object) -> None:
|
|
path.write_text(
|
|
json.dumps(value, ensure_ascii=False, indent=2, sort_keys=True) + "\n",
|
|
encoding="utf-8",
|
|
)
|
|
|
|
|
|
def test_historical_report_parser_retains_complete_six_cell_matrix() -> None:
|
|
retained = audit.parse_retained_outputs()
|
|
assert retained["cell_count"] == 6
|
|
assert retained["output_count"] == 49
|
|
assert {
|
|
(cell["arm"], cell["stage"]): cell["pair_count"]
|
|
for cell in retained["cells"]
|
|
} == audit.EXPECTED_COUNTS
|
|
comparisons = audit.selected_comparisons(retained)
|
|
assert len(comparisons) == 8
|
|
assert {row["stage"] for row in comparisons} == set(audit.STAGES)
|
|
assert all(set(row["arms"]) == set(audit.ARMS) for row in comparisons)
|
|
|
|
|
|
def test_judge_requests_are_arm_blind_and_bound_to_exact_outputs() -> None:
|
|
retained = audit.parse_retained_outputs()
|
|
for comparison in audit.selected_comparisons(retained):
|
|
mapping = audit.blind_mapping(comparison["case_id"])
|
|
payload = audit.judge_payload(comparison, mapping, "judge-model")
|
|
serialized = json.dumps(payload, ensure_ascii=False).lower()
|
|
assert "qk_norm_muon" not in serialized
|
|
assert '"original"' not in serialized
|
|
user_payload = json.loads(payload["messages"][1]["content"])
|
|
for label, arm in mapping.items():
|
|
assert user_payload["candidates"][label]["historical_output"] == comparison["arms"][arm]["output"]
|
|
|
|
|
|
def make_validation_copy(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> tuple[Path, Path]:
|
|
latest = json.loads(audit.LATEST_PATH.read_text(encoding="utf-8"))
|
|
canonical_run = audit.EXPERIMENT_DIR / latest["run_dir"]
|
|
temp_repo = tmp_path / "repo"
|
|
temp_experiment = temp_repo / "chapter8/MiniMind-pretrain"
|
|
temp_run = temp_experiment / latest["run_dir"]
|
|
temp_run.parent.mkdir(parents=True)
|
|
shutil.copytree(canonical_run, temp_run)
|
|
|
|
manifest = json.loads((canonical_run / "manifest.json").read_text(encoding="utf-8"))
|
|
for record in manifest["inputs"]:
|
|
source = audit.REPO_ROOT / record["path"]
|
|
destination = temp_repo / record["path"]
|
|
destination.parent.mkdir(parents=True, exist_ok=True)
|
|
shutil.copy2(source, destination)
|
|
|
|
temp_latest = temp_experiment / "validation/latest.json"
|
|
temp_latest.parent.mkdir(parents=True, exist_ok=True)
|
|
shutil.copy2(audit.LATEST_PATH, temp_latest)
|
|
monkeypatch.setattr(validator, "REPO_ROOT", temp_repo)
|
|
monkeypatch.setattr(validator, "EXPERIMENT_DIR", temp_experiment)
|
|
return temp_latest, temp_run
|
|
|
|
|
|
def refresh_outer_hashes(latest_path: Path, run_dir: Path, artifact_name: str) -> None:
|
|
manifest_path = run_dir / "manifest.json"
|
|
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
|
|
artifact_path = run_dir / artifact_name
|
|
record = next(record for record in manifest["artifacts"] if record["path"] == artifact_name)
|
|
record["bytes"] = artifact_path.stat().st_size
|
|
record["sha256"] = digest(artifact_path)
|
|
write_json(manifest_path, manifest)
|
|
latest = json.loads(latest_path.read_text(encoding="utf-8"))
|
|
latest["manifest_sha256"] = digest(manifest_path)
|
|
write_json(latest_path, latest)
|
|
|
|
|
|
def test_canonical_evidence_passes_fail_closed_validator() -> None:
|
|
result = validator.validate()
|
|
assert result["status"] == "passed"
|
|
assert result["outputs_verified"] == 49
|
|
assert result["judge_receipts_verified"] == 8
|
|
|
|
|
|
def test_validator_rejects_raw_response_normalization_tamper(
|
|
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
) -> None:
|
|
latest_path, run_dir = make_validation_copy(tmp_path, monkeypatch)
|
|
receipts_path = run_dir / "judge_receipts.json"
|
|
receipts = json.loads(receipts_path.read_text(encoding="utf-8"))
|
|
response = receipts["calls"][0]["response"]
|
|
raw_judgment = json.loads(response["choices"][0]["message"]["content"])
|
|
raw_judgment["winner"] = "tie" if raw_judgment["winner"] != "tie" else "A"
|
|
response["choices"][0]["message"]["content"] = json.dumps(raw_judgment)
|
|
write_json(receipts_path, receipts)
|
|
refresh_outer_hashes(latest_path, run_dir, "judge_receipts.json")
|
|
with pytest.raises(AssertionError, match="normalized judgment"):
|
|
validator.validate(latest_path)
|
|
|
|
|
|
def test_validator_rejects_retained_output_request_binding_tamper(
|
|
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
) -> None:
|
|
latest_path, run_dir = make_validation_copy(tmp_path, monkeypatch)
|
|
retained_path = run_dir / "retained_outputs.json"
|
|
retained = json.loads(retained_path.read_text(encoding="utf-8"))
|
|
retained["cells"][0]["pairs"][3]["output"] += " altered"
|
|
write_json(retained_path, retained)
|
|
refresh_outer_hashes(latest_path, run_dir, "retained_outputs.json")
|
|
with pytest.raises(AssertionError, match="not bound to the retained output"):
|
|
validator.validate(latest_path)
|
|
|
|
|
|
def test_validator_rejects_frozen_dataset_revision_tamper(
|
|
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
) -> None:
|
|
latest_path, run_dir = make_validation_copy(tmp_path, monkeypatch)
|
|
contract_path = run_dir / "reproduction_contract.json"
|
|
contract = json.loads(contract_path.read_text(encoding="utf-8"))
|
|
contract["future_reproduction"]["dataset"]["revision"] = "0" * 40
|
|
write_json(contract_path, contract)
|
|
refresh_outer_hashes(latest_path, run_dir, "reproduction_contract.json")
|
|
with pytest.raises(AssertionError, match="dataset revision mismatch"):
|
|
validator.validate(latest_path)
|