def decide(c):
    # Fail closed on missing decisive identity/provenance.
    if not c.get('scan_session_id') or not c.get('operator_id') or not c.get('expected_context_id') or not c.get('evidence_ref'):
        return 'HOLD'
    adapter=c.get('capture_adapter')
    if c.get('transfer_changed') and not c.get('adapter_recomputed',False):
        return 'UNKNOWN'
    # BarcodeDetector-specific capability gates apply only to that adapter.
    if adapter=='barcode_detector':
        if not c.get('secure_context',False) or not c.get('detector_available',False):
            return 'FALLBACK_REQUIRED'
        expected=set(c.get('expected_symbology_set',[]))
        supported=set(c.get('supported_formats',[]))
        if not expected or not expected.issubset(supported):
            return 'FALLBACK_REQUIRED'
    if c.get('manual_entry_used',False) and not c.get('fallback_reason'):
        return 'HOLD'
    if c.get('decoded_format') not in set(c.get('expected_symbology_set',[])):
        return 'REJECTED_SYMBOLOGY'
    if not c.get('parse_ok',False) or not c.get('structure_ok',False):
        return 'REJECTED_STRUCTURE'
    if not c.get('context_match',False):
        return 'REJECTED_CONTEXT_MISMATCH'
    if c.get('duplicate_status')=='DUPLICATE':
        return 'DUPLICATE_SUPPRESSED'
    if not c.get('normalized_identifier') or not c.get('duplicate_key'):
        return 'HOLD'
    return 'ACCEPTED_VERIFIED'

def invariants(c):
    got=decide(c)
    return [
      # Decoder success alone cannot yield business acceptance.
      not (got=='ACCEPTED_VERIFIED' and not c.get('context_match',False)),
      # A duplicate can never be accepted again.
      not (got=='ACCEPTED_VERIFIED' and c.get('duplicate_status')=='DUPLICATE'),
      # Accepted evidence must be structurally valid.
      not (got=='ACCEPTED_VERIFIED' and (not c.get('parse_ok',False) or not c.get('structure_ok',False))),
      # Manual fallback must preserve why fallback happened.
      not (got=='ACCEPTED_VERIFIED' and c.get('manual_entry_used',False) and not c.get('fallback_reason')),
      # A changed platform/adapter cannot reuse an unrecomputed baseline decision.
      not (got=='ACCEPTED_VERIFIED' and c.get('transfer_changed') and not c.get('adapter_recomputed',False)),
    ]

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