MISSING={None,"","MISSING","UNKNOWN","N/A"}
REQ=['run_id','attempt_id','failure_class','failure_phase','error_evidence_ref','side_effect_state','idempotency_key','retry_eligible','attempt_budget','attempt_number','backoff_policy_ref','dependency_health','reconciliation_required','authoritative_state_ref','reconciliation_state','human_escalation_required','final_disposition','audit_ref']
SIDE_EFFECTING_PHASES={'DURING_TOOL','AFTER_TOOL_REQUEST','AFTER_TOOL_RESPONSE'}

def missing(v): return v in MISSING

def evaluate(x):
    for k in REQ:
        if k not in x: return 'HOLD_MISSING_FIELD'
    # identity/evidence fields must not be placeholders
    for k in ['run_id','attempt_id','failure_class','failure_phase','error_evidence_ref','audit_ref']:
        if missing(x.get(k)): return 'HOLD_MISSING_FIELD'
    try:
        budget=int(x['attempt_budget']); attempt=int(x['attempt_number'])
    except Exception:
        return 'HOLD_ATTEMPT_BUDGET_INVALID'
    if budget < 1 or attempt < 1: return 'HOLD_ATTEMPT_BUDGET_INVALID'
    if attempt > budget: return 'STOP_ATTEMPT_BUDGET_EXHAUSTED'
    retry=x['retry_eligible'] is True or str(x['retry_eligible']).lower()=='true'
    recon=x['reconciliation_required'] is True or str(x['reconciliation_required']).lower()=='true'
    esc=x['human_escalation_required'] is True or str(x['human_escalation_required']).lower()=='true'
    phase=x['failure_phase']; side=x['side_effect_state']; dep=x['dependency_health']
    # phase/side-effect consistency
    if phase=='BEFORE_TOOL_INVOCATION' and side not in {'NOT_STARTED','CONFIRMED_ABSENT'}:
        return 'HOLD_PHASE_SIDE_EFFECT_CONFLICT'
    if side=='UNKNOWN_AFTER_TIMEOUT':
        if retry and missing(x.get('idempotency_key')) and not recon:
            return 'HOLD_AMBIGUOUS_SIDE_EFFECT'
        if not recon and x.get('final_disposition') in {'SUCCESS','RECONCILED_SUCCESS_NO_RETRY'}:
            return 'HOLD_RECONCILIATION_REQUIRED'
        if retry and missing(x.get('idempotency_key')):
            return 'HOLD_IDEMPOTENCY_REQUIRED'
    # side-effecting replay protection
    if retry and phase in SIDE_EFFECTING_PHASES and side not in {'NOT_STARTED','CONFIRMED_ABSENT'}:
        if missing(x.get('idempotency_key')):
            return 'HOLD_IDEMPOTENCY_REQUIRED'
    # retry control
    if retry and missing(x.get('backoff_policy_ref')):
        return 'HOLD_BACKOFF_REQUIRED'
    if retry and dep in {'DEGRADED','DOWN','OVERLOADED'}:
        return 'HOLD_DEPENDENCY_DEGRADED'
    if recon:
        if missing(x.get('authoritative_state_ref')): return 'HOLD_AUTHORITATIVE_STATE_REQUIRED'
        if x.get('reconciliation_state') in {'NOT_RUN','PENDING','UNKNOWN','MISMATCH_UNRESOLVED'} or missing(x.get('reconciliation_state')):
            return 'HOLD_RECONCILIATION_UNRESOLVED'
    # disposition consistency
    final=x['final_disposition']
    if final in {'SUCCESS','RECONCILED_SUCCESS_NO_RETRY'}:
        if side=='UNKNOWN_AFTER_TIMEOUT' and not (recon and x.get('reconciliation_state') in {'MATCHED_APPLIED','MATCHED_ABSENT'}):
            return 'HOLD_SUCCESS_WITH_UNRESOLVED_SIDE_EFFECT'
    if final=='SAFE_RETRY_SCHEDULED' and not retry:
        return 'HOLD_RETRY_DISPOSITION_CONFLICT'
    if final in {'ESCALATION_REVIEW_REQUIRED','TRANSFER_ESCALATION_REQUIRED'} and not esc:
        return 'HOLD_ESCALATION_FLAG_REQUIRED'
    # approved baseline states
    if final=='RECONCILED_SUCCESS_NO_RETRY' and recon and x.get('reconciliation_state')=='MATCHED_APPLIED' and not retry:
        return 'RECONCILED_SUCCESS_NO_RETRY'
    if final=='SAFE_RETRY_SCHEDULED' and retry and dep=='HEALTHY' and attempt < budget:
        return 'SAFE_RETRY_SCHEDULED'
    if final=='TRANSFER_ESCALATION_REQUIRED' and phase=='BEFORE_TOOL_INVOCATION' and side=='NOT_STARTED' and dep in {'DEGRADED','DOWN','OVERLOADED'} and esc and not retry:
        return 'TRANSFER_ESCALATION_REQUIRED'
    if final=='ESCALATION_REVIEW_REQUIRED' and esc:
        return 'ESCALATION_REVIEW_REQUIRED'
    return 'HOLD_DISPOSITION_NOT_PROVEN'
