
    Ij"7                       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mZ  ee	      j                         j                  Zej                  j                  d ee             ddlmZ ddlmZmZmZ dZdZd	Zd
ZdZdZdZdZeeeeeeegZdZddZ ddZ!ddZ"	 	 	 d	 	 	 	 	 	 	 	 	 	 	 ddZ#	 d	 	 	 	 	 	 	 	 	 	 	 	 	 ddZ$ddZ%e&dk(  r ejN                   e%              yy)u   merge_group_evidence_auditor.py — merge_group evidence freshness auditor (task-2781).
얇은 오케스트레이션: 역추적 → PR별 evaluate_gate(pr, pr_head_sha) → 재해석/집계 → check publish + exit.    )annotationsN)Path)evaluate_gate)resolve_merge_group_prsMergeGroupPRsUnresolvedDEFAULT_REPOPASSMERGE_GROUP_PRS_UNRESOLVED"GEMINI_EVIDENCE_SKIPPED_OR_MISSINGGEMINI_EVIDENCE_STALEHEAD_ADVANCED_AFTER_ADMISSIONUNRESOLVED_HIGH_OR_CRITICALOWNER_RESOLVE_PENDINGAUDITOR_INTERNAL_ERRORzmerge-group-evidence-auditc                   	 t        j                  ddd|  d| gddd      }|j                  dk7  s|j                  j	                         sy	t        j                  |j                        }t        |t              sy	|j                  d
i       j                  d      }t        |t              r|j	                         r|S y	# t        $ r Y y	w xY w)uX   gh api repos/{repo}/pulls/{pr_number} → head.sha 반환. 실패/파싱불가 시 None.ghapirepos/z/pulls/T   )capture_outputtexttimeoutr   Nheadsha)
subprocessrun
returncodestdoutstripjsonloads
isinstancedictgetstr	Exception)repo	pr_numberprocdatar   s        ;/home/jay/workspace/scripts/merge_group_evidence_auditor.pyfetch_current_pr_headr,   +   s    ~~5F4&	{;<dB
 ??at{{'8'8':zz$++&$%hhvr"&&u-c3CIIKJ s   AC /C >AC 	CCc                     y)u   OWNER resolve 필요 미해결 thread 존재 여부.
    기본 구현: False (신호 없으면 pending 아님).
    실제로는 review thread resolve 상태 조회 가능하나, 신호 부재=not pending으로 처리.F )r'   r(   s     r+   fetch_owner_resolve_pendingr/   ?   s         c                4   |d   }|d   }t        |||       }|d   }t        | |      }|||t        d|dS ||k7  r||t        d|d||dS |d   j	                  d	g       }|r||t
        d
| |dS |dk(  rM|d   j	                  dg       }|r$t        d |D              r||t        d|d    |dS ||t        d|d    |dS |dk(  r||t        d|d    |dS |dk(  r)t        | |      }	|	r||t        d|dS ||t        |d   |dS ||t        d||dS )u  pr={"pr_number":int,"pr_head_sha":str} 단일 PR 감사.
    반환 {"pr_number","pr_head_sha","status","reason","gate_state"}.

    판정 순서(★ 정확히 이 순서):
      1) gate = evaluate_gate(pr_number, pr_head_sha, repo)   ← ★강제1: 반드시 pr_head_sha 사용
      2) current = fetch_current_pr_head(repo, pr_number)
         - current is None → AUDITOR_INTERNAL_ERROR (fail-closed: 현재 head 확인 불가)
         - current != pr_head_sha → HEAD_ADVANCED_AFTER_ADMISSION
      3) high_severity_hits 비어있지 않음 → UNRESOLVED_HIGH_OR_CRITICAL
      4) gate.state == "block":
           primary 존재 AND 모든 primary entry 가 stale=True → GEMINI_EVIDENCE_STALE
           그 외 → GEMINI_EVIDENCE_SKIPPED_OR_MISSING
      5) gate.state == "hold" → GEMINI_EVIDENCE_SKIPPED_OR_MISSING  (★강제2: 대기 없이 fail-closed)
      6) gate.state == "pass":
           fetch_owner_resolve_pending → True 면 OWNER_RESOLVE_PENDING
           아니면 PASS
    r(   pr_head_shastateu?   fetch_current_pr_head returned None — cannot confirm head SHAr(   r2   statusreason
gate_statez+PR head advanced after admission: admitted=z	 current=evidencehigh_severity_hitsz,Unresolved high/critical severity findings: blockprimaryc              3  B   K   | ]  }|j                  d       du   yw)staleTNr$   ).0es     r+   	<genexpr>zaudit_pr.<locals>.<genexpr>   s     Ca155>T1Cs   z.All primary evidence is stale (SHA mismatch): r6   z!Gemini evidence blocked/missing: holdz;Gemini evidence hold interpreted as missing (fail-closed): passz9Owner resolve required: unresolved review thread detectedzUnknown gate state: )r   r,   r   r   r$   r   allr   r   r/   r   r	   )
r'   prr(   r2   gater7   currentr9   r;   pendings
             r+   audit_prrI   F   s   $ _I-(K K6DgJ $D)4G"&,W$
 	
 +"&3CK?R[\c[fg$
 	
 j)--.BBG"&1DEWDXY$
 	
 Wz"&&y"5sC7CC&*/J4PX>JZ[(  #&89$x.9IJ$
 	
 V"&8STXYaTbScd$
 	
 V-dI>&*/U(  #&8n$
 	
 "((7  r0   c                (   	 t        | ||||      }|st        dg |g | |g ddS g }|D ]   }	 t        | |      }	|j                  |	       " |D 
cg c]  }
|
d   t        k7  s|
 }}
