test: DFG Re20 provenance refuses dirty trees; versioned receipts

Per review of aceea70e: a dirty checkout is refused (HEAD would not
describe the code that runs, and a dirty-to-dirty edit would pass the
end-of-run check). On a clean tree HEAD pins the imported helpers too.
plan.json and result.json carry schema=2; schema-1 receipts (bare list,
e.g. evidence 70f6617) are documented. Adds a dirty-refusal test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Claude
2026-10-20 18:28:25 +00:00
co-authored by Claude Opus 5.5
parent 8064834c37
commit 2220cdd157
3 changed files with 22 additions and 5 deletions
+3
View File
@@ -46,4 +46,7 @@ CUDA_VISIBLE_DEVICES=<n> python tests/validation/run_dfg_re20.py --case both \
The uniform D80 control separates method error from refinement error. `--plan` writes `plan.json` only. A GPU run writes `plan.json` and `result.json`
(per case: lattice parameters, every sample, window ranges, status, verdict).
Receipts: `plan.json` and `result.json` carry `schema` 2 and `provenance` (HEAD, src tree,
runner hash; clean checkout required, refused if it changes during the run). Receipts
written before this (schema 1, e.g. evidence 70f6617) are a bare list of cases.
Host contract tests: `tests/unit/test_dfg_re20.py`.
+9 -1
View File
@@ -82,7 +82,15 @@ class Re20Provenance(unittest.TestCase):
self.assertEqual(len(record["checkout_revision"]), 40)
self.assertEqual(len(record["src_tree"]), 40)
self.assertEqual(len(record["runner_sha256"]), 64)
self.assertIn("checkout_dirty", record)
self.assertFalse(record["checkout_dirty"])
self.assertEqual(v.RESULT_SCHEMA, 2)
def test_dirty_checkout_is_refused(self):
from unittest.mock import patch
with patch("source_provenance.subprocess.check_output",
side_effect=["f" * 40 + "\n", " M src/x.py\n"]):
with self.assertRaisesRegex(RuntimeError, "clean checkout"):
v.provenance(rr.DEFAULT_SOURCE)
if __name__ == "__main__":
+10 -4
View File
@@ -57,6 +57,8 @@ WINDOW_TSTAR = 10.0 # trailing 5 s, 21 samples
BANDS = dict(cd=(5.57, 5.59), cl=(0.0104, 0.0110), p_diff=(0.1172, 0.1176))
STEADY_RANGE = {k: (hi - lo) / 10 for k, (lo, hi) in BANDS.items()}
STEADY_SLOPE = {k: (hi - lo) / 50 for k, (lo, hi) in BANDS.items()}
# 2: {schema, provenance, cases}; the earlier receipts (schema 1) are a bare list of cases.
RESULT_SCHEMA = 2
DEVIATIONS = ("pressure-NEQ outlet (neq_extrap), not do-nothing", "local Zou-He inlet")
_re100_config = rr.config
@@ -201,8 +203,11 @@ def provenance(source):
from source_provenance import checkout_provenance
tree = subprocess.check_output(["git", "-C", str(source), "rev-parse", "HEAD:src"],
text=True).strip()
return dict(source_mode="current_checkout", **checkout_provenance(source), src_tree=tree,
runner_sha256=hashlib.sha256(Path(__file__).read_bytes()).hexdigest())
record = dict(source_mode="current_checkout", **checkout_provenance(source), src_tree=tree,
runner_sha256=hashlib.sha256(Path(__file__).read_bytes()).hexdigest())
if record["checkout_dirty"]: # HEAD would not describe the code that runs
raise RuntimeError("DFG Re20 runs require a clean checkout")
return record
def main():
@@ -215,7 +220,7 @@ def main():
parser.add_argument("--max-wall-seconds", type=float)
a = parser.parse_args()
a.source = a.source.resolve()
record = dict(plan(a.collision), provenance=provenance(a.source))
record = dict(plan(a.collision), schema=RESULT_SCHEMA, provenance=provenance(a.source))
a.output.mkdir(parents=True, exist_ok=False)
(a.output / "plan.json").write_text(json.dumps(record, indent=2, allow_nan=False))
if a.plan:
@@ -230,7 +235,8 @@ def main():
after = provenance(a.source)
if after != record["provenance"]:
raise RuntimeError("Source checkout changed during the run")
(a.output / "result.json").write_text(json.dumps(dict(provenance=after, cases=results),
(a.output / "result.json").write_text(json.dumps(dict(schema=RESULT_SCHEMA, provenance=after,
cases=results),
indent=2, allow_nan=False))