
    nj[              
         U d Z ddlmZ ddlZddlZddlZddlZddlZddlZddl	Z	ddl
Z
ddlmZmZ ddlmZmZ ddlmZ ddlmZ ddlmZmZmZ  eej0                  j3                  d	 e ee      j9                         j:                  j:                                    Z ee      j9                         j:                  j:                  Z ee      e
j@                  vr"e
j@                  jC                  d ee             dd
l"m#Z#m$Z$m%Z%  ejL                  e'      Z(dZ)dZ*dZ+dZ,dddddddddddddZ-de.d<   d>dZ/g dg dg dg dg d d!Z0d"e.d#<    G d$ d%ee      Z1e G d& d'             Z2ed(ef   Z3de)fd?d)Z4h d*Z5d@d+Z6e*fdAd,Z7 ejp                  d-ejr                        Z: ejp                  d.ejv                        Z<dBd/Z=dCd0Z>dDd1Z?dEd2Z@de)d3	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 dFd4ZAd5dde)e*d6dd7	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 dGd8ZBdd9	 	 	 	 	 	 	 	 	 	 	 dHd:ZCdIdJd;ZDdIdKd<ZEe'd=k(  r e
j                   eE              yy)Lu  utils/post_merge_smoke_runner.py — task-2512 5 모듈 #4.

회장 명시: 자동 머지 직후 origin/main 기준 smoke를 자동 실행하고,
smoke 실패 시에만 Critical #7 (POST_MERGE_SMOKE_FAILED) escalation packet 생성.

산출물 = 코드 + 회귀 테스트. wiring(task-2514)/reporting(task-2513) 보류.

automation_contracts.py(task-2509+2 freeze) 그대로 import:
  - SmokeResult, CriticalEscalationType, EscalationPacket
  - to_json
    )annotationsN)	dataclassasdict)datetimetimezone)Enum)Path)AnyCallableOptionalWORKSPACE_ROOT)CriticalEscalationTypeEscalationPacketSmokeResultiX  i      z
...[TRUNCATED {n} bytes]...
4486ea368   )merge_commit_hintpr2cd8178b7   38334b09:   59ec8d37>   )	task-2506	task-2507	task-2509	task-2511zdict[str, dict[str, Any]]REPLAY_FIXTURESc                ,    t         j                  |       S )uM   REPLAY_FIXTURES에서 task_id에 해당하는 fixture 반환. 없으면 None.)r    get)task_ids    4/home/jay/workspace/utils/post_merge_smoke_runner.pyget_replay_fixturer%   ?   s    w''    )pytestz*tests/regression/test_critical_gap_2506.py-q)r'   z*tests/regression/test_git_evidence_2507.pyr(   )r'   z2tests/regression/test_merge_queue_executor_2509.pyr(   )r'   z0tests/regression/test_auto_gemini_triage_2511.pyr(   )r'   z5tests/regression/test_post_merge_smoke_runner_2512.pyr(   )r   r   r   r   z	task-2512zdict[str, list[str]]SMOKE_COMMAND_REGISTRYc                       e Zd ZdZdZdZdZdZy)SmokeStatusPASSFAILSKIPPEDTIMEOUTBLOCKEDN)__name__
__module____qualname__r,   r-   r.   r/   r0    r&   r$   r+   r+   P   s    DDGGGr&   r+   c                      e Zd ZU dZded<   ded<   ded<   ded<   d	ed
<   ded<   ded<   ded<   ded<   ded<   ddZddZy)PostMergeSmokeRunu  Smoke 실행 envelope. 회장 §6 정보량을 freeze contract 위반 없이 전달.

    Fields:
      merge_commit:     입력 SHA (origin/main HEAD와 일치 검증 후 보존)
      task_id:          task spec에서 추출한 task_id
      status:           SmokeStatus enum
      smoke_result:     freeze contract `SmokeResult` (transport)
      duration_ms:      subprocess 실행 시간(ms)
      smoke_command:    실제 실행한 command (None=정의 안됨)
      allow_continuation: PASS/SKIPPED 시 True. merge_queue 다음 단계 신호.
      escalation:       FAIL/TIMEOUT/BLOCKED 시 EscalationPacket. 그 외 None.
      stale:            merge_commit ≠ origin/main HEAD 시 True
      dry_run:          입력 dry_run 플래그 보존
    strmerge_commitr#   r+   statusr   smoke_resultintduration_msOptional[list[str]]smoke_commandboolallow_continuationzOptional[EscalationPacket]
