#!/usr/bin/env python3
"""
Article 11 - INDEPENDENT CHAIN VERIFIER  (a11_independent_chain_verifier)
=========================================================================
Purpose: let ANY third party (a federal buyer, an auditor, a court) verify the
Article 11 IRONLEDGER's integrity from the RAW BLOCKS - without trusting Article
11's own verdict. It pulls the public chain, RE-COMPUTES the canonical-spine
linkage independently, locates the strict-table seam, and then checks whether
Article 11's self-reported verdict MATCHES this outside recomputation.

This is the assurance thesis made runnable: the Constitution is the product, and
the verification is infrastructure. Trust is optional; verification is not.

Verifies, read-only, no writes:
  CHECK 1  Canonical spine: walk prev_hash backward from the head to genesis;
           every pointer must resolve to a real block. (The unbroken-history claim.)
  CHECK 2  Strict sequential: walk blocks by id; report the FIRST and ALL breaks.
  CHECK 3  Genesis: the head-reachable genesis hash equals the documented one.
  CROSS    Compare CHECKS 1-3 against the live self-report endpoints. AGREEMENT
           means the public verdict is independently corroborated.
  STATE    Global X-Chain / X-Strict-Table headers + /api/v1/certify PREVIEW gate.

Usage:  python a11_independent_chain_verifier.py
Author: S2_CASE (Witness). CC0 - fork it, run it, prove it yourself.
"""
import json, urllib.request, urllib.error, hashlib

BASE = "https://article11-chat-api.steviesonz.workers.dev"
DOCUMENTED_GENESIS = "6760277edfdda62a2716e5ea82f0c57e0ec21dc23bfe716aa95117035cfefbf9"
OUT = r"A:\article11\coordination\_s2_independent_verifier_result.json"
UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36"
ZERO = "0" * 64

def fetch(path, timeout=45, method="GET", body=None):
    data = json.dumps(body).encode() if body is not None else None
    r = urllib.request.Request(BASE + path, data=data, method=method)
    r.add_header("User-Agent", UA); r.add_header("Accept", "application/json, text/plain, */*")
    if data is not None: r.add_header("Content-Type", "application/json")
    try:
        with urllib.request.urlopen(r, timeout=timeout) as resp:
            raw = resp.read().decode("utf-8", "replace")
            hdrs = {k.lower(): v for k, v in resp.headers.items()}
            try: return resp.status, json.loads(raw), hdrs
            except Exception: return resp.status, None, hdrs
    except urllib.error.HTTPError as e:
        try: return e.code, json.loads(e.read().decode("utf-8","replace")), {}
        except Exception: return e.code, None, {}
    except Exception as e:
        return "ERR:"+str(e), None, {}

def norm(b):
    """Normalize a block record across endpoint shapes."""
    bid = b.get("id", b.get("block_id", b.get("index")))
    return {"id": bid, "hash": b.get("hash") or b.get("block_hash"),
            "prev": b.get("prev_hash") or b.get("prev_block_hash"),
            "event": b.get("event_type"), "canonical": b.get("canonical")}

result = {"base": BASE}

# ---- Pull the raw public chain ----
st, j, _ = fetch("/api/chain")
blocks = []
if isinstance(j, dict) and isinstance(j.get("blocks"), list):
    blocks = [norm(b) for b in j["blocks"]]
st_rows, j_rows, _ = fetch("/api/ironledger/strict-table?preimage=true")
strict_blocks = []
strict_source = "/api/chain"
strict_raw_rows = []
if isinstance(j_rows, dict) and isinstance(j_rows.get("rows"), list):
    strict_raw_rows = j_rows["rows"]
    strict_blocks = [norm(b) for b in strict_raw_rows]
    strict_source = "/api/ironledger/strict-table"
else:
    strict_blocks = blocks
