
    <jo                    "   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
m
Z
mZ ddlmZ  eej                  j                  dd            Zedz  d	z  Zed
z  dz  Zedz  dz  Zedz  dz  Zedz  Zedz  Zedz  ZdZdZ G d d      Zd8dZ	 	 d9	 	 	 	 	 	 	 d:dZd;dZd<dZd=d>dZ	 d=	 	 	 	 	 d?dZ 	 d=	 	 	 	 	 d?dZ!	 d=	 	 	 	 	 	 	 	 	 d@dZ"	 d=ddd	 	 	 	 	 	 	 dAdZ#ddd d d d d d!	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 dBd"Z$d=dCd#Z%dd$	 	 	 	 	 dDd%Z&dEd&Z'dFd'Z(dGd(Z)dd d)	 	 	 	 	 	 	 dHd*Z*dddd+	 	 	 	 	 	 	 	 	 dId,Z+dJd-Z,dKd.Z-dddd/	 	 	 	 	 	 	 	 	 dLd0Z.ddd1	 	 	 	 	 	 	 	 	 dMd2Z/d=dCd3Z0dNd4Z1dd$	 	 	 	 	 dOd5Z2	 d=ddd6	 	 	 	 	 	 	 dPd7Z3y)Qu  
lifecycle_guards.py — task-2467+3 silent corruption 4대 결함 차단 공통 Guard 모듈 (task-2468)

회장 명시: "task-2467+3 사례 재현 후 차단됨 증명 필수".

P0-1  ~ P0-10 가드 구현:
  P0-1  : .g3-fail 마커 존재 시 .done 발행 금지
  P0-2  : done 직전 G3 PASS evidence 재검증
  P0-3  : Gemini High severity 판정 표준화
  P0-4  : auto-approve / auto-merge 7조건 통합 차단
  P0-5  : approver identity 시스템화
  P0-6  : merge commit SHA 검증
  P0-7  : state transition 엄격화
  P0-8  : bypass / override audit 강제
  P0-9  : bot author allowlist 외부 config화
  P0-10 : TASKCTL_CWD 제거 / --worktree 정식화
    )annotationsN)datetimetimezone)PathWORKSPACE_ROOTz/home/jay/workspacememoryeventsz.tasksevidencespecszorchestration-auditzadmin-override.jsonlzallowed_bot_accounts.jsonzallowed_approvers.json)zg3-failz	g3-failedg3_fail	g3_failed)zg3-pass.jsonzg3.jsonzg3_verifier.jsonc                  >    e Zd ZdZddd	 	 	 	 	 	 	 ddZddZd	dZy)
GuardResultu   공통 결과 컨테이너.N)detailblockingc               L    || _         || _        |xs i | _        |xs g | _        y Nokreasonr   r   )selfr   r   r   r   s        I/home/jay/workspace/.worktrees/task-2551-dev6/scripts/lifecycle_guards.py__init__zGuardResult.__init__8   s(     l B    c                ~    | j                   rdnd| j                   | j                  | j                  | j                  dS )NPASSFAIL)resultr   r   r   r   r   )r   s    r   as_dictzGuardResult.as_dictE   s3     $fV''kkkk
 	
r   c                H    | j                   rdnd}d| d| j                  dS )Nr   r   zGuardResult(z: )r   r   )r   statuss     r   __repr__zGuardResult.__repr__N   s(    77fXRa88r   )r   boolr   strr   dict | Noner   zlist[str] | None)returndictr(   r&   )__name__
__module____qualname____doc__r   r   r$    r   r   r   r   5   sD    % #%)' ' 	'
 ' #'
9r   r   c                 d    t        j                  t        j                        j	                         S r   )r   nowr   utc	isoformatr/   r   r   _now_isor4   X   s    <<%//11r   c           	        	 t        j                  | dd||rt        |      nd      }|j                  |j                  j                         |j                  j                         fS # t         j                  $ r ddd| dfcY S t        $ r}ddd| fcY d}~S d}~wt        $ r}ddt        |      fcY d}~S d}~ww xY w)	z5subprocess.run wrapper. (returncode, stdout, stderr).TN)capture_outputtexttimeoutcwd ztimeout after szcommand not found: )

