
     jCa                       U d Z ddlmZ ddlZddlmc 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
j2                  v r!e
j2                  j5                   ee             e
j2                  j7                  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/m0Z0 d:d;d
Z1d<dZ2d=dZ3dddd	 	 	 	 	 	 	 d>dZ4d?dZ5dddddddddZ6de7d<   ddddddddZ8de7d <   d!dddd"d#d$ddZ9de7d%<   d& Z:d' Z;d( Z<d) Z=d* Z>d+ Z?d, Z@d- ZAd. ZBd/ ZCd0 ZDd1 ZEd2 ZFd3 ZGd4 ZHd5 ZId6 ZJd7 ZKd8 ZLd9 ZMy)@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                 j   t        t              D  ch c]  } | j                   }} h d}|j                  } ||      }|st	        j
                  d||z
         dz   dt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      dt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      dz  }t        t	        j                  |            dx}}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=zL
>assert %(py5)s
{%(py5)s = %(py2)s
{%(py2)s = %(py0)s.issubset
}(%(py3)s)
}expectedfield_names)py0py2py3py5N)r   r
   nameissubset
@pytest_ar_format_assertmsg@py_builtinslocals_should_repr_global_name	_safereprAssertionError_format_explanation)fr   r   @py_assert1@py_assert4@py_format6s         r"   /test_1_1_bot_merge_identity_six_field_dataclassr      s   #)*:#;<a166<K<H  [) )   #8k#9":;                   )    )    *      =s   D0c                 B   t        dt        i      } t        dddg|       }|j                  }d}||u }|st	        j
                  d|fd||f      dt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      t	        j                  |      d	z  }d
d|iz  }t        t	        j                  |            dx}x}}|j                  }d}||k(  }|st	        j
                  d|fd||f      dt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      t	        j                  |      d	z  }d
d|iz  }t        t	        j                  |            dx}x}}|j                  }d}||u }|st	        j
                  d|fd||f      dt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      t	        j                  |      d	z  }d
d|iz  }t        t	        j                  |            dx}x}}|j                  }d}||k(  }|st	        j
                  d|fd||f      dt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      t	        j                  |      d	z  }d
d|iz  }t        t	        j                  |            dx}x}}|j                  }d}||u }|st	        j
                  d|fd||f      dt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      t	        j                  |      d	z  }d
d|iz  }t        t	        j                  |            dx}x}}|j                   }t#        |      }d}||k(  }	|	s
t	        j
                  d|	fd||f      dt        j                         v st	        j                  t"              rt	        j                  t"              nddt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      t	        j                  |      t	        j                  |      dz  }
dd|
iz  }t        t	        j                  |            dx}x}x}	}|j                   d   }|j$                  }d}||k(  }|st	        j
                  d|fd||f      dt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      t	        j                  |      d	z  }d
d|iz  }t        t	        j                  |            dx}x}}|j&                  }d}||k(  }|st	        j
                  d|fd||f      dt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      t	        j                  |      d	z  }d
d|iz  }t        t	        j                  |            dx}x}}|j(                  }d}||u }|st	        j
                  d|fd||f      dt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      t	        j                  |      d	z  }d
d|iz  }t        t	        j                  |            dx}x}}y) uW   (2) task-2518 PR #70 replay — mergedBy=JonghyukJeon → fallback_to_owner_token=True.r}   Jeon-Jonghyukdev_workspacer8   TiszH%(py2)s
{%(py2)s = %(py0)s.fallback_to_owner_token_detected
} is %(py5)sidentityr   r   r   assert %(py7)spy7Nr~   ==)z9%(py2)s
{%(py2)s = %(py0)s.merge_actor_login
} == %(py5)sFz:%(py2)s
{%(py2)s = %(py0)s.merge_actor_is_bot
} is %(py5)s	owner_patz4%(py2)s
{%(py2)s = %(py0)s.token_source
} == %(py5)sz<%(py2)s
{%(py2)s = %(py0)s.bot_can_merge_as_app
} is %(py5)sr,   zZ%(py5)s
{%(py5)s = %(py0)s(%(py3)s
{%(py3)s = %(py1)s.merge_identity_audit
})
} == %(py8)sr.   r   py1r   r   py8assert %(py10)spy10r   z1%(py2)s
{%(py2)s = %(py0)s.pr_number
} == %(py5)srecz7%(py2)s
{%(py2)s = %(py0)s.merged_by_login
} == %(py5)sz?%(py2)s
{%(py2)s = %(py0)s.fallback_to_owner_token
} is %(py5)s)r=   r   r   r   r   _call_reprcomparer   r   r   r   r   r   r   r   r   r   r   r.   rA   merged_by_loginfallback_to_owner_token)r8   r   r   r   @py_assert3r   @py_format8@py_assert2@py_assert7@py_assert6@py_format9@py_format11r   s                r"   'test_1_2_pr_70_replay_fallback_detectedr      sg    "m!45F'2$W]^H44<<4<<<<4<<<<<<8<<<8<<<4<<<<<<<<<<%%77%7777%77777787778777%7777777777&&/%/&%////&%//////8///8///&///%///////  /K/ K//// K//////8///8/// ///K///////((1E1(E1111(E11111181118111(111E1111111,,23,-22-2222-22222232223222222x222x222,222-2222222222

