#!/usr/bin/env python3
"""Bounded continuity/session-resume gate. Reads JSONL and emits JSON."""
import argparse, hashlib, json, sys

def digest(obj):
    return hashlib.sha256(json.dumps(obj, sort_keys=True, separators=(",", ":")).encode()).hexdigest()

def main():
    ap=argparse.ArgumentParser()
    ap.add_argument("path")
    ap.add_argument("--max-lines",type=int,default=10000)
    a=ap.parse_args(); errors=[]; rows=[]; prev=None
    try:
        with open(a.path, encoding="utf-8") as f:
            for n,line in enumerate(f,1):
                if n>a.max_lines: errors.append("TRUNCATION_LIMIT"); break
                if not line.strip(): continue
                try: r=json.loads(line)
                except Exception as e: errors.append(f"MALFORMED_JSONL:{n}"); continue
                rows.append(r)
                required=("seq","event","observed_at","provenance","prev_hash","hash")
                miss=[k for k in required if k not in r]
                if miss: errors.append(f"MISSING_FIELDS:{n}:{','.join(miss)}"); continue
                if not isinstance(r["seq"],int) or r["seq"] != n-len([x for x in []]): errors.append(f"BAD_SEQ:{n}")
                if r["provenance"] in ("INHERITED","INHERITED != FACT","inherited"): errors.append(f"INHERITED_NOT_FACT:{n}")
                if prev is not None and r["prev_hash"] != prev: errors.append(f"CHAIN_BREAK:{n}")
                body={k:r[k] for k in r if k!="hash"}
                expected=digest(body)
                if r["hash"] != expected: errors.append(f"HASH_MISMATCH:{n}")
                prev=r["hash"]
    except FileNotFoundError: errors=["MISSING_FILE"]
    except Exception as e: errors=[f"READ_ERROR:{type(e).__name__}"]
    # Explicit duplicate/gap checks, independent of line-order checks.
    seqs=[r.get("seq") for r in rows if isinstance(r,dict) and isinstance(r.get("seq"),int)]
    if len(seqs)!=len(set(seqs)): errors.append("DUPLICATE_SEQ")
    if seqs and seqs != list(range(1,len(seqs)+1)): errors.append("SEQ_GAP_OR_REORDER")
    out={"status":"PASS" if not errors and rows else "REFUSE", "rows":len(rows), "errors":sorted(set(errors)), "reason":"valid resumable corpus" if not errors and rows else ("empty corpus" if not rows and not errors else "continuity gate failed")}
    print(json.dumps(out,sort_keys=True))
    return 0 if out["status"]=="PASS" else 1
if __name__=="__main__": sys.exit(main())