result["chain_pull"] = {"status": st, "chain_length_reported": (j.get("chain_length") if isinstance(j,dict) else None),
                        "blocks_received": len(blocks),
                        "strict_table_status": st_rows,
                        "strict_table_rows_received": len(strict_blocks) if strict_source != "/api/chain" else 0,
                        "strict_table_source": strict_source}

by_hash = {b["hash"]: b for b in blocks if b["hash"]}
have_ids = all(b["id"] is not None for b in blocks) and len(blocks) > 0
by_id = {}
if have_ids:
    by_id = {b["id"]: b for b in blocks}

# ---- CHECK 1: canonical spine = backward prev_hash walk from the head to genesis ----
head = None
if have_ids:
    head = max(blocks, key=lambda b: b["id"])
elif blocks:
    head = blocks[-1]
spine_len, dangling, genesis_reached, genesis_hash = 0, None, False, None
seen = set()
cur = head
steps = 0
while cur and steps < len(blocks) + 5:
    steps += 1; spine_len += 1
    if cur["hash"] in seen:
        dangling = {"loop_at_hash": cur["hash"][:16]}; break
    seen.add(cur["hash"])
    p = cur["prev"]
    if p in (None, "", ZERO):
        genesis_reached = True; genesis_hash = cur["hash"]; break
    nxt = by_hash.get(p)
    if nxt is None:
        dangling = {"at_block_id": cur["id"], "unresolved_prev_hash": (p[:16] if p else None)}; break
    cur = nxt
check1 = {"spine_length": spine_len, "genesis_reached": genesis_reached,
          "dangling_pointer": dangling, "off_spine_blocks": (len(blocks) - spine_len) if genesis_reached else None,
          "PASS": bool(genesis_reached and dangling is None)}

# ---- CHECK 2: strict sequential walk by id (first break + count) ----
strict_first_break, strict_breaks = None, 0
strict_have_ids = all(b["id"] is not None for b in strict_blocks) and len(strict_blocks) > 0
strict_by_id = {b["id"]: b for b in strict_blocks} if strict_have_ids else {}
if strict_have_ids:
    ids = sorted(strict_by_id.keys())
    for i in range(1, len(ids)):
        a, b = strict_by_id[ids[i-1]], strict_by_id[ids[i]]
        if b["prev"] != a["hash"]:
            strict_breaks += 1
            if strict_first_break is None:
                strict_first_break = {"break_at_block": b["id"], "expected_prev": (a["hash"][:16] if a["hash"] else None),
                                      "found_prev": (b["prev"][:16] if b["prev"] else None)}
check2 = {"strict_first_break": strict_first_break, "strict_total_breaks": strict_breaks,
          "source": strict_source,
          "note": "Strict all-row sequential walk; breaks occur where a canonical block links past non-canonical operational rows."}

# ---- CHECK 4: hash recompute from public opt-in preimages ----
hash_checked, hash_valid, preimage_sha_mismatch = 0, 0, 0
public_redaction_preimage_count, unexplained_preimage_sha_mismatch = 0, 0
hash_mismatches = []
preimage_mismatch_rows = []
if strict_raw_rows:
    for r in strict_raw_rows:
        preimage = r.get("hash_preimage")
        if not isinstance(preimage, str):
            continue
        hash_checked += 1
        public_redacted = "[redacted" in preimage.lower()
        if public_redacted:
            public_redaction_preimage_count += 1
        recomputed = hashlib.sha256(preimage.encode("utf-8")).hexdigest()
        if r.get("hash_preimage_sha256") and recomputed != r.get("hash_preimage_sha256"):
            preimage_sha_mismatch += 1
            if not public_redacted:
                unexplained_preimage_sha_mismatch += 1
            preimage_mismatch_rows.append({
                "id": r.get("id"),
                "event_type": r.get("event_type"),
                "public_redacted": public_redacted,
                "server_preimage_sha_prefix": (r.get("hash_preimage_sha256") or "")[:16],
                "client_preimage_sha_prefix": recomputed[:16]
            })
        if recomputed == r.get("hash"):
            hash_valid += 1
        else:
            hash_mismatches.append({
                "id": r.get("id"),
                "event_type": r.get("event_type"),
                "canonical": r.get("canonical"),
                "stored_hash_prefix": (r.get("hash") or "")[:16],
                "recomputed_hash_prefix": recomputed[:16],
                "stored_hash_is_sha256": bool(isinstance(r.get("hash"), str) and len(r.get("hash")) == 64 and all(c in "0123456789abcdefABCDEF" for c in r.get("hash")))
            })