'
'
*C==B=B=B33=B0.0.0000.00000030003000000.0000000&&.$.&$....&$......3...3...&...$.......r$   c                    t        dt        i      } t        dddg|       }|j                  }d}||u }|st	        j
                  d|fd||f      dt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      t	        j                  |      d	z  }d
d|iz  }t        t	        j                  |            dx}x}}|j                  }d}||k(  }|st	        j
                  d|fd||f      dt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      t	        j                  |      d	z  }d
d|iz  }t        t	        j                  |            dx}x}}|j                  d   }|j                  }d}	||	k(  }|st	        j
                  d|fd||	f      t	        j                  |      t	        j                  |      t	        j                  |	      dz  }
dd|
iz  }t        t	        j                  |            dx}x}x}}	y)uA   (3) task-2519 PR #69 replay — 동일 owner_pat fallback 패턴.r   r   r   r   Tr   r   r   r   r   r   Nr   r   r   r   z1%(py3)s
{%(py3)s = %(py1)s.pr_number
} == %(py6)sr   r   py6assert %(py8)sr   )r=   r   r   r   r   r   r   r   r   r   r   r   r   r   rA   r8   r   r   r   r   r   r   @py_assert0r   @py_assert5@py_format7r   s               r"   'test_1_3_pr_69_replay_fallback_detectedr      x    "m!45F'2$W]^H44<<4<<<<4<<<<<<8<<<8<<<4<<<<<<<<<<  /K/ K//// K//////8///8/// ///K///////((+;+55;;5;;;;5;;;+;;;5;;;;;;;;;;r$   c                    t        dt        i      } t        dddg|       }|j                  }d}||u }|st	        j
                  d|fd||f      dt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      t	        j                  |      d	z  }d
d|iz  }t        t	        j                  |            dx}x}}|j                  }d}||k(  }|st	        j
                  d|fd||f      dt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      t	        j                  |      d	z  }d
d|iz  }t        t	        j                  |            dx}x}}|j                  d   }|j                  }d}	||	k(  }|st	        j
                  d|fd||	f      t	        j                  |      t	        j                  |      t	        j                  |	      dz  }
