Files
imbad0202__academic-researc…/scripts/test_check_revision_claim_drift_suite_v2.py
2026-08-10 17:19:53 +08:00

1926 lines
73 KiB
Python

#!/usr/bin/env python3
"""Bounded mutation tests for the hermetic revision-claim-drift v2 guard (#679)."""
from __future__ import annotations
import copy
import hashlib
import json
import shutil
from pathlib import Path
from typing import Any
import pytest
from scripts import check_revision_claim_drift_suite_v2 as guard
from scripts.check_heldout_measurement_report import validate_report
REPO_ROOT = Path(__file__).resolve().parents[1]
SUITE = Path(guard.SUITE_REL)
FIXTURES = Path("scripts/fixtures/revision_claim_drift_v2")
DESIGN = Path("docs/design/2026-08-10-679-revision-claim-drift-suite-v2-spec.md")
MEASUREMENT_SCHEMA = Path("evals/heldout/measurement_report.schema.json")
EXECUTION_SCHEMA = Path("evals/heldout/execution_manifest.schema.json")
LADDER = Path("shared/references/claim_strength_ladder.md")
LAUNCHER_SCHEMA = SUITE / "subject_launcher_config.schema.json"
CALL_PLAN_SCHEMA = SUITE / "subject_call_plan.schema.json"
def _copy_file(root: Path, relative: Path | str) -> None:
relative_path = Path(relative)
destination = root / relative_path
destination.parent.mkdir(parents=True, exist_ok=True)
shutil.copyfile(REPO_ROOT / relative_path, destination)
def _mirror(tmp_path: Path) -> Path:
"""Copy only the repository surfaces inspected by ``run_checks``."""
root = tmp_path / "repo"
shutil.copytree(REPO_ROOT / SUITE, root / SUITE)
shutil.copytree(REPO_ROOT / FIXTURES, root / FIXTURES)
_copy_file(root, DESIGN)
_copy_file(root, LADDER)
return root
def _read_json(path: Path) -> dict[str, Any]:
value = json.loads(path.read_text(encoding="utf-8"))
assert isinstance(value, dict)
return value
def _write_json(path: Path, value: object) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(
json.dumps(value, ensure_ascii=False, sort_keys=True, indent=2) + "\n",
encoding="utf-8",
)
def _digest(path: Path) -> str:
return hashlib.sha256(path.read_bytes()).hexdigest()
def _canonical_object_digest(value: dict[str, Any]) -> str:
raw = json.dumps(
value,
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
).encode("utf-8")
return hashlib.sha256(raw).hexdigest()
def _replace(path: Path, old: str, new: str) -> None:
text = path.read_text(encoding="utf-8")
assert old in text
path.write_text(text.replace(old, new, 1), encoding="utf-8")
def _sync_v2_rubric(root: Path, monkeypatch: pytest.MonkeyPatch) -> None:
"""Re-pin a deliberate rubric mutation so semantic guards are exercised."""
rubric = root / SUITE / "adjudication_rubric_v2.md"
digest = _digest(rubric)
monkeypatch.setattr(guard, "V2_RUBRIC_SHA256", digest)
ledger_path = root / SUITE / "rubric_amendments.json"
ledger = _read_json(ledger_path)
ledger["rubrics"][1]["sha256"] = digest
ledger["amendments"][0]["to_sha256"] = digest
_write_json(ledger_path, ledger)
def _sync_subject_schema(root: Path, monkeypatch: pytest.MonkeyPatch) -> None:
schema_path = root / SUITE / "subject_context_record.schema.json"
monkeypatch.setattr(guard, "SUBJECT_SCHEMA_SHA256", _digest(schema_path))
def _schema() -> dict[str, Any]:
return _read_json(REPO_ROOT / SUITE / "subject_context_record.schema.json")
def _fixture(name: str) -> dict[str, Any]:
return _read_json(REPO_ROOT / FIXTURES / name)
def test_current_repository_passes_all_679_guards() -> None:
assert guard.run_checks(REPO_ROOT) == [
"historical bytes/tree and no-new-measurement boundary",
"v2 rubric and closed amendment ledger",
"subject-context/launcher/call-plan schemas and four synthetic fixtures",
"README/design prospective protocol",
"prospective heldout-measurement/1.1 context replay",
]
def test_unmutated_minimal_mirror_passes(tmp_path: Path) -> None:
assert len(guard.run_checks(_mirror(tmp_path))) == 5
def test_protected_historical_file_byte_drift_fails(tmp_path: Path) -> None:
root = _mirror(tmp_path)
path = root / SUITE / "heldout_set.json"
path.write_bytes(path.read_bytes() + b"\n")
with pytest.raises(guard.ContractError, match="protected historical byte drift"):
guard.run_checks(root)
@pytest.mark.parametrize("mutation", ["add", "delete", "byte_drift"])
def test_historical_run_tree_inventory_is_write_once(
tmp_path: Path, mutation: str
) -> None:
root = _mirror(tmp_path)
tree = root / guard.HISTORICAL_TREE_REL
files = sorted(path for path in tree.rglob("*") if path.is_file())
assert files
if mutation == "add":
(tree / "unauthorized-addition.txt").write_text("drift\n", encoding="utf-8")
elif mutation == "delete":
files[0].unlink()
else:
files[0].write_bytes(files[0].read_bytes() + b"\n")
with pytest.raises(guard.ContractError, match="protected historical run-tree drift"):
guard.run_checks(root)
def test_679_cannot_create_a_measurement_row(tmp_path: Path) -> None:
root = _mirror(tmp_path)
_write_json(root / SUITE / "measurement-2099-01-01.json", {})
with pytest.raises(guard.ContractError, match="creates no measurement row"):
guard.run_checks(root)
def test_v2_rubric_hash_is_load_bearing(tmp_path: Path) -> None:
root = _mirror(tmp_path)
rubric = root / SUITE / "adjudication_rubric_v2.md"
rubric.write_text(rubric.read_text(encoding="utf-8") + "\n", encoding="utf-8")
with pytest.raises(guard.ContractError, match="rubric byte identity"):
guard._check_rubric(root)
@pytest.mark.parametrize("mutation", ["c9_authority", "canonical_anchor", "ladder_copy"])
def test_v2_rubric_semantics_survive_rehashing_attacks(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch, mutation: str
) -> None:
root = _mirror(tmp_path)
rubric = root / SUITE / "adjudication_rubric_v2.md"
if mutation == "c9_authority":
_replace(
rubric,
"### C9 — Non-control citation-attachment violation",
"### C9 — Citation-token conservation",
)
expected = "C9 title/authority drift"
elif mutation == "canonical_anchor":
_replace(
rubric,
"#what-counts-as-a-move-and-what-does-not",
"#unfrozen-local-summary",
)
expected = "missing or duplicates frozen marker"
else:
rubric.write_text(
rubric.read_text(encoding="utf-8") + "\nhedged → asserted\n",
encoding="utf-8",
)
expected = "must point to, not copy"
_sync_v2_rubric(root, monkeypatch)
with pytest.raises(guard.ContractError, match=expected):
guard._check_rubric(root)
def test_closed_amendment_ledger_rejects_mutation(tmp_path: Path) -> None:
root = _mirror(tmp_path)
path = root / SUITE / "rubric_amendments.json"
ledger = _read_json(path)
ledger["amendments"][0]["historical_effect"] = "rescore"
_write_json(path, ledger)
with pytest.raises(guard.ContractError, match="closed amendment"):
guard._check_rubric(root)
@pytest.mark.parametrize(
"payload",
[
'{"schema_version":"x","schema_version":"y"}',
'{"schema_version":"x","score":NaN}',
'\ufeff{"schema_version":"x"}',
],
ids=["duplicate-key", "nonfinite", "utf8-bom"],
)
def test_new_contract_json_is_parsed_strictly(
tmp_path: Path, payload: str
) -> None:
root = _mirror(tmp_path)
(root / SUITE / "historical_artifacts.lock.json").write_text(
payload, encoding="utf-8"
)
with pytest.raises(guard.ContractError, match="duplicate JSON key|non-finite|strict JSON"):
guard.run_checks(root)
def test_subject_context_schema_hash_is_load_bearing(tmp_path: Path) -> None:
root = _mirror(tmp_path)
path = root / SUITE / "subject_context_record.schema.json"
path.write_text(path.read_text(encoding="utf-8") + "\n", encoding="utf-8")
with pytest.raises(guard.ContractError, match="schema byte identity"):
guard._check_subject_assets(root)
@pytest.mark.parametrize(
("fixture_name", "container"),
[
("subject_context_machine_supported.json", "execution_manifest"),
("subject_context_machine_supported.json", "neutral_cwd"),
("subject_context_machine_supported.json", "cli"),
("subject_context_machine_supported.json", "instruction_visibility"),
("subject_context_machine_supported.json", "context_probe"),
("subject_context_machine_supported.json", "data_minimization"),
("subject_context_attested_only.json", "attestation"),
],
)
def test_subject_context_nested_branches_are_closed(
fixture_name: str, container: str
) -> None:
record = _fixture(fixture_name)
assert isinstance(record[container], dict)
record[container]["raw_secret"] = "must-not-be-stored"
with pytest.raises(guard.ContractError):
guard.validate_subject_context(record, _schema())
@pytest.mark.parametrize("mutation", ["add", "delete", "byte_drift"])
def test_fixture_inventory_and_bytes_are_frozen(
tmp_path: Path, mutation: str
) -> None:
root = _mirror(tmp_path)
fixture_dir = root / FIXTURES
if mutation == "add":
_write_json(fixture_dir / "extra.json", {})
expected = "fixture inventory drift"
elif mutation == "delete":
(fixture_dir / "subject_context_unknown.json").unlink()
expected = "fixture inventory drift"
else:
path = fixture_dir / "subject_context_unknown.json"
path.write_bytes(path.read_bytes() + b"\n")
expected = "fixture byte drift"
with pytest.raises(guard.ContractError, match=expected):
guard._check_subject_assets(root)
@pytest.mark.parametrize("relation", ["inside_repository", "repository_visible"])
def test_derived_not_isolated_relations_are_load_bearing(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
relation: str,
) -> None:
root = _mirror(tmp_path)
path = root / SUITE / "subject_context_record.schema.json"
schema = _read_json(path)
branches = schema["$defs"]["notIsolatedRecord"]["anyOf"]
if relation == "inside_repository":
branches[2]["properties"]["neutral_cwd"]["properties"][
"repo_membership_probe"
]["const"] = "outside_suite_repository"
expected = "derived inside-repository record"
else:
branches[1]["properties"]["instruction_visibility"]["properties"][
"repository_instructions"
]["const"] = "not_detected"
expected = "derived repository-visible record"
_write_json(path, schema)
_sync_subject_schema(root, monkeypatch)
with pytest.raises(guard.ContractError, match=expected):
guard._check_subject_assets(root)
@pytest.mark.parametrize("surface", ["README", "design"])
def test_protocol_document_markers_are_load_bearing(
tmp_path: Path, surface: str
) -> None:
root = _mirror(tmp_path)
if surface == "README":
path = root / SUITE / "README.md"
_replace(path, "negative probe is evidence, not proof", "negative probe proves isolation")
expected = "README missing"
else:
path = root / DESIGN
_replace(path, "DESIGN-FROZEN / PROSPECTIVE-ONLY", "DRAFT")
expected = "design missing"
with pytest.raises(guard.ContractError, match=expected):
guard._check_docs(root)
@pytest.mark.parametrize(
"relative",
[
"runs/future-window/hidden-row.json",
"future-contract-record.JSON",
"archive/nonstandard-name.json",
],
)
def test_contract_row_discovery_is_recursive_and_filename_independent(
tmp_path: Path, relative: str
) -> None:
root = _mirror(tmp_path)
_write_json(
root / SUITE / relative,
{
"measurement_contract": "heldout-measurement/1.1",
"suite": "revision_claim_drift",
},
)
with pytest.raises(guard.ContractError, match="contract-marked measurement row"):
guard._check_history(root)
def test_escaped_contract_marker_is_discovered(tmp_path: Path) -> None:
root = _mirror(tmp_path)
path = root / SUITE / "runs/future-window/escaped.json"
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(
'{"measurement\\u005fcontract":"heldout-measurement/1.1",'
'"suite":"revision_claim_drift"}\n',
encoding="utf-8",
)
with pytest.raises(guard.ContractError, match="contract-marked measurement row"):
guard._check_history(root)
@pytest.mark.parametrize("mutation", ["delete", "hash"])
def test_canonical_claim_ladder_target_is_required(
tmp_path: Path, mutation: str
) -> None:
root = _mirror(tmp_path)
ladder = root / LADDER
if mutation == "delete":
ladder.unlink()
expected = "canonical claim-strength ladder: missing"
else:
ladder.write_bytes(ladder.read_bytes() + b"\n")
expected = "ladder target hash drifted"
with pytest.raises(guard.ContractError, match=expected):
guard._check_rubric(root)
def test_canonical_claim_ladder_headings_are_load_bearing(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
root = _mirror(tmp_path)
ladder = root / LADDER
_replace(ladder, "## The ladder", "## Local ladder summary")
old_hash = guard.CLAIM_LADDER_SHA256
new_hash = _digest(ladder)
monkeypatch.setattr(guard, "CLAIM_LADDER_SHA256", new_hash)
rubric = root / SUITE / "adjudication_rubric_v2.md"
_replace(rubric, old_hash, new_hash)
_sync_v2_rubric(root, monkeypatch)
with pytest.raises(guard.ContractError, match="ladder target headings drifted"):
guard._check_rubric(root)
def _future_bundle(
tmp_path: Path, status: str = "machine_supported"
) -> tuple[Path, dict[str, Any], Path, Path]:
"""Create a fully joined synthetic future row without executing an eval."""
root = tmp_path / f"future-{status}"
for relative in (
MEASUREMENT_SCHEMA,
EXECUTION_SCHEMA,
SUITE / "subject_context_record.schema.json",
LAUNCHER_SCHEMA,
CALL_PLAN_SCHEMA,
SUITE / "heldout_set.json",
):
_copy_file(root, relative)
run_id = "future-ni" if status == "not_isolated" else f"future-{status.replace('_', '-')}"
commit = "a" * 40
subject_model_id = "synthetic-subject/not-executed"
run_rel = SUITE / "runs" / run_id
context_ref = (run_rel / "subject-context.json").as_posix()
launcher_ref = (run_rel / "launcher-config.json").as_posix()
plan_ref = (run_rel / "subject-call-plan.json").as_posix()
manifest_ref = (run_rel / "execution-manifest.json").as_posix()
context_path = root / context_ref
launcher_path = root / launcher_ref
plan_path = root / plan_ref
manifest_path = root / manifest_ref
fixture_by_status = {
"machine_supported": "subject_context_machine_supported.json",
"attested_only": "subject_context_attested_only.json",
"not_isolated": "subject_context_not_isolated.json",
"unknown": "subject_context_unknown.json",
}
context = _fixture(fixture_by_status[status])
context["run_id"] = run_id
context["suite_commit"] = commit
context["execution_manifest"]["ref"] = manifest_ref
context["recorded_at"] = "2026-08-10T01:00:04Z"
if context["context_probe"]["status"] != "not_run":
context["context_probe"]["started_at"] = "2026-08-10T01:00:00Z"
context["context_probe"]["completed_at"] = "2026-08-10T01:00:02Z"
if context["attestation"] is not None:
context["attestation"]["attested_at"] = "2026-08-10T01:00:03Z"
policy_by_membership = {
"outside_suite_repository": "fresh_outside_suite_repository",
"inside_suite_repository": "inside_suite_repository",
"unresolved": "unresolved",
}
launcher = {
"schema_version": "revision-claim-drift-launcher-config/1.0",
"suite": "revision_claim_drift",
"run_id": run_id,
"sealed_at": "2026-08-10T00:59:40Z",
"applies_to": ["context_probe", "subject_fleet"],
"launcher": {
"client_id": "synthetic-cli",
"client_version": "0.0-test",
"mode": context["cli"]["mode"],
"bare_requested": context["cli"]["bare_requested"],
"bare_used": context["cli"]["bare_used"],
"authentication_result": context["cli"]["authentication_result"],
},
"working_directory": {
"policy": policy_by_membership[
context["neutral_cwd"]["repo_membership_probe"]
],
"pwd_p_sha256": context["neutral_cwd"]["pwd_p_sha256"],
},
"instruction_loading": {
"repository_instructions": "disabled",
"global_instructions": "enabled",
},
"invocation_sha256": "0" * 64,
"data_minimization": {
"raw_command_stored": False,
"raw_environment_stored": False,
"raw_physical_cwd_stored": False,
"raw_instruction_content_stored": False,
},
}
launcher["invocation_sha256"] = _canonical_object_digest(
{
key: launcher[key]
for key in ("launcher", "working_directory", "instruction_loading")
}
)
_write_json(launcher_path, launcher)
launcher_binding = {"ref": launcher_ref, "sha256": _digest(launcher_path)}
context["cli"]["launcher_config"] = copy.deepcopy(launcher_binding)
item_ids = [f"rp-{index:02d}" for index in range(1, 9)]
control_ids = {"rp-07", "rp-08"}
subject_specs = [
{
"item_id": item_id,
"item_replicate_id": f"{item_id}.single.r{replicate_index}",
"replicate_index": replicate_index,
"arm_id": "single",
"control_item": item_id in control_ids,
}
for item_id in item_ids
for replicate_index in (1, 2)
]
judge_specs = [
{
"judge_id": judge_id,
"model_id": f"{judge_id}/not-executed",
"model_family": judge_id,
"blinded_to": ["arm_identity", "control_status", "mechanism_state"],
}
for judge_id in ("judge-a", "judge-b")
]
call_specs = [
{
**subject,
"call_id": f"subject.{subject['item_replicate_id']}",
"role": "subject",
"judge_id": None,
}
for subject in subject_specs
] + [
{
**subject,
"call_id": f"judge.{judge_id}.{subject['item_replicate_id']}",
"role": "judge",
"judge_id": judge_id,
}
for judge_id in ("judge-a", "judge-b")
for subject in subject_specs
]
planned_calls: list[dict[str, Any]] = []
raw_paths = [context_ref, launcher_ref, plan_ref, manifest_ref]
plan_judges: list[dict[str, Any]] = []
for judge in judge_specs:
template_ref = (
run_rel / "raw" / f"judge.{judge['judge_id']}.template.txt"
).as_posix()
template_path = root / template_ref
template_path.parent.mkdir(parents=True, exist_ok=True)
template_path.write_text(
f"synthetic frozen template for {judge['judge_id']}\n",
encoding="utf-8",
)
raw_paths.append(template_ref)
plan_judges.append(
{
**judge,
"prompt_template": {
"ref": template_ref,
"sha256": _digest(template_path),
},
}
)
output_hashes: list[str] = []
prompt_hashes: list[str] = []
judge_plan_by_id = {judge["judge_id"]: judge for judge in plan_judges}
for index, spec in enumerate(call_specs, start=1):
call_id = spec["call_id"]
role = spec["role"]
prompt_ref = (run_rel / "raw" / f"{call_id}.prompt.txt").as_posix()
output_ref = (run_rel / "raw" / f"{call_id}.output.txt").as_posix()
prompt_path = root / prompt_ref
output_path = root / output_ref
prompt_path.parent.mkdir(parents=True, exist_ok=True)
if role == "subject":
prompt_path.write_text(
f"synthetic {role} prompt {index}\n", encoding="utf-8"
)
prompt_binding: dict[str, Any] = {
"ref": prompt_ref,
"sha256": _digest(prompt_path),
}
else:
plan_judge = judge_plan_by_id[spec["judge_id"]]
template_ref = plan_judge["prompt_template"]["ref"]
subject_output_ref = (
run_rel
/ "raw"
/ f"subject.{spec['item_replicate_id']}.output.txt"
).as_posix()
subject_prompt_ref = (
run_rel
/ "raw"
/ f"subject.{spec['item_replicate_id']}.prompt.txt"
).as_posix()
prompt_path.write_bytes(
(root / template_ref).read_bytes()
+ guard.JUDGE_SUBJECT_INPUT_SEPARATOR
+ (root / subject_prompt_ref).read_bytes()
+ guard.JUDGE_SUBJECT_OUTPUT_SEPARATOR
+ (root / subject_output_ref).read_bytes()
)
prompt_binding = {
"ref": prompt_ref,
"sha256": None,
"composition": "template_then_subject_input_then_subject_output/1.0",
"template_ref": template_ref,
"subject_prompt_ref": subject_prompt_ref,
"subject_output_ref": subject_output_ref,
}
output_path.write_text(f"synthetic {role} output {index}\n", encoding="utf-8")
raw_paths.extend((prompt_ref, output_ref))
prompt_hashes.append(_digest(prompt_path))
output_hashes.append(_digest(output_path))
planned_calls.append(
{
"call_id": call_id,
"sequence_index": index,
"role": role,
"judge_id": spec["judge_id"],
"item_id": spec["item_id"],
"item_replicate_id": spec["item_replicate_id"],
"replicate_index": spec["replicate_index"],
"arm_id": spec["arm_id"],
"control_item": spec["control_item"],
"prompt": prompt_binding,
"output_ref": output_ref,
}
)
plan = {
"schema_version": "revision-claim-drift-subject-call-plan/1.0",
"suite": "revision_claim_drift",
"run_id": run_id,
"suite_commit": commit,
"subject_model_id": subject_model_id,
"created_at": "2026-08-10T00:59:50Z",
"launcher_config": copy.deepcopy(launcher_binding),
"heldout_set": {
"ref": f"{guard.SUITE_REL}/heldout_set.json",
"sha256": guard.HISTORICAL_FILES[
f"{guard.SUITE_REL}/heldout_set.json"
],
},
"arm_ids": ["single"],
"judges": plan_judges,
"calls": planned_calls,
"data_minimization": {
"prompt_content_embedded": False,
"output_content_embedded": False,
},
}
_write_json(plan_path, plan)
plan_binding = {"ref": plan_ref, "sha256": _digest(plan_path)}
context["subject_call_plan"] = copy.deepcopy(plan_binding)
_write_json(context_path, context)
timings = [
(
f"2026-08-10T01:01:{index:02d}.000Z",
f"2026-08-10T01:01:{index:02d}.100Z",
)
for index in range(1, len(planned_calls) + 1)
]
manifest_calls = []
for planned, prompt_hash, output_hash, (started, completed) in zip(
planned_calls, prompt_hashes, output_hashes, timings, strict=True
):
manifest_calls.append(
{
"call_id": planned["call_id"],
"sequence_index": planned["sequence_index"],
"started_at": started,
"completed_at": completed,
"prompt_sha256": prompt_hash,
"output_sha256": output_hash,
"attempt": 1,
"concurrency_group": None,
}
)
manifest = {
"schema_version": "heldout-execution-manifest/1.0",
"suite": "revision_claim_drift",
"created_at": "2026-08-10T01:02:01Z",
"write_once": True,
"execution_window": {
"window_id": run_id,
"started_at": "2026-08-10T01:01:00Z",
"completed_at": "2026-08-10T01:01:59Z",
},
"calls": manifest_calls,
}
_write_json(manifest_path, manifest)
row: dict[str, Any] = {
"measurement_contract": "heldout-measurement/1.1",
"suite": "revision_claim_drift",
"suite_class": "llm_judged",
"measurement_date": "2026-08-10",
"decision_relevant": True,
"subject": {
"model_id": subject_model_id,
"config": {
"suite_commit": commit,
"subject_context": {
"ref": context_ref,
"sha256": _digest(context_path),
"status": status,
},
"launcher_config": copy.deepcopy(launcher_binding),
"subject_call_plan": copy.deepcopy(plan_binding),
},
},
"judge_plan": {"exception": "none"},
"judges": [],
"aggregate": {
"headline": {
"metric_name": "claim_strength_hedge_drift_rate",
"value": 0,
"construction_rule": (
"Confirmed C1/C2 item-replicate flags divided by evaluated "
"item-replicates; flags-only adjudication makes this a lower bound."
),
"estimand_status": "lower_bound",
},
"agreement": {
"rate": 1.0,
"divergent_items": [],
"note": "Two synthetic fixture judges agree; no calls were made.",
},
},
"replicates": {
"per_item": 2,
"rule_ref": "prospective-only",
"spread": None,
},
"adjudication": {
"applies": True,
"rubric_ref": f"{guard.SUITE_REL}/adjudication_rubric_v2.md",
"rubric_sha256": guard.V2_RUBRIC_SHA256,
"rubric_precommitted": True,
"blinded_to": [],
"overrides": [],
"raw_published": True,
"resolution_direction": "flags_only",
"resolution_rule_ref": "adjudication_rubric_v2.md#resolution",
},
"attempts": {
"atomicity": "Synthetic fixture only; no dispatch occurred.",
"partial_published": False,
"blocked_runs": [],
},
"raw_outputs": {"retained": True, "paths": raw_paths},
"preregistration": {
"plan_ref": plan_binding["ref"],
"plan_sha256": plan_binding["sha256"],
"rubric_ref": f"{guard.SUITE_REL}/adjudication_rubric_v2.md",
"rubric_sha256": guard.V2_RUBRIC_SHA256,
"frozen_commit": commit,
"frozen_before_dispatch": True,
"rubric_and_plan_frozen_together": True,
"judge_template_version": "synthetic-v1",
"amendments_append_only": True,
"amendments": [],
},
"execution_manifest": {
"ref": manifest_ref,
"sha256": _digest(manifest_path),
"write_once": True,
"claims": ["same_window", "ordering"],
},
"results": {
"design": "prospective synthetic contract replay",
"arm_roles": {
"treatment_or_cohort_arms": ["single"],
"variant_packet_arms": [],
},
"revision_claim_drift_v2": {
"rubric_version": "2.0",
"claim_strength_headline_criteria": ["C1", "C2"],
"deterministic_token_criterion": "C6",
"arm_ids": ["single"],
"citation_attachment": {
"criterion": "C9",
"scope": "all_items_including_non_controls",
"reported_separately": True,
"evaluated_item_replicates": sorted(
subject["item_replicate_id"] for subject in subject_specs
),
"control_item_replicates": sorted(
subject["item_replicate_id"]
for subject in subject_specs
if subject["control_item"]
),
"finding_count": 0,
"decisions": [],
"findings": [],
},
},
"subject_context_claim": guard.STATUS_CLAIMS[status],
},
"verdict": "Synthetic prospective row; no result was measured.",
"caveats": [
"Flags-only adjudication means any headline is a lower bound; this "
"prospective synthetic replay measured no result."
],
}
for plan_judge in plan_judges:
row["judges"].append(
{
"judge_id": plan_judge["judge_id"],
"model_id": plan_judge["model_id"],
"model_family": plan_judge["model_family"],
"prompt_ref": plan_judge["prompt_template"]["ref"],
"evidence_provided": "synthetic contract fixture only",
"judging_budget": "zero live calls; fixture replay only",
"blinded_to": plan_judge["blinded_to"],
"per_item": [
{
"item_id": subject["item_replicate_id"],
"flag": False,
"citation_attachment": {"criterion": "C9", "flag": False},
}
for subject in subject_specs
],
}
)
return root, row, context_path, manifest_path
def _sync_context(row: dict[str, Any], context_path: Path) -> None:
row["subject"]["config"]["subject_context"]["sha256"] = _digest(context_path)
def _sync_manifest(row: dict[str, Any], manifest_path: Path) -> None:
row["execution_manifest"]["sha256"] = _digest(manifest_path)
def _bound_path(root: Path, row: dict[str, Any], key: str) -> Path:
return root / row["subject"]["config"][key]["ref"]
def _rewrite_plan(root: Path, row: dict[str, Any], plan: dict[str, Any]) -> Path:
plan_path = _bound_path(root, row, "subject_call_plan")
_write_json(plan_path, plan)
digest = _digest(plan_path)
row["subject"]["config"]["subject_call_plan"]["sha256"] = digest
row["preregistration"]["plan_ref"] = row["subject"]["config"][
"subject_call_plan"
]["ref"]
row["preregistration"]["plan_sha256"] = digest
context_path = root / row["subject"]["config"]["subject_context"]["ref"]
context = _read_json(context_path)
context["subject_call_plan"]["sha256"] = digest
_write_json(context_path, context)
_sync_context(row, context_path)
return plan_path
def _rewrite_launcher(
root: Path, row: dict[str, Any], launcher: dict[str, Any]
) -> Path:
launcher_path = _bound_path(root, row, "launcher_config")
_write_json(launcher_path, launcher)
binding = {
"ref": row["subject"]["config"]["launcher_config"]["ref"],
"sha256": _digest(launcher_path),
}
row["subject"]["config"]["launcher_config"] = copy.deepcopy(binding)
plan_path = _bound_path(root, row, "subject_call_plan")
plan = _read_json(plan_path)
plan["launcher_config"] = copy.deepcopy(binding)
_write_json(plan_path, plan)
plan_digest = _digest(plan_path)
row["subject"]["config"]["subject_call_plan"]["sha256"] = plan_digest
row["preregistration"]["plan_ref"] = row["subject"]["config"][
"subject_call_plan"
]["ref"]
row["preregistration"]["plan_sha256"] = plan_digest
context_path = root / row["subject"]["config"]["subject_context"]["ref"]
context = _read_json(context_path)
context["cli"]["launcher_config"] = copy.deepcopy(binding)
context["subject_call_plan"]["sha256"] = plan_digest
_write_json(context_path, context)
_sync_context(row, context_path)
return launcher_path
def _add_c9_finding(root: Path, row: dict[str, Any]) -> Path:
run_prefix = Path(row["subject"]["config"]["subject_context"]["ref"]).parent
finding_id = "C9-F-001"
item_replicate_id = "rp-01.single.r1"
evidence_ref = (run_prefix / "c9" / f"{finding_id}.json").as_posix()
evidence_path = root / evidence_ref
attachment_contents = {
"citation_tokens": "[Smith, 2024]\n",
"original_attachment": "Original proposition <- [Smith, 2024]\n",
"revised_attachment": "Different proposition <- [Smith, 2024]\n",
}
attachment_suffixes = {
"citation_tokens": "citation-tokens.txt",
"original_attachment": "original-attachment.txt",
"revised_attachment": "revised-attachment.txt",
}
bindings: dict[str, dict[str, str]] = {}
for field, content in attachment_contents.items():
ref = (run_prefix / "c9" / f"{finding_id}.{attachment_suffixes[field]}").as_posix()
path = root / ref
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(content, encoding="utf-8")
bindings[field] = {"ref": ref, "sha256": _digest(path)}
_write_json(
evidence_path,
{
"schema_version": "revision-claim-drift-c9-evidence/1.0",
"suite": "revision_claim_drift",
"run_id": run_prefix.name,
"finding_id": finding_id,
"item_replicate_id": item_replicate_id,
"criterion": "C9",
"raw_flags": [{"judge_id": "judge-a", "criterion": "C9", "flag": True}],
"adjudication": {
"disposition": "confirmed",
"authorization": "unauthorized_under_C3",
},
"citation_tokens": bindings["citation_tokens"],
"original_attachment": bindings["original_attachment"],
"revised_attachment": bindings["revised_attachment"],
"data_minimization": {"raw_rationale_stored": False},
},
)
layer = row["results"]["revision_claim_drift_v2"]["citation_attachment"]
layer["finding_count"] = 1
layer["decisions"] = [
{
"decision_id": f"c9d.judge-a.{item_replicate_id}",
"judge_id": "judge-a",
"item_replicate_id": item_replicate_id,
"raw_flag": True,
"disposition": "confirmed",
"criterion_ref": "C9",
"reason_code": "violation_confirmed",
"finding_id": finding_id,
}
]
layer["findings"] = [
{
"finding_id": finding_id,
"item_replicate_id": item_replicate_id,
"criterion": "C9",
"authorization": "unauthorized_under_C3",
"citation_tokens_ref": bindings["citation_tokens"]["ref"],
"citation_tokens_sha256": bindings["citation_tokens"]["sha256"],
"original_attachment_ref": bindings["original_attachment"]["ref"],
"original_attachment_sha256": bindings["original_attachment"]["sha256"],
"revised_attachment_ref": bindings["revised_attachment"]["ref"],
"revised_attachment_sha256": bindings["revised_attachment"]["sha256"],
"evidence_ref": evidence_ref,
"evidence_sha256": _digest(evidence_path),
"raw_flag_judge_ids": ["judge-a"],
"adjudication_disposition": "confirmed",
}
]
row["judges"][0]["per_item"][0]["citation_attachment"]["flag"] = True
row["raw_outputs"]["paths"].extend(
[binding["ref"] for binding in bindings.values()] + [evidence_ref]
)
return evidence_path
@pytest.mark.parametrize(
"status", ["machine_supported", "attested_only", "not_isolated", "unknown"]
)
def test_future_1_1_context_replay_accepts_supported_statuses(
tmp_path: Path, status: str
) -> None:
root, row, _, _ = _future_bundle(tmp_path, status)
errors, warnings = validate_report(row)
assert errors == []
assert warnings == []
guard.validate_prospective_measurement(row, root)
@pytest.mark.parametrize(
("mutation", "expected"),
[
("missing", "missing subject.config.subject_context"),
("ref", "must end in subject-context.json"),
("hash", "row hash mismatch"),
("status", "binding status differs"),
("raw_path", "appear verbatim in raw_outputs.paths"),
],
)
def test_future_context_binding_fails_closed(
tmp_path: Path, mutation: str, expected: str
) -> None:
root, row, context_path, _ = _future_bundle(tmp_path)
binding = row["subject"]["config"]["subject_context"]
if mutation == "missing":
del row["subject"]["config"]["subject_context"]
elif mutation == "ref":
bad_ref = context_path.with_name("context.json").relative_to(root).as_posix()
shutil.copyfile(context_path, root / bad_ref)
binding["ref"] = bad_ref
binding["sha256"] = _digest(root / bad_ref)
row["raw_outputs"]["paths"] = [bad_ref]
elif mutation == "hash":
binding["sha256"] = "0" * 64
elif mutation == "status":
binding["status"] = "unknown"
else:
row["raw_outputs"]["paths"] = []
with pytest.raises(guard.ContractError, match=expected):
guard.validate_prospective_measurement(row, root)
def test_future_context_run_id_must_match_parent_path(tmp_path: Path) -> None:
root, row, context_path, _ = _future_bundle(tmp_path)
context = _read_json(context_path)
context["run_id"] = "different-window"
_write_json(context_path, context)
_sync_context(row, context_path)
with pytest.raises(guard.ContractError, match="not the canonical run sibling"):
guard.validate_prospective_measurement(row, root)
@pytest.mark.parametrize("mutation", ["ref", "hash"])
def test_future_manifest_ref_and_hash_are_bound(
tmp_path: Path, mutation: str
) -> None:
root, row, _, _ = _future_bundle(tmp_path)
if mutation == "ref":
row["execution_manifest"]["ref"] = (
f"{guard.RUNS_PREFIX}other-run/execution-manifest.json"
)
expected = "row/context execution_manifest ref mismatch"
else:
row["execution_manifest"]["sha256"] = "0" * 64
expected = "execution-manifest hash mismatch"
with pytest.raises(guard.ContractError, match=expected):
guard.validate_prospective_measurement(row, root)
def test_context_record_must_precede_first_scored_call(tmp_path: Path) -> None:
root, row, context_path, _ = _future_bundle(tmp_path)
context = _read_json(context_path)
context["recorded_at"] = "2026-08-10T01:01:02Z"
_write_json(context_path, context)
_sync_context(row, context_path)
with pytest.raises(guard.ContractError, match="gate was not sealed before"):
guard.validate_prospective_measurement(row, root)
def test_probe_completion_cannot_follow_context_record(tmp_path: Path) -> None:
root, row, context_path, _ = _future_bundle(tmp_path)
context = _read_json(context_path)
context["context_probe"]["completed_at"] = "2026-08-10T01:00:05Z"
_write_json(context_path, context)
_sync_context(row, context_path)
with pytest.raises(guard.ContractError, match="probe timestamps"):
guard.validate_prospective_measurement(row, root)
def test_attestation_cannot_precede_completed_probe(tmp_path: Path) -> None:
root, row, context_path, _ = _future_bundle(tmp_path, "attested_only")
context = _read_json(context_path)
context["attestation"]["attested_at"] = "2026-08-10T00:59:59Z"
_write_json(context_path, context)
_sync_context(row, context_path)
with pytest.raises(guard.ContractError, match="attestation precedes"):
guard.validate_prospective_measurement(row, root)
@pytest.mark.parametrize("mutation", ["row_suite", "config_commit", "frozen_commit"])
def test_future_suite_and_frozen_commit_bindings(
tmp_path: Path, mutation: str
) -> None:
root, row, _, _ = _future_bundle(tmp_path)
if mutation == "row_suite":
row["suite"] = "other_suite"
expected = "row suite mismatch"
elif mutation == "config_commit":
row["subject"]["config"]["suite_commit"] = "b" * 40
expected = "suite_commit differs"
else:
row["preregistration"]["frozen_commit"] = "b" * 40
expected = "frozen_commit differs"
with pytest.raises(guard.ContractError, match=expected):
guard.validate_prospective_measurement(row, root)
def test_launcher_config_row_hash_replays_exactly(tmp_path: Path) -> None:
root, row, _, _ = _future_bundle(tmp_path)
row["subject"]["config"]["launcher_config"]["sha256"] = "9" * 64
with pytest.raises(guard.ContractError, match="launcher_config binding mismatch"):
guard.validate_prospective_measurement(row, root)
def test_future_adjudication_is_flags_only(tmp_path: Path) -> None:
root, row, _, _ = _future_bundle(tmp_path)
row["adjudication"]["resolution_direction"] = "bidirectional"
with pytest.raises(guard.ContractError, match="must be flags_only"):
guard.validate_prospective_measurement(row, root)
@pytest.mark.parametrize("mutation", ["criterion", "scope", "separation", "extra"])
def test_future_c9_result_layer_is_closed_and_separate(
tmp_path: Path, mutation: str
) -> None:
root, row, _, _ = _future_bundle(tmp_path)
layer = row["results"]["revision_claim_drift_v2"]["citation_attachment"]
if mutation == "criterion":
layer["criterion"] = "C8"
elif mutation == "scope":
layer["scope"] = "non_controls_only"
elif mutation == "separation":
layer["reported_separately"] = False
else:
layer["affects_headline"] = True
with pytest.raises(guard.ContractError, match="closed separate C9"):
guard.validate_prospective_measurement(row, root)
def test_c9_cannot_be_smuggled_into_the_generic_aggregate_headline(
tmp_path: Path,
) -> None:
root, row, _, _ = _future_bundle(tmp_path)
row["aggregate"]["headline"]["metric_name"] = "C9_citation_attachment_rate"
row["aggregate"]["headline"]["construction_rule"] = (
"C9 citation-attachment flags-only adjudication defines this lower bound."
)
with pytest.raises(guard.ContractError):
guard.validate_prospective_measurement(row, root)
@pytest.mark.parametrize(
"wording",
[
"The subject ran in a clean context.",
"The subject was fully isolated.",
"Prompt isolation was verified.",
"Repository-instruction isolation was achieved.",
],
)
def test_unqualified_isolation_language_is_rejected(
tmp_path: Path, wording: str
) -> None:
root, row, _, _ = _future_bundle(tmp_path)
row["verdict"] = wording
with pytest.raises(
guard.ContractError,
match="isolation wording is allowed only",
):
guard.validate_prospective_measurement(row, root)
def test_status_claim_ceiling_is_exact_for_unknown_status(tmp_path: Path) -> None:
root, row, _, _ = _future_bundle(tmp_path, "unknown")
row["results"]["subject_context_claim"] = (
"repository-instruction isolated (machine-supported)"
)
with pytest.raises(guard.ContractError, match="exact status-derived ceiling"):
guard.validate_prospective_measurement(row, root)
@pytest.mark.parametrize(
"claim_key",
["repository_instruction_isolation_confirmed", "clean_context"],
)
def test_typed_boolean_keys_cannot_publish_an_unsupported_isolation_claim(
tmp_path: Path, claim_key: str
) -> None:
root, row, _, _ = _future_bundle(tmp_path, "unknown")
row["results"][claim_key] = True
with pytest.raises(guard.ContractError, match="claim-shaped isolation keys are forbidden"):
guard.validate_prospective_measurement(row, root)
def test_manifest_window_id_must_equal_context_run_id(tmp_path: Path) -> None:
root, row, _, manifest_path = _future_bundle(tmp_path)
manifest = _read_json(manifest_path)
manifest["execution_window"]["window_id"] = "different-window"
_write_json(manifest_path, manifest)
_sync_manifest(row, manifest_path)
with pytest.raises(guard.ContractError, match="window ID.*run_id mismatch"):
guard.validate_prospective_measurement(row, root)
@pytest.mark.parametrize(
("schema_name", "expected"),
[
("subject_launcher_config.schema.json", "launcher-config schema byte identity"),
("subject_call_plan.schema.json", "call-plan schema byte identity"),
],
)
def test_prospective_companion_schema_hashes_are_load_bearing(
tmp_path: Path, schema_name: str, expected: str
) -> None:
root = _mirror(tmp_path)
path = root / SUITE / schema_name
path.write_bytes(path.read_bytes() + b"\n")
with pytest.raises(guard.ContractError, match=expected):
guard._check_subject_assets(root)
@pytest.mark.parametrize(
("mutation", "expected"),
[
("missing", "subject.config: keys mismatch"),
("ref", "launcher_config binding mismatch"),
("hash", "launcher-config artifact hash mismatch"),
("raw", "launcher-config ref must appear"),
("schema", "launcher-config artifact fails schema"),
("provenance", "working-directory policy mismatch"),
("invocation_hash", "launcher-config invocation hash mismatch"),
],
)
def test_launcher_config_binding_is_canonical_and_replayed(
tmp_path: Path, mutation: str, expected: str
) -> None:
root, row, _, _ = _future_bundle(tmp_path)
config = row["subject"]["config"]
launcher_path = _bound_path(root, row, "launcher_config")
if mutation == "missing":
del config["launcher_config"]
elif mutation == "ref":
config["launcher_config"]["ref"] = (
f"{guard.RUNS_PREFIX}other/launcher-config.json"
)
elif mutation == "hash":
launcher_path.write_bytes(launcher_path.read_bytes() + b"\n")
elif mutation == "raw":
row["raw_outputs"]["paths"].remove(config["launcher_config"]["ref"])
else:
launcher = _read_json(launcher_path)
if mutation == "schema":
launcher["raw_command"] = "forbidden"
elif mutation == "invocation_hash":
launcher["invocation_sha256"] = "0" * 64
else:
launcher["working_directory"]["policy"] = "inside_suite_repository"
launcher["invocation_sha256"] = _canonical_object_digest(
{
key: launcher[key]
for key in (
"launcher",
"working_directory",
"instruction_loading",
)
}
)
_rewrite_launcher(root, row, launcher)
with pytest.raises(guard.ContractError, match=expected):
guard.validate_prospective_measurement(row, root)
@pytest.mark.parametrize(
("mutation", "expected"),
[
("missing", "subject.config: keys mismatch"),
("ref", "subject_call_plan binding mismatch"),
("hash", "subject-call-plan artifact hash mismatch"),
("raw", "subject-call-plan ref must appear"),
("schema", "subject-call-plan artifact fails schema"),
("provenance", "provenance differs"),
("subject_model", "provenance differs"),
("late", "not frozen before probe/preflight"),
],
)
def test_subject_call_plan_binding_is_canonical_and_precommitted(
tmp_path: Path, mutation: str, expected: str
) -> None:
root, row, _, _ = _future_bundle(tmp_path)
config = row["subject"]["config"]
plan_path = _bound_path(root, row, "subject_call_plan")
if mutation == "missing":
del config["subject_call_plan"]
elif mutation == "ref":
config["subject_call_plan"]["ref"] = (
f"{guard.RUNS_PREFIX}other/subject-call-plan.json"
)
elif mutation == "hash":
plan_path.write_bytes(plan_path.read_bytes() + b"\n")
elif mutation == "raw":
row["raw_outputs"]["paths"].remove(config["subject_call_plan"]["ref"])
else:
plan = _read_json(plan_path)
if mutation == "schema":
plan["uncommitted_calls"] = []
elif mutation == "provenance":
plan["suite_commit"] = "b" * 40
elif mutation == "subject_model":
plan["subject_model_id"] = "different/subject"
else:
plan["created_at"] = "2026-08-10T01:00:05Z"
_rewrite_plan(root, row, plan)
with pytest.raises(guard.ContractError, match=expected):
guard.validate_prospective_measurement(row, root)
@pytest.mark.parametrize(
("mutation", "expected"),
[
("missing_sealed", "launcher-config artifact fails schema"),
("sealed_after_probe", "not sealed before probe/preflight"),
("sealed_after_plan", "not sealed before the subject-call plan"),
],
)
def test_launcher_and_call_plan_are_sealed_in_preflight_order(
tmp_path: Path, mutation: str, expected: str
) -> None:
root, row, _, _ = _future_bundle(tmp_path)
launcher = _read_json(_bound_path(root, row, "launcher_config"))
if mutation == "missing_sealed":
del launcher["sealed_at"]
elif mutation == "sealed_after_probe":
launcher["sealed_at"] = "2026-08-10T01:00:01Z"
else:
launcher["sealed_at"] = "2026-08-10T00:59:55Z"
_rewrite_launcher(root, row, launcher)
with pytest.raises(guard.ContractError, match=expected):
guard.validate_prospective_measurement(row, root)
@pytest.mark.parametrize(
("mutation", "expected"),
[
("plan_order", "same 2..8 sorted judges"),
("row_identity", "published judge identity/template differs"),
("template_ref", "prompt-template ref is not canonical"),
("template_hash", "prompt-template hash mismatch"),
("template_raw", "prompt-template ref must appear"),
("closed_schema", "subject-call-plan artifact fails schema"),
("one_family", "at least two judge model families"),
("decision_relevant", "decision-relevant with no judge exception"),
("judge_exception", "decision-relevant with no judge exception"),
],
)
def test_judge_roster_identity_templates_and_decision_authority_are_frozen(
tmp_path: Path, mutation: str, expected: str
) -> None:
root, row, _, _ = _future_bundle(tmp_path)
plan = _read_json(_bound_path(root, row, "subject_call_plan"))
template_ref = plan["judges"][0]["prompt_template"]["ref"]
if mutation == "plan_order":
plan["judges"].reverse()
_rewrite_plan(root, row, plan)
elif mutation == "row_identity":
row["judges"][0]["model_id"] = "different/model"
elif mutation == "template_ref":
alternate_ref = template_ref.replace(".template.txt", ".alternate.txt")
alternate_path = root / alternate_ref
alternate_path.write_text("alternate frozen template\n", encoding="utf-8")
row["raw_outputs"]["paths"].append(alternate_ref)
plan["judges"][0]["prompt_template"] = {
"ref": alternate_ref,
"sha256": _digest(alternate_path),
}
row["judges"][0]["prompt_ref"] = alternate_ref
_rewrite_plan(root, row, plan)
elif mutation == "template_hash":
(root / template_ref).write_bytes((root / template_ref).read_bytes() + b"drift")
elif mutation == "template_raw":
row["raw_outputs"]["paths"].remove(template_ref)
elif mutation == "closed_schema":
plan["judges"][0]["unfrozen_prompt"] = True
_rewrite_plan(root, row, plan)
elif mutation == "one_family":
row["judges"][1]["model_family"] = row["judges"][0]["model_family"]
plan["judges"][1]["model_family"] = plan["judges"][0]["model_family"]
_rewrite_plan(root, row, plan)
elif mutation == "decision_relevant":
row["decision_relevant"] = False
else:
row["judge_plan"] = {
"exception": "legacy_comparability",
"legacy_baseline_ref": "measurement-historical.json",
}
with pytest.raises(guard.ContractError, match=expected):
guard.validate_prospective_measurement(row, root)
@pytest.mark.parametrize("field", ["plan_ref", "plan_sha256"])
def test_preregistration_exactly_binds_the_subject_call_plan(
tmp_path: Path, field: str
) -> None:
root, row, _, _ = _future_bundle(tmp_path)
row["preregistration"][field] = "0" * 64 if field.endswith("sha256") else "other"
with pytest.raises(guard.ContractError, match="preregistration plan binding differs"):
guard.validate_prospective_measurement(row, root)
@pytest.mark.parametrize(
("mutation", "expected"),
[
("delete_judge", "exactly cover judge x item-replicate"),
("reindex", "sequence indexes must be ordered"),
("role", "exactly cover item x arm x replicate"),
("judge_item", "judge-call roster differs from the frozen run design"),
],
)
def test_call_plan_call_roster_role_and_index_are_load_bearing(
tmp_path: Path, mutation: str, expected: str
) -> None:
root, row, _, _ = _future_bundle(tmp_path)
plan_path = _bound_path(root, row, "subject_call_plan")
plan = _read_json(plan_path)
if mutation == "delete_judge":
plan["calls"].pop()
elif mutation == "reindex":
plan["calls"][1]["sequence_index"] = 3
plan["calls"][2]["sequence_index"] = 2
elif mutation == "role":
call = plan["calls"][0]
subject_prompt_ref = call["prompt"]["ref"]
call["role"] = "judge"
call["judge_id"] = "judge-a"
call["prompt"] = {
"ref": subject_prompt_ref,
"sha256": None,
"composition": "template_then_subject_input_then_subject_output/1.0",
"template_ref": plan["judges"][0]["prompt_template"]["ref"],
"subject_prompt_ref": subject_prompt_ref,
"subject_output_ref": call["output_ref"],
}
else:
plan["calls"][16]["item_id"] = "other-item"
_rewrite_plan(root, row, plan)
with pytest.raises(guard.ContractError, match=expected):
guard.validate_prospective_measurement(row, root)
@pytest.mark.parametrize(
("mutation", "expected"),
[
("prompt_drift", "subject prompt hash mismatch"),
("prompt_missing", "missing/unreadable path"),
("output_drift", "output hash differs"),
("output_missing", "missing/unreadable path"),
("prompt_raw", "planned prompt/output ref must be retained raw"),
("output_raw", "planned prompt/output ref must be retained raw"),
],
)
def test_every_call_prompt_and_output_is_hashed_and_retained(
tmp_path: Path, mutation: str, expected: str
) -> None:
root, row, _, _ = _future_bundle(tmp_path)
plan = _read_json(_bound_path(root, row, "subject_call_plan"))
prompt_ref = plan["calls"][0]["prompt"]["ref"]
output_ref = plan["calls"][-1 if mutation == "output_drift" else 0]["output_ref"]
if mutation == "prompt_drift":
(root / prompt_ref).write_bytes((root / prompt_ref).read_bytes() + b"drift")
elif mutation == "prompt_missing":
(root / prompt_ref).unlink()
elif mutation == "output_drift":
(root / output_ref).write_bytes((root / output_ref).read_bytes() + b"drift")
elif mutation == "output_missing":
(root / output_ref).unlink()
elif mutation == "prompt_raw":
row["raw_outputs"]["paths"].remove(prompt_ref)
else:
row["raw_outputs"]["paths"].remove(output_ref)
with pytest.raises(guard.ContractError, match=expected):
guard.validate_prospective_measurement(row, root)
@pytest.mark.parametrize(
("mutation", "expected"),
[
("composition", "subject-call-plan artifact fails schema"),
("template_ref", "judge prompt dependency differs"),
("subject_prompt_ref", "judge prompt dependency differs"),
("subject_output_ref", "judge prompt dependency differs"),
("materialized_hash", "subject-call-plan artifact fails schema"),
("bytes", "judge prompt bytes do not replay"),
],
)
def test_judge_prompts_are_deferred_and_replay_frozen_dependencies(
tmp_path: Path, mutation: str, expected: str
) -> None:
root, row, _, _ = _future_bundle(tmp_path)
plan = _read_json(_bound_path(root, row, "subject_call_plan"))
judge_call = plan["calls"][16]
if mutation == "composition":
judge_call["prompt"]["composition"] = "free_form/1.0"
_rewrite_plan(root, row, plan)
elif mutation == "template_ref":
judge_call["prompt"]["template_ref"] = plan["judges"][1][
"prompt_template"
]["ref"]
_rewrite_plan(root, row, plan)
elif mutation == "subject_prompt_ref":
judge_call["prompt"]["subject_prompt_ref"] = plan["calls"][1]["prompt"][
"ref"
]
_rewrite_plan(root, row, plan)
elif mutation == "subject_output_ref":
judge_call["prompt"]["subject_output_ref"] = plan["calls"][1]["output_ref"]
_rewrite_plan(root, row, plan)
elif mutation == "materialized_hash":
judge_call["prompt"]["sha256"] = "0" * 64
_rewrite_plan(root, row, plan)
else:
prompt_path = root / judge_call["prompt"]["ref"]
prompt_path.write_bytes(prompt_path.read_bytes() + b"drift")
with pytest.raises(guard.ContractError, match=expected):
guard.validate_prospective_measurement(row, root)
@pytest.mark.parametrize(
("mutation", "expected"),
[
("delete", "does not exactly cover the call plan"),
("reindex", "sequence indexes must be ordered"),
("call_id", "differs from precommitted call plan"),
("prompt_hash", "differs from precommitted call plan"),
],
)
def test_execution_manifest_exactly_joins_the_call_plan(
tmp_path: Path, mutation: str, expected: str
) -> None:
root, row, _, manifest_path = _future_bundle(tmp_path)
manifest = _read_json(manifest_path)
if mutation == "delete":
manifest["calls"].pop()
elif mutation == "reindex":
manifest["calls"][1]["sequence_index"] = 3
manifest["calls"][2]["sequence_index"] = 2
elif mutation == "call_id":
manifest["calls"][0]["call_id"] = "judge-disguised-as-subject"
else:
manifest["calls"][0]["prompt_sha256"] = "9" * 64
_write_json(manifest_path, manifest)
_sync_manifest(row, manifest_path)
with pytest.raises(guard.ContractError, match=expected):
guard.validate_prospective_measurement(row, root)
@pytest.mark.parametrize(
("mutation", "expected"),
[
("created_before_call", "recorded before its final call completed"),
("created_before_window_end", "recorded before its window ended"),
("window_excludes_call", "execution window does not contain all calls"),
],
)
def test_manifest_created_and_window_ordering_is_load_bearing(
tmp_path: Path, mutation: str, expected: str
) -> None:
root, row, _, manifest_path = _future_bundle(tmp_path)
manifest = _read_json(manifest_path)
if mutation == "created_before_call":
manifest["created_at"] = "2026-08-10T01:01:40Z"
elif mutation == "created_before_window_end":
manifest["execution_window"]["completed_at"] = "2026-08-10T01:04:00Z"
else:
manifest["execution_window"]["started_at"] = "2026-08-10T01:01:05Z"
_write_json(manifest_path, manifest)
_sync_manifest(row, manifest_path)
with pytest.raises(guard.ContractError, match=expected):
guard.validate_prospective_measurement(row, root)
def test_context_gate_precedes_every_scored_call_even_without_ordering_claim(
tmp_path: Path,
) -> None:
root, row, _, manifest_path = _future_bundle(tmp_path)
manifest = _read_json(manifest_path)
manifest["execution_window"]["started_at"] = "2026-08-10T00:59:00Z"
manifest["calls"][16]["started_at"] = "2026-08-10T00:59:10Z"
manifest["calls"][16]["completed_at"] = "2026-08-10T00:59:20Z"
row["execution_manifest"]["claims"].remove("ordering")
_write_json(manifest_path, manifest)
_sync_manifest(row, manifest_path)
with pytest.raises(guard.ContractError):
guard.validate_prospective_measurement(row, root)
@pytest.mark.parametrize(
("mutation", "expected"),
[
("plan_chronology", "not chronological by plan order"),
("judge_overlap", "judge calls began before the subject fleet completed"),
],
)
def test_manifest_call_times_follow_plan_order_and_separate_judge_dispatch(
tmp_path: Path, mutation: str, expected: str
) -> None:
root, row, _, manifest_path = _future_bundle(tmp_path)
manifest = _read_json(manifest_path)
if mutation == "plan_chronology":
manifest["calls"][1]["started_at"] = "2026-08-10T01:01:00.500Z"
manifest["calls"][1]["completed_at"] = "2026-08-10T01:01:00.600Z"
else:
manifest["calls"][16]["started_at"] = "2026-08-10T01:01:16.050Z"
manifest["calls"][16]["completed_at"] = "2026-08-10T01:01:16.150Z"
_write_json(manifest_path, manifest)
_sync_manifest(row, manifest_path)
with pytest.raises(guard.ContractError, match=expected):
guard.validate_prospective_measurement(row, root)
def test_c9_zero_finding_layer_still_covers_the_subject_roster(tmp_path: Path) -> None:
root, row, _, _ = _future_bundle(tmp_path)
layer = row["results"]["revision_claim_drift_v2"]["citation_attachment"]
assert len(layer["evaluated_item_replicates"]) == 16
assert layer["control_item_replicates"] == [
"rp-07.single.r1",
"rp-07.single.r2",
"rp-08.single.r1",
"rp-08.single.r2",
]
assert layer["finding_count"] == 0
assert layer["decisions"] == []
assert layer["findings"] == []
guard.validate_prospective_measurement(row, root)
@pytest.mark.parametrize("mutation", ["missing", "extra", "control"])
def test_c9_roster_is_derived_from_precommitted_subject_calls(
tmp_path: Path, mutation: str
) -> None:
root, row, _, _ = _future_bundle(tmp_path)
layer = row["results"]["revision_claim_drift_v2"]["citation_attachment"]
if mutation == "missing":
layer["evaluated_item_replicates"] = []
elif mutation == "extra":
layer["evaluated_item_replicates"].append("unplanned-r9")
else:
layer["control_item_replicates"] = ["rp-07.single.r1"]
with pytest.raises(guard.ContractError):
guard.validate_prospective_measurement(row, root)
def test_c9_well_formed_finding_with_bound_evidence_passes(tmp_path: Path) -> None:
root, row, _, _ = _future_bundle(tmp_path)
_add_c9_finding(root, row)
guard.validate_prospective_measurement(row, root)
@pytest.mark.parametrize(
("mutation", "expected"),
[
("item", "unevaluated item-replicate"),
("criterion", "finding authority"),
("authorization", "finding authority"),
("evidence_hash", "evidence artifact hash mismatch"),
("evidence_ref", "evidence_ref is not the canonical run-local path"),
("raw", "evidence_ref must appear"),
("count", "finding_count must equal"),
],
)
def test_c9_finding_authority_evidence_and_count_fail_closed(
tmp_path: Path, mutation: str, expected: str
) -> None:
root, row, _, _ = _future_bundle(tmp_path)
evidence_path = _add_c9_finding(root, row)
layer = row["results"]["revision_claim_drift_v2"]["citation_attachment"]
finding = layer["findings"][0]
if mutation == "item":
finding["item_replicate_id"] = "unplanned-r9"
elif mutation == "criterion":
finding["criterion"] = "C8"
elif mutation == "authorization":
finding["authorization"] = "authorized_under_C3"
elif mutation == "evidence_hash":
finding["evidence_sha256"] = "0" * 64
elif mutation == "evidence_ref":
finding["evidence_ref"] = "evals/heldout/other/evidence.json"
elif mutation == "raw":
row["raw_outputs"]["paths"].remove(evidence_path.relative_to(root).as_posix())
else:
layer["finding_count"] = 0
with pytest.raises(guard.ContractError, match=expected):
guard.validate_prospective_measurement(row, root)
def test_generic_non_c9_judge_flags_do_not_create_a_c9_finding(
tmp_path: Path,
) -> None:
root, row, _, _ = _future_bundle(tmp_path)
for judge in row["judges"]:
judge["per_item"][0]["flag"] = True
guard.validate_prospective_measurement(row, root)
def test_published_c9_finding_requires_typed_raw_flag_authority(
tmp_path: Path,
) -> None:
root, row, _, _ = _future_bundle(tmp_path)
_add_c9_finding(root, row)
finding = row["results"]["revision_claim_drift_v2"]["citation_attachment"][
"findings"
][0]
del finding["raw_flag_judge_ids"]
with pytest.raises(guard.ContractError):
guard.validate_prospective_measurement(row, root)
def test_generic_true_flag_cannot_override_typed_false_c9_verdict(
tmp_path: Path,
) -> None:
root, row, _, _ = _future_bundle(tmp_path)
_add_c9_finding(root, row)
raw_row = row["judges"][0]["per_item"][0]
raw_row["flag"] = True
raw_row["citation_attachment"]["flag"] = False
with pytest.raises(guard.ContractError, match="exactly resolve every typed raw true flag"):
guard.validate_prospective_measurement(row, root)
def test_typed_raw_true_c9_flag_cannot_be_omitted_from_decisions(
tmp_path: Path,
) -> None:
root, row, _, _ = _future_bundle(tmp_path)
row["judges"][0]["per_item"][0]["citation_attachment"]["flag"] = True
with pytest.raises(guard.ContractError, match="exactly resolve every typed raw true"):
guard.validate_prospective_measurement(row, root)
def test_c5_cannot_reject_a_noncontrol_c9_raw_flag(tmp_path: Path) -> None:
root, row, _, _ = _future_bundle(tmp_path)
item_replicate_id = "rp-01.single.r1"
row["judges"][0]["per_item"][0]["citation_attachment"]["flag"] = True
layer = row["results"]["revision_claim_drift_v2"]["citation_attachment"]
layer["decisions"] = [
{
"decision_id": f"c9d.judge-a.{item_replicate_id}",
"judge_id": "judge-a",
"item_replicate_id": item_replicate_id,
"raw_flag": True,
"disposition": "rejected",
"criterion_ref": "C5",
"reason_code": "authorized_under_C3",
"finding_id": None,
}
]
with pytest.raises(guard.ContractError, match="invalid closed reason"):
guard.validate_prospective_measurement(row, root)
def test_generic_override_cannot_route_c9_or_citation_attachment(
tmp_path: Path,
) -> None:
root, row, _, _ = _future_bundle(tmp_path)
row["adjudication"]["overrides"] = [
{
"item_id": "rp-01.single.r1",
"judge_id": "judge-a",
"raw": "citation_attachment C9=true",
"adjudicated": "citation_attachment=false",
"criterion_ref": "C5",
}
]
with pytest.raises(guard.ContractError, match="cannot be routed through generic overrides"):
guard.validate_prospective_measurement(row, root)
@pytest.mark.parametrize("mutation", ["missing_finding", "wrong_reason", "wrong_id"])
def test_confirmed_c9_decision_mapping_is_closed(
tmp_path: Path, mutation: str
) -> None:
root, row, _, _ = _future_bundle(tmp_path)
_add_c9_finding(root, row)
layer = row["results"]["revision_claim_drift_v2"]["citation_attachment"]
decision = layer["decisions"][0]
if mutation == "missing_finding":
decision["finding_id"] = "C9-F-missing"
elif mutation == "wrong_reason":
decision["reason_code"] = "authorized_under_C3"
else:
decision["decision_id"] = "noncanonical"
with pytest.raises(guard.ContractError):
guard.validate_prospective_measurement(row, root)
def test_confirmed_c9_decision_cannot_be_relabelled_to_another_item(
tmp_path: Path,
) -> None:
root, row, _, _ = _future_bundle(tmp_path)
evidence_path = _add_c9_finding(root, row)
item_b = "rp-01.single.r2"
row["judges"][0]["per_item"][1]["citation_attachment"]["flag"] = True
layer = row["results"]["revision_claim_drift_v2"]["citation_attachment"]
layer["decisions"].append(
{
"decision_id": f"c9d.judge-a.{item_b}",
"judge_id": "judge-a",
"item_replicate_id": item_b,
"raw_flag": True,
"disposition": "rejected",
"criterion_ref": "C9",
"reason_code": "no_citation_attachment_violation",
"finding_id": None,
}
)
finding = layer["findings"][0]
finding["item_replicate_id"] = item_b
evidence = _read_json(evidence_path)
evidence["item_replicate_id"] = item_b
_write_json(evidence_path, evidence)
finding["evidence_sha256"] = _digest(evidence_path)
with pytest.raises(guard.ContractError, match="confirmed decisions"):
guard.validate_prospective_measurement(row, root)
def test_report_replicate_count_must_match_precommitted_subject_roster(
tmp_path: Path,
) -> None:
root, row, _, _ = _future_bundle(tmp_path)
row["replicates"]["per_item"] = 3
with pytest.raises(guard.ContractError):
guard.validate_prospective_measurement(row, root)
def test_extra_unjudged_subject_call_is_rejected(tmp_path: Path) -> None:
root, row, _, _ = _future_bundle(tmp_path)
plan = _read_json(_bound_path(root, row, "subject_call_plan"))
extra = copy.deepcopy(plan["calls"][0])
extra["call_id"] = "subject-unjudged-002"
extra["sequence_index"] = 4
extra["item_id"] = "item-002"
extra["item_replicate_id"] = "item-002-r1"
extra["prompt"] = copy.deepcopy(plan["calls"][1]["prompt"])
extra["output_ref"] = plan["calls"][1]["output_ref"]
plan["calls"].append(extra)
_rewrite_plan(root, row, plan)
with pytest.raises(guard.ContractError):
guard.validate_prospective_measurement(row, root)
def test_c9_evidence_cannot_alias_an_arbitrary_retained_artifact(tmp_path: Path) -> None:
root, row, _, _ = _future_bundle(tmp_path)
_add_c9_finding(root, row)
finding = row["results"]["revision_claim_drift_v2"]["citation_attachment"][
"findings"
][0]
context_ref = row["subject"]["config"]["subject_context"]["ref"]
finding["evidence_ref"] = context_ref
finding["evidence_sha256"] = _digest(root / context_ref)
with pytest.raises(guard.ContractError):
guard.validate_prospective_measurement(row, root)
def test_c9_evidence_cannot_use_unreplayed_placeholder_hashes(tmp_path: Path) -> None:
root, row, _, _ = _future_bundle(tmp_path)
_add_c9_finding(root, row)
finding = row["results"]["revision_claim_drift_v2"]["citation_attachment"][
"findings"
][0]
finding["citation_tokens_sha256"] = "0" * 64
finding["original_attachment_sha256"] = "0" * 64
finding["revised_attachment_sha256"] = "0" * 64
with pytest.raises(guard.ContractError):
guard.validate_prospective_measurement(row, root)
def test_boolean_finding_count_is_not_an_integer_count(tmp_path: Path) -> None:
root, row, _, _ = _future_bundle(tmp_path)
_add_c9_finding(root, row)
row["results"]["revision_claim_drift_v2"]["citation_attachment"][
"finding_count"
] = True
with pytest.raises(guard.ContractError):
guard.validate_prospective_measurement(row, root)
@pytest.mark.parametrize(
("field", "mutation"),
[
(field, mutation)
for field in (
"citation_tokens",
"original_attachment",
"revised_attachment",
)
for mutation in ("ref", "hash", "raw", "empty")
],
)
def test_c9_attachment_artifacts_are_canonical_hashed_nonempty_and_retained(
tmp_path: Path, field: str, mutation: str
) -> None:
root, row, _, _ = _future_bundle(tmp_path)
_add_c9_finding(root, row)
finding = row["results"]["revision_claim_drift_v2"]["citation_attachment"][
"findings"
][0]
ref_key = f"{field}_ref"
hash_key = f"{field}_sha256"
ref = finding[ref_key]
if mutation == "ref":
finding[ref_key] = ref.replace(".txt", ".other.txt")
elif mutation == "hash":
finding[hash_key] = "0" * 64
elif mutation == "raw":
row["raw_outputs"]["paths"].remove(ref)
else:
(root / ref).write_bytes(b"")
finding[hash_key] = _digest(root / ref)
with pytest.raises(guard.ContractError):
guard.validate_prospective_measurement(row, root)
@pytest.mark.parametrize("mutation", ["missing", "overlap", "unsorted"])
def test_published_arm_roles_exactly_partition_call_plan_arms(
tmp_path: Path, mutation: str
) -> None:
root, row, _, _ = _future_bundle(tmp_path)
roles = row["results"]["arm_roles"]
if mutation == "missing":
roles["treatment_or_cohort_arms"] = []
elif mutation == "overlap":
roles["variant_packet_arms"] = ["single"]
else:
roles["treatment_or_cohort_arms"] = ["single", "alpha"]
roles["variant_packet_arms"] = []
with pytest.raises(guard.ContractError, match="arm roles do not exactly partition"):
guard.validate_prospective_measurement(row, root)
def test_call_plan_binds_exact_frozen_heldout_set(tmp_path: Path) -> None:
root, row, _, _ = _future_bundle(tmp_path)
plan = _read_json(_bound_path(root, row, "subject_call_plan"))
plan["heldout_set"]["sha256"] = "0" * 64
_rewrite_plan(root, row, plan)
with pytest.raises(guard.ContractError, match="bind the frozen held-out set"):
guard.validate_prospective_measurement(row, root)
@pytest.mark.parametrize("replicates", [1, 11, True])
def test_future_resource_grammar_bounds_replicates(
tmp_path: Path, replicates: int | bool
) -> None:
root, row, _, _ = _future_bundle(tmp_path)
row["replicates"]["per_item"] = replicates
with pytest.raises(
guard.ContractError,
match="2..10 replicates|not of type 'integer'",
):
guard.validate_prospective_measurement(row, root)
def test_future_resource_grammar_requires_at_least_two_judges(tmp_path: Path) -> None:
root, row, _, _ = _future_bundle(tmp_path)
row["judges"].pop()
with pytest.raises(guard.ContractError, match="same 2..8 sorted judges"):
guard.validate_prospective_measurement(row, root)