|st        dt        |       d|||d
S |D 
cg c]  }
|
d   	 }}
t
        t        D ]
  }||v s| n t        fd|D        d|       }||||d
S # t        $ rK}t        d| g |g t        |dd      xs | t        |dd      xs |t        |dd      xs g ddcY d}~S d}~wt        $ r}t
        d	| g |g d
cY d}~S d}~ww xY w# t        $ r8}|j                  d      |j                  d      t
        d| dd}	Y d}~Hd}~ww xY wc c}
w c c}
w )uk  반환 {"status", "reason", "pr_results":[...], "merge_group_sha","resolved_prs":[...]}.
      - resolve_merge_group_prs 호출. MergeGroupPRsUnresolved → status=MERGE_GROUP_PRS_UNRESOLVED.
      - resolve 중 기타 예외 → AUDITOR_INTERNAL_ERROR (fail-closed).
      - 각 PR audit_pr 루프를 try/except 로 감싸 예외 시 AUDITOR_INTERNAL_ERROR (★케이스12).
      - 집계: 모든 pr_results status==PASS 면 PASS. 아니면 실패한 것들 중 FAILURE_PRECEDENCE 최우선 status 채택.
      - resolved_prs 가 비어있으면(정상 resolve인데 0건) MERGE_GROUP_PRS_UNRESOLVED 로 fail-closed.r'   merge_group_shabase_shahead_refeventzPR resolution failed: r'   NrL   diagnostics)r'   rL   tiers)r5   r6   
pr_resultsrL   resolved_prsrP   z'Unexpected error during PR resolution: )r5   r6   rR   rL   rS   z<resolve_merge_group_prs returned empty list (0 PRs resolved)r(   r2   zaudit_pr raised exception: r4   r5   zAll z PR(s) passed evidence auditc              3  :   K   | ]  }|d    k(  s|d     yw)r5   r6   Nr.   )r?   rbest_statuss     r+   rA   z$audit_merge_group.<locals>.<genexpr>  s      C({(B8Cs   
zOne or more PRs failed audit: )r   r   r
   getattrr&   r   rI   r$   appendr	   lenFAILURE_PRECEDENCEnext)r'   rL   rM   rN   rO   rS   r@   rR   rE   resultrU   failedfailed_statuses	candidatebest_reasonrV   s                  @r+   audit_merge_groupra      s&   