dd|
iz  }t        t	        j                  |            dx}x}x}}	y)uA   (4) task-2520 PR #71 replay — 동일 owner_pat fallback 패턴.r   r   r   r   Tr   r   r   r   r   r   Nr   r   r   r   r   r   r   r   )r=   r   r   r   r   r   r   r   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 }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      d	z  }d
d|iz  }t        t        j                  |            dx}x}}|j                  }d}||u }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      d	z  }d
d|iz  }t        t        j                  |            dx}x}}|j                  }t!        |      }d}||k(  }	|	s
t        j                  d|	fd||f      dt        j                         v st        j                  t               rt        j                  t               nddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      dz  }
dd|
iz  }t        t        j                  |            dx}x}x}	}d |j                  D        }t#        |      }|sddt        j                         v st        j                  t"              rt        j                  t"              ndt        j                  |      t        j                  |      dz  }t        t        j                  |            dx}}t%        |      }t&        j(                  }||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      nddt        j                         v st        j                  t&              rt        j                  t&              ndt        j                  |      dz  }dd|iz  }t        t        j                  |            dx}}t+        dddddd      }t-        d d!i||      }|j.                  }d"}||k(  }|st        j                  d|fd#||f      d$t        j                         v st        j                  |      rt        j                  |      nd$t        j                  |      t        j                  |      d	z  }d
d|iz  }t        t        j                  |            dx}x}}|j0                  }d}||u }|st        j                  d|fd%||f      d$t        j                         v st        j                  |      rt        j                  |      nd$t        j                  |      t        j                  |      d	z  }d
d|iz  }t        t        j                  |            dx}x}}|j2                  }d}||u }|st        j                  d|fd&||f      d$t        j                         v st        j                  |      rt        j                  |      nd$t        j                  |      t        j                  |      d	z  }d
d|iz  }t        t        j                  |            dx}x}}|j4                  }t&        j(                  }||k(  }|st        j                  d|fd'||f      d$t        j                         v st        j                  |      rt        j                  |      nd$t        j                  |      dt        j                         v st        j                  t&              rt        j                  t&              ndt        j                  |      d(z  }d)d*|iz  }
t        t        j                  |
            dx}x}}y)+u   (5) owner_pat fallback identity → AUTOMATION_CAPABILITY_GAP 분류 + select_merge_path
    가 escalate_capability_gap path 선택.)r}   r   r   r   r   r   Tr   r   r   r   r   r   NFr   r(   r   r   r.   r   r   r   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   z,assert %(py4)s
{%(py4)s = %(py0)s(%(py2)s)
}allr   r   py4)zA%(py0)s == %(py4)s
{%(py4)s = %(py2)s.AUTOMATION_CAPABILITY_GAP
}blockedr	   assert %(py6)sr   )can_squash_mergerequires_approvalrequires_thread_resolutionauto_merge_enabledbot_can_mergeadmin_override_requiredr   r   escalate_capability_gap)z.%(py2)s
{%(py2)s = %(py0)s.action
} == %(py5)splan)z6%(py2)s
{%(py2)s = %(py0)s.capability_gap
} is %(py5)s)z6%(py2)s
{%(py2)s = %(py0)s.requires_chair
} is %(py5)s)z]%(py2)s
{%(py2)s = %(py0)s.reason
} == %(py6)s
{%(py6)s = %(py4)s.AUTOMATION_CAPABILITY_GAP
})r   r   r   r   r   r   )r=   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r.   r   r   r	   AUTOMATION_CAPABILITY_GAPr   r   actioncapability_gaprequires_chairreason)r8   r   r   r   r   r   r   r   r   r   r   r   @py_format5r   r   capr   r   s                     r"   :test_1_5_owner_token_fallback_classified_as_capability_gapr     s    !" F
 (,vH 44<<4<<<<4<<<<<<8<<<8<<<4<<<<<<<<<<((1E1(E1111(E11111181118111(111E1111111,,23,-22-2222-22222232223222222x222x222,222-2222222222P(2O2OPP3PPPPPPPPP3PPP3PPPPPPPPPPPPPP%h/G#===7=====7=======7===7======m===m=========== #( %C ,XrNCID;;333;33333;333333343334333;33333333333&$&$&&&&$&&&&&&4&&&4&&&&&&$&&&&&&&'%'%''''%''''''4'''4''''''%''''''';;A-AAA;AAAAA;AAAAAAA4AAA4AAA;AAAAAA-AAA-AAAAAAAAAAAr$   c                    ddddd} t        d| i      }t        dddg|      }|j                  }d	}||u }|st        j                  d
|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}}|j                  }d}||u }|st        j                  d
|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}}|j                  }d	}||u }|st        j                  d
|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}}|j                  }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}}t        |      }d}	||	u }|st        j                  d
|fd||	f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |	      dz  }
dd|
iz  }t        t        j                  |            dx}}	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   Tr   r   r   r   r   r   NFr   r   installation_appr   r   )z%(py0)s is %(py3)sr   r   r   assert %(py5)sr   )r=   r   r   r   r   r   r   r   r   r   r   r   r   r   r   )bot_pr_fixturer8   r   r   r   r   r   r   r   r   @py_format4s              r"   2test_1_6_bot_app_identity_merge_treated_as_successr  *  sl    3UCN !#~!67F'3%H ((0D0(D0000(D00000080008000(000D000000044==4====4======8===8===4==========&&.$.&$....&$......8...8...&...$.......  6$66 $66666 $666666686668666 666$66666666%h/G7d?7d77dr$   c            	        t        dddddd      } | d   }d	}||k(  }|slt        j                  d
|fd||f      t        j                  |      t        j                  |      dz  }dd|iz  }t	        t        j
                  |            dx}x}}| d   }d}||u }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }dd|iz  }t	        t        j
                  |            dx}x}}| d   }d}||u }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }dd|iz  }t	        t        j
                  |            dx}x}}| d   }t        j                  }d}d} |||      }	||	k(  }|st        j                  d
