
     jCa                       U d Z ddlmZ ddlZddlZddlmZ ddlmZ ddl	m
Z
mZmZ ddlZ ee      j                         j                   j                   j                   Z ee      ej&                  v r!ej&                  j)                   ee             ej&                  j+                  d ee             ddlmZmZmZmZmZmZmZmZmZ ddl m!Z!m"Z"m#Z#m$Z$m%Z%m&Z& dd	l'm(Z(m)Z)m*Z* d:d;d
Z+d<dZ,d=dZ-dddd	 	 	 	 	 	 	 d>dZ.d?dZ/dddddddddZ0de1d<   ddddddddZ2de1d <   d!dddd"d#d$ddZ3de1d%<   d& Z4d' Z5d( Z6d) Z7d* Z8d+ Z9d, Z:d- Z;d. Z<d/ Z=d0 Z>d1 Z?d2 Z@d3 ZAd4 ZBd5 ZCd6 ZDd7 ZEd8 ZFd9 ZGy)@u   tests/regression/test_automation_autonomy_hardening_2521.py

회귀 테스트 — task-2521 automation autonomy hardening
영역 1: bot identity merge capability (#1)
영역 2: Gemini async race (#2)
영역 3: bot session stuck signal (#3)

회장 명시 본질:
  task-2518 PR#70 / task-2519 PR#69 / task-2520 PR#71 모두 mergedBy=JonghyukJeon
  → autonomy 7/10. 본 task 회귀 테스트는 위 3건의 audit fixture replay 필수.

gh api 실호출 금지 — 모두 runner mock / dataclass 직접 주입.
    )annotationsN)fields)Path)AnyDictList)	BlockedReasonBotMergeIdentityMergeIdentityRecordMergePathPlanRepositoryCapabilityclassify_capability_gapinfer_token_source_from_actorprobe_bot_merge_identityselect_merge_path))GEMINI_REVIEW_WAIT_BUDGET_SECONDS_DEFAULTWAITING_FOR_GEMINI_REVIEWExecutorContextTaskSpecevaluate_gemini_async_raceevaluate_pr)LifecycleEvidenceStuckReasondetect_stuck_casesc                4    t        j                  g | ||      S )u   CompletedProcess 생성 헬퍼.)args
returncodestdoutstderr)
subprocessCompletedProcess)r   r   r   s      O/home/jay/workspace/tests/regression/test_automation_autonomy_hardening_2521.pycpr#   @   s    &&B:f]cdd    c                "     ddl dd fd}|S )u   gh pr view <N> --json number,mergedBy 호출에 응답하는 runner factory.

    pr_payloads: {pr_number: dict | None}. None이면 returncode=1.
    r   Nc                   ~t        |       dk  s
| dd g dk7  rt        ddd      S 	 t        | d         }j	                  |      }|t        ddd	      S t        dj                  |      d      S # t        $ r t        ddd      cY S w xY w)
N   r      )ghprview    zunexpected callzbad pr numberz	Not Found)lenr#   int
ValueErrorgetdumps)r   cwdnpayload_jsonpr_payloadss       r"   runnerz#make_pr_view_runner.<locals>.runnerL   s    t9q=D1I)==a.//	.DGA //!$?a[))!U[[)2..  	.a_--	.s   A3 3BBN)r   z	List[str]r3   r   returnsubprocess.CompletedProcess)json)r7   r8   r6   s   ` @r"   make_pr_view_runnerr=   E   s    
 / Mr$   c                     t        di ddddddddddd	dd
ddddddddddddddddddddddddddddddddddd}|j                  |        t        di |S )uN   LifecycleEvidence default builder (모든 task-2521 §3 신규 필드 포함).task_id	task-9999	pr_numberNpr_statemerge_commitmerged_into_mainF	ci_statussmoke_statustimer_statustimer_end_timehas_donehas_done_ackedhas_merge_donehas_qc_resulthas_followuphas_escalate_markerescalate_marker_age_minutestelegram_reply_truncatedbot_session_statusworktree_existsbranch_pushed_to_remoteworktree_mtime_seconds_agohas_pushed_commitsreport_artifact_presentprocess_alive )dictupdater   	overridesbases     r"   make_lifecycle_evidencer^   ]   s      	
           "  %)!" "'#$  %& '( !&), $(-. !/0 !&12 3D6 	KK	$t$$r$   r@   r?   expected_files
