Files
ARCADIA-Studio/harness/scenario-group-planner-evidence-20260924/checks/selfcheck.py
T

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()