test: final source recheck for V1 jet CV and V3 Taylor-Green runs
Both runners now re-derive the recorded source identity after the run (source_provenance.final_source_check) and only then mark the receipt complete. A changed source, or a recheck that fails, rewrites the receipt as invalid and raises: - V1: in-run receipts carry run_status=INCOMPLETE; finish_receipt sets COMPLETE or INVALID_SOURCE_CHANGED (+ source_recheck/source_after). - V3: completed stays False, invalid=SOURCE_CHANGED_DURING_RUN, no screening_pass; the assessor requires source_recheck UNCHANGED (frozen mode still accepts receipts from before the field existed). Interrupted runs keep INCOMPLETE / completed=False. Host tests cover unchanged, changed and failed rechecks and incomplete runs. No threshold or solver change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8cae58cdf1
commit
df0f586331
7 files changed
+159
-15
No files matched your search
@@ -144,6 +144,10 @@ def assess(records, expected_revision=None, current=False):
|
||||
checks[label + " reference"] = (r.get("numerical_revision") == NUMERICAL_REVISION
|
||||
and "source_mode" not in r
|
||||
and r.get("reference_harness") == REFERENCE_HARNESS)
|
||||
# Final source recheck (run_case); receipts from before it existed carry no field
|
||||
# and are accepted only in the frozen mode.
|
||||
recheck = r.get("source_recheck") if current else r.get("source_recheck", "UNCHANGED")
|
||||
checks[label + " source_recheck"] = recheck == "UNCHANGED" and "invalid" not in r
|
||||
checks[label + " numerics"] = r.get("numerics") == NUMERICS and r.get("initialization") == INITIALIZATION
|
||||
runtime = r.get("runtime_source", {})
|
||||
package = Path(runtime.get("checkout", "")) / "src/CelerisLab"
|
||||
|
||||
@@ -319,6 +319,17 @@ def main():
|
||||
),
|
||||
flush=True,
|
||||
)
|
||||
from tests.validation.source_provenance import final_source_check
|
||||
recheck = final_source_check(
|
||||
dict(source=source, runtime_source=runtime),
|
||||
lambda: dict(source=verify_source(repo, args.expected_revision,
|
||||
current=args.current_checkout),
|
||||
runtime_source=runtime_source(repo)))
|
||||
report.update(recheck)
|
||||
if recheck["source_recheck"] != "UNCHANGED":
|
||||
report.update(invalid="SOURCE_CHANGED_DURING_RUN") # completed stays False
|
||||
save()
|
||||
raise RuntimeError("Source changed during the run; receipt marked invalid")
|
||||
report.update(
|
||||
completed=True,
|
||||
seconds=time.monotonic() - started,
|
||||
|
||||
@@ -12,7 +12,7 @@ import sys
|
||||
import tempfile
|
||||
from types import SimpleNamespace
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
from unittest.mock import call, patch
|
||||
|
||||
import numpy as np
|
||||
|
||||
@@ -322,6 +322,7 @@ class ProjectedPairsTests(unittest.TestCase):
|
||||
source_mode="current_checkout",
|
||||
source_capture=run_case.CURRENT_CAPTURE)
|
||||
r["numerical_revision"], r["source_mode"] = None, "current_checkout"
|
||||
r["source_recheck"] = "UNCHANGED"
|
||||
ok = assess(current, MOCK_REVISION, current=True)
|
||||
self.assertTrue(ok["checks"]["clean_current_checkout_source"])
|
||||
self.assertTrue(all(v for k, v in ok["checks"].items() if k.endswith(" reference")))
|
||||
@@ -333,6 +334,23 @@ class ProjectedPairsTests(unittest.TestCase):
|
||||
self.assertFalse(assess(current, None, current=True)["checks"]["clean_current_checkout_source"])
|
||||
self.assertTrue(assess(frozen, MOCK_REVISION)["checks"]["clean_a135252_numerical_source"])
|
||||
|
||||
def test_assessor_requires_unchanged_final_source(self):
|
||||
frozen = records()
|
||||
label = lambda r: f"{r['n']}/{r['depth']}/{r['explosion']} source_recheck"
|
||||
self.assertTrue(all(assess(frozen, MOCK_REVISION)["checks"][label(r)] for r in frozen))
|
||||
for mutate in (dict(source_recheck="CHANGED"), dict(invalid="SOURCE_CHANGED_DURING_RUN")):
|
||||
bad = deepcopy(frozen)
|
||||
bad[0].update(mutate)
|
||||
out = assess(bad, MOCK_REVISION)
|
||||
self.assertFalse(out["checks"][label(bad[0])])
|
||||
self.assertFalse(out["passed"])
|
||||
current = deepcopy(frozen)
|
||||
for r in current:
|
||||
r["source"] = dict(r["source"], source_mode="current_checkout")
|
||||
r["numerical_revision"], r["source_mode"] = None, "current_checkout"
|
||||
# Current-checkout receipts are new: a missing recheck is not accepted.
|
||||
self.assertFalse(assess(current, MOCK_REVISION, current=True)["checks"][label(current[0])])
|
||||
|
||||
def test_explicit_opt_in_selector_and_dry_run_without_cuda(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
output = Path(tmp) / "case.json"
|
||||
@@ -393,7 +411,8 @@ class ProjectedPairsTests(unittest.TestCase):
|
||||
# Replace the GPU context and steppers before import. No CUDA module or flow is used.
|
||||
for early, depth, fail in ((False, 1, None), (False, 2, None), (True, 2, None),
|
||||
(True, 2, "resource"), (True, 2, "nonfinite"),
|
||||
(True, 2, "probe_nonfinite")):
|
||||
(True, 2, "probe_nonfinite"),
|
||||
(False, 2, "changed"), (False, 1, "recheck_error")):
|
||||
calls, selectors = [], []
|
||||
|
||||
class FakeRun:
|
||||
@@ -448,13 +467,29 @@ class ProjectedPairsTests(unittest.TestCase):
|
||||
patch.object(run_case, "runtime_identity", return_value=deepcopy(RUNTIME)), \
|
||||
patch.object(run_case, "build_capture", return_value=nullcontext()), \
|
||||
patch.object(run_case, "metrics", side_effect=metric), redirect_stdout(io.StringIO()):
|
||||
if fail:
|
||||
if fail in ("changed", "recheck_error"):
|
||||
verify.side_effect = [source(), dict(source(), source_tree_sha256="d" * 64)
|
||||
if fail == "changed" else RuntimeError("Wrong numerical source fingerprint")]
|
||||
with self.assertRaisesRegex(RuntimeError, "Source changed during the run"):
|
||||
run_case.main()
|
||||
elif fail:
|
||||
with self.assertRaises(MemoryError if fail == "resource" else FloatingPointError):
|
||||
run_case.main()
|
||||
else:
|
||||
self.assertEqual(run_case.main(), 0)
|
||||
receipt = json.loads(output.read_text())
|
||||
verify.assert_called_once_with(REPO, MOCK_REVISION, current=False)
|
||||
rechecked = fail in (None, "changed", "recheck_error")
|
||||
self.assertEqual(verify.call_args_list,
|
||||
[call(REPO, MOCK_REVISION, current=False)] * (2 if rechecked else 1))
|
||||
if fail in ("changed", "recheck_error"):
|
||||
# Full trajectory, but invalid: never a complete-looking receipt.
|
||||
self.assertEqual(receipt["source_recheck"], "CHANGED")
|
||||
self.assertEqual(receipt["invalid"], "SOURCE_CHANGED_DURING_RUN")
|
||||
self.assertFalse(receipt["completed"])
|
||||
self.assertNotIn("screening_pass", receipt)
|
||||
self.assertEqual(receipt["source_after"] is None, fail == "recheck_error")
|
||||
continue
|
||||
self.assertEqual(receipt.get("source_recheck"), None if fail else "UNCHANGED")
|
||||
self.assertEqual(selectors, ["projected_linear" if early else "constant"] if depth == 2 else [])
|
||||
self.assertEqual(receipt["source"], source())
|
||||
self.assertEqual(receipt["runtime_identity"], RUNTIME)
|
||||
|
||||
Reference in new issue
Block a user