117 lines
5.4 KiB
Python
117 lines
5.4 KiB
Python
"""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()
|