"""Visible deterministic rules for MKN-025 synthetic reputation/trust-signal lab.
They validate evidence consistency only; they do not decide real-person trustworthiness or misconduct.
"""

def inspect_case(f):
    issues=[]
    if not isinstance(f,dict) or f.get('evidence_kind')!='SYNTHETIC': return ['INPUT_INVALID']
    signals=f.get('signals') or []; decision=f.get('decision') or {}; appeal=f.get('appeal') or {}
    if not signals: issues.append('SIGNALS_MISSING')
    if decision.get('auto_misconduct_conclusion'): issues.append('ANOMALY_AUTO_ACCUSATION_FORBIDDEN')
    if decision.get('activity_volume_as_trust'): issues.append('ACTIVITY_VOLUME_AS_TRUST_FORBIDDEN')
    evidence_seen={}
    for s in signals:
        for k in ['signal_id','signal_type','evidence_ref','verification_state','freshness_state','independence_class','correlation_group','manual_review_state']:
            if s.get(k) in (None,'',[]): issues.append('MISSING_'+k.upper())
        if not s.get('provenance_ref'): issues.append('PROVENANCE_MISSING')
        e=s.get('evidence_ref')
        if e:
            evidence_seen.setdefault(e,[]).append(s)
        if s.get('expiry_state')=='EXPIRED' and s.get('included'): issues.append('EXPIRED_ENDORSEMENT_INCLUDED')
        if s.get('revocation_state')=='REVOKED' and s.get('included'): issues.append('REVOKED_CREDENTIAL_INCLUDED')
        if s.get('freshness_state')=='STALE' and s.get('included'): issues.append('STALE_SIGNAL_INCLUDED')
        if s.get('signal_type')=='ACTIVITY_VOLUME' and s.get('included'): issues.append('ACTIVITY_VOLUME_AS_TRUST_FORBIDDEN')
        if s.get('reciprocity_flag') and s.get('manual_review_state') not in {'OPEN','REVIEW_REQUIRED','CLOSED_AFTER_REVIEW'}: issues.append('RECIPROCITY_REVIEW_REQUIRED')
    for e, group in evidence_seen.items():
        if len(group)>1 and any(s.get('duplicate_signal') for s in group) and any(s.get('independence_class')=='INDEPENDENT' for s in group): issues.append('DUPLICATE_INDEPENDENCE_INFLATION')
    groups={}
    for s in signals:
        if s.get('signal_type')=='ENDORSEMENT': groups.setdefault(s.get('correlation_group'),[]).append(s)
    for g, group in groups.items():
        if g and len(group)>1 and all(s.get('independence_class')=='INDEPENDENT' for s in group): issues.append('CORRELATED_ENDORSERS_INDEPENDENCE_MISMATCH')
    if appeal.get('state')=='IN_REVIEW' and appeal.get('new_independent_evidence_ref') and not appeal.get('evidence_considered'): issues.append('APPEAL_EVIDENCE_IGNORED')
    return sorted(set(issues))

def expected_case(f):
    issues=inspect_case(f)
    if issues:
        if issues==['PROVENANCE_MISSING']: return 'INSUFFICIENT_EVIDENCE'
        return 'HOLD'
    signals=f.get('signals') or []
    review=f.get('review') or {}
    if review.get('state') in {'OPEN','REVIEW_REQUIRED'} or any(s.get('manual_review_state')=='OPEN' for s in signals): return 'REVIEW_REQUIRED'
    return 'EXPLAINABLE_TRUST_BOUNDED'

def inspect_transfer(f):
    issues=inspect_case(f)
    if f.get('baseline_decision_copied'): issues.append('BASELINE_DECISION_REUSE_FORBIDDEN')
    hist=f.get('history') or []
    if len(hist)<2: issues.append('DECISION_HISTORY_NOT_PRESERVED')
    if not any(s.get('signal_type')=='INDEPENDENT_REVIEW' and s.get('included') for s in (f.get('signals') or [])): issues.append('APPEAL_INDEPENDENT_EVIDENCE_MISSING')
    if not any(s.get('revocation_state')=='REVOKED' and not s.get('included') for s in (f.get('signals') or [])): issues.append('REVOKED_SIGNAL_NOT_EXCLUDED')
    if not any(s.get('expiry_state')=='EXPIRED' and not s.get('included') for s in (f.get('signals') or [])): issues.append('EXPIRED_SIGNAL_NOT_EXCLUDED')
    if (f.get('appeal') or {}).get('state')!='IN_REVIEW': issues.append('APPEAL_STATE_NOT_RECOMPUTED')
    return sorted(set(issues))

def expected_transfer(f):
    return 'HOLD' if inspect_transfer(f) else 'REVIEW_REQUIRED'
