test: DFG Re20 provenance refuses dirty trees; versioned receipts
Per review ofaceea70e: 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. evidence70f6617) are documented. Adds a dirty-refusal test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
8064834c37
commit
2220cdd157
@@ -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`.
|
||||
|
||||
@@ -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__":
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user