#!/usr/bin/env python3
"""Clean-room verifier for the CAIN-42 Evolution 17 multi-agent world action fabric proof bundle.

IMPORTS NO CAIN-42 CODE. Standard library plus `cryptography` (Ed25519). From the published JSON alone it re-derives:
file hashes (MANIFEST), canonical serialization and domain-separated digests, collective identity vs the naive member
sum, every member and membership-snapshot digest, the collective state digest, the mission and drift digests, the
negotiation/decision/dissent digests, the trajectory proposal digest, the authority intersection, the consequence and
blast-radius digests, the containment and recovery records, the fifteen-digest commit binding and the E8 canonical
action hash, the Q-invariant matrix, the >=75-scenario bench, the end-to-end steps, and the master proof signature.

Every published governed object is a {"body": <exactly the digested dict>, "digest": <domain-separated sha256>}, so
the verifier can recompute every digest without trusting any of them.

Usage:  python3 verify_e17.py <bundle-directory>
"""
from __future__ import annotations

import base64
import hashlib
import json
import sys
from pathlib import Path

from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.primitives.asymmetric import ed25519

D = {"collective": "CAIN42/E17-GOVERNED-COLLECTIVE/v1", "identity": "CAIN42/E17-COLLECTIVE-IDENTITY/v1",
     "member": "CAIN42/E17-COLLECTIVE-MEMBER/v1", "membership": "CAIN42/E17-MEMBERSHIP-SNAPSHOT/v1",
     "membership_change": "CAIN42/E17-MEMBERSHIP-CHANGE/v1", "role": "CAIN42/E17-GOVERNED-ROLE/v1",
     "mission": "CAIN42/E17-GOVERNED-MISSION/v1", "mission_drift": "CAIN42/E17-MISSION-DRIFT/v1",
     "authority": "CAIN42/E17-COLLECTIVE-AUTHORITY/v1", "negotiation": "CAIN42/E17-GOVERNED-NEGOTIATION/v1",
     "contract": "CAIN42/E17-AGENT-CONTRACT/v1", "decision": "CAIN42/E17-COLLECTIVE-DECISION/v1",
     "dissent": "CAIN42/E17-DISSENT/v1", "epistemic": "CAIN42/E17-COLLECTIVE-EPISTEMIC/v1",
     "communication": "CAIN42/E17-COLLECTIVE-COMMUNICATION/v1", "world": "CAIN42/E17-COLLECTIVE-WORLD-STATE/v1",
     "trajectory": "CAIN42/E17-COLLECTIVE-TRAJECTORY/v1", "consequence": "CAIN42/E17-COLLECTIVE-CONSEQUENCE/v1",
     "blast": "CAIN42/E17-COLLECTIVE-BLAST-RADIUS/v1", "budget": "CAIN42/E17-COLLECTIVE-BUDGET/v1",
     "containment": "CAIN42/E17-COLLECTIVE-CONTAINMENT/v1", "health": "CAIN42/E17-COLLECTIVE-HEALTH/v1",
     "sybil": "CAIN42/E17-SYBIL-ASSESSMENT/v1", "consensus": "CAIN42/E17-COLLECTIVE-CONSENSUS/v1",
     "commit": "CAIN42/E17-COLLECTIVE-COMMIT/v1", "topology": "CAIN42/E17-COLLECTIVE-TOPOLOGY/v1",
     "improvement": "CAIN42/E17-COLLECTIVE-IMPROVEMENT/v1", "tx": "CAIN42/E17-COLLECTIVE-TRANSACTION/v1",
     "action": "CAIN42/E17-COLLECTIVE-ACTION/v1", "adversarial": "CAIN42/E17-COLLECTIVE-ADVERSARIAL/v1",
     "master": "CAIN42/E17-MASTER/v1", "e8_action": "CAIN42/E8-CANONICAL-ACTION/v1"}
BOUND_FIELDS = ("collective_digest", "membership_digest", "mission_digest", "world_state_digest",
                "temporal_state_digest", "causal_state_digest", "decision_digest", "capability_digest",
                "authority_digest", "policy_digest", "risk_digest", "consequence_digest", "trajectory_digest",
                "authorization_digest", "command_digest")