|fd||	f      t        j                  |      dt        j                         v st        j                  t              rt        j                  t              ndt        j                  |      t        j                  |      t        j                  |      t        j                  |	      dz  }
dd|
iz  }t	        t        j
                  |            dx}x}x}x}x}}	| d   }t        j                  }d}| }d} |||      }||k(  }|st        j                  d
|fd ||f      t        j                  |      dt        j                         v st        j                  t              rt        j                  t              ndt        j                  |      t        j                  |      t        j                  |      t        j                  |      d!z  }d"d#|iz  }t	        t        j
                  |            dx}x}x}x}x}x}}| d$   }d}||u}|slt        j                  d%|fd&||f      t        j                  |      t        j                  |      dz  }dd|iz  }t	        t        j
                  |            dx}x}}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_pendingr   z%(py1)s == %(py4)sr   r   r   r   Npremature_gate_fail_detectedTr   z%(py1)s is %(py4)s should_block_with_waiting_markerpush_to_ci_complete_secondsg     V@MbP?relz[%(py1)s == %(py11)s
{%(py11)s = %(py5)s
{%(py5)s = %(py3)s.approx
}(%(py7)s, rel=%(py9)s)
}pytestr   r   r   r   py9py11assert %(py13)spy13push_to_gemini_review_secondsg     r@)z]%(py1)s == %(py12)s
{%(py12)s = %(py5)s
{%(py5)s = %(py3)s.approx
}(-%(py7)s, rel=%(py10)s)
})r   r   r   r   r   py12zassert %(py14)spy14gemini_gate_wait_secondsis not)z%(py1)s is not %(py4)sr   r   r   r   r   r   r  approxr   r   r   )racer   r   r   r   r   r   r   @py_assert8@py_assert10@py_format12@py_format14@py_assert9@py_assert11@py_format13@py_format15s                   r"   <test_2_1_submitted_before_pushed_classified_as_async_pendingr1  C  s   %(2-(!D >,_,>_,,,,>_,,,>,,,_,,,,,,,./747/47777/4777/7774777777723;t;3t;;;;3t;;;3;;;t;;;;;;;-.O&--OO$O-$2OO.2OOOOO.2OOOO.OOOOOO&OOO&OOO-OOOOOO$OOO2OOOOOOOOO/0SFMMS5S5&SdSM&d4SS04SSSSS04SSSS0SSSSSSFSSSFSSSMSSS5SSSdSSS4SSSSSSSSS*+747+47777+4777+77747777777r$   c            	     p   t        dddddd      } | d   }d	}||k(  }|slt        j                  d
|fd||f      t        j                  |      t        j                  |      dz  }dd|iz  }t	        t        j
                  |            dx}x}}| d   }d}||u }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }dd|iz  }t	        t        j
                  |            dx}x}}| d   }d}||u }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }dd|iz  }t	        t        j
                  |            dx}x}}| d   }t        j                  }d}d} |||      }	||	k(  }|st        j                  d
|fd||	f      t        j                  |      dt        j                         v st        j                  t              rt        j                  t              ndt        j                  |      t        j                  |      t        j                  |      t        j                  |	      dz  }
dd|
iz  }t	        t        j
                  |            dx}x}x}x}x}}	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   r  r  r   r   Nr  Fr   r  r  r   g     f@r  r  r  r  r  r  r  r&  )r(  r   r   r   r   r   r   r   r)  r*  r+  r,  s               r"   %test_2_2_within_budget_arrival_passesr4  V  s   %(2-(!D >!T!>T!!!!>T!!!>!!!T!!!!!!!./858/58888/5888/8885888888823<u<3u<<<<3u<<<3<<<u<<<<<<</0RFMMR%RTRM%T4RR04RRRRR04RRRR0RRRRRRFRRRFRRRMRRR%RRRTRRR4RRRRRRRRRr$   c                 F   t        dddddd      } | d   }d	}||k(  }|slt        j                  d
|fd||f      t        j                  |      t        j                  |      dz  }dd|iz  }t	        t        j
                  |            dx}x}}t        dddddd      }|d   }d}||k(  }|slt        j                  d
|fd||f      t        j                  |      t        j                  |      dz  }dd|iz  }t	        t        j
                  |            dx}x}}|d   }d}||u }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }dd|iz  }t	        t        j
                  |            dx}x}}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  r   r   r  r  r  Tr   r  )r   r   r   r   r   r   )race_ar   r   r   r   r   race_bs          r"   :test_2_3_budget_exhausted_distinguished_from_quota_timeoutr:  f  s;    (( -0!F (66666666666666666666666'( -(!F (...................019T91T99991T9991999T9999999r$   c                    t        dddddd      } h d}|j                  }| j                  } |       }t        |      } ||      }|sSd	d
