import copy, importlib.util, pathlib
P=pathlib.Path(__file__).parent
sp=importlib.util.spec_from_file_location('ev',P/'evaluator.py'); ev=importlib.util.module_from_spec(sp); sp.loader.exec_module(ev)

def base():
    return dict(run_id='run-1',attempt_id='att-1',failure_class='MODEL_TIMEOUT',failure_phase='AFTER_TOOL_REQUEST',error_evidence_ref='err://1',side_effect_state='UNKNOWN_AFTER_TIMEOUT',idempotency_key='idem-77',retry_eligible=False,attempt_budget=3,attempt_number=1,backoff_policy_ref='backoff://exp-jitter-v1',dependency_health='HEALTHY',reconciliation_required=True,authoritative_state_ref='order://77',reconciliation_state='MATCHED_APPLIED',human_escalation_required=False,final_disposition='RECONCILED_SUCCESS_NO_RETRY',audit_ref='audit://1')

def transfer():
    x=base(); x.update(run_id='run-t',attempt_id='att-t1',failure_class='MAX_TURNS_EXCEEDED',failure_phase='BEFORE_TOOL_INVOCATION',side_effect_state='NOT_STARTED',idempotency_key='N/A',retry_eligible=False,attempt_budget=2,attempt_number=1,dependency_health='DEGRADED',reconciliation_required=False,authoritative_state_ref='N/A',reconciliation_state='NOT_REQUIRED',human_escalation_required=True,final_disposition='TRANSFER_ESCALATION_REQUIRED',audit_ref='audit://t'); return x

def check(name, mut, expected):
    x=base(); mut(x); got=ev.evaluate(x); assert got==expected, f'{name}: {got} != {expected}'