check4 = {
    "source": strict_source,
    "preimages_checked": hash_checked,
    "preimage_sha256_mismatch_count": preimage_sha_mismatch,
    "public_redaction_preimage_count": public_redaction_preimage_count,
    "unexplained_preimage_sha256_mismatch_count": unexplained_preimage_sha_mismatch,
    "preimage_sha256_mismatch_rows": preimage_mismatch_rows[:50],
    "hash_valid_current_recipe_count": hash_valid,
    "hash_mismatch_current_recipe_count": len(hash_mismatches),
    "mismatch_rows": hash_mismatches[:50],
    "endpoint_hash_verification": (j_rows.get("hash_verification") if isinstance(j_rows, dict) else None),
    "note": "Rows with legacy/manual stored hash formats are disclosed instead of hidden. Public redaction can intentionally prevent exact preimage recompute for sensitive rows. Linkage validity and current-recipe hash validity are separate checks."
}

# ---- CHECK 3: genesis ----
check3 = {"head_reachable_genesis_hash": (genesis_hash[:24] + "..." if genesis_hash else None),
          "documented_genesis": DOCUMENTED_GENESIS[:24] + "...",
          "PASS": bool(genesis_hash == DOCUMENTED_GENESIS)}

# ---- CROSS-CHECK vs the live self-report ----
st_s, js, hdrs_s = fetch("/api/chain/status")
st_v, jv, _ = fetch("/api/ironledger/verify")
selfreport = {
    "chain_status.status": (js.get("status") if isinstance(js,dict) else None),
    "chain_status.canonical_spine_valid": (js.get("canonical_spine_valid") if isinstance(js,dict) else None),
    "chain_status.strict_table_valid": (js.get("strict_table_valid") if isinstance(js,dict) else None),
    "chain_status.strict_table_break_at_block": (js.get("strict_table_break_at_block") if isinstance(js,dict) else None),
    "ironledger_verify.canonical_spine_valid": (jv.get("canonical_spine_valid") if isinstance(jv,dict) else None),
    "ironledger_verify.strict_table_valid": (jv.get("strict_table_valid") if isinstance(jv,dict) else None),
    "ironledger_verify.error": (jv.get("error") if isinstance(jv,dict) else None),
}
# /api/chain returns the CANONICAL view (non-canonical operational rows excluded),
# so a strict all-row walk over it shows 0 breaks - consistent with a clean spine, NOT
# a contradiction. To INDEPENDENTLY corroborate the strict-table seam we pull the
# individual rows around the reported break (these DO include non-canonical rows).
reported_break = selfreport["chain_status.strict_table_break_at_block"]
window_break, window_detail = None, None
if strict_source == "/api/ironledger/strict-table" and strict_first_break:
    window_break = strict_first_break["break_at_block"]
    window_detail = {"break_at_block": strict_first_break["break_at_block"],
                     "expected_prev_prior_row_hash": strict_first_break["expected_prev"],
                     "found_prev": strict_first_break["found_prev"],
                     "method": "one-call strict-table endpoint"}
