
    j!R              
         U d Z ddlmZ ddlZddlmZmZmZmZm	Z	 ddl
mZmZ ddlmZmZmZmZmZ ddlmZ ddlmZ d	Zd
ed<   dZd
ed<   dZd
ed<   dZd
ed<   dZd
ed<   dZd
ed<   dZd
ed<    eeeeeeeeh      Z ded<   dZ!d
ed<   dZ"d
ed<   dZ#d
ed<   dZ$d
ed <   d!Z%d
ed"<    ejL                  d#$       G d% d&             Z'	 	 	 	 	 	 	 	 d/d'Z(d0d(Z)d1d)Z*d*d*d+dddddd#d,		 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 d2d-Z+g d.Z,y)3u  anu_v2.ci_gemini_watcher_runner — CI + Gemini watcher 단일 멱등 cycle runner (task-2718).

회장 인가: 기존 anu_v2 빌딩블록을 import/call 로 결선하는 얇은 runner.
신규 로직 최소화. 실제 GitHub write 0 (decision/dry-run only). task-2718.

책임:
  - 기존 빌딩블록(freshness_checker / validate_decision / AutoGeminiTriage /
    ExecutorScheduler) 을 결선해 단일 1회 실행 → terminal enum 1 개 반환.
  - 세션-bound sleep/polling 절대 금지 (1 회 실행 → 즉시 반환).
  - 실제 GitHub write 0 (github_writes 카운터 항상 0 유지).
  - 모든 부수효과는 주입형 callable 로 추상화.