subprocessrunr&   
returncodestdoutstripstderrTimeoutExpiredFileNotFoundError	Exception)argsr8   r9   proces        r   _run_cmdrI   \   s    ~~CT
  1 1 3T[[5F5F5HHH$$ 32y222 12,QC000 2s1v~s6   A%A( (B?B?BB?B?'B:4B?:B?c                x    g }t         D ].  }||  d| z  }|j                         s|j                  |       0 |S )uD   events_dir에서 task_id 관련 g3 fail marker 파일 경로 목록..)G3_FAIL_MARKER_NAMESexistsappend)task_id
events_dirfoundnameps        r   _g3_fail_marker_pathsrT   s   sF    E$ G9AdV,,88:LLO Lr   c                    || z  }g }|j                         s|S t        D ])  }||z  }|j                         s|j                  |       + |S )uE   evidence_dir/<task_id>/ 에서 g3 PASS evidence 파일 경로 목록.)rM   G3_PASS_EVIDENCE_NAMESrN   )rO   evidence_dirtask_ev_dirrQ   rR   rS   s         r   _g3_pass_evidence_pathsrY   }   sV    (KE& $88:LLO Lr   c                .    |xs t         }t        | |      S )uw   task_id의 g3 fail 관련 마커 모두 탐지.

    (.g3-fail / .g3-failed / .g3_fail / .g3_failed 모두 인식)
    )
EVENTS_DIRrT   )rO   rP   _dirs      r   find_g3_fail_markersr]      s    
 #D $//r   c                   |xs t         }t        | |      }|rK|D cg c]  }|j                   }}dg|z   }t        dd| dd|D cg c]  }t	        |       c}i|      S t        ddd	t        t              i
      S c c}w c c}w )u5   P0-1: .g3-fail 마커 존재 → .done 발행 차단.g3_fail_markerFu   G3 fail marker 존재: u    — .done 발행 차단 (P0-1)markersr   Tu-   G3 fail marker 없음 — .done 발행 허용checked_namesr   r   r   )r[   r]   rR   r   r&   listrL   )rO   rP   r\   r`   mnamesblocking_entriess          r   check_g3_fail_blocks_donerg      s    
 #D"7D1G!()A)),-5,UG3RS81A89%	
 	
 >&:!;<  *  9s   BBc                   |xs t         }||  dz  }t        | |      }|j                         }t        |      dkD  }|ra|r_t	        dd|  d|j
                   d|D cg c]  }|j
                   c} t        |      |D cg c]  }t        |       c}ddd	g
      S |rt	        dddt        |      i      S |r't	        ddd|D cg c]  }t        |       c}i      S t	        ddi       S c c}w c c}w c c}w )u@   P0-7 보조: .done + .g3-fail 동시 존재 시 conflict 판정.z.doner   Fu@   CONFLICT: .done과 .g3-fail 마커가 동시에 존재 — task=z done=z fail_markers=T)	done_filefail_markersconflictdone_fail_conflictr   u.   .done 존재, g3-fail 마커 없음 — 정상ri   rb   u=   .g3-fail 마커 존재, .done 없음 — 정상 (fail 상태)rj   u1   .done과 .g3-fail 마커 모두 없음 — 정상)r[   r]   rM   lenr   rR   r&   )rO   rP   r\   ri   r`   done_existsfail_existsrd   s           r   check_done_fail_conflictrp      s6   
 #D'%((I"7D1G""$Kg,"K{yy~~&6nV]E^QRaffE^D_a !^18 9AQ 9 
 ++
 	
 CY0
 	

 R"W$=SV$=>
 	

 B + F_ !: %>s   C2C7	C<c           	     d   |xs t         }t        | |      }|s&t        ddt         ddt	        || z        idg      S |d   }	 t        j                  |j                  d	            }|j                  dd      }	|	dk7  r t        dd|	dt	        |      |	ddg      S |j                  dd      }
