CRITICAL=['portfolio_item_id','canonical_resource_id','canonical_uri','content_digest','content_version','source_system','source_owner','rights_state','provenance_ref','copy_policy','reference_health','digest_verification_state','metadata_freshness','last_verified_at','evidence_ref']
ALLOWED={'VERIFIED','STALE','BROKEN','HOLD','PARTIAL','DISPUTED','BLOCKED','UNKNOWN'}
def evaluate(case):
    missing=[k for k in CRITICAL if case.get(k) in (None,'',[],'UNKNOWN')]
    reference_ok=bool(case.get('canonical_exists',True) and case.get('uri_resolves',True) and case.get('reference_health')=='HEALTHY')
    digest_ok=bool(case.get('observed_digest')==case.get('content_digest') and case.get('digest_verification_state')=='MATCH')
    rights_ok=case.get('rights_state') in {'ALLOWED','OWNED','LICENSED'}
    provenance_ok=bool(case.get('provenance_ref') not in (None,'','UNKNOWN') and case.get('source_owner') not in (None,'','UNKNOWN'))
    stale=case.get('metadata_age_days',0)>case.get('max_metadata_age_days',90) or case.get('metadata_freshness')=='STALE'
    state='HOLD';reason='INSUFFICIENT_EVIDENCE'
    if case.get('blocked_by_policy') or case.get('rights_state')=='PROHIBITED':
        state='BLOCKED';reason='RIGHTS_OR_POLICY_BLOCK'
    elif missing:
        state='HOLD';reason='CRITICAL_EVIDENCE_MISSING'
    elif case.get('evidence_conflict'):
        state='DISPUTED';reason='RIGHTS_OR_PROVENANCE_CONFLICT'
    elif not reference_ok:
        state='BROKEN';reason='CANONICAL_REFERENCE_BROKEN'
    elif stale or not digest_ok:
        state='STALE';reason='DIGEST_OR_METADATA_STALE'
    elif case.get('material_change_not_recomputed') or case.get('copy_policy')!='REFERENCE_ONLY':
        state='PARTIAL';reason='REGISTRY_UPDATE_OR_COPY_POLICY_REPAIR_REQUIRED'
    elif rights_ok and provenance_ok and digest_ok and reference_ok:
        state='VERIFIED';reason='REFERENCE_DIGEST_PROVENANCE_VERIFIED'
    else:
        state='HOLD';reason='EVIDENCE_NOT_SUFFICIENT_FOR_VERIFIED'
    return {'decision_state':state,'reason_code':reason,'reference_ok':reference_ok,'digest_ok':digest_ok,'rights_ok':rights_ok,'provenance_ok':provenance_ok,'missing_critical_count':len(missing),'recompute_required':bool(stale or not digest_ok or case.get('evidence_conflict') or case.get('material_change_not_recomputed'))}