t	        j
                         v st        j                  |      rt        j                  |      nd
t        j                  |      dt	        j
                         v st        j                  t              rt        j                  t              nddt	        j
                         v st        j                  |       rt        j                  |       ndt        j                  |      t        j                  |      t        j                  |      t        j                  |      dz  }t        t        j                  |            dx}x}x}x}}| d   }t        j                  }	d}
d} |	|
|      }||k(  }|st        j                  d|fd||f      t        j                  |      dt	        j
                         v st        j                  t              rt        j                  t              ndt        j                  |	      t        j                  |
      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}x}	x}
x}}| d   }t        j                  }	d}
d} |	|
|      }||k(  }|st        j                  d|fd||f      t        j                  |      dt	        j
                         v st        j                  t              rt        j                  t              ndt        j                  |	      t        j                  |
      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}x}	x}
x}}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  zassert %(py12)s
{%(py12)s = %(py2)s
{%(py2)s = %(py0)s.issubset
}(%(py10)s
{%(py10)s = %(py3)s(%(py8)s
{%(py8)s = %(py6)s
{%(py6)s = %(py4)s.keys
}()
})
})
}expected_keyssetr(  )r   r   r   r   r   r   r   r!  Nr  g      N@r  r  r   r  r  r  r  r  r   g     @j@)r   r   keysr=  r   r   r   r   r   r   r   r  r'  r   )r(  r<  r   r   r   r-  r.  r/  r   r   r   r)  r*  r   r+  r,  s                   r"   $test_2_4_metrics_five_keys_populatedr?    sk   %(2-(!DM !!3dii3ik3#k"23!"233333333=333=333!333333#333#333333d333d333i333k333"233333333333-.O&--OO$O-$2OO.2OOOOO.2OOOO.OOOOOO&OOO&OOO-OOOOOO$OOO2OOOOOOOOO/0RFMMR%RTRM%T4RR04RRRRR04RRRR0RRRRRRFRRRFRRRMRRR%RRRTRRR4RRRRRR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t        j                  d"|fd#|t        f      d$t        j                         v st        j                  |      rt        j                  |      nd$t        j                  |      d%t        j                         v st        j                  t              rt        j                  t              nd%d&z  }	t        j                  | d'|j                   d(|j                  d)      d*z   d+|	iz  }
t        t        j                  |
            d,x}}|j                  }d-}||u }|st        j                  d.|fd/||f      d$t        j                         v st        j                  |      rt        j                  |      nd$t        j                  |      t        j                  |      d0z  }d1d2|iz  }t        t        j                  |            d,x}x}}|j                   }d,}||u}|st        j                  d3|fd4||f      d$t        j                         v st        j                  |      rt        j                  |      nd$t        j                  |      t        j                  |      d0z  }d1d2|iz  }t        t        j                  |            d,x}x}}|j                   }d5}||k  }|st        j                  d6|fd7||f      d$t        j                         v st        j                  |      rt        j                  |      nd$t        j                  |      t        j                  |      d0z  }d1d2|iz  }t        t        j                  |            d,x}x}}|j"                  }d,}||u}|st        j                  d3|fd8||f      d$t        j                         v st        j                  |      rt        j                  |      nd$t        j                  |      t        j                  |      d0z  }d1d2|iz  }t        t        j                  |            d,x}x}} y,)9u   (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  detailsr3  )r  
unresolvedsubmitted_at)rA   	task_specpr_head_shaeffective_filesmerge_stateci_stategemini_statectxr   )z0%(py2)s
{%(py2)s = %(py0)s.decision
} == %(py4)sdecisionr   r   u+   : 기대 WAITING_FOR_GEMINI_REVIEW, 실제 z	 (reason=)
>assert %(py6)sr   NTr   )zD%(py2)s
{%(py2)s = %(py0)s.premature_gate_fail_detected
} is %(py5)sr   r   r   r$  )zI%(py2)s
{%(py2)s = %(py0)s.push_to_gemini_review_seconds
} is not %(py5)sr   )<)zD%(py2)s
{%(py2)s = %(py0)s.push_to_gemini_review_seconds
} < %(py5)s)zD%(py2)s
{%(py2)s = %(py0)s.gemini_gate_wait_seconds
} is not %(py5)s)rk   r|   r   rV  r   r   r   r   r   r   r   r   r   r   r   r  r   r#  )fixture_pr70fixture_pr71speclabelfixrU  rV  r   r   r   r   r   r   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 :
 
    	
 $== 	
 	
 	
 $= 	
 	
 