dependencyc                <    t        | |xs dgd|xs g ddddd 	      S )Nzutils/foo.pytestserial_onlyr,   F)	r?   r`   	risk_areara   parallel_policymerge_queue_positionstale_recheck_requiredcherry_pick_allowedsmoke_command)r   r_   s      r"   make_task_specrk   ~   s9     %9.)9#%$!
 
r$   c            	     f    t        dddddd dd       }|j                  |        t        d	i |S )
uN   ExecutorContext default — 회귀 테스트용 (task-2521 §2 fields 포함).Nc                    t        ddd      S )Nr   r-   )r#   )r   r3   timeouts      r"   <lambda>z#make_executor_ctx.<locals>.<lambda>   s    "QB- r$   Tc                     y)NTrX   )_ts    r"   ro   z#make_executor_ctx.<locals>.<lambda>       r$   aaaa1111c                     y r9   rX   )_ss    r"   ro   z#make_executor_ctx.<locals>.<lambda>   rr   r$   )r8   
pr_workdirrj   no_auditmain_log_grepfixture_main_shasleeper)N<   rX   )rY   rZ   r   r[   s     r"   make_executor_ctxr|      s=    ?%#	D 	KK	"T""r$   F   JonghyukJeonUserlogintype	task-2518z2026-05-09T04:50:46Z(257fd5259e8e17d2b02a9cc5aa73f4d3670a3bdb)r?   mergedAtmergeCommit)numbermergedBy_metazDict[str, Any]PR_70_FIXTUREE   z	task-2519z2026-05-09T02:04:50Z)r?   r   PR_69_FIXTUREG   z	task-2520z2026-05-09T06:31:34Z(69b4d14ba33b4f7616870bce5ec329585eafe08bPR_71_FIXTUREc                     t        t              D  ch c]  } | j                   }} h d}|j                  |      sJ d||z
          yc c} w )u8   (1) BotMergeIdentity dataclass에 6 field 모두 존재.>   token_sourcemerge_actor_loginmerge_actor_is_botbot_can_merge_as_appmerge_identity_audit fallback_to_owner_token_detectedu   6 field 누락: missing=N)r   r
   nameissubset)ffield_namesexpecteds      r"   /test_1_1_bot_merge_identity_six_field_dataclassr      sX    #)*:#;<a166<K<H [) 
"8k#9":;) =s   A
c                    t        dt        i      } t        dddg|       }|j                  du sJ |j                  dk(  sJ |j
                  du sJ |j                  dk(  sJ |j                  du sJ t        |j                        d	k(  sJ |j                  d
   }|j                  dk(  sJ |j                  dk(  sJ |j                  du sJ y)uW   (2) task-2518 PR #70 replay — mergedBy=JonghyukJeon → fallback_to_owner_token=True.r}   Jeon-Jonghyukdev_workspacer8   Tr~   F	owner_patr,   r   N)r=   r   r   r   r   r   r   r   r.   r   rA   merged_by_loginfallback_to_owner_token)r8   identityrecs      r"   'test_1_2_pr_70_replay_fallback_detectedr      s     "m!45F'2$W]^H44<<<%%777&&%///  K///((E111x,,-222

'
'
*C==B.000&&$...r$   c                     t        dt        i      } t        dddg|       }|j                  du sJ |j                  dk(  sJ |j
                  d   j                  dk(  sJ y)	uA   (3) task-2519 PR #69 replay — 동일 owner_pat fallback 패턴.r   r   r   r   Tr   r   N)r=   r   r   r   r   r   rA   r8   r   s     r"   'test_1_3_pr_69_replay_fallback_detectedr      k     "m!45F'2$W]^H44<<<  K///((+55;;;r$   c                     t        dt        i      } t        dddg|       }|j                  du sJ |j                  dk(  sJ |j
                  d   j                  dk(  sJ y)	uA   (4) task-2520 PR #71 replay — 동일 owner_pat fallback 패턴.r   r   r   r   Tr   r   N)r=   r   r   r   r   r   rA   r   s     r"   'test_1_4_pr_71_replay_fallback_detectedr      r   r$   c                 ,   t        t        t        t        d      } t	        ddg d|       }|j
                  du sJ |j                  du sJ t        |j                        dk(  sJ t        d |j                  D              sJ t        |      }|t        j                  k(  sJ t        dddddd	      }t        d
di||      }|j                  dk(  sJ |j                   du sJ |j"                  du sJ |j$                  t        j                  k(  sJ y)u   (5) owner_pat fallback identity → AUTOMATION_CAPABILITY_GAP 분류 + select_merge_path
    가 escalate_capability_gap path 선택.)r}   r   r   r   r   r   TFr(   c              3  4   K   | ]  }|j                     y wr9   )r   ).0rs     r"   	<genexpr>zMtest_1_5_owner_token_fallback_classified_as_capability_gap.<locals>.<genexpr>  s     PQq((Ps   )can_squash_mergerequires_approvalrequires_thread_resolutionauto_merge_enabledbot_can_mergeadmin_override_requiredr   r   escalate_capability_gapN)r=   r   r   r   r   r   r   r.   r   allr   r	   AUTOMATION_CAPABILITY_GAPr   r   actioncapability_gaprequires_chairreason)r8   r   blockedcapplans        r"   :test_1_5_owner_token_fallback_classified_as_capability_gapr     s1    !" F
 (,vH 44<<<((E111x,,-222P(2O2OPPPP%h/Gm===== #( %C ,XrNCID;;3333$&&&%''';;-AAAAAr$   c                     ddddd} t        d| i      }t        dddg|      }|j                  d	u sJ |j                  d
u sJ |j                  d	u sJ |j
                  dk(  sJ t        |      }|J y)u   (6) bot/app identity로 머지된 fixture (github-actions[bot]) → success 간주
    (capability_gap=None / classify_capability_gap returns None).i  github-actions[bot]Botr   )r   r   r   r   r   TFinstallation_appN)r=   r   r   r   r   r   r   )bot_pr_fixturer8   r   r   s       r"   2test_1_6_bot_app_identity_merge_treated_as_successr   *  s     3UCN !#~!67F'3%H ((D00044===&&$...  $6666%h/G??r$   c                     t        dddddd      } | d   d	k(  sJ | d
   du sJ | d   du sJ | d   t        j                  dd      k(  sJ | d   t        j                  dd      k(  sJ | d   J y)u[   (7) submitted_at < pushed_at → status='async_pending', premature_gate_fail_detected=True.2026-05-09T05:00:00Zz2026-05-09T04:55:00Z2026-05-09T05:01:30ZGEMINI_COMPLETEDg   XA     v@	pushed_atgemini_submitted_atci_complete_atgemini_status	now_epochwait_budget_secondsstatusasync_pendingpremature_gate_fail_detectedT should_block_with_waiting_markerpush_to_ci_complete_secondsg     V@MbP?relpush_to_gemini_review_secondsg     rgemini_gate_wait_secondsNr   pytestapproxraces    r"   <test_2_1_submitted_before_pushed_classified_as_async_pendingr   C  s    %(2-(!D >_,,,./477723t;;;-.&--$2OOOO/0FMM&d4SSSS*+777r$   c                     t        dddddd      } | d   d	k(  sJ | d
   du sJ | d   du sJ | d   t        j                  dd      k(  sJ y)uQ   (8) submitted_at > pushed_at AND within budget → status='ok' (premature=False).r   z2026-05-09T05:03:00Zr   r   g   =Ar   r   r   okr   Fr   r   g     f@r   r   Nr   r   s    r"   %test_2_2_within_budget_arrival_passesr   V  sy    %(2-(!D >T!!!./588823u<<</0FMM%T4RRRRr$   c                     t        dddddd      } | d   d	k(  sJ t        dddd
dd      }|d   dk(  sJ |d   du sJ y)u   (9) wait budget 초과 + gemini_status가 quota/timeout이면 'wait_budget_exhausted'.
    quota/timeout 신호 없으면 보수적으로 async_pending 유지.r   Nr   GEMINI_UNAVAILABLE_QUOTAg   KAr   r   r   wait_budget_exhaustedr   r   r   T)r   )race_arace_bs     r"   :test_2_3_budget_exhausted_distinguished_from_quota_timeoutr   f  s{     (( -0!F (6666'( -(!F (...01T999r$   c                     t        dddddd      } h d}|j                  t        | j                                     sJ | d	   t	        j
                  d
d      k(  sJ | d   t	        j
                  dd      k(  sJ y)u>   (10) 5 metric (push_to_ci_complete_seconds 등) 정상 수집.r   z2026-05-09T05:03:30Zz2026-05-09T05:01:00Zr   g   Ar   r   >   r   r   r   r   r   r   g      N@r   r   r   g     @j@N)r   r   setkeysr   r   )r   expected_keyss     r"   $test_2_4_metrics_five_keys_populatedr     s    %(2-(!DM !!#diik"2333-.&--$2OOOO/0FMM%T4RRRRr$   c                    ddddd} dddd	d}t        d
dg      }d| fd|ffD ]  \  }}t        |d   |d   |d   |d   fd      }t        |dk(  rdnd|d|dk(  rdndz   dgdddddgddg |d   d |!      }|j                  t        k(  s$J | d"|j                   d#|j
                  d$       |j                  d%u sJ |j                  J |j                  d'k  sJ |j                  J  y&)(u   (11) PR #70/#71 race fixture replay — pushed_at 직후 stale Gemini review 도착 →
    evaluate_pr가 WAITING_FOR_GEMINI_REVIEW 분류 + premature_gate_fail_detected=True.z2026-05-09T04:50:00Zz2026-05-09T04:48:00Zz2026-05-09T04:51:30Zg   \A)r   r   r   r   z2026-05-09T06:30:00Zz2026-05-09T06:28:00Zz2026-05-09T06:31:00Zg   GAr   z)utils/lifecycle_reconciliation_manager.py)r?   r`   zPR#70zPR#71r   r   r   r   c                    | S r9   rX   )vs    r"   ro   zLtest_2_5_pr_70_71_race_replay_premature_gate_fail_detected.<locals>.<lambda>  s    A r$   )r   r   r   now_providerr}   r   dead7071CLEANmain)mergeStateStatusbaseRefNameSUCCESS)r   detailsr   )r   