REQUIRED_FILES = ("MANIFEST.json", "SCHEMAS.json", "CAIN42_EVOLUTION17_PROOF.json",
                  "CAIN42_EVOLUTION17_MASTER_PROOF.json", "CAIN42_EVOLUTION17_CLAIMS.json", "MACHINE_STATUS.json",
                  "COLLECTIVE_EXAMPLES.json", "MEMBERSHIP_EXAMPLES.json", "MISSION_EXAMPLES.json",
                  "NEGOTIATION_EXAMPLES.json", "DECISION_EXAMPLES.json", "TRAJECTORY_EXAMPLES.json",
                  "AUTHORITY_EXAMPLES.json", "CONSEQUENCE_EXAMPLES.json", "CONTAINMENT_EXAMPLES.json",
                  "RECOVERY_EXAMPLES.json", "COMMIT_EXAMPLES.json", "END_TO_END_DEMO.json", "ATTACK_MANIFEST.json",
                  "TEST_VECTORS.json", "PERFORMANCE.json", "LIMITATIONS.json", "LIMITATIONS.md",
                  "CLEAN_ROOM_VERIFIER.json", "verify_e17.py.txt", "REPRODUCE.txt", "index.html")
FORBIDDEN = ("private_key", "private key", "-----begin", "secret_key", "password")


def canon(o) -> bytes:
    return json.dumps(o, sort_keys=True, separators=(",", ":"), ensure_ascii=True).encode()


def h(o) -> str:
    return hashlib.sha256(canon(o)).hexdigest()


def dig(domain: str, fields: dict) -> str:
    return h({"domain": domain, **fields})


def sig_ok(pub_b64: str, sig_b64: str, domain: str, fields: dict) -> bool:
    try:
        ed25519.Ed25519PublicKey.from_public_bytes(base64.b64decode(pub_b64)).verify(
            base64.b64decode(sig_b64), dig(domain, fields).encode())
        return True
    except (InvalidSignature, ValueError, TypeError):
        return False


def strip(d: dict, *keys: str) -> dict:
    return {k: v for k, v in d.items() if k not in keys}


class Checker:
    def __init__(self) -> None:
        self.checks, self.problems = [], []

    def check(self, name: str, ok: bool, detail: str = "") -> None:
        self.checks.append({"check": name, "result": "PASS" if ok else "FAIL", "detail": detail})
        if not ok:
            self.problems.append(name)

    def digest(self, name: str, domain: str, obj: dict) -> None:
        ok = isinstance(obj, dict) and "body" in obj and dig(domain, obj["body"]) == obj.get("digest")
        self.check(name, ok, str(obj.get("digest", ""))[:16])