|
r)|
| k7  r$t        dd|
d| dt	        |      |
| ddg      S |^|j                  d      }	 |rt        |      nd}	 |t        |      nd}|+|)||k7  r$t        dd| d| dt	        |      ||ddg      S |rf|j                  d       xs( |j                  d!      xs |j                  d"      xs d}|r)||k7  r$t        dd#|d|d$t	        |      ||d%d&g      S t        d'd(|j                   d)t	        |      |d*+      S # t        $ r(}t        dd
| dt	        |      idg      cY d}~S d}~ww xY w# t        t        f$ r d}Y w xY w# t        t        f$ r d}Y w xY w),uS  P0-2: done 직전 G3 PASS evidence 재검증.

    - evidence file 없음 → FAIL
    - evidence['result'] != 'PASS' → FAIL
    - evidence['task_id'] != task_id → FAIL (다른 task PASS 재사용)
    - evidence['pr_number'] != pr_number → FAIL (이전 PR PASS 재사용)
    - evidence['sha'] != head_sha → FAIL (stale PASS)
    Fu    G3 PASS evidence 파일 없음 (u   ) — .done 차단 (P0-2)rW   no_g3_pass_evidencer   r   utf-8encodingu'   G3 PASS evidence 파일 파싱 실패: pathevidence_parse_errorNr   r;   r   zG3 evidence result=u&    (PASS 필요) — .done 차단 (P0-2))rv   evidence_resultg3_not_passrO   zG3 PASS evidence task_id=z != u-    — 다른 task PASS 재사용 차단 (P0-2))rv   evidence_task_idexpectedevidence_task_id_mismatch	pr_numberzG3 PASS evidence pr_number=u+    — 이전 PR PASS 재사용 차단 (P0-2))rv   evidence_prexpected_previdence_pr_mismatchshahead_shamerge_commit_shazG3 PASS evidence sha=u    — stale PASS 차단 (P0-2))rv   evidence_shaexpected_shaevidence_sha_mismatchTu   G3 PASS evidence 유효 (path=z, result=PASS))rv   r
   rb   )EVIDENCE_DIRrY   r   rV   r&   jsonloads	read_textrE   getint
ValueError	TypeErrorrR   )rO   r}   r   rW   _ev_dirpathsev_pathevrH   	ev_resultev_taskev_prpr_n_cmp	ev_pr_cmpev_shas                  r   check_done_g3_pass_evidencer      s    *lG#GW5E56L5MMfg"C'(9$:;+,	
 	
 AhG
ZZ))7);< x$IF(5[\LYG#_	
 	
 ffY#G7g%.wkg[HuvLgSZ[12	
 	
 {#	)2s9~H	&+&7E
TI  X%9i8>S1)D
 KA B  L#,#+
 11  X"&&"4X?Q8RXVXf(+F:T( F3 4  L$*$,
 22  /~^LG"5 Q  
<QC@CL),-	
 	

B I& 	H	 I& 	I	sB   %G 2H H 	G?G:4G?:G?HHH/.H/)r}   repoc          
     8   t        t        t              j                        }|t        j
                  vr t        j
                  j                  d|       	 ddl}| |j                  |       }n&||r|j                  ||      }nt        dddg      S |j                  d	d      }|d
k\  rt        dd| d|dg      S t        dd|j                  dd       d|j                  dd       d|      S # t        $ r}t        dd| dg      cY d}~S d}~ww xY w)u   P0-3: gemini_severity_parser.count_severities 위임. high>=1이면 FAIL.

    text 우선, 없으면 pr_number+repo로 PR 리뷰 fetch.
    r   NFu&   gemini_severity_parser import 실패: import_errorr   r   r   u8   check_gemini_severity: text 또는 pr_number+repo 필요missing_argshigh   zGemini High severity u/   건 검출 — auto-approve/merge 차단 (P0-3)gemini_high_severityr   Tu-   Gemini High severity 0건 — 통과 (medium=mediumz, low=lowr!   rb   )r&   r   __file__parentsysrv   insertgemini_severity_parserImportErrorr   count_severitiesparse_pr_review_severitiesr   )r7   r}   r   _scripts_dir_gsprH   svr   s           r   check_gemini_severityr   S  s@    tH~,,-L388#<(