.+
< 0T.#2
 	
  J "		dB'F 	&!" $;Aq{d':a;F;S_--IJ$.(
 	
 -33qq{3O3(K' 	'#K CfC
((9:K  *$ [ # 
0.qc2.6408D#*1.?#F#Y/ M48>B
 	
  
,?sC.
 	

:  	VVK0!vvm407s;"F	 < 4sY   C EF
#F
F	EA DEE*D>8E>E	F-FFc           	         ||d|||dd |dd dd}t        j                  dddd	d
|  dddgt        j                  |d      ddd      }|j                  |j
                  |j                  dS )uf   GitHub check run을 gh api POST로 발행.
    gemini_review_gate.publish_check_run 와 동일 패턴.	completedNi  )titlesummaryr   )namehead_shar5   
conclusionoutputr   r   z-XPOSTr   z/check-runsz--input-Fensure_asciiTr   )inputr   r   r   )rcr   stderr)r   r   r    dumpsr   r   rp   )r'   r   rf   rh   re   detailspayloadr)   s           r+   publish_check_runrt   $  s      vFUO

G >>	udFfTF+$>	3Ojju5D //T[[DKKPPr0   c                 
   t        j                  d      } | j                  ddd       | j                  dd d	       | j                  d
d d	       | j                  dt        d	       | j                  dd d	       | j                  ddd       | j                  dddd       | j	                         }d }|j
                  r9	 t        j                  t        |j
                        j                  d            }t        |j                  |j                   |j"                  |j$                  |      }|d    }|t&        k(  rd!nd"}| d#|d$    }|j(                  r>t+        |j                  |j                   t,        ||t        j                  |dd%&             |t.        k(  ro|j1                  d'      r^|d'   }	d(j3                  d) |	j1                  d*g       D              }
t        d+|	j1                  d,       d-|
 d.t4        j6                  /       t        t        j                  |dd%&             |t&        k(  rd0S dS # t        $ r6}t        d| d}t        t        j                  |d             Y d }~yd }~ww xY w)1NuF   merge_group_evidence_auditor — merge_group evidence freshness 감사)descriptionz--merge-group-shaTzmerge_group head SHA)requiredhelpz
--base-shazmerge_group base SHA)defaultrx   z
--head-refzmerge_group head_refz--repoz
OWNER/REPOz--event-jsonu   GitHub event JSON 파일 경로z--publish-check
store_trueu   GitHub check run 발행)actionrx   z--jsonjson_outu   JSON 출력)r{   destrx   zutf-8)encodingzevent-json load failed: )r5   r6   Frl      rK   r5   successfailurez: r6      )rm   indentrP   ,c              3     K   | ]:  }|j                  d       r'|j                  d       d|j                  d        < yw)invokedtier:reject_reasonNr>   )r?   ts     r+   rA   zmain.<locals>.<genexpr>q  s@       
aeeI.> uuV}oQquu_567 
s   A ArQ   z[diagnostics] repo=r'   z tiers=[])filer   )argparseArgumentParseradd_argumentr   
parse_args
event_jsonr    r!   r   	read_textr&   r   printrq   ra   r'   rL   rM   rN   r	   publish_checkrt   
CHECK_NAMEr
   r$   joinsysrp   )apargsrO   r@   errr\   r5   rh   re   diagtier_summarys              r+   mainr   C  sK   		 	 \
B OO'$=SOTOOL$5KOLOOL$5KOLOOHlOFOOND7XOYOO%lAZO[OOH\
OW==?DE	JJtDOO4>>>PQE YY,,F HF$n)J6(+,-GII  JJvE!<	
 ++

=0Im$xx  
XXgr* 
 
 	#DHHV$4#5Xl^1MTWT^T^_	$**V%
:;$1%A%K  	3AYZ[Y\?]^C$**Su56	s   8I 	J,I==J__main__)r'   r%   r(   intreturn
str | None)r'   r%   r(   r   r   bool)r'   r%   rE   r#   r   r#   )NNN)r'   r%   rL   r%   rM   r   rN   r   rO   zdict | Noner   r#   ) )r'   r%   r   r%   rf   r%   rh   r%   re   r%   rr   r%   r   r#   )r   r   )(__doc__
__future__r   r   r    r   r   pathlibr   __file__resolveparentROOTpathinsertr%   gemini_evidence_verifyr   merge_group_pr_resolverr   r   r   r	   r
   r   r   r   r   r   r   rZ   r   r,   r/   rI   ra   rt   r   __name__exitr.   r0   r+   <module>r      s  C "    
 H~&& 3t9  0 b b 9 %I "/  ? ; / 1  !&  *

(nh  j
jj j 	j
 j 
jf Q
Q	Q Q 	Q
 Q Q 
Q>6&r zCHHTV r0   