#!/usr/bin/env python3
"""NULLIUS single-field sweep: change every field of the published sample evidence, one at a time, and record
which edits each clean-room verifier refuses and which it still accepts. No CAIN code; runs the published verifiers.

    mkdir nullius && cd nullius
    curl -sO https://cainstudio.online/nullius-sweep.py
    python3 -I nullius-sweep.py --download      # fetches the 3 verifiers (checked against SHA256SUMS) + samples
    python3 -I nullius-sweep.py                 # runs the sweep (a few minutes), prints a summary, writes sweep.json

Every edit changes exactly one leaf value: a string gets its last character changed (or becomes "x" if empty), a
number gets +1, a boolean flips, a null becomes "x". A verifier "refuses" when it exits non-zero. An edit that is
still accepted is listed by its JSON path so you can judge whether that field matters. Exit 0 always (it measures).
Needs Python 3.8+ and the `cryptography` package.
"""
import hashlib
import json
import os
import subprocess
import sys
import urllib.request
from concurrent.futures import ThreadPoolExecutor

SITE = os.environ.get("NULLIUS_SITE", "https://cainstudio.online")
REL = "/evidence/nullius-verifiers-2026-10-27/"
FILES = {
    REL: ["SHA256SUMS", "verify_mission_journal.py", "verify_execution_truth.py", "verify_launchpad.py"],
    "/evidence/evolution-16-durable-mission-runtime/sample/": ["mission.journal.jsonl", "trust.public.json", "head.json"],
    "/proof/bundle/sovereign-reality-evolution18-2026-10-12/fixtures/": ["valid.receipt.json", "valid.anchors.json"],
    "/evidence/evolution-14-sovereign-launchpad/sample_deployment_export/": ["export.json", ("trust.public.json", "lp.trust.json")],
}


def download():
    for base, names in FILES.items():
        for n in names:
            src, dst = (n, n) if isinstance(n, str) else n
            with urllib.request.urlopen(urllib.request.Request(SITE + base + src, headers={"user-agent": "nullius-sweep/1"}), timeout=60) as r:
                open(dst, "wb").write(r.read())
    for line in open("SHA256SUMS"):
        h, f = line.split()
        if hashlib.sha256(open(f, "rb").read()).hexdigest() != h:
            sys.exit(f"{f} does not match SHA256SUMS: stop, do not run it")
    print("downloaded; verifiers match SHA256SUMS")


def leaves(o, path=()):
    if isinstance(o, dict):
        for k, v in o.items():
            yield from leaves(v, path + (k,))
    elif isinstance(o, list):
        for i, v in enumerate(o):
            yield from leaves(v, path + (i,))
    else:
        yield path, o


def mutated(v):
    if isinstance(v, bool):
        return not v
    if isinstance(v, (int, float)):
        return v + 1
    if v is None:
        return "x"
    if isinstance(v, str):
        return (v[:-1] + ("0" if v[-1] != "0" else "1")) if v else "x"
    return "x"


def put(o, path, v):
    for p in path[:-1]:
        o = o[p]
    o[path[-1]] = v


def run(args):
    p = subprocess.run([sys.executable, "-I", *args], capture_output=True, text=True, timeout=120)
    return p.returncode, "Traceback" in p.stderr


def sweep_json(label, doc_file, cmd, slot):
    """cmd is the verifier command with the evidence file at index `slot`."""
    base = json.load(open(doc_file))
    rc, _ = run(cmd)
    jobs = []
    for i, (path, v) in enumerate(leaves(base)):
        x = json.loads(json.dumps(base))
        put(x, path, mutated(v))
        f = f".sweep-{label}-{i}.json"
        json.dump(x, open(f, "w"))
        jobs.append((path, f))
    return finish(label, rc, jobs, cmd, slot)


def sweep_jsonl(label, doc_file, cmd, slot):
    lines = [json.loads(l) for l in open(doc_file) if l.strip()]
    rc, _ = run(cmd)
    jobs = []
    for li, obj in enumerate(lines):
        for path, v in leaves(obj):
            x = json.loads(json.dumps(obj))
            if path:
                put(x, path, mutated(v))
            else:
                x = mutated(v)
            out = [json.dumps(l, separators=(",", ":")) for l in lines]
            out[li] = json.dumps(x, separators=(",", ":"))
            f = f".sweep-{label}-{len(jobs)}.jsonl"
            open(f, "w").write("\n".join(out) + "\n")
            jobs.append(((li,) + path, f))
    return finish(label, rc, jobs, cmd, slot)


def finish(label, baseline_rc, jobs, cmd, slot):
    def one(job):
        path, f = job
        c = list(cmd)
        c[slot] = f
        rc, tb = run(c)
        os.remove(f)
        return path, rc, tb
    with ThreadPoolExecutor(max_workers=2) as ex:
        res = list(ex.map(one, jobs))
    accepted = [list(p) for p, rc, _ in res if rc == 0]
    crashed = [list(p) for p, _, tb in res if tb]
    out = {"verifier": cmd[0], "baseline_exit": baseline_rc, "fields_edited": len(res),
           "refused": len(res) - len(accepted), "still_accepted": len(accepted),
           "crashes": len(crashed), "accepted_paths": accepted, "crash_paths": crashed}
    print(f"{label:16s} baseline exit {baseline_rc}  edited {len(res):5d}  refused {out['refused']:5d}  "
          f"still accepted {len(accepted):4d}  crashes {len(crashed)}")
    return out


def main():
    if "--download" in sys.argv:
        download()
        return 0
    report = {
        "mission_journal": sweep_jsonl("mission-journal", "mission.journal.jsonl",
                                       ["verify_mission_journal.py", "mission.journal.jsonl", "--trust", "trust.public.json", "--head", "head.json"], 1),
        "execution_truth_receipt": sweep_json("exec-receipt", "valid.receipt.json",
                                              ["verify_execution_truth.py", "valid.receipt.json", "valid.anchors.json"], 1),
        "execution_truth_anchors": sweep_json("exec-anchors", "valid.anchors.json",
                                              ["verify_execution_truth.py", "valid.receipt.json", "valid.anchors.json"], 2),
        "launchpad_export": sweep_json("launchpad", "export.json",
                                       ["verify_launchpad.py", "deployment", "export.json", "--trust", "lp.trust.json"], 2),
    }
    report["verifier_sha256"] = {f: hashlib.sha256(open(f, "rb").read()).hexdigest()
                                 for f in ("verify_mission_journal.py", "verify_execution_truth.py", "verify_launchpad.py")}
    json.dump(report, open("sweep.json", "w"), indent=1)
    print("wrote sweep.json")
    return 0


if __name__ == "__main__":
    sys.exit(main())
