"""Exercise packaging of explicit real closed runs; mutate only in-memory test copies.""" from pathlib import Path import argparse import contextlib import copy import io import json import subprocess import sys import zipfile import prepare from verify import RAW_FILES, need, pin, run_name, verify_data, verify_package def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--root", type=Path, required=True) parser.add_argument("--runs", nargs="+", required=True) parser.add_argument("--output", type=Path, required=True, help="fresh package under this tools directory") args = parser.parse_args() root = args.root.resolve() output = args.output.resolve() need(output.is_relative_to(prepare.HERE) and output != prepare.HERE, "selfcheck output must stay beneath the tools directory") need(all(run_name(run) for run in args.runs) and len(args.runs) == len(set(args.runs)), "unique explicit run names") need(not output.exists(), "fresh selfcheck output required") for path in prepare.HERE.glob("*.py"): compile(path.read_bytes(), str(path), "exec") payload, manifest, expected = prepare.collect(root, args.runs, []) command = [sys.executable, "-B", str(prepare.HERE / "prepare.py"), "--root", str(root), "--runs", *args.runs, "--output", str(output)] subprocess.run(command, check=True) need(verify_package(output) == expected, "actual ZIP readback summary") gates = root / ".local/scenario-group-gates" with zipfile.ZipFile(output / "evidence.zip") as archive: for run in args.runs: for name in RAW_FILES | ({"source-proof.json"} if "sourceProof" in manifest["runs"][run] else set()): logical = "cargo/" + run + "/" + name need(archive.read(manifest["logicalFiles"][logical]) == (gates / run / name).read_bytes(), "original raw byte equality: " + logical) for name, stored in manifest["captureHelpers"].items(): need(archive.read(stored) == (gates / name).read_bytes(), "named original helper: " + name) repeats = subprocess.run(command, capture_output=True, text=True) need(repeats.returncode != 0 and "fresh directory" in repeats.stderr, "existing output must be refused") checked = [] def rejected(label, changed, altered): try: with contextlib.redirect_stdout(io.StringIO()): verify_data(changed, altered.__getitem__) except (ValueError, KeyError, zipfile.BadZipFile): checked.append(label) else: raise AssertionError("corruption accepted: " + label) run = args.runs[0] prefix = "cargo/" + run + "/" # Re-pin the outer member to ensure the verifier reaches receipt relationships. altered = dict(payload) changed = copy.deepcopy(manifest) stream = changed["logicalFiles"][prefix + "stdout.log"] altered[stream] += b"\nselfcheck changed log\n" changed["members"][stream] = pin(altered[stream]) rejected("raw stream/receipt mismatch", changed, altered) altered = dict(payload) changed = copy.deepcopy(manifest) helper = changed["runs"][run]["helper"] altered[helper] += b"\n# selfcheck changed helper\n" changed["members"][helper] = pin(altered[helper]) rejected("helper bytes/name mismatch", changed, altered) changed = copy.deepcopy(manifest) changed["runs"][run]["exitCode"] = 99 rejected("run/result exit mismatch", changed, payload) changed = copy.deepcopy(manifest) changed["logicalFiles"][prefix + "inputs.zip"] = changed["logicalFiles"][prefix + "inputs.json"] rejected("input archive mapping mismatch", changed, payload) changed = copy.deepcopy(manifest) del changed["logicalFiles"][prefix + "result.json"] rejected("missing closed receipt", changed, payload) for proof_run, spec in manifest["runs"].items(): if "sourceProof" not in spec: continue proof_path = "cargo/" + proof_run + "/source-proof.json" for field, value in [("run", "wrong-run"), ("commit", "0"*40), ("result", {"bytes":0,"sha256":"0"*64}), ("inputCount", 1)]: altered = dict(payload) changed = copy.deepcopy(manifest) proof = json.loads(altered[proof_path]) proof[field] = value altered[proof_path] = json.dumps(proof).encode() changed["members"][proof_path] = pin(altered[proof_path]) changed["runs"][proof_run]["sourceProof"]["pin"] = pin(altered[proof_path]) rejected("source proof " + field + " relationship", changed, altered) changed = copy.deepcopy(manifest) del changed["logicalFiles"][proof_path] rejected("missing declared optional source proof", changed, payload) altered = dict(payload) changed = copy.deepcopy(manifest) comparator = spec["sourceProof"]["comparator"] altered[comparator] += b"\n# corrupted comparator\n" changed["members"][comparator] = pin(altered[comparator]) rejected("source comparator bytes mismatch", changed, altered) break print("PASS original raw/optional-proof/helper archive equality, existing-output refusal, and corruption rejection:", checked) print("Historical captured failures remain failures:", expected["recordedFailures"]) print("No Cargo, archived code, or GUI executed.") if __name__ == "__main__": main()