unresolvedsubmitted_at)rA   	task_specpr_head_shaeffective_filesmerge_stateci_stategemini_statectxu+   : 기대 WAITING_FOR_GEMINI_REVIEW, 실제 z	 (reason=)TNr   )	rk   r|   r   decisionr   r   r   r   r   )fixture_pr70fixture_pr71speclabelfixr  r
  s          r"   :test_2_5_pr_70_71_race_replay_premature_gate_fail_detectedr    s   
 ,50!	L ,50!	L +?j>klD.,0GH =
s+& #$9 :/0"%k"25	
 !W,b"%7*:$EHI-4VL )ykB  #$9 :
 
   $== 	
g@ARAR@S T),	
= 44<<<55AAA5599900<<<;=r$   c                     t        ddddd      } t        |       }|D ch c]  }|j                   }}t        j                  |v s,J d|D cg c]  }|j                  j
                   c}        yc c}w c c}w )uc   (12) bot_session=cancelled + timer_running + pr_missing → BOT_CANCELLED_TIMER_RUNNING_PR_MISSING.	cancelledrunningNT)rQ   rG   rA   rB   rR   u5   BOT_CANCELLED_TIMER_RUNNING_PR_MISSING 누락. cases=)r^   r   r   r   &BOT_CANCELLED_TIMER_RUNNING_PR_MISSINGvalueevidencecasescreasonss       r"   /test_3_1_bot_cancelled_timer_running_pr_missingr    s    &&H x(E!&'Aqxx'G'==H 