elif isinstance(reported_break, int):
    win = {}
    for n in range(reported_break - 6, reported_break + 4):
        _sw, jw, _h = fetch("/api/chain/block/%d" % n, timeout=20)
        nb = norm(jw.get("block") if isinstance(jw, dict) and isinstance(jw.get("block"), dict) else jw) if isinstance(jw, dict) else None
        if nb and nb["id"] is not None:
            win[nb["id"]] = nb
    wids = sorted(win.keys())
    for i in range(1, len(wids)):
        a, b = win[wids[i-1]], win[wids[i]]
        if b["prev"] != a["hash"]:
            window_break = b["id"]
            window_detail = {"break_at_block": b["id"], "prior_row_id": a["id"],
                             "expected_prev_prior_row_hash": (a["hash"][:16] if a["hash"] else None),
                             "found_prev": (b["prev"][:16] if b["prev"] else None)}
            break
check2b_method = "one-call strict-table endpoint" if strict_source == "/api/ironledger/strict-table" else "independent individual-row pull around the reported break (includes non-canonical rows)"
check2b = {"method": check2b_method,
           "window_first_break": window_break, "detail": window_detail}

indep_break = strict_first_break["break_at_block"] if strict_first_break else None
agreement = {
    "independent_canonical_spine_valid": check1["PASS"] and check3["PASS"],
    "selfreport_canonical_spine_valid": bool(selfreport["chain_status.canonical_spine_valid"]),
    "canonical_verdict_agrees": (check1["PASS"] and check3["PASS"]) == bool(selfreport["chain_status.canonical_spine_valid"]),
    "full_table_strict_break": indep_break,
    "full_table_strict_source": strict_source,
    "api_chain_is_canonical_view": (strict_source == "/api/chain" and indep_break is None),
    "independent_window_strict_break": window_break,
    "selfreport_strict_break_at_block": reported_break,
    "strict_seam_independently_corroborated": (window_break == reported_break),
}

# ---- DEPLOY STATE: global headers + certify PREVIEW gate ----
st_c, jc, _ = fetch("/api/v1/certify", method="POST",
                    body={"prompt": "independent verifier", "response": "verifying"})
cd = (jc or {}).get("data", jc) if isinstance(jc, dict) else {}
state = {
    "x_chain_header": hdrs_s.get("x-chain"),
    "x_strict_table_header": hdrs_s.get("x-strict-table"),
    "certify_status": (cd.get("status") if isinstance(cd,dict) else None),
    "certify_chain_anchor": (cd.get("chain_anchor") if isinstance(cd,dict) else None),
    "certify_has_chain_proof": ("chain_proof" in cd) if isinstance(cd,dict) else None,
}

result.update({"CHECK1_canonical_spine": check1, "CHECK2_strict_sequential_over_api_chain": check2,
               "CHECK2b_strict_window_corroboration": check2b,
               "CHECK3_genesis": check3, "CHECK4_hash_recompute": check4, "self_report": selfreport,
               "AGREEMENT": agreement, "deploy_state": state})

result["VERDICT"] = {
    "canonical_chain_independently_unbroken_to_genesis": check1["PASS"] and check3["PASS"],
    "canonical_verdict_matches_self_report": agreement["canonical_verdict_agrees"],
    "strict_seam_independently_corroborated_at_reported_block": agreement["strict_seam_independently_corroborated"],
    "hash_preimages_independently_recomputed": hash_checked > 0 and unexplained_preimage_sha_mismatch == 0,
    "public_redaction_preimage_count": public_redaction_preimage_count,
    "unexplained_preimage_sha256_mismatch_count": unexplained_preimage_sha_mismatch,
    "current_recipe_hash_valid_count": hash_valid,
    "current_recipe_hash_mismatch_count": len(hash_mismatches),
    "all_current_recipe_hashes_match": (hash_checked > 0 and len(hash_mismatches) == 0),
    "certify_safely_in_preview": (state["certify_status"] == "PREVIEW" and state["certify_chain_anchor"] is None and state["certify_has_chain_proof"] is False),
}

json.dump(result, open(OUT, "w", encoding="utf-8"), indent=2)
print("WROTE", OUT)
print("VERDICT:", json.dumps(result["VERDICT"]))