- ""4(		4,,T9=M$%
 	
 66&!Dqy*4&0_`,-	
 	
 >rvvhq?Q>RRXY[Y_Y_`efgYhXiijk 7  
;A3?$%
 	

s   C4 4	D=DDDFTgemini_highr   ci_passhidden_path_passmerge_sha_ok	author_okapprover_okc                   g }|dkD  r|j                  d| d       |r|j                  d       |s|j                  d       |s|j                  d       |s|j                  d       |s|j                  d       |s|j                  d	       |r)t        d
dt        |       d|  | |||||||dd|      S t        dd|  | |||||||dd      S )uZ   P0-4: 7조건 중 하나라도 fail → block. blocking 리스트로 사유 모두 반환.r   zgemini_high=u    (High severity 검출)u-   g3_fail=True (G3 verifier fail marker 존재)u,   ci_pass=False (required CI checks 미통과)z/hidden_path_pass=False (hidden path audit fail)u3   merge_sha_ok=False (merge commit SHA 검증 실패)u/   author_ok=False (PR author allowlist 불일치)u/   approver_ok=False (approver identity 불일치)Fu   auto-block 7조건 위반 u   건 — task=r   )rO   
conditionsr   Tu*   auto-block 7조건 모두 통과 — task=rb   )rN   r   rm   )	rO   r   r   r   r   r   r   r   r   s	            r   check_auto_block_conditionsr     s    HQ,{m3JKLGHFGIJMNIJIJ/HmG9U"#.&&(8$0!*#. 
 	
$ ;G9E*""$4 ,&*
 r   c                   | xs t         }|j                         sg S 	 t        j                  |j	                  d            }|j                  dg       }t        |t              sg S g }|D ]^  }t        |t              r'|j                  dd      }|s(|j                  |       :t        |t              sK|sN|j                  |       ` |S # t        $ r g cY S w xY w)ux   allowed_approvers.json에서 시스템 승인자 list 로드.

    누락/공백/오류 → 빈 list (fail-closed).
    rs   rt   	approversloginr;   )DEFAULT_ALLOWED_APPROVERS_PATHrM   r   r   r   r   
isinstancerc   r)   rN   r&   rE   )rv   _pathdatar   loginsentryr   s          r   load_allowed_approversr     s    
 22E<<>	zz%//7/;<HH["-	)T*I 	%E%&		'2.MM%(E3'Ee$	%  	s*   AC '+C "C 6C 9C CC)allowlist_pathc               X   | st        dddg      S t        |      }|xs t        }g }	 |j                         rgt	        j
                  |j                  d            }|j                  dg       }t        |t              r |D cg c]  }t        |t              s| }}d| v r| j                  d      d	   n| }||v rt        dd
|d|d|ddg      S |st        dddg      S ||v rt        dd
|d|dd      S t        dd
|d||ddg      S c c}w # t        $ r Y w xY w)u   P0-5: approver가 시스템 승인자(taskctl-gate, anu-verifier, GitHub App bot)인지.

    JonghyukJeon 등 사람 → manual approval 분류 (auto evidence 인정 X).
    Fu1   approver 미지정 — auto approve 차단 (P0-5)no_approverr   rs   rt   manual_logins r   z	approver=u_    는 human(manual) 승인자 — auto evidence 인정 불가 (P0-5). manual approval로 분류.manual)approvertyper   #human_approver_not_allowed_for_autor   uD   allowed_approvers.json 미설정/비어있음 — fail-closed (P0-5)empty_approver_allowlistTu;    시스템 승인자 확인 — auto evidence 인정 (P0-5)system)r   r   rb   uS    가 시스템 승인자 allowlist에 없음 — auto evidence 인정 불가 (P0-5))r   allowedapprover_not_in_allowlist)r   r   r   rM   r   r   r   r   r   rc   r&   rE   split)	r   r   r   r   r   r   mlxapprover_logins	            r   check_approver_identityr     s    F#_
 	
 %^4G <<E!M<<>::eoowo?@D/2.B"d#,. Eq*Q2D E E 03hX^^C(+HN &N- .Q R !/S`a;<
 	
 Y01
 	
  ~00kl .A
 	
 ) *1 2 +w?-. C !F s*   AD DDD D 	D)(D)c                n   t        ddd| d|  gd      \  }}}|dk7  s|sddd	i |xs d