one-way isolation: anu_v2/* 만 import. 외부 (utils/dispatch/scripts/dashboard) 의존성 0.
    )annotationsN)AnyCallableFinalMappingSequence)check_gemini_evidence_freshRESULT_FRESH)validate_decisionSCHEMA_NAMEALLOWED_ACTIONALLOWED_COMMENT_BODYDecisionInvalidError)AutoGeminiTriage)ExecutorSchedulerMERGE_READY_CANDIDATEz
Final[str]TERMINAL_MERGE_READY_CANDIDATELOOP_BOUNDARYTERMINAL_LOOP_BOUNDARYBLOCKED_BY_CAPABILITYTERMINAL_BLOCKED_BY_CAPABILITY GEMINI_EXTERNAL_TRIGGER_REQUIRED)TERMINAL_GEMINI_EXTERNAL_TRIGGER_REQUIREDCI_FAILED_NON_REMEDIABLE!TERMINAL_CI_FAILED_NON_REMEDIABLEHOLD_STALE_HEADTERMINAL_HOLD_STALE_HEADHOLD_SCOPE_UNCLEANTERMINAL_HOLD_SCOPE_UNCLEANzFinal[frozenset[str]]ALL_TERMINALSALLOW_OWNER_TRIGGERTRIGGER_ALLOW_OWNEROWNER_TRIGGER_DEDUPEDTRIGGER_OWNER_DEDUPEDTRIGGER_EXTERNAL_REQUIREDSELF_GEMINI_TRIGGER_BLOCKEDTRIGGER_SELF_BLOCKEDNONETRIGGER_NONET)frozenc                      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ed<   ded<   ddZy)WatchResultu  run_watch_cycle 결과 객체 (frozen — 불변 값 객체).

    fields:
      terminal        : ALL_TERMINALS 중 하나.
      trigger_decision: TRIGGER_* 상수 중 하나.
      pr_number       : 평가된 PR 번호.
      expected_head   : queue/expected head SHA (입력).
      actual_head     : gh_runner 로 실측한 PR HEAD SHA.
      ci_state        : CI rollup state ("SUCCESS" | "FAILURE" | "PENDING" | "UNKNOWN").
      gemini_freshness: FRESH | STALE | NO_REVIEW | "UNKNOWN".
      triage_summary  : triage_batch 결과 count 요약 (FRESH 시 채움).
      decision_json   : ALLOW_OWNER_TRIGGER 시 validate 통과 decision dict.
      github_writes   : 항상 0 (실 write 0 입증).
      dry_run         : 기본 True.
      reason          : 짧은 사유 문자열.
    strterminaltrigger_decisionint	pr_numberexpected_headactual_headci_stategemini_freshnessdict | Nonetriage_summarydecision_jsongithub_writesbooldry_runreasonc                ,    t        j                  |       S )u:   JSON 직렬화용 dict 반환 (dataclasses.asdict 기반).)dataclassesasdict)selfs    P/home/jay/workspace/.worktrees/task-2723-dev2/anu_v2/ci_gemini_watcher_runner.pyto_jsonzWatchResult.to_jsonh   s    !!$''    N)returndict)__name__
__module____qualname____doc____annotations__rB    rC   rA   r,   r,   H   sQ    " MNMMK(rC   r,   c               (    | j                  ||      S )u^  ExecutorScheduler._has_active_trigger_for_head 를 thin-wrap 하는 dedupe 헬퍼.

    프로덕션: run_watch_cycle(dedupe_checker=lambda pr, sha: scheduler_backed_dedupe(sched, pr_number=pr, head_sha=sha))
    테스트  : dedupe_checker=mock 주입.

    ExecutorScheduler 구성은 runner 외부에서 담당 (thin runner 책임 최소화).
    )r1   head_sha)_has_active_trigger_for_head)	schedulerr1   rM   s      rA   scheduler_backed_deduperP   q   s     11IPX1YYrC   c                    t        | t              rt        |       dk7  rt        | d|       | j	                         }t        d |D              rt        | d|       |S )uB   40-char hex SHA 검증 후 lower 정규화. 실패 시 ValueError.(   u7    는 40-char hex SHA 문자열이어야 합니다, got c              3  $   K   | ]  }|d v 
 yw)0123456789abcdefNrK   ).0cs     rA   	<genexpr>z$_validate_hex_sha.<locals>.<genexpr>   s     
811&&
8s   u+    는 40-char hex SHA (hex chars only), got )
isinstancer-   len
ValueErrorlowerany)valuenamelowereds      rA   _validate_hex_shar`      se    eS!SZ2%5D6!XY^XabcckkmG

8
88D6!LUIVWWNrC   c                    t        | j                  d      xs g       t        | j                  d      xs g       t        | j                  d      xs g       dS )u3   triage_batch 결과에서 count 요약 dict 생성.applied	dismissed	escalated)applied_countdismissed_countescalated_count)rY   get)outs    rA   _triage_count_summaryrj      sP     SWWY/526sww{39r:sww{39r: rC    unknown)	ownerrepotask_idowner_proofdedupe_checkerrecord_triggertriageaudit_writerr;   c                    t         ddddddd	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 d> fd}t         t              rt         t              s dk  rt	        d       t        d	      } |t        t         d
      S  d       }|r't        |      j                         j                         nd}||k7  r |t        |d|dd  d|dd  d      S  d       xs g }t        |      }|D cg c]	  }||vs| }}|r3 |t        |dt        |       d|dd t        |      dkD  rdnd       S  d       xs i }|j                  dd      }|dk(  r"|j                  dd      s |t        ||d      S fd}t!         ||||      }|j"                  }|j"                  t$        k(  r d        xs g }|
}|||nd! }t'        ||"      }|j)                  ||      }t+        |      }|j                  d#      xs g }|r |t,        ||||d$t        |       d%&      S |d'k(  r |t.        ||||d(&      S  |t,        ||||d)| d*&      S |'|j                  d+      r |t        t0        |||d,-      S t        |      xrR t        |j                  d.            xr6 t        |j                  d/            xs t        |j                  d0            } | s |t2        t4        |||d1-      S |	 | |      nd}!|!r  |t2        t6        |||d2  d3|dd  d4-      S t8        | |d5d5ddt:        t<        d5d6}"	 t?        |"|7       |		 |	 |        |t2        tF        ||||"d:  d3|dd  d;|j"                   d< =      S c c}w # t@        $ r:}# |t        t         |||d8|#jB                   d9|#jD                   -      cY d}#~#S d}#~#ww xY w)?uF  CI + Gemini watcher 단일 멱등 cycle runner (1회 실행 → terminal enum 1개 반환).

    세션-bound sleep/polling 절대 금지. github_writes 는 항상 0.
    결정 로직 순서는 명세 §결정로직 1~7 1:1.

    Args:
      pr_number      : 평가 대상 PR 번호 (positive int).
      expected_head  : queue/expected head SHA (40-char hex).
      expected_files : 기대 diff 파일 경로 목록 (scope 검사 기준).
      gh_runner      : 주입형 부수효과 callable (op 분기 — 명세 §gh_runner 참조).
      owner          : GitHub owner (freshness checker 에 전달).
      repo           : GitHub repo (freshness checker 에 전달).
      task_id        : 감사 기록용 task ID.
      owner_proof    : OWNER 권한 증빙 dict (is_owner/admin/push/self_key). None 가능.
      dedupe_checker : (pr_number, head_sha) → bool. 이미 trigger 된 head 중복 방지.
      record_trigger : (pr_number, head_sha) → None. dry-run trigger 기록 (부수효과 없음).
      triage         : AutoGeminiTriage 인스턴스. None 이면 내부 생성.
      audit_writer   : triage 내부 audit 기록용 callable.
      dry_run        : 기본 True (실 write 0 보장).

    Returns:
      WatchResult (frozen dataclass).

    Raises:
      ValueError: pr_number <= 0 또는 expected_head 형식 오류.
    rk   UNKNOWNN)r/   r3   r4   r5   r7   r8   r<   r3   c               X    | t         v sJ d| d       t        | |
	|||||d|      S )u=   내부 WatchResult 생성 헬퍼. github_writes 는 항상 0.z	terminal z not in ALL_TERMINALSr   )r.   r/   r1   r2   r3   r4   r5   r7   r8   r9   r;   r<   )r    r,   )r.   r/   r3   r4   r5   r7   r8   r<   r;   r2   r1   s           rA   _makezrun_watch_cycle.<locals>._make   sS     =(WIh\AV*WW(-'#-)'
 	
rC   r   u1   pr_number 은 양의 정수여야 합니다, got r2   u-   gh_runner 이 None — capability gate 차단)r/   r<   )r1   zactual HEAD (   z...) != expected HEAD (u   ...) — stale head)r3   r<   
diff_pathsu   diff 에 scope 외 경로 u   건 포함:    z...	ci_rollupstateFAILURE
remediableFu4   CI FAILURE (non-remediable) — 자동 복구 불가)r3   r4   r<   c                     d| |      S )Nreviews)methodpathrK   )r   r   	gh_runners     rA   <lambda>z!run_watch_cycle.<locals>.<lambda>  s    YydK rC   )r1   current_head_sha
github_apirm   rn   findingsc                     y )NrK   )recs    rA   r   z!run_watch_cycle.<locals>.<lambda>1  s    rC   )rt   ro   rd   ztriage escalated u   건 — LOOP_BOUNDARY)r3   r4   r5   r7   r<   SUCCESSu3   triage clean + CI SUCCESS → MERGE_READY_CANDIDATEu   triage clean 이지만 CI=u    — 보수적 LOOP_BOUNDARYself_keyu8   owner_proof.self_key=True — self Gemini trigger 차단)r/   r3   r4   r5   r<   is_owneradminpushuA   owner proof 없음/불충분 — GEMINI_EXTERNAL_TRIGGER_REQUIREDz(pr=z, head=u#   ...) 이미 trigger 됨 — DEDUPEDT)schemaro   prcurrent_head
queue_headcurrent_head_confirmedgemini_evidence_freshnudge_count_for_pr_headallowed_actioncomment_bodyallowed)current_head_actualu   validate_decision 실패 code=z: u   owner trigger 허가 — pr=z..., freshness=z
, dry_run=)r/   r3   r4   r5   r8   r<   )r.   r-   r/   r-   r3   r-   r4   r-   r5   r-   r7   r6   r8   r6   r<   r-   rD   r,   )$r)   rX   r0   r:   rZ   r`   r   r-   r[   stripr   setr   rY   rh   r   r	   statusr
   r   triage_batchrj   r   r   r'   r   r%   r$   r   r   r   r   r   codemessager"   )$r1   r2   expected_filesr   rm   rn   ro   rp   rq   rr   rs   rt   r;   rx   expected_head_normactual_head_rawr3   rz   expected_files_setpout_of_scopecir4   _github_apifrr5   r   _triage_audit_writer
triage_outsummary
unresolvedr   already_triggereddecisionexcs$   `` `        `                       rA   run_watch_cycler      s7   \ !-! )&*%)

 
 	

 
 
 $
 #
 
 

: i%It)D	UVLYMZ[[*=/J *)B
 	
  CO ;J#o&,,.446rK (($#BQ0 1""4Ra"8!99LN	
 	
 &liHNBJ^,)I!Q6H-HAILI'#,S->,?|#&L0AA0Eu2&NP	
 	
 	:@bBFF7I.H9RVVL%%@-#I	
 	
 	L  
%$
B II 
yyL (yIOR ? , 8?O  '*G #//.I
'
3%>>+6<"
&'!!1&*3z?*;;PQ  y .'!!1&L  "#-"/z9UV
 	
 ;??:#>*1#-M
 	
 	[ 	N,-	N+//'*+LtKOOF4K/L  56#-V
 	
 3A2Ly+.RW  52#-)GKO+<<_`
 	
 #"&!&#$(,H
(D !y+. 1,)*9+W[!_<M N:gY8 O Jn   	
*)#-3CHH:R}M
 	
	
s*   2	N
<N
N 	O/OOO)r   r   r   r   r   r   r   r    r"   r$   r%   r'   r)   r,   r   rP   )rO   r   r1   r0   rM   r-   rD   r:   )r]   r-   r^   r-   rD   r-   )ri   rE   rD   rE   )r1   r0   r2   r-   r   zSequence[str]r   zCallable[..., Any]rm   r-   rn   r-   ro   r-   rp   zMapping[str, Any] | Nonerq   z!Callable[[int, str], bool] | Nonerr   z!Callable[[int, str], None] | Noners   zAutoGeminiTriage | Nonert   z*Callable[[Mapping[str, Any]], None] | Noner;   r:   rD   r,   )-rI   
__future__r   r>   typingr   r   r   r   r   (anu_v2.gemini_evidence_freshness_checkerr	   r
   anu_v2.owner_trigger_decisionr   r   r   r   r   anu_v2.auto_gemini_triager   anu_v2.executor_schedulerr   r   rJ   r   r   r   r   r   r   	frozensetr    r"   r$   r%   r'   r)   	dataclassr,   rP   r`   rj   r   __all__rK   rC   rA   <module>r      s   #  : :  7 7 .E 
 D%4 
 4-D 
 D8Z ): Z0J !: J'8 * 8*> Z >'0""-%2 ($  #8 Z 7$; z ;(J : J#@ j @!j ! d#!( !( $!(PZ Z Z 	Z
 
Z(& ,08<8<&*?CZZ Z "	Z
 "Z Z Z Z *Z 6Z 6Z $Z =Z Z ZB	rC   