6	
 	
   	
 	
 
	  	
 	
 
	 ! 	
 	
 
6	
 	
  %> 	
 	
 
	 %> 	
 	
  g@ARAR@S T),	
 	
 	
 	
 	
 44<<4<<<<4<<<<<<x<<<x<<<4<<<<<<<<<<55ATA5TAAAA5TAAAAAAxAAAxAAA5AAATAAAAAAA5599599995999999x999x9995999999999900<<0<<<<0<<<<<<x<<<x<<<0<<<<<<<<<<;=r$   c                    t        ddddd      } t        |       }|D ch c]  }|j                   }}t        j                  }||v }|st        j                  d|fd||f      dt        j                         v st        j                  t              rt        j                  t              ndt        j                  |      d	t        j                         v st        j                  |      rt        j                  |      nd	d
z  }t        j                  d|D cg c]  }|j                  j                   c}       dz   d|iz  }t        t        j                  |            dx}}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   inzN%(py2)s
{%(py2)s = %(py0)s.BOT_CANCELLED_TIMER_RUNNING_PR_MISSING
} in %(py4)sr   reasonsr   u5   BOT_CANCELLED_TIMER_RUNNING_PR_MISSING 누락. cases=rX  r   )r^   r   r   r   &BOT_CANCELLED_TIMER_RUNNING_PR_MISSINGr   r   r   r   r   r   r   valuer   r   evidencecasescrf  r   r   r   r   s           r"   /test_3_1_bot_cancelled_timer_running_pr_missingrm    sA   &&H x(E!&'Aqxx'G'== =H   =              >      BI    BI    @Y^@_TU@_?`a     (@_   E.E3
c                    t        ddddd      } t        |       }|D ch c]  }|j                   }}t        j                  }||v }|st        j                  d|fd||f      d	t        j                         v st        j                  t              rt        j                  t              nd	t        j                  |      d
t        j                         v st        j                  |      rt        j                  |      nd
dz  }t        j                  d|D cg c]  }|j                  j                   c}       dz   d|iz  }t        t        j                  |            dx}}yc c}w c c}w )ud   (13) bot_session=cancelled + worktree_active(mtime<5min) →
    BOT_CANCELLED_WITH_ACTIVE_WORKTREE.ra  	completedNTg      ^@)rQ   rG   rA   rR   rT   rc  )zJ%(py2)s
{%(py2)s = %(py0)s.BOT_CANCELLED_WITH_ACTIVE_WORKTREE
} in %(py4)sr   rf  r   u1   BOT_CANCELLED_WITH_ACTIVE_WORKTREE 누락. cases=rX  r   )r^   r   r   r   "BOT_CANCELLED_WITH_ACTIVE_WORKTREEr   r   r   r   r   r   r   rh  r   r   ri  s           r"   +test_3_2_bot_cancelled_with_active_worktreerr    s@    '& #(H x(E!&'Aqxx'G'99 9WD   9W              :      >E    >E    <UZ<[PQQXX^^<[;\]     (<[rn  c                    t        dddddd      } t        |       }|D ch c]  }|j                   }}t        j                  }||v }|st        j                  d|fd||f      dt        j                         v st        j                  t              rt        j                  t              ndt        j                  |      d	t        j                         v st        j                  |      rt        j                  |      nd	d
z  }t        j                  d|D cg c]  }|j                  j                   c}       dz   d|iz  }t        t        j                  |            dx}}yc c}w c c}w )uf   (14) bot_session=cancelled + commits_pushed + pr_missing →
    BOT_CANCELLED_AFTER_COMMIT_BEFORE_PR.ra  rp  NT)rQ   rG   rA   rU   rS   rR   rc  )zL%(py2)s
{%(py2)s = %(py0)s.BOT_CANCELLED_AFTER_COMMIT_BEFORE_PR
} in %(py4)sr   rf  r   u3   BOT_CANCELLED_AFTER_COMMIT_BEFORE_PR 누락. cases=rX  r   )r^   r   r   r   $BOT_CANCELLED_AFTER_COMMIT_BEFORE_PRr   r   r   r   r   r   r   rh  r   r   ri  s           r"   -test_3_3_bot_cancelled_after_commit_before_prru    sE    '&  $H x(E!&'Aqxx'G';; ;wF   ;w              <      @G    @G    >W\>]RSqxx~~>]=^_     (>]s   E/E4
c            
        t        dddddddd      } t        |       }|D ch c]  }|j                   }}t        j                  }||v }|st        j                  d|fd	||f      d