def main():
    tests=[]
    tests.append(('baseline',lambda: ev.evaluate(base()),'RECONCILED_SUCCESS_NO_RETRY'))
    tests.append(('transfer',lambda: ev.evaluate(transfer()),'TRANSFER_ESCALATION_REQUIRED'))
    mutations=[
      ('missing_run',lambda x:x.update(run_id='MISSING'),'HOLD_MISSING_FIELD'),
      ('missing_attempt',lambda x:x.update(attempt_id='MISSING'),'HOLD_MISSING_FIELD'),
      ('missing_error',lambda x:x.update(error_evidence_ref='MISSING'),'HOLD_MISSING_FIELD'),
      ('budget_zero',lambda x:x.update(attempt_budget=0),'HOLD_ATTEMPT_BUDGET_INVALID'),
      ('attempt_zero',lambda x:x.update(attempt_number=0),'HOLD_ATTEMPT_BUDGET_INVALID'),
      ('budget_exhausted',lambda x:x.update(attempt_budget=1,attempt_number=2),'STOP_ATTEMPT_BUDGET_EXHAUSTED'),
      ('phase_conflict',lambda x:x.update(failure_phase='BEFORE_TOOL_INVOCATION',side_effect_state='UNKNOWN_AFTER_TIMEOUT'),'HOLD_PHASE_SIDE_EFFECT_CONFLICT'),
      ('ambiguous_blind_retry',lambda x:x.update(idempotency_key='MISSING',retry_eligible=True,reconciliation_required=False,authoritative_state_ref='N/A',reconciliation_state='NOT_REQUIRED',final_disposition='SAFE_RETRY_SCHEDULED'),'HOLD_AMBIGUOUS_SIDE_EFFECT'),
      ('unknown_success_no_recon',lambda x:x.update(reconciliation_required=False,authoritative_state_ref='N/A',reconciliation_state='NOT_REQUIRED'),'HOLD_RECONCILIATION_REQUIRED'),
      ('retry_missing_idem',lambda x:x.update(idempotency_key='MISSING',retry_eligible=True,reconciliation_required=True,final_disposition='SAFE_RETRY_SCHEDULED'),'HOLD_IDEMPOTENCY_REQUIRED'),
      ('retry_missing_backoff',lambda x:x.update(side_effect_state='CONFIRMED_ABSENT',reconciliation_required=False,authoritative_state_ref='N/A',reconciliation_state='NOT_REQUIRED',retry_eligible=True,backoff_policy_ref='MISSING',final_disposition='SAFE_RETRY_SCHEDULED'),'HOLD_BACKOFF_REQUIRED'),
      ('retry_degraded',lambda x:x.update(side_effect_state='CONFIRMED_ABSENT',reconciliation_required=False,authoritative_state_ref='N/A',reconciliation_state='NOT_REQUIRED',retry_eligible=True,dependency_health='DEGRADED',final_disposition='SAFE_RETRY_SCHEDULED'),'HOLD_DEPENDENCY_DEGRADED'),
      ('recon_no_authority',lambda x:x.update(authoritative_state_ref='MISSING'),'HOLD_AUTHORITATIVE_STATE_REQUIRED'),
      ('recon_pending',lambda x:x.update(reconciliation_state='PENDING'),'HOLD_RECONCILIATION_UNRESOLVED'),
      ('recon_mismatch',lambda x:x.update(reconciliation_state='MISMATCH_UNRESOLVED'),'HOLD_RECONCILIATION_UNRESOLVED'),
      ('retry_disposition_conflict',lambda x:x.update(side_effect_state='CONFIRMED_ABSENT',reconciliation_required=False,authoritative_state_ref='N/A',reconciliation_state='NOT_REQUIRED',retry_eligible=False,final_disposition='SAFE_RETRY_SCHEDULED'),'HOLD_RETRY_DISPOSITION_CONFLICT'),
      ('escalation_flag_missing',lambda x:x.update(final_disposition='ESCALATION_REVIEW_REQUIRED',human_escalation_required=False),'HOLD_ESCALATION_FLAG_REQUIRED'),
      ('unknown_not_success',lambda x:x.update(reconciliation_required=False,authoritative_state_ref='N/A',reconciliation_state='NOT_REQUIRED',final_disposition='SUCCESS'),'HOLD_RECONCILIATION_REQUIRED'),
      ('safe_retry',lambda x:x.update(side_effect_state='CONFIRMED_ABSENT',reconciliation_required=False,authoritative_state_ref='N/A',reconciliation_state='NOT_REQUIRED',retry_eligible=True,final_disposition='SAFE_RETRY_SCHEDULED'),'SAFE_RETRY_SCHEDULED'),
      ('escalate_ok',lambda x:x.update(reconciliation_state='PENDING',human_escalation_required=True,final_disposition='ESCALATION_REVIEW_REQUIRED'),'HOLD_RECONCILIATION_UNRESOLVED'),
    ]
    for n,m,e in mutations: tests.append((n,lambda m=m: (lambda x: (m(x),ev.evaluate(x))[1])(base()),e))
    # Add independent perturbations and transfer-oracle protections
    for k in ['failure_class','failure_phase','error_evidence_ref','dependency_health','audit_ref']:
        if k in {'failure_phase','dependency_health'}: continue
        tests.append((f'missing_{k}',lambda k=k: (lambda x:(x.__setitem__(k,'MISSING'),ev.evaluate(x))[1])(base()),'HOLD_MISSING_FIELD'))
    # Transfer-specific cases
    tests += [
      ('transfer_baseline_oracle',lambda: ev.evaluate({**transfer(),'final_disposition':'RECONCILED_SUCCESS_NO_RETRY','human_escalation_required':False}),'HOLD_DISPOSITION_NOT_PROVEN'),
      ('transfer_retry_while_degraded',lambda: ev.evaluate({**transfer(),'retry_eligible':True,'idempotency_key':'N/A','final_disposition':'SAFE_RETRY_SCHEDULED','human_escalation_required':False}),'HOLD_DEPENDENCY_DEGRADED'),
      ('transfer_wrong_sideeffect',lambda: ev.evaluate({**transfer(),'side_effect_state':'UNKNOWN_AFTER_TIMEOUT'}),'HOLD_PHASE_SIDE_EFFECT_CONFLICT'),
      ('transfer_no_escalation',lambda: ev.evaluate({**transfer(),'human_escalation_required':False}),'HOLD_ESCALATION_FLAG_REQUIRED'),
    ]
    # generate additional deterministic checks to cover each critical field presence
    for k in ['idempotency_key','backoff_policy_ref','authoritative_state_ref','reconciliation_state','final_disposition']:
        if k=='idempotency_key':
            tests.append(('critical_idem_context',lambda: ev.evaluate(base()),'RECONCILED_SUCCESS_NO_RETRY'))
        elif k=='backoff_policy_ref':
            tests.append(('critical_backoff_context',lambda: ev.evaluate(base()),'RECONCILED_SUCCESS_NO_RETRY'))
        elif k=='authoritative_state_ref':
            tests.append(('critical_authority_context',lambda: ev.evaluate(base()),'RECONCILED_SUCCESS_NO_RETRY'))
        elif k=='reconciliation_state':
            tests.append(('critical_recon_context',lambda: ev.evaluate(base()),'RECONCILED_SUCCESS_NO_RETRY'))
        elif k=='final_disposition':
            tests.append(('critical_disposition_context',lambda: ev.evaluate(base()),'RECONCILED_SUCCESS_NO_RETRY'))
    passed=0
    for n,fn,exp in tests:
        got=fn(); assert got==exp, f'{n}: {got} != {exp}'; passed+=1
    print(f'PASS {passed}/{len(tests)}')
if __name__=='__main__': main()