escalationstaledry_runc                v   t        |       }t        | j                  t              r| j                  j                  n| j                  |d<   | j
                  b|d   }t        | j
                  j                  t              r | j
                  j                  j                  n| j
                  j                  |d<   |S )Nr9   rA   escalation_type)r   
isinstancer9   r   valuerA   rE   )selfdescs      r$   to_dictzPostMergeSmokeRun.to_dictw   s    4L+5dkk4+Hdkk''dkk(??&L/C doo==tD //55__44 !"
 r&   c                H    t        j                  | j                               S N)jsondumpsrK   )rH   s    r$   to_jsonzPostMergeSmokeRun.to_json   s    zz$,,.))r&   N)returndict)rQ   r7   )r1   r2   r3   __doc____annotations__rK   rP   r4   r&   r$   r6   r6   \   sL     L&&**KM*r&   r6   .c                X    t        j                  | |xs t        t              dd|      S )NT)cwdcapture_outputtexttimeout)
subprocessrunr7   	WORKSPACE)argsrV   rY   s      r$   _default_runnerr^      s+    >>!3y> r&   >   -f--force--no-verify--force-with-leasec                    t        |       }|D cg c]  }|t        v s|dk(  s| }}|rt        d|       d|v rt        d      d|v rt        d      yc c}w )uI   smoke command에 force/admin/rebase/cherry-pick 등 금지 인자 차단.z--adminzFORBIDDEN_GIT_FLAGS detected: rebaseREBASE_FORBIDDENzcherry-pickCHERRY_PICK_FORBIDDENN)listFORBIDDEN_GIT_FLAGSRuntimeError)r]   flatabads       r$   assert_no_forbidden_git_flagsrm      su    :D
Ia#66!y.1
IC
I
;C5ABB4-..233  Js
   AAc                
   | y| j                  dd      }t        |      }||k  r| S |t        z  }|d| }|| d }|d|z  z
  }t        j	                  |      }|j                  dd      |z   |j                  dd      z   S )uS   text를 cap byte로 자른 결과 반환. 초과 시 head + truncate marker + tail.N utf-8replace)errorsr   )n)encodelenHEAD_TAIL_RATIOTRUNCATE_MARKER_FMTformatdecode)	rX   capencodedrs   half
head_bytes
tail_bytestruncated_nmarkers	            r$   capture_head_tailr      s    |kk')k4GGACx/!D$J$Ja$h,K ''+'6F')4
	


GI