t        j                         v st        j                  t              rt        j                  t              nd
t        j                  |      dt        j                         v st        j                  |      rt        j                  |      nddz  }t        j                  d|D cg c]  }|j                  j                   c}       dz   d|iz  }t        t        j                  |            dx}}yc c}w c c}w )uf   (15) bot_session=cancelled + pr_open + finalize 미완 →
    BOT_CANCELLED_AFTER_PR_BEFORE_FINALIZE.ra  rp  *   OPENFT)rQ   rG   rA   rB   rJ   rK   rR   rU   rc  )zN%(py2)s
{%(py2)s = %(py0)s.BOT_CANCELLED_AFTER_PR_BEFORE_FINALIZE
} in %(py4)sr   rf  r   u5   BOT_CANCELLED_AFTER_PR_BEFORE_FINALIZE 누락. cases=rX  r   N)r^   r   r   r   &BOT_CANCELLED_AFTER_PR_BEFORE_FINALIZEr   r   r   r   r   r   r   rh  r   r   ri  s           r"   /test_3_4_bot_cancelled_after_pr_before_finalizerz    sL    '& 	H x(E!&'Aqxx'G'== =H   =              >      BI    BI    @Y^@_TU@_?`a     (@_s   E1 E6
c                 n   t        ddddddddd	      } t        |       }|D ch c]  }|j                   }}t        j                  }||v }|st        j                  d	|fd
||f      dt        j                         v st        j                  t              rt        j                  t              ndt        j                  |      dt        j                         v st        j                  |      rt        j                  |      nddz  }dd|iz  }t        t        j                  |            dx}}t        j                  }||v}|st        j                  d|fd||f      dt        j                         v st        j                  t              rt        j                  t              ndt        j                  |      dt        j                         v st        j                  |      rt        j                  |      nddz  }dd|iz  }t        t        j                  |            dx}}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   ra  rb  NFTg      n@)	r?   rQ   rG   rA   rB   rU   rR   rT   rP   rc  re  r   rf  r   r   r   )not in)zN%(py2)s
{%(py2)s = %(py0)s.BOT_CANCELLED_WITH_ACTIVE_WORKTREE
} not in %(py4)s)r^   r   r   r   rg  r   r   r   r   r   r   r   r   rq  )dagda_evidencerk  rl  rf  r   r   r   r   s           r"   6test_3_5_dagda_hang_fixture_replay_2518_classificationr~    sS   
 -& #(!%
N ~.E!&'Aqxx'G'==H=HHHH=HHHHHH;HHH;HHH=HHHHHHHHHHHHHHHH99H9HHHH9HHHHHH;HHH;HHH9HHHHHHHHHHHHHHHH	 (s   H2c            	        t        ddddddd      } t        |       }|D ch c]  }|j                   }}t        j                  t        j
                  t        j                  t        j                  h}||z  }| }|st        j                  d||z         d	z   d
t        j                         v st        j                  |      rt        j                  |      nd
dt        j                         v st        j                  |      rt        j                  |      nddz  }t        t        j                  |            dx}}t        j                   }||v }|st        j"                  d|fd||f      dt        j                         v st        j                  t              rt        j                  t              ndt        j                  |      dt        j                         v st        j                  |      rt        j                  |      nddz  }	dd|	iz  }
t        t        j                  |
            dx}}yc c}w )u   (17) Telegram 무응답만 있고 bot_session/PR/CI/worktree/commit 모두 정상 →
    BOT_CANCELLED_* 분류 X (회장 §명시: Telegram 무응답만으로 stuck 판정 절대 X).r3  rb  TNg      $@F)rQ   rG   rP   rA   rR   rT   rU   uA   Telegram 무응답만으로 BOT_CANCELLED_* stuck 분류 발생: z 