?Y^@_TU@_?`aH (@_   A4A9c                     t        ddddd      } t        |       }|D ch c]  }|j                   }}t        j                  |v s,J d|D cg c]  }|j                  j
                   c}        yc c}w c c}w )ud   (13) bot_session=cancelled + worktree_active(mtime<5min) →
    BOT_CANCELLED_WITH_ACTIVE_WORKTREE.r  	completedNTg      ^@)rQ   rG   rA   rR   rT   u1   BOT_CANCELLED_WITH_ACTIVE_WORKTREE 누락. cases=)r^   r   r   r   "BOT_CANCELLED_WITH_ACTIVE_WORKTREEr  r  s       r"   +test_3_2_bot_cancelled_with_active_worktreer     s     '& #(H x(E!&'Aqxx'G'99WD 
;UZ<[PQQXX^^<[;\]D (<[r  c                     t        dddddd      } t        |       }|D ch c]  }|j                   }}t        j                  |v s,J d|D cg c]  }|j                  j
                   c}        yc c}w c c}w )uf   (14) bot_session=cancelled + commits_pushed + pr_missing →
    BOT_CANCELLED_AFTER_COMMIT_BEFORE_PR.r  r  NT)rQ   rG   rA   rU   rS   rR   u3   BOT_CANCELLED_AFTER_COMMIT_BEFORE_PR 누락. cases=)r^   r   r   r   $BOT_CANCELLED_AFTER_COMMIT_BEFORE_PRr  r  s       r"   -test_3_3_bot_cancelled_after_commit_before_prr#    s     '&  $H x(E!&'Aqxx'G';;wF 
=W\>]RSqxx~~>]=^_F (>]s   A5A:c            
        t        dddddddd      } t        |       }|D ch c]  }|j                   }}t        j                  |v s,J d|D cg c]  }|j                  j
                   c}        y	c c}w c c}w )
uf   (15) bot_session=cancelled + pr_open + finalize 미완 →
    BOT_CANCELLED_AFTER_PR_BEFORE_FINALIZE.r  r  *   OPENFT)rQ   rG   rA   rB   rJ   rK   rR   rU   u5   BOT_CANCELLED_AFTER_PR_BEFORE_FINALIZE 누락. cases=N)r^   r   r   r   &BOT_CANCELLED_AFTER_PR_BEFORE_FINALIZEr  r  s       r"   /test_3_4_bot_cancelled_after_pr_before_finalizer(    s     '& 	H x(E!&'Aqxx'G'==H 
?Y^@_TU@_?`aH (@_s   A7A<c                     t        ddddddddd	      } t        |       }|D ch c]  }|j                   }}t        j                  |v sJ t        j
                  |vsJ yc c}w )	u#  (16) 다그다 hang fixture (task-2518) replay — bot session ended + worktree active +
    pr 미생성 + commit 미발생 + timer 살아있음 → BOT_CANCELLED_TIMER_RUNNING_PR_MISSING.

    회장 §명시 7번 완료조건: 4 신규 case 중 정확히 어디로 분류되는지 박제.r   r  r  NFTg      n@)	r?   rQ   rG   rA   rB   rU   rR   rT   rP   )r^   r   r   r   r  r  )dagda_evidencer  r  r  s       r"   6test_3_5_dagda_hang_fixture_replay_2518_classificationr+    sy    
 -& #(!%