| ddS 	 t        j                  |      }|j	                  d      xs d}|j	                  d      xs i j	                  d      xs d}||d|dS # t        j                  $ r}ddd	i d| dcY d}~S d}~ww xY w)u   gh api로 PR merge_commit_sha + base.ref 조회.

    Returns: {"merge_commit_sha": str, "base_ref": str, "ok": bool, "raw": dict}
    ghapizrepos/z/pulls/   )r8   r   r;   Fu   gh api 호출 실패 (rc=r!   )r   base_refr   rawerroru   JSON 파싱 실패: Nr   baserefT)r   r   r   r   )rI   r   r   JSONDecodeErrorr   )	r}   r   rcr@   rB   r   rH   	merge_shar   s	            r   fetch_pr_merge_shar   5  s    
 "	utfGI;78B 
Qwf "@!:2$a@
 	
	
zz&! +,2I &B++E28bH%	   
 "+A3/
 	

s   B B4
B/)B4/B4c                J    d|  d|  }t        dddd|gd|      \  }}}|d	k(  S )
u   Race-safe ``git fetch origin --no-tags +refs/heads/<base>:refs/remotes/origin/<base>``.

    subprocess shell=False, timeout=30. 실패 시 ``False``.
    z+refs/heads/z:refs/remotes/origin/gitfetchoriginz	--no-tagsr   r8   r9   r   )rI   )r   r9   refspecr   _s        r   _safe_git_fetchr   [  sF    
 XJ&;H:FG	;8HB1
 7Nr   c                h    t        dddd|  gd|      \  }}}|dk7  ry|j                         xs dS )	uM   ``git rev-parse --verify refs/remotes/origin/<base>`` (정확한 ref 경로).r   z	rev-parsez--verifyzrefs/remotes/origin/   r   r   N)rI   rA   )r   r9   r   r@   r   s        r   _rev_parse_originr   i  sI    	Z+?z)JKMB
 
Qw<<>!T!r   r9   force_fetchc               .   ddl }|xs t        }|rt        | |       t        | |      }|y|j	                  d       t        | |      }|y||k(  r|S |rt        | |       t        | |      }|y|j	                  d       t        | |      }|||k7  ry|S )u  Race-safe origin HEAD SHA fetch (P0-6).

    Sequence
    --------
    1. ``force_fetch=True`` 면 ``git fetch origin --no-tags +refs/heads/<base>:...``.
    2. 1차 ``git rev-parse origin/<base>`` -> ``sha_a``.
    3. ``time.sleep(0.5)`` (race window 노출).
    4. 2차 ``git rev-parse`` -> ``sha_b``.
    5. ``sha_a == sha_b`` 이면 그 값을 반환.
       불일치 시 1회 재 fetch + 재 조회. 그래도 불일치면 ``None``.

    Parameters
    ----------
    base_ref:
        Base branch (보통 ``main``).
    cwd:
        Git 작업 디렉토리. 기본 ``WORKSPACE``.
    force_fetch:
        ``True`` (기본) 면 호출 직전에 ``git fetch`` 강제 실행. 테스트에서
        ``False`` 로 끄고 mock 가능.
    r   Ng      ?)time	WORKSPACEr   r   sleep)	r   r9   r   r   work_dirsha_asha_bsha_csha_ds	            r   fetch_origin_head_shar   u  s    6 iH(+h1E}JJsOh1E}~ (+h1E}JJsOh1E}Lr   )r   base_branchr9   c                  | r|st        dddg      S t        | |      }|d   s$t        dd|j                  dd       d	|d
g      S |d   }|st        dd|dg      S |xs |d   }|st        dd|dg      S t        ||d      }|t        dd| d||ddg      S ||k7  r<t        ||d      }|||k7  r%t        dd|dd  d| d|dd  d||||ddg      S |}t        dd | d!|dd  d"|||d#$      S )%u   P0-6: merge_commit_sha <-> origin/<base> HEAD 일치 검증.

    - merge_commit_sha empty/null -> FAIL
    - base_branch 미지정 -> PR base.ref에서 동적 조회 (hardcoded 'main' 금지)
    - origin/<base> HEAD != merge_commit_sha -> FAIL
    Fu7   check_merge_commit_sha: pr_number와 repo 필요 (P0-6)r   r   r   u   PR 정보 조회 실패: r   unknownz (P0-6)pr_fetch_failedr   r   u2   merge_commit_sha empty/null — done 차단 (P0-6)empty_merge_shar   uF   PR base branch 조회 실패 — hardcoded 'main' 사용 금지 (P0-6)no_base_branchTr   Nzorigin/u    HEAD SHA 조회 실패 (P0-6))r   r   origin_sha_fetch_failedzSHA mismatch: merge_commit_sha(   u   ...) ≠ origin/z HEAD(u   ...) — done 차단 (P0-6))r   origin_head_sharetry_origin_shar   sha_mismatchzmerge_commit_sha == origin/z HEAD (u   ...) — 검증 통과 (P0-6))r   r  r   rb   )r   r   r   r   )	r}   r   r   r9   pr_infor   _base
