#!/usr/bin/env python3
"""Check run receipts against an independently sourced expected-slot list and witness events."""
import json, sys

def load(path):
    with open(path, encoding='utf-8') as f: return json.load(f)

def check(doc):
    slots = {x['slot'] for x in doc['expected_slots']}
    receipts = {x['slot']: x for x in doc['run_receipts']}
    witness = {x['event_id']: x for x in doc.get('external_witness', [])}
    errors=[]
    if len(slots) != len(doc['expected_slots']): errors.append('DUPLICATE_EXPECTED_SLOT')
    missing = sorted(slots - receipts.keys())
    extra = sorted(receipts.keys() - slots)
    if missing: errors.append('MISSING_RUN_RECEIPT:' + ','.join(missing))
    if extra: errors.append('UNEXPECTED_RUN_RECEIPT:' + ','.join(extra))
    for slot, r in receipts.items():
        if r.get('status') not in {'changed','confirmed','inconclusive','no_op','refused','failed'}:
            errors.append('BAD_STATUS:' + slot)
        if r.get('status') == 'no_op' and not r.get('items_evaluated', 0):
            errors.append('NOOP_WITHOUT_EVALUATION:' + slot)
    # A public write must have a local receipt; this witness is independent of the ledger.
    receipt_events = {e for r in receipts.values() for e in r.get('event_ids', [])}
    unwitnessed_receipts = sorted(set(witness) - receipt_events)
    if unwitnessed_receipts: errors.append('EXTERNAL_EVENT_MISSING_LOCAL_RECEIPT:' + ','.join(unwitnessed_receipts))
    return {'ok': not errors, 'expected_slots':len(slots), 'receipts':len(receipts),
            'missing_receipts':missing, 'unwitnessed_external_events':unwitnessed_receipts,
            'errors':errors,
            'limits':['External witness covers only events it records; it cannot prove a silent/no-op wake occurred.',
                      'Expected-slot source must be independently maintained; this checker does not validate the schedule itself.']}

if __name__ == '__main__':
    result=check(load(sys.argv[1]))
    print(json.dumps(result, indent=2, sort_keys=True))
    raise SystemExit(0 if result['ok'] else 1)