6	7r&   z```yaml\s*\n(.*?)```z^# (task-\d+(?:\+\d+)?)c                j    t         j                  |       }|r|j                  d      S |j                  S )N   )_TASK_ID_HEADER_REsearchgroupstem)	task_text	task_filems      r$   extract_task_idr      s+    !!),A1771:.	.r&   c                   d}t         j                  |       }|r |j                  d      }t        |      }|r|S t	        j                  d| t        j
                        }|rW|j                  d      j                         j                  d      }|r'|j                         dvrt        j                  |      S |t        v rt        t        |         S y)u  task md 본문에서 smoke_command 추출.

    우선순위:
      1) yaml block 안 `smoke_command:` (list 또는 quoted scalar)
      2) `smoke_command:` 단독 라인 (markdown 스칼라)
      3) SMOKE_COMMAND_REGISTRY[task_id]
      4) None (미정의)
    ro   r   ^smoke_command:\s*(.+)$`>   nonenullN)_YAML_BLOCK_REr   r   _parse_smoke_command_in_yamlre	MULTILINEstriplowershlexsplitr)   rg   )r   r#   
yaml_blockr   cmdline_mraws          r$   extract_smoke_commandr      s     Ji(AWWQZ
*:6JYY19bllKFll1o##%++C0399;&66;;s##((*7344r&   c                   t        j                  d| t         j                        }|rg }|j                  d      j	                         D ]  }|j                         }|j                  d      s%|dd  j                         }|j                  d      r|j                  d      r|dd }n'|j                  d      r|j                  d      r|dd }|j                  |        |r|S d S t        j                  d| t         j                        }|rf|j                  d      j                         j                  d      j                  d      }|r'|j                         dvrt        j                  |      S y )	Nz'^smoke_command:\s*\n((?:\s*-\s*.+\n?)+)r   -"'r   >   r   r   )r   r   r   r   
splitlinesr   
startswithendswithappendr   r   r   )r   list_mitemslinesr   scalar_ms          r$   r   r      s;   YY2
F
 LLO..0 		D

A<<$AB%++-C~~c"s||C'8!Bi$c):!BiLL		 u'4'yy3ZNHnnQ%%'--c288=399;&66;;s##r&   c                J   	  |g dd        |g dd      }t        |dd      dk7  ry	t        |d
d      xs dj                         }|sy	|| k(  xs$ |j                  |       xs | j                  |       S # t        $ r }t        j                  d|       Y d}~y	d}~ww xY w)u   git fetch origin main → rev-parse origin/main과 merge_commit 비교.

    return True if origin/main HEAD가 merge_commit과 다름 (stale).
    runner 실패 시 stale=True 보수적 판정.
    )gitfetchoriginmain<   rY   )r   z	rev-parsezorigin/main   
returncoder   r   Tstdoutro   z check_main_head_stale failed: %sN)getattrr   r   	Exceptionloggerwarning)r8   runnerrpheadexcs        r$   check_main_head_staler     s    12>7D2|Q'1,Hb)/R668L(jDOOL,Ij\MdMdeiMjkk 93?s"   (A9 !A9 +A9 9	B"BB")extra_evidencetimeout_secc        	           t         j                  dt         j                  d| dt         j                  di}	g d}
d}|| ||j                  |j
                  |j                  |j                  |j                  |t        j                  t        j                        j                         d
}|r|j                  |       t        | |t         j"                  |	j%                  |d      d	|
||
      S )u{   POST_MERGE_SMOKE_FAILED escalation packet.

    회장 명시 — Critical 7종 외 보고 금지. enum 정확 매칭.
    z,smoke command exited non-zero on origin/mainz smoke command exceeded timeout (zs)zPsmoke_command undefined and dry_run=False (auto merge requires smoke definition))3Re-run smoke locally on freshly fetched origin/mainz3Disable auto-merge for this task and route to chairz+Define smoke_command in task spec and retryr   )
r8   r#   r>   r9   	exit_codestdout_tailstderr_tailfailure_reasonr<   	timestampzsmoke failureu   Post-merge smoke against origin/main failed. Continuing auto-merge of subsequent PRs without human review violates 자동 머지 10조건 #10.)r#   	pr_numberrE   reasonwhy_auto_cannot_continuesafe_optionsrecommended_optionevidence)r+   r-   r/   r0   rG   r   r   r   r   r   nowr   utc	isoformatupdater   r   POST_MERGE_SMOKE_FAILEDr"   )r#   r   r8   r:   r9   r>   r<   r   r   
reason_mapr   recommendedr   s                r$   build_smoke_failed_packetr   (  s    " 	H?}BOoJ
L
 HK$&,,!++#//#//&55"\\(,,/99; H '.FF~~fo6Z "& r&   TF)rC   r   r   r   output_cap_bytesskip_stale_checkexpected_task_idc        	           |xs t         }| j                  d      }	|xs t        |	|       }
t        |	|
      }|rt	        |       |rdnt        ||      }|i|rt        ||
||      S t        dddddd	
      }t        |
|||t        j                  ddd|i|	      }t        ||
t        j                  |ddd|||
      S dj                  |      }t        j                  t        j                         }	  |||      }t'        t        j                  t        j                         |z
  j)                         dz        }t-        |dd      }t+        t-        |dd      xs d|      }t+        t-        |dd      xs d|      }|dk(  r5t        |dd||d
      }t        ||
t        j:                  |||| d||
      S t        |d|||d| 
      }t        |
|||t        j8                  ||d|i|	      }t        ||
t        j8                  |||d|||
      S # t"        j$                  $ r}t'        t        j                  t        j                         |z
  j)                         dz        }t+        t-        |dd      xs d|      }t+        t-        |dd      xs d|      }t        |dd||d
      }t        |
|||t        j.                  ||d|i|	      }t        ||
t        j.                  |||d|||
      cY d}~S d}~wt0        $ r}t'        t        j                  t        j                         |z
  j)                         dz        }t        |dddt+        t3        |      |      dt5        |      j6                   
      }t        |
|||t        j8                  ||d|i|	      }t        ||
t        j8                  |||d|||
      cY d}~S d}~ww xY w)u  task spec 기준 main smoke 실행.

    Args:
        task_file: task md 절대경로
        merge_commit: 자동 머지된 commit SHA
        dry_run: smoke_command가 None일 때만 의미 있음. None+dry_run=True → SKIPPED,
                 None+dry_run=False → BLOCKED. smoke_command가 정의된 경우 dry_run과
                 무관하게 항상 실제 실행 (회장 §10 표 정합).
        runner: subprocess wrapper (test injection)
        pr_number: PR 번호 (없으면 0)
        timeout_sec: subprocess timeout (default 600). escalation evidence에 그대로 기록.
        output_cap_bytes: stdout/stderr head/tail cap (default 64KB)
        skip_stale_check: True면 main HEAD 비교 skip (test 격리용)
        expected_task_id: 외부에서 task_id를 직접 주입 (testing)

    Returns:
        PostMergeSmokeRun envelope. stale=True가 발견되면 PASS여도
        allow_continuation=False로 강등된다 (자동 머지 큐 진행 차단).
    rp   )encodingFN)r8   r#   rB   rC   ro   r   z7smoke_command undefined; auto-merge requires definitionSMOKE_COMMAND_UNDEFINEDcommandpassedr   r   r   r   r   rB   )	r#   r   r8   r:   r9   r>   r<   r   r   
r8   r#   r9   r:   r<   r>   r@   rA   rB   rC    r   i  r   stderrr/   zRUNNER_ERROR: r   TEXIT_)r^   	read_textr   r   rm   r   _build_skipped_runr   r   r+   r0   r6   joinr   r   r   r   rZ   TimeoutExpiredr;   total_secondsr   r   r/   r   r7   typer1   r-   r,   )r   r8   rC   r   r   r   r   r   r   r   r#   r>   rB   srpacketcmd_strstartedresultr   r<   r   r   rcs                          r$   run_post_merge_smoker   d  s&   > &F##W#5IG/)Y"GG))W=M %m4 &E+@v+VE %)	  Q4
 +y%B&&d7E*:#
 !%w&&R$
 	
 hh}%Gll8<<(G.
{;^ x||HLL1G;JJLtSTK	r	*B#GFHb$A$GRIYZK#GFHb$A$GRIYZK	QwDA#
 !%w##"#=#(yT
 	
 
[rd|
B
 '9!}%0@F !7b} VW ] $$ 
8<<5?NNPSWWX'Xr(B(HbJZ['Xr(B(HbJZ[ER#$

 +y%B&&m#We4D#
 !%w&&R#=$
 	
  
8<<5?NNPSWWXER)#c(4DE+DI,>,>+?@	
 +y%B##=#We4D#
 !%w##"#=$
 	

s2   6
H O+CK>8O>O
C O
OO)r>   c                    |rdj                  |      nd}t        |dddd|sdnd       }t        | |t        j                  |d|dd ||
      S )Nr   ro   Tr   r.   r   r   )r   r   r6   r+   r.   )r8   r#   rB   rC   r>   r   r   s          r$   r   r     sa     *7chh}%BG	B(5y4
B
 !7""]DW r&   c                   t        j                  d      }|j                  ddd       |j                  ddd       |j                         }|j                  d	d
d       |j                  dd
d       |j                  dd
d       |j                  dt        dd       |j                  dt        t
               |j                  dd
d       |j                  |       S )Npost_merge_smoke_runner)progz--task-fileTu   task md 절대 경로)requiredhelpz--merge-commitu   자동 머지된 commit SHAz	--dry-run
store_trueuO   smoke_command 미정의 시 SKIPPED 처리 (정의된 경우는 항상 실행).)actionr   z--applyuL   smoke_command 미정의 시 BLOCKED 처리 (정의된 경우 항상 실행).z
--no-auditu'   audit 로그 기록 skip (테스트용)z--pr-numberr   u   PR 번호 (없으면 0))r   defaultr   z	--timeout)r   r   z--skip-stale-checku   origin/main HEAD 비교 skip)argparseArgumentParseradd_argumentadd_mutually_exclusive_groupr;   DEFAULT_TIMEOUT_SEC
parse_args)argvpmodes      r$   _parse_argsr  $  s    %>?ANN=46MNNNN#d9VNW))+DL^   	,[   NN<;dNeNN=sA<UNVNN;S2ENFNN'CaNb<<r&   c                   t        |       }|j                  rdn|j                   }t        t	        |j
                        |j                  ||j                  |j                  |j                        }t        |j                                |j                  t        j                  k(  s|j                  t        j                  k(  ry|j                  t        j                   k(  ry|j                  t        j"                  k(  ryy)NT)r   r8   rC   r   r   r   r   r   r      )r  rC   applyr   r	   r   r8   r   rY   r   printrP   r9   r+   r,   r.   r-   r/   )r   r]   rC   r[   s       r$   r   r   8  s    tDlldTZZG
t~~&&&..LL..C 
#++-
zz[%%%{7J7J)J
zz[%%%
zz[(((r&   __main__)r#   r7   rQ   zOptional[dict[str, Any]])r]   	list[str]rV   Optional[str]rY   r;   )r]   r  rQ   None)rX   r	  rz   r;   rQ   r7   )r   r7   r   r	   rQ   r7   )r   r7   r#   r7   rQ   r=   )r   r7   rQ   r=   )r8   r7   r   
RunnerTyperQ   r?   )r#   r7   r   r;   r8   r7   r:   r   r9   r+   r>   r=   r<   r;   r   zOptional[dict]r   r;   rQ   r   )r   r	   r8   r7   rC   r?   r   zOptional[RunnerType]r   r;   r   r;   r   r;   r   r?   r   r	  rQ   r6   )r8   r7   r#   r7   rB   r?   rC   r?   r>   r=   rQ   r6   rM   )r   r=   rQ   zargparse.Namespace)r   r=   rQ   r;   )GrS   
__future__r   r   rN   loggingosr   r   rZ   sysdataclassesr   r   r   r   enumr   pathlibr	   typingr
   r   r   environr"   r7   __file__resolveparentr\   _HEREpathinsertutils.automation_contractsr   r   r   	getLoggerr1   r   r   DEFAULT_OUTPUT_CAP_BYTESrv   rw   r    rT   r%   r)   r+   r6   r  r^   rh   rm   r   compileDOTALLr   r   r   r   r   r   r   r   r   r   r  r   exitr4   r&   r$   <module>r!     sJ  
 #    	 	   
 ) '   * * 0#d8n6L6L6N6U6U6\6\2]^_	 	X ''..u:SXXHHOOAs5z"  
		8	$  $ 7 
 (2<'1<'1<'1<	.* ( POWUZ0 , #t  (* (* (*^ c3h
 ;?Ob  M 	4  7O 0 3RYY?RZZ :BLLI /
<BB &**55 5 	5
 5 5 '5 5 #5 5 5@ #'*4"&*ff f 	f
 !f f f f f $f fV *.#&/3>B& .(* zCHHTV r&   