origin_sha	retry_shas	            r   check_merge_commit_shar	    s    DL$%
 	
 !D1G4=.w{{7I/N.OwW'(	
 	
 *+IG'(	
 	
 .7:.E[&'	
 	
 'u#4HJUG#AB#(yA/0	
 	
 Y)%SdK		Y 65in5E F""'z#2.? @-.
 )2'1(1#(	 ))   
,UG79Sb>:JJgh )) 
 r   c                    t        t              j                  dz  } | j                         si S 	 t        j
                  j                  dt        |             }||j                  i S t        j
                  j                  |      }|j                  j                  |       t        |di       S # t        $ r i cY S w xY w)uM   scripts/taskctl.py의 ALLOWED_TRANSITIONS를 동적 import (circular 회피).z
taskctl.py_taskctl_moduleALLOWED_TRANSITIONS)r   r   r   rM   	importlibutilspec_from_file_locationr&   loadermodule_from_specexec_modulegetattrrE   )taskctl_pathspecmods      r   _load_allowed_transitionsr    s    >((<7L 	~~556G\IZ[<4;;.Inn--d3$s1266 	s   8B0 )AB0 0B>=B>c                T   t               }|st        dddg      S |j                  | t                     }h d}| |v r | dk7  rt        dd| d|d	| |d
ddg      S ||vr0t        dd| d|dt	        |       d| |t	        |      ddg      S t        d
d| d|d| |d      S )u   P0-7: ALLOWED_TRANSITIONS 기반 엄격 검증. fail->done 등 금지 전이 차단.

    동적 import로 scripts.taskctl 참조 (circular import 회피).
    Fu8   ALLOWED_TRANSITIONS 로드 실패 — fail-closed (P0-7)transitions_load_failedr   >   DONEFAILED	CANCELLEDADMIN_OVERRIDE_USEDr  u   terminal 상태 u   에서 전이 불가 — u    차단 (P0-7)T)srctargetterminalterminal_state_no_transitionr   u   금지된 상태 전이: z -> u
    (허용: z) (P0-7))r  r  r   forbidden_transitionu   상태 전이 허용: z (P0-7))r  r  rb   )r  r   r   setsorted)r  r  allowed_transitionsallowed_nextterminal_statess        r   check_state_transitionr(     s   
 45M/0
 	
 '**36L VO
o#/%cW,EfZ~^&dC45	
 	
 \!+C7$vj A"<01; &VL=QR,-
 	
 'wd6*GDf- r   )env
audit_path
productionc                  ||nt        t        j                        }|xs t        }|j	                  dd      dk(  }t        |j	                  dd            }|s|st        dd      S |}||j	                  d	d      dk(  }|r?g }	|r|	j                  d
       |r|	j                  d       t        dd|	 d|	dddg      S |j                         s!t        dd| dt        |      ||ddg      S 	 |j                  d      j                         }
