from datetime import datetime

def _dt(s):
    if not s: return None
    return datetime.fromisoformat(s.replace('Z','+00:00'))

def decide(c):
    # Fail closed before interpreting provider semantics.
    if not c.get('authenticity_ref') or not c.get('payload_digest') or not c.get('evidence_ref'):
        return 'HOLD'
    if c.get('duplicate_seen', False):
        return 'DUPLICATE'
    event_time=_dt(c.get('provider_event_time'))
    cursor=_dt(c.get('canonical_event_time'))
    if event_time and cursor and event_time < cursor:
        return 'OUT_OF_ORDER'
    if c.get('seen_in_pull',False) and not c.get('seen_in_push',False):
        return 'RECONCILIATION_GAP'
    if not c.get('adapter_recomputed',False) and c.get('carrier') != c.get('baseline_carrier',c.get('carrier')):
        return 'UNKNOWN'
    if c.get('event_kind') == 'ESTIMATE_UPDATE':
        return 'ESTIMATE_ONLY'
    mapped=c.get('mapped_state')
    if mapped in ('IN_TRANSIT','EXCEPTION','DELIVERED'):
        return mapped
    return 'UNKNOWN'

def invariants(c):
    got=decide(c)
    event_time=_dt(c.get('provider_event_time')); cursor=_dt(c.get('canonical_event_time'))
    return [
      # Delivery cannot be created from ETA or descriptive text alone.
      not (got=='DELIVERED' and (c.get('event_kind')=='ESTIMATE_UPDATE' or c.get('delivery_by_text_only',False))),
      # Duplicate must not be allowed to mutate canonical business state.
      not (got=='DUPLICATE' and c.get('would_mutate_canonical',False)),
      # Late older event must not regress canonical state.
      not (event_time and cursor and event_time < cursor and c.get('would_regress_state',False)),
      # Mapping key must be provider code/context, never free text.
      c.get('mapping_key_kind') != 'provider_event_text',
      # Carrier transfer requires adapter recomputation.
      not (c.get('carrier') != c.get('baseline_carrier',c.get('carrier')) and not c.get('adapter_recomputed',False) and got not in ('UNKNOWN','HOLD')),
    ]

def check_case(c):
    got=decide(c)
    return got, got==c['expected']