N ~.E!&'Aqxx'G'==HHH99HHH	 (s   A"c            	     B   t        ddddddd      } t        |       }|D ch c]  }|j                   }}t        j                  t        j
                  t        j                  t        j                  h}||z  rJ d||z          t        j                  |v sJ yc c}w )	u   (17) Telegram 무응답만 있고 bot_session/PR/CI/worktree/commit 모두 정상 →
    BOT_CANCELLED_* 분류 X (회장 §명시: Telegram 무응답만으로 stuck 판정 절대 X).r   r  TNg      $@F)rQ   rG   rP   rA   rR   rT   rU   uA   Telegram 무응답만으로 BOT_CANCELLED_* stuck 분류 발생: )	r^   r   r   r   r  r  r"  r'  TELEGRAM_REPLY_CUT_OFF)r  r  r  r  bot_cancelled_sets        r"   2test_3_6_telegram_only_signal_does_not_imply_stuckr/  -  s     '!%#' H x(E!&'Aqxx'G' 	::6688::	 "G+ 
KGVgLgKhi, --888 (s   Bc                     t         dk(  sJ y)uN   sanity: GEMINI_REVIEW_WAIT_BUDGET_SECONDS_DEFAULT가 360s (6분)으로 정의.r   N)r   rX   r$   r"   ,test_wait_budget_default_constant_documentedr1  N  s    4===r$   c                     t        dd      dk(  sJ t        dd      dk(  sJ t        dd      dk(  sJ t        dd      d	k(  sJ t        d
d      dk(  sJ y)u\   sanity: infer_token_source_from_actor 4 경로 (installation_app/owner_pat/bot_pat/unknown).r   r   r   r~   r   r   r   r-   unknownzauto-bot-runnerbot_patN)r   rX   r$   r"   #test_infer_token_source_known_pathsr5  S  sn    ()>FJ\\\\(@KOOO(&A[PPP(R0I===():FCyPPPr$   c                     t        dddddd      } | j                  dk(  sJ | j                  dk(  sJ | j                  du sJ y)	u2   sanity: MergeIdentityRecord dataclass에 6 필드.r}   r~   r   Fr   T)rA   r   merged_by_typeis_botinferred_token_sourcer   N)r   rA   r   r   )r   s    r"   !test_merge_identity_record_fieldsr:  a  sZ    
&) $C ==B.000&&$...r$   )r   r-   r-   )r   r/   r   strr   r;  r:   r;   )r7   zDict[int, Any])r:   r   )r?   r;  r`   list[str] | Nonera   r<  r:   r   )r:   r   )H__doc__
__future__r   r    sysdataclassesr   pathlibr   typingr   r   r   r   __file__resolveparent_WORKTREE_ROOTr;  pathremoveinsertutils.repository_policy_adapterr	   r
   r   r   r   r   r   r   r   utils.merge_queue_executorr   r   r   r   r   r   &utils.lifecycle_reconciliation_managerr   r   r   r#   r=   r^   rk   r|   r   __annotations__r   r   r   r   r   r   r   r   r   r   r   r   r  r  r   r#  r(  r+  r/  r1  r5  r:  rX   r$   r"   <module>rN     s   #  
   " " 
 h'')0077>>~#(("HHOOC'( 3~& '
 
 
  e
0%F '+#'	 % !	
 &#, 
 *A!~  
 *
!~ 
 
 *A!~ & /"<<"BJ28&S :4S,0=p "$(I09B>
Q/r$   