g }|
D ]P  }|j                         }|s	 t        j                  |      }|j	                  d      | k(  r|j                  |       R 	 |s t        dd| dt        |      | ddg      S t        dd t#        |       d!| |d"#      S # t        j                  $ r Y w xY w# t         $ r}t        dd| ddg      cY d}~S d}~ww xY w)$u  P0-8: TASKCTL_BYPASS=1 / TASKCTL_PR_AUTHOR_OVERRIDE 사용 시 audit log 필수.

    - production env에서는 bypass/override 기본 금지 (PRODUCTION=1 fail-fast)
    - audit path 미생성/항목 없음 -> FAIL
    - audit jsonl에 본 task_id + 사용 사유 기록 필요
    NTASKCTL_BYPASSr;   1TASKCTL_PR_AUTHOR_OVERRIDETu4   bypass/override 미사용 — audit 불필요 (P0-8)r"   
PRODUCTIONzTASKCTL_BYPASS=1Fu7   production 환경에서 bypass/override 사용 금지: u    — fail-fast (P0-8))env_varsr+  production_bypass_forbiddenr   u3   bypass/override 사용 중이나 audit log 없음: u    — fail-closed (P0-8))r*  bypassoverridemissing_audit_logrs   rt   rO   u   audit log 읽기 실패: z (P0-8)audit_read_errorr   u   audit log에 task_id=u>    항목 없음 — bypass/override 사유 기록 필수 (P0-8))r*  rO   missing_audit_entryu%   bypass/override audit log 확인 — u   건 기록됨 (P0-8))rO   audit_entriesrb   )r)   osenvironADMIN_OVERRIDE_LOGr   r%   r   rN   rM   r&   r   
splitlinesrA   r   r   r   rE   rm   )rO   r)  r*  r+  _env_audit_pathbypass_activeoverride_active_productionactive_varslinestask_entriesliner   rH   s                  r   check_bypass_auditrF  P  s[    /3tBJJ'7D2 2KHH-r2c9M488$@"EFOI
 	

 Khh|R0C7!#12;<I+ W' ( !,4@34
 	
 Ek] S) * #&k"2mYhi)*
 	

%%w%7BBD 		D::<D

4(99Y'72 ''.		" '{ 3> ? #&k"2wG+,
 	
 6s<7H6II]^"\B ) ''  
.qc9()
 	

sB   5:G 0:F-*G -G G GG 	G,G'!G,'G,)r}   r*  c               T   |xs t         }|j                  j                  dd       | |t               |t        j
                  j                  dd      d}|j                  dd      5 }|j                  t        j                  |d	
      dz          ddd       |S # 1 sw Y   |S xY w)u7   admin-override.jsonl append. ADMIN_OVERRIDE_LOG 기본.T)parentsexist_okUSERr   )rO   r   tsr}   actorars   rt   F)ensure_ascii
N)r;  r   mkdirr4   r9  r:  r   openwriter   dumps)rO   r   r}   r*  r   r   fs          r   append_admin_override_auditrU    s     ,,E	LLtd3j	2E 
C'	* >a	

