#!/usr/bin/env python3
"""
SFT Audit: Witness Stability Certificate (WSC)

This script is intentionally lightweight and transparent:

- validates required fields exist
- checks basic margin inequalities (LCM < pi, HMR < P/2)
- optionally checks internal consistency of the Syncre Stability Modulus (if present)
- optionally checks convergence certificate structure (if present)

It is designed as a template that can later be extended to recompute degree/holonomy
from concrete witness data when such data is included.
"""
import json, math, sys
from pathlib import Path

def fail(msg: str) -> None:
    print("FAIL:", msg)
    sys.exit(1)

def approx_equal(a: float, b: float, tol: float = 1e-9) -> bool:
    return abs(a - b) <= tol

def main() -> None:
    if len(sys.argv) != 2:
        fail("Usage: audit_wsc.py path/to/WSC.json")

    p = Path(sys.argv[1])
    if not p.exists():
        fail(f"File not found: {p}")

    data = json.loads(p.read_text(encoding="utf-8"))

    required = [
        "sft_version","certificate_id","witness_tier","mechanism","target",
        "period_P","witness_value","perturbation_model","margins","assumptions","artifacts"
    ]
    missing = [k for k in required if k not in data]
    if missing:
        fail("Missing required fields: " + ", ".join(missing))

    P = float(data["period_P"])
    if not (P > 0):
        fail("period_P must be > 0")

    # --- basic margin checks ---
    margins = data.get("margins", {}) or {}

    if "LCM_pi" in margins:
        lcm = float(margins["LCM_pi"])
        if not (0 < lcm < math.pi):
            fail(f"LCM_pi must satisfy 0 < LCM_pi < pi. Got {lcm}")

    if "HMR_P" in margins:
        hmr = float(margins["HMR_P"])
        if not (0 < hmr < P/2):
            fail(f"HMR_P must satisfy 0 < HMR_P < P/2. Got {hmr}, P={P}")

    # --- stability modulus consistency (optional) ---
    sm = data.get("stability_modulus", None)
    if sm is not None:
        if not isinstance(sm, dict):
            fail("stability_modulus must be an object when present")

        # check k against witness_value.integer if both exist
        wv = data.get("witness_value", {}) or {}
        wv_int = wv.get("integer", None)
        sm_k = sm.get("k", None)
        if sm_k is not None:
            if not isinstance(sm_k, int):
                fail("stability_modulus.k must be an integer")
            if wv_int is not None:
                try:
                    wv_int_i = int(wv_int)
                except Exception:
                    fail("witness_value.integer must be an integer-like value")
                if wv_int_i != sm_k:
                    fail(f"stability_modulus.k ({sm_k}) must match witness_value.integer ({wv_int_i})")

        # LCM_pi consistency
        if "LCM_pi" in sm:
            sm_lcm = float(sm["LCM_pi"])
            if not (0 < sm_lcm < math.pi):
                fail(f"stability_modulus.LCM_pi must satisfy 0 < LCM_pi < pi. Got {sm_lcm}")
            if "LCM_pi" in margins:
                lcm = float(margins["LCM_pi"])
                if not approx_equal(sm_lcm, lcm, tol=1e-6):
                    fail(f"stability_modulus.LCM_pi ({sm_lcm}) must match margins.LCM_pi ({lcm})")

        # HMR_P consistency
        if "HMR_P" in sm:
            sm_hmr = float(sm["HMR_P"])
            if not (0 < sm_hmr < P/2):
                fail(f"stability_modulus.HMR_P must satisfy 0 < HMR_P < P/2. Got {sm_hmr}, P={P}")
            if "HMR_P" in margins:
                hmr = float(margins["HMR_P"])
                if not approx_equal(sm_hmr, hmr, tol=1e-6):
                    fail(f"stability_modulus.HMR_P ({sm_hmr}) must match margins.HMR_P ({hmr})")

        # distortion bound
        if "b" in sm:
            b = float(sm["b"])
            if not (b > 0):
                fail(f"stability_modulus.b must be > 0. Got {b}")

        # target rank
        if "target_rank" in sm:
            tr = int(sm["target_rank"])
            if tr <= 0:
                fail(f"stability_modulus.target_rank must be >= 1. Got {tr}")

        # convergence rate
        if "lambda" in sm:
            lam = float(sm["lambda"])
            if lam < 0:
                fail(f"stability_modulus.lambda must be >= 0. Got {lam}")

    # --- convergence certificates (optional) ---
    ccs = data.get("convergence_certificates", None)
    if ccs is not None:
        if not isinstance(ccs, list):
            fail("convergence_certificates must be an array when present")
        for i, cc in enumerate(ccs):
            if not isinstance(cc, dict):
                fail(f"convergence_certificates[{i}] must be an object")
            for k in ["convergence_id","kind","target","baseline","assumptions"]:
                if k not in cc:
                    fail(f"convergence_certificates[{i}] missing field: {k}")
            if cc["kind"] not in ("RNF","NDB_MXC"):
                fail(f"convergence_certificates[{i}].kind must be RNF or NDB_MXC")
            if not isinstance(cc["assumptions"], list):
                fail(f"convergence_certificates[{i}].assumptions must be an array")
            if cc["kind"] == "NDB_MXC":
                # For diffusion mixing claims, require a rate or a ledger reference
                if cc.get("rate_lambda") in (None, "") and cc.get("ledger_ref") in (None, ""):
                    fail(f"convergence_certificates[{i}] NDB_MXC must include rate_lambda or ledger_ref")

    print("PASS: Basic WSC checks succeeded")
    print("Summary")
    print("  certificate_id:", data["certificate_id"])
    print("  witness_tier:", data["witness_tier"])
    print("  mechanism:", data["mechanism"])
    print("  target:", data["target"])
    print("  assumptions:", ", ".join(data.get("assumptions", [])))

if __name__ == "__main__":
    main()