def main(d: Path) -> int:
    C = Checker()
    load = lambda n: json.loads((d / n).read_text())
    manifest = load("MANIFEST.json")
    presentation = set(manifest.get("unhashed_presentation", [])) | {"index.html"}
    bad = [n for n, want in manifest["files"].items()
           if n not in presentation and (not (d / n).exists()
                                         or hashlib.sha256((d / n).read_bytes()).hexdigest() != want)]
    C.check("manifest_file_hashes", not bad, ",".join(bad))
    C.check("required_files_present", all((d / f).exists() for f in REQUIRED_FILES)
            and all(f in manifest["files"] for f in REQUIRED_FILES if f not in ("MANIFEST.json", "index.html")),
            "missing required")
    C.check("presentation_page_unhashed", presentation <= {"index.html"}, str(sorted(presentation)))

    # ---- canonical serialization & domain separation ----
    tv = load("TEST_VECTORS.json")
    C.check("vectors.canonical_json", hashlib.sha256(canon(tv["canonical_json"]["input"])).hexdigest()
            == tv["canonical_json"]["sha256"], tv["canonical_json"]["sha256"])
    sep = tv["domain_separation"]
    C.check("vectors.domain_separation", len(set(sep["digests"].values())) == len(sep["digests"])
            and all(dig(n["domain"], sep["fields"]) == sep["digests"][n["domain"]] for n in tv["domains"]),
            str(list(sep["digests"].values()))[:80])
    C.check("vectors.negative_cases", tv["negative"]["consensus_not_authorization"] is True
            and tv["negative"]["member_sum_not_identity"] is True
            and tv["negative"]["trajectory_is_proposal"] is True
            and tv["negative"]["budget_cannot_mint_by_agreement"] is True, str(tv["negative"]))
    C.check("vectors.topologies", tv["topologies"] == ["CENTRALIZED", "DECENTRALIZED", "HIERARCHICAL", "MESH",
            "SWARM", "TEMPORARY_COALITION", "FEDERATED", "CROSS_DOMAIN"], str(tv["topologies"]))

    # ---- membership ----
    me = load("MEMBERSHIP_EXAMPLES.json")
    members = me["members"]
    member_digests = {m["body"]["member_id"]: m["digest"] for m in members}
    for m in members:
        C.digest(f"membership.member_digest.{m['body']['member_id']}", D["member"], m)
    C.check("membership.member_digests_unique", len(set(member_digests.values())) == len(members), "unique")
    C.digest("membership.snapshot_digest", D["membership"], me["snapshot"])
    C.check("membership.snapshot_members_match",
            me["snapshot"]["body"]["member_ids"] == sorted(m["body"]["member_id"] for m in members)
            and me["snapshot"]["body"]["member_digests"] == sorted(member_digests.values()), "snapshot members")
    C.digest("membership.change_digest", D["membership_change"], me["change"])
    C.check("membership.change_requires_reevaluation", me["change"]["body"]["requires_reevaluation"] is True, "reeval")

    # ---- collective identity vs member sum ----
    ce = load("COLLECTIVE_EXAMPLES.json")
    ident = ce["identity"]
    C.digest("identity.digest", D["identity"], ident)
    sum_body = {"members": sorted(member_digests.values()),
                "identities": sorted(m["body"]["identity_digest"] for m in members)}
    member_sum = dig(D["membership"], sum_body)
    C.check("identity.not_member_sum", ident["digest"] != member_sum and ce["member_identity_sum"] == member_sum,
            "identity vs sum")
    C.check("identity.revocable_and_bounded", ident["body"]["revoked"] is False
            and ident["body"]["valid_until"] > ident["body"]["valid_from"]
            and bool(ident["body"]["formation_authority"]), "identity fields")
    C.check("identity.authority_none", ce["identity_authority"] == "NONE", ce["identity_authority"])
    C.digest("collective.state_digest", D["collective"], ce["state"])
    C.check("collective.topology_valid", ce["topology"] in tv["topologies"], ce["topology"])

    # ---- authority ----
    auth = load("AUTHORITY_EXAMPLES.json")
    C.digest("authority.digest", D["authority"], auth)
    ab = auth["body"]
    C.check("authority.intersection_not_sum", ab["additive"] is False
            and ab["method"] == "CONSTRAINED_INTERSECTION", ab["method"])
    C.check("authority.within_grant", set(ab["effective"]) <= set(auth["grant"])
            and set(ab["effective"]) <= set(auth["mission_capabilities"]), "within grant")
    ceil = set(auth["grant"]) & set(auth["mission_capabilities"]) & set(auth["policy_allowed"])
    C.check("authority.ceiling_correct", set(ab["ceiling"]) == ceil, str(sorted(ceil)))
    live = {m["body"]["member_id"] for m in members if m["executable"]}
    C.check("authority.holders_live", all(mid in live for hs in ab["holders"].values() for mid in hs), "holders")
    C.check("authority.authority_none", ab["authority"] == "NONE" and auth["authority"] == "NONE", ab["authority"])

    # ---- mission & drift ----
    mi = load("MISSION_EXAMPLES.json")
    C.digest("mission.digest", D["mission"], mi["mission"])
    C.check("mission.no_capability_prohibited_conflict",
            not (set(mi["mission"]["body"]["capabilities"]) & set(mi["mission"]["body"]["prohibited_actions"])),
            "conflict")
    C.digest("mission.drift_digest", D["mission_drift"], mi["drift"])
    C.check("mission.drift_material_reauthorize", mi["drift"]["body"]["material"] is True
            and mi["drift"]["body"]["requirement"] in ("REAUTHORIZE", "SAFE_STATE", "HUMAN_REVIEW", "DENY"),
            mi["drift"]["body"]["requirement"])

    # ---- negotiation ----
    ne = load("NEGOTIATION_EXAMPLES.json")
    neg = ne["negotiation"]
    C.digest("negotiation.digest", D["negotiation"], neg)
    C.check("negotiation.is_proposal", neg["body"]["status"] == "PROPOSAL"
            and neg["body"]["is_authorization"] is False and neg["body"]["authorization"] == "NONE", neg["body"]["status"])

    # ---- contract ----
    de = load("DECISION_EXAMPLES.json")
    ct = de["contract"]
    C.digest("contract.digest", D["contract"], ct)
    C.check("contract.grants_no_authority", ct["authority_granted"] == "NONE" and ct["replay_refused"] is True,
            "contract")

    # ---- decision / consensus / dissent ----
    dec = de["decision"]
    C.digest("decision.digest", D["decision"], dec)
    C.check("decision.no_authority", de["authority"] == "NONE", de["authority"])
    con = de["consensus"]
    C.digest("consensus.digest", D["consensus"], con)
    C.check("consensus.no_authority", con["body"]["authority"] == "NONE"
            and con["body"]["is_authorization"] is False, "consensus")
    C.digest("dissent.digest", D["dissent"], de["dissent"])
    C.check("dissent.preserved", de["dissent"]["body"]["preserved"] is True
            and de["dissent"]["body"]["discardable"] is False, "dissent")

    # ---- trajectory ----
    te = load("TRAJECTORY_EXAMPLES.json")
    traj = te["trajectory"]
    C.digest("trajectory.digest", D["trajectory"], traj)
    C.check("trajectory.is_proposal", traj["body"]["status"] == "PROPOSAL"
            and traj["body"]["is_authorization"] is False and traj["body"]["authority"] == "NONE", traj["body"]["status"])
    C.check("trajectory.collision_detected", any(p.startswith("COLLISION")
            for p in te["colliding"]["body"]["problems"]) and te["colliding"]["body"]["valid"] is False, "collision")

    # ---- consequence & blast ----
    cg = load("CONSEQUENCE_EXAMPLES.json")
    C.digest("consequence.digest", D["consequence"], cg["consequence"])
    C.check("consequence.levels_present", set(cg["consequence"]["body"]["levels"]) == {"DIRECT_EFFECT",
            "SECOND_ORDER_EFFECT", "COLLECTIVE_EFFECT", "SYSTEM_EFFECT", "ENVIRONMENTAL_EFFECT", "HUMAN_EFFECT"},
            str(sorted(cg["consequence"]["body"]["levels"])))
    C.check("consequence.unknown_worst_case", cg["unmeasurable"]["body"]["evidence_class"] == "UNKNOWN"
            and cg["unmeasurable"]["body"]["worst_case"] is True, "unknown")
    C.digest("blast.digest", D["blast"], cg["blast"])
    C.check("blast.one_action_many_effects", cg["blast"]["body"]["affected_agents"] > 1
            and cg["blast"]["body"]["one_action_many_effects"] is True
            and cg["blast"]["body"]["requires_pre_authorization"] is True, "fleet")
    C.check("blast.single_action_no_preauth", cg["single"]["body"]["requires_pre_authorization"] is False, "single")

    # ---- containment & recovery ----
    cn = load("CONTAINMENT_EXAMPLES.json")
    C.digest("containment.digest", D["containment"], cn["containment"])
    cb = cn["containment"]["body"]
    C.check("containment.requires_reauthorization", cb["requires_reauthorization"] is True
            and cb["authorization_state"] == "REVOKED" and cb["assumes_collective_safe"] is False, "containment")
    C.check("containment.steps_complete", len(cb["steps"]) == 9, str(len(cb["steps"])))
    rc = load("RECOVERY_EXAMPLES.json")
    C.check("recovery.no_authority", rc["authorization_state"] == "NONE" and rc["health_state"] == "RECOVERING"
            and rc["recovery_mints_authority"] is False, "recovery")
    C.digest("recovery.change_digest", D["membership_change"], rc["change"])

    # ---- commit binding ----
    cme = load("COMMIT_EXAMPLES.json")
    committed, denied = cme["committed"], cme["denied"]
    C.check("commit.bound_fields", sorted(committed["body"]["bound_fields"]) == sorted(BOUND_FIELDS)
            and len(committed["body"]["bound_fields"]) == 15, str(len(committed["body"]["bound_fields"])))
    C.digest("commit.digest", D["commit"], committed)
    C.digest("commit.denied_digest", D["commit"], denied)
    C.check("commit.authorized_through_e8", committed["body"]["decision"] == "AUTHORIZED"
            and committed["authority"] == "E8" and committed["body"]["reasons"] == [], committed["body"]["decision"])
    C.check("commit.denied_without_e8", denied["body"]["decision"] == "DENY"
            and any("E8_BOUNDARY_NOT_CONFIGURED" in r for r in denied["body"]["reasons"])
            and denied["authority"] == "NONE", str(denied["body"]["reasons"]))
    C.check("commit.e8_action_bound", cme["e8_action"]["action_hash"] == dig(D["e8_action"],
            cme["e8_action"]["body"]), cme["e8_action"]["action_hash"][:16])
    C.check("commit.e8_action_carries_bindings", sorted(cme["e8_action"]["body"]["parameters"]) == sorted(BOUND_FIELDS),
            "parameters")

    # ---- end-to-end ----
    e2e = load("END_TO_END_DEMO.json")
    C.check("e2e.all_steps_ok", e2e["all_steps_ok"] is True and all(s["ok"] for s in e2e["steps"]), "e2e steps")
    C.check("e2e.all_mutations_governed", e2e["all_mutations_governed"] is True
            and all(m["invalidated"] for m in e2e["mutations"]), "e2e mutations")
    C.check("e2e.required_steps", {"COLLECTIVE_FORMATION", "NEGOTIATION_PROPOSAL", "DISSENT_PRESERVED",
            "WORLD_STATE", "CAUSAL_PREDICTION", "COLLECTIVE_DECISION", "COLLECTIVE_TRAJECTORY", "CAPABILITY",
            "AUTHORITY", "CONSEQUENCE", "BLAST_RADIUS", "E8_COMMIT", "CONTAINMENT", "REVALIDATION",
            "RECOVERY_NO_AUTHORITY", "EVIDENCE"} <= {s["step"] for s in e2e["steps"]}, "steps")

    # ---- invariants ----
    proof = load("CAIN42_EVOLUTION17_PROOF.json")
    inv = proof["invariants"]
    C.check("invariants.count", inv["checked"] >= 50 and len(inv["checks"]) == inv["checked"], str(inv["checked"]))
    C.check("invariants.ids", [x["id"] for x in inv["checks"]] == [f"Q{i:02d}" for i in range(1, inv["checked"] + 1)],
            "ids")
    C.check("invariants.all_hold", inv["all_hold"] is True and not inv["failed"], str(inv["failed"]))
    C.check("invariants.entries_hold", all(x.get("holds") is True for x in inv["checks"]),
            str([x["id"] for x in inv["checks"] if not x.get("holds")]))
    C.check("invariants.failed_consistent",
            list(inv["failed"]) == [x["id"] for x in inv["checks"] if not x.get("holds")], str(inv["failed"]))

    # ---- adversarial bench ----
    am = load("ATTACK_MANIFEST.json")
    C.check("bench.minimum", am["total"] >= 75 and len(am["attacks"]) == am["total"], str(am["total"]))
    C.check("bench.all_contained", am["all_contained"] is True and am["contained"] == am["total"],
            f"{am['contained']}/{am['total']}")
    C.check("bench.honest", am["real_world_attack_validation"] == "NOT_PERFORMED" and am["authority"] == "NONE",
            "honesty")
    C.check("bench.entries_contained", all(v["contained"] is True and v["state"] == "CONTAINED"
            for v in am["attacks"].values()), "entries")
    C.check("bench.required_classes", {"majority_attack_consensus_as_authority", "coalition_amplification",
            "world_state_fork", "sybil_agents_shared_origin", "compromised_coordinator", "gateway_bypass",
            "direct_actuator_bypass", "mcp_bypass", "toctou", "budget_amplification"} <= set(am["attacks"]), "classes")

    # ---- performance & limitations ----
    perf = load("PERFORMANCE.json")
    C.check("performance.conditions", perf["conditions"]["concurrency"] == 1
            and "in-process" in perf["conditions"]["workload"], "conditions")
    C.check("performance.results", perf["results"] and all("p50_us" in v and "p99_us" in v
            for v in perf["results"].values()), "results")
    lim = load("LIMITATIONS.json")
    C.check("limitations.classified", len(lim["limitations"]) >= 5 and all(x["status"] in
            ("NOT_IMPLEMENTED", "UNKNOWN", "UNVERIFIED", "NOT_PERFORMED") for x in lim["limitations"]), "limitations")
    C.check("limitations.no_real_fleet_claim", any(x["status"] == "NOT_IMPLEMENTED" for x in lim["limitations"]),
            "limitations honest")

    # ---- claims / status / schemas / verifier ----
    cl = load("CAIN42_EVOLUTION17_CLAIMS.json")
    C.check("claims.states_valid", all(c["state"] in ("IMPLEMENTED", "TESTED", "VERIFIED", "SIMULATED", "UNVERIFIED",
            "UNKNOWN", "NOT_IMPLEMENTED", "NOT_PERFORMED") for c in cl["claims"]), "claims")
    C.check("claims.distinguish_real_world", any(c["state"] == "NOT_IMPLEMENTED" for c in cl["claims"]), "claims honest")
    ms = load("MACHINE_STATUS.json")
    C.check("status.no_real_fleet", ms["states"]["real_world_integration"] == "NOT_IMPLEMENTED"
            and ms["states"]["hardware_attestation"] == "UNKNOWN"
            and ms["states"]["third_party_review"] == "NOT_PERFORMED", str(ms["states"]))
    C.check("status.counts_match", ms["invariants_checked"] == inv["checked"] and ms["attacks_total"] == am["total"],
            "counts")
    sc = load("SCHEMAS.json")
    C.check("schemas.present", len(sc["schemas"]) >= 20 and "GovernedCollective" in sc["schemas"], str(len(sc["schemas"])))
    crv = load("CLEAN_ROOM_VERIFIER.json")
    C.check("clean_room.imports_no_cain", crv["imports_cain"] is False and crv["checks"] >= 50, "clean room")
    C.check("clean_room.sha_matches", hashlib.sha256((d / "verify_e17.py.txt").read_text().encode()).hexdigest()
            == crv["verifier_sha256"], "verifier sha")

    # ---- master proof signature ----
    master = load("CAIN42_EVOLUTION17_MASTER_PROOF.json")
    mbody = strip(master, "signature_b64", "signer_public_key_b64")
    C.check("master.signature", sig_ok(master["signer_public_key_b64"], master["signature_b64"], D["master"], mbody),
            "sig")
    C.check("master.says_tested", master["evidence_level"] == "TESTED" and master["signing_key_class"] == "EPHEMERAL",
            master["evidence_level"])
    C.check("master.no_overclaim", master["status"]["REAL_WORLD_INTEGRATION"] == "NOT_IMPLEMENTED"
            and master["status"]["HARDWARE_ATTESTATION"] == "UNKNOWN", str(master["status"]))
    C.check("master.counts_match", master["invariants_checked"] == inv["checked"]
            and master["attacks_total"] == am["total"], "counts")
    blob = "".join((d / n).read_text(errors="ignore").lower() for n in manifest["files"] if n.endswith(".json"))
    C.check("no_forbidden_material", not any(f in blob for f in FORBIDDEN), "forbidden material present")

    passed = sum(1 for c in C.checks if c["result"] == "PASS")
    out = {"schema": "cain42.e17.verifier.v1", "result": "INTACT" if not C.problems else "FAILED",
           "checks": len(C.checks), "passed": passed, "problems": C.problems, "detail": C.checks}
    print(json.dumps(out))
    return 0 if not C.problems else 1


if __name__ == "__main__":
    raise SystemExit(main(Path(sys.argv[1])))