>assert not (%(py0)s & %(py1)s)bot_cancelled_setrf  )r   r   rc  )z>%(py2)s
{%(py2)s = %(py0)s.TELEGRAM_REPLY_CUT_OFF
} in %(py4)sr   r   r   r   )r^   r   r   r   rg  rq  rt  ry  r   r   r   r   r   r   r   r   TELEGRAM_REPLY_CUT_OFFr   )rj  rk  rl  rf  r  r   r   r   r   r   r   s              r"   2test_3_6_telegram_only_signal_does_not_imply_stuckr  -  s    '!%#' H x(E!&'Aqxx'G' 	::6688::	 "G+ +, ,   LGVgLgKhi     "    "      %,    %,      --8-8888-888888;888;888-8888888888888888 (s   Ic                 p   d} t         | k(  }|st        j                  d|fdt         | f      dt        j                         v st        j
                  t               rt        j                  t               ndt        j                  |       dz  }dd|iz  }t        t        j                  |            dx}} y)	uN   sanity: GEMINI_REVIEW_WAIT_BUDGET_SECONDS_DEFAULT가 360s (6분)으로 정의.r  r   )z%(py0)s == %(py3)sr   r   r   r   N)	r   r   r   r   r   r   r   r   r   )r   r   r   r   s       r"   ,test_wait_budget_default_constant_documentedr  N  s_    8==4====4======4===4==========r$   c                 
   d} d}t        | |      }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j
                  t               rt        j                  t               ndt        j                  |       t        j                  |      t        j                  |      t        j                  |      dz  }dd	|iz  }t        t        j                  |            d
x} x}x}x}}d} d}t        | |      }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j
                  t               rt        j                  t               ndt        j                  |       t        j                  |      t        j                  |      t        j                  |      dz  }dd	|iz  }t        t        j                  |            d
x} x}x}x}}d} d}t        | |      }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j
                  t               rt        j                  t               ndt        j                  |       t        j                  |      t        j                  |      t        j                  |      dz  }dd	|iz  }t        t        j                  |            d
x} x}x}x}}d} d}t        | |      }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j
                  t               rt        j                  t               ndt        j                  |       t        j                  |      t        j                  |      t        j                  |      dz  }dd	|iz  }t        t        j                  |            d
x} x}x}x}}d} d}t        | |      }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j
                  t               rt        j                  t               ndt        j                  |       t        j                  |      t        j                  |      t        j                  |      dz  }dd	|iz  }t        t        j                  |            d
x} x}x}x}}y
)u\   sanity: infer_token_source_from_actor 4 경로 (installation_app/owner_pat/bot_pat/unknown).r   r   r   r   )z9%(py6)s
{%(py6)s = %(py0)s(%(py2)s, %(py4)s)
} == %(py9)sr   )r   r   r   r   r  zassert %(py11)sr  Nr~   r   r   r   r-   unknownzauto-bot-runnerbot_pat)	r   r   r   r   r   r   r   r   r   )r   r   r   r)  r   @py_format10r+  s          r"   #test_infer_token_source_known_pathsr  S  s   )>\\()>F\J\\FJ\\\\\FJ\\\\\\\(\\\(\\\)>\\\\\\F\\\J\\\\\\\\)7OO(@OKO@KOOOO@KOOOOOO(OOO(OOOOOOOOO@OOOKOOOOOOO)8P&P(&AP[PA[PPPPA[PPPPPP(PPP(PPPPPP&PPPAPPP[PPPPPPP)+=R=(R0=I=0I====0I======(===(======R===0===I=======):PFP():FCPyPCyPPPPCyPPPPPP(PPP(PPP):PPPFPPPCPPPyPPPPPPPPr$   c                    t        dddddd      } | j                  }d}||k(  }|st        j                  d|fd	||f      d
t	        j
                         v st        j                  |       rt        j                  |       nd
t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}}| j                  }d}||k(  }|st        j                  d|fd||f      d
t	        j
                         v st        j                  |       rt        j                  |       nd
t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}}| j                  }d}||u }|st        j                  d|fd||f      d
t	        j
                         v st        j                  |       rt        j                  |       nd
t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}}y)u2   sanity: MergeIdentityRecord dataclass에 6 필드.r}   r~   r   Fr   T)rA   r   merged_by_typeis_botinferred_token_sourcer   r   r   r   r   r   r   Nr   r   r   )r   rA   r   r   r   r   r   r   r   r   r   r   )r   r   r   r   r   r   s         r"   !test_merge_identity_record_fieldsr  a  sq   
&) $C ==B=B=B33=B0.0.0000.00000030003000000.0000000&&.$.&$....&$......3...3...&...$.......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   )N__doc__
__future__r   builtinsr   _pytest.assertion.rewrite	assertionrewriter   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  r1  r4  r:  r?  r_  rm  rr  ru  rz  r~  r  r  r  r  rX   r$   r"   <module>r     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$   