5u5<=>L>Ls   )*BB'c                   | xs t         }|j                         sg S 	 t        j                  |j	                  d            }|j                  dg       }|j                  dg       }g }||fD ]+  }t        |t              s|j                  d |D               - t               }g }|D ])  }	|	|vs|j                  |	       |j                  |	       + |S # t        $ r g cY S w xY w)ut   allowed_bot_accounts.json 로드.

    파일 없음 / 비어있음 -> [] (fail-closed 호출자에서 처리).
    rs   rt   patternsexactc              3  H   K   | ]  }t        |t              s|s|  y wr   )r   r&   ).0r   s     r   	<genexpr>z,load_allowed_bot_accounts.<locals>.<genexpr>  s     Ka*Q2DKs   """)DEFAULT_ALLOWED_BOT_PATHrM   r   r   r   r   r   rc   extendr#  addrN   rE   )
rv   r   r   rW  rX  combinedlstseenr   items
             r   load_allowed_bot_accountsrc    s    
 ,,E<<>	zz%//7/;<88J+"% e$ 	LC#t$K3KK	L  	$D4d#	$  	s   A"C /C 1%C C%$C%c                   | r|sy|D ]  }| |k(  r y|dk(  r| j                  d      r y|j                  d      r| j                  |      r y|dk(  r| j                  d      r y|j                  d      sp| j                  d      s y y)u~   author를 allowlist 패턴과 매칭.

    지원 패턴:
    - 정확 일치
    - *[bot] suffix
    - app/<slug> prefix
    FTz*[bot]z[bot]/zapp/)endswith
startswith)author	allowlistpatterns      r   _match_bot_authorrk    s      Wh6??7#;C V%6%6w%?f!2!26!:f%&*;*;F*C r   c                   | st        dddg      S t        |      }|st        ddd| idg      S t        | |      rt        d	d
| dd| i      S t        dd
| d| | |ddg      S )u   P0-9: PR author가 allowlist에 매칭하는지.

    - allowlist 미설정/빈 list -> FAIL (fail-closed)
    - 매칭 패턴: 정확 일치 + *[bot] suffix + app/<slug> prefix
    Fu:   PR author 미지정 — bot allowlist 검증 불가 (P0-9)	no_authorr   ub   allowed_bot_accounts.json 미설정/비어있음 — fail-closed (P0-9). 허용 bot 계정 없음.rh  empty_bot_allowlistr   Tz
PR author=u'    bot allowlist 매칭 — 허용 (P0-9)rb   uE    가 bot allowlist에 없음 — merge 차단 (P0-9). 허용 패턴: )rh  ri  author_not_in_allowlist)r   rc  rk  )rh  r   ri  s      r   check_bot_author_allowlistrp    s     O!]
 	
 *.9I, f%+,
 	
 +z)PQf%
 	
 
 #'[* !y9+, r   )r)  defaultc               F   ||nt        t        j                        }d}| /t        | t               r| j	                  d      }nt        | dd      }|rt        |      S |j	                  dd      }|r't        j                  dt        d       t        |      S |xs t        S )u   P0-10: --worktree CLI 인자 정식화.

    - argparse Namespace 또는 dict 모두 허용
    - args.worktree 우선
    - TASKCTL_CWD env는 deprecated warning 출력 후 채택
    - 둘 다 없으면 default 또는 WORKSPACE 반환
    NworktreeTASKCTL_CWDr;   z9TASKCTL_CWD is deprecated. Use --worktree <path> instead.   )
stacklevel)r)   r9  r:  r   r   r  r   warningswarnDeprecationWarningr   )rF   r)  rq  r=  worktree_valcwd_envs         r   resolve_worktree_pathr|  4  s     /3tBJJ'7D#LdD!88J/L"4T:LL!!hh}b)GG	

 G}ir   r*   )r   N)rF   	list[str]r8   r   r9   Path | Noner(   ztuple[int, str, str])rO   r&   rP   r   r(   
list[Path])rO   r&   rW   r   r(   r  r   )rO   r&   rP   r~  r(   r  )rO   r&   rP   r~  r(   r   )
rO   r&   r}   int | str | Noner   
str | NonerW   r~  r(   r   )r7   r  r}   
int | Noner   r  r(   r   )rO   r&   r   r   r   r%   r   r%   r   r%   r   r%   r   r%   r   r%   r(   r   )rv   r~  r(   r}  )r   r  r   r~  r(   r   )r}   r   r   r&   r(   r)   )r   r&   r9   r   r(   r%   )r   r&   r9   r   r(   r  )r   r&   r9   r~  r   r%   r(   r  )
r}   r  r   r  r   r  r9   r~  r(   r   )r(   zdict[str, set[str]])r  r&   r  r&   r(   r   )
rO   r&   r)  r'   r*  r~  r+  zbool | Noner(   r   )
rO   r&   r   r&   r}   r  r*  r~  r(   r   )rh  r&   ri  r}  r(   r%   )rh  r  r   r~  r(   r   )rF   zobject | Noner)  r'   rq  r~  r(   r   )4r.   
__future__r   importlib.utilr  r   r9  r=   r   rw  r   r   pathlibr   r:  r   r   r[   r   	SPECS_DIR	AUDIT_DIRr;  r\  r   rL   rV   r   r4   rI   rT   rY   r]   rg   rp   r   r   r   r   r   r   r   r   r   r	  r  r(  rF  rU  rc  rk  rp  r|  r/   r   r   <module>r     s  " #   	  
  '   02GHI	!H,
8#j0 7*	 #88	!77 $'BB !*-E!E  H H 9 9F2 
 
 	.
$0 # 4 #*** *l !%	iii i 	i
 id . !	.
. . 	.
 .r !?? ? 	?
 ? ? ? ? ? ?N: #'CC  C 	CV#L	" 	99 
9 	9
 9~ "WW W 	W
 
W W~ (f ""[[ 
[ 	[
 [ [D #'"  	
  
66< #'--  - 	-l "  	" 
"  
"  	" 
 
" r   