
    C jB?                        d 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
mZ  e ee      j                         j                  j                        Zeej"                  vrej"                  j%                  de       ddlmZmZmZmZmZ ddlmZmZmZ dZd	Zd
Ze G d d             Z de de!de fdZ"de de fdZ#de de fdZ$de de fdZ%de de fdZ&de de fdZ'de de fdZ(edde)de)de)de!fdZ*ddddddede)ded ede d!e
e	eef      d"e
e   d#e
e)   d$e
e   de	eef   fd%Z+d&ed'ed(e)de deeef   f
d)Z,dddd*dede)d+ed,ed-ed.e!de d/e
e   d0e
e   d1e
e   de	eef   fd2Z-ddddd3de)d4ed5ed$e
e   d6e!d7e
e   d8e
e   d9e
e)   d:e
e)   de	eef   fd;Z.dd<ded=e!d>e!d?e!d@e!dAe
e   de	eef   fdBZ/dFdCe
e0   de)fdDZ1e2dEk(  r ejf                   e1              yy)Gu  scripts/ci_watch_handoff_runner.py — CI_WATCH_HANDOFF progress watcher runner.

task-2729 Phase 1 — progress watcher dispatch gate (watcher 골격).

회장 목표:
  dispatch 직후 등록되는 progress watcher 의 러너 골격.
  6 상태(head change · non-force push · CI · finish-task .done · normal callback ·
  fallback prune) 추적 + review-settle quiet-window 골격 + terminal 도달 시
  ANU normal callback 발사를 담당한다.

  ★ ACTIVE=false (Phase 1):
    본 러너는 --dry-run/--once 모드에서 네트워크 호출(gh 폴링) 없이 import-safe 하게
    동작하는 골격이다. 실제 live gh polling 루프는 골격만 두고 미실행한다
    (watcher 위임 골격). production ACTIVE 전환은 별도 회장 승인 대상.

  ★ fallback 은 progress trigger 아님:
    fallback_prune 추적 상태는 dead-man safety-net 의 관측 항목일 뿐이며
    progress trigger 가 아니다. progress 판단은 watcher 의 6 상태/terminal classify 가
    담당한다.

기존 classifier(utils.pr_watcher_terminal_state_classifier) 를 재사용하며
raw ANU 키 문자열을 절대 작성하지 않는다 (anu_key=None → registrar 가 env→default 해결).
    N)	dataclass)Path)AnyDictOptionalTuple)
PRSnapshotclassifyTERMINAL_STATESbuild_callback_enveloperegister_terminal_callback)WATCHER_TRACKED_STATES watcher_terminal_callback_statusall_states_trackedx      i  c                       e Zd ZU dZdZeed<   dZeed<   dZeed<   dZ	eed<   dZ
eed<   dZeed<   d	Zeed
<   d	Zeed<   deeef   fdZdefdZy)WatcherStateu.   watcher 6 상태 추적 + poll/elapsed 메타.Fhead_changenon_force_pushcifinish_donenormal_callbackfallback_pruner   pollselapsed_secreturnc                     | j                   | j                  | j                  | j                  | j                  | j
                  dS )u+   WATCHER_TRACKED_STATES 키 → bool 매핑.r   r   r   r   r   r   r   selfs    "scripts/ci_watch_handoff_runner.pyas_tracked_dictzWatcherState.as_tracked_dictI   sB      ++"11''++#33"11
 	
    c                 4    t        | j                               S )u;   6 상태가 모두 True 인지 (all_states_tracked 사용).)r   r#   r    s    r"   all_trackedzWatcherState.all_trackedT   s    !$"6"6"899r$   N)__name__
__module____qualname____doc__r   bool__annotations__r   r   r   r   r   r   intr   r   strr#   r&    r$   r"   r   r   <   st    8K ND BK!OT! ND E3NK	
c4i 	
:T :r$   r   stateflagsr   c                 t    |j                         D ]$  \  }}|t        v st        | |t        |             & | S )u7   6 상태 갱신 (WATCHER_TRACKED_STATES 키만 허용).)itemsr   setattrr+   )r0   r1   keyvalues       r"   update_stater7   Y   s;    kkm -
U((E3U,- Lr$   c                     d| _         | S NT)r   r0   s    r"   mark_head_changer;   a       ELr$   c                     d| _         | S r9   )r   r:   s    r"   mark_non_force_pushr>   f       ELr$   c                     d| _         | S r9   )r   r:   s    r"   mark_cirA   k   s    EHLr$   c                     d| _         | S r9   )r   r:   s    r"   mark_finish_donerC   p   r<   r$   c                     d| _         | S r9   )r   r:   s    r"   mark_normal_callbackrE   u   s     ELr$   c                     d| _         | S r9   )r   r:   s    r"   mark_fallback_prunerG   z   r?   r$   )quiet_window_seclast_event_secnow_secrH   c                     || z
  |k\  S )u   review-settle quiet-window 판정 골격.

    마지막 이벤트 이후 quiet_window_sec 이상 무이벤트면 settled.
    r/   )rI   rJ   rH   s      r"   is_quiet_window_settledrL      s     n$)999r$   )last_snapshotanu_keychat_idrunnertask_id	pr_numberterminal_statereasonrM   rN   rO   rP   c        	   	          t        | ||||j                  |j                  |      }	t        |	|||      }
|
j                  rd|_        |
j                  |t        d|
j                        dS )u  terminal 도달 시 ANU normal callback 발사 (classifier 재사용).

    - build_callback_envelope 로 envelope 생성 → register_terminal_callback 호출.
    - ★ anu_key=None 이면 registrar 가 env→ANU_KEY_DEFAULT 로 해결 (self-key 차단은
      그쪽 책임). raw 키 문자열 절대 작성 금지.
    - result.fired=False 면 callback_status == WATCHER_TERMINAL_CALLBACK_NOT_WIRED.
    )rQ   rR   rS   rT   polls_completedr   rM   )enveloperN   rO   rP   T)terminal_reachedcallback_fired)firedrS   callback_status)r   r   r   r   rZ   r   r   )rQ   rR   rS   rT   r0   rM   rN   rO   rP   rW   results              r"   fire_terminal_callbackr]      s|    & '%%%#H (	F || $(;!!<<
 r$   snapexpected_headelapsed_watcher_secc                    |xj                   dz  c_         ||_        t        | ||      \  }}|r|t        v r||fS d|fS )u   단일 classify 평가 → (terminal_state_or_empty, reason).

    terminal 이면 반환 state 는 TERMINAL_STATES 에 포함된다.
       )r`   r_    )r   r   r
   r   )r^   r_   r`   r0   rS   rT   s         r"   run_oncerd      sS     
KK1K+E%/#NF
 .O;v%%v:r$   )triggeralready_fired_heads
audit_pathownerrepocurrent_head_shahead_changedre   rf   rg   c        
             |sdddS 	 ddl m}
 |
j                  ||||| d|	|dd|      }|S # t        $ r0}ddt	        |      j
                   d	| d
||ddddd	}Y d}~|S d}~ww xY w)u  결함6 결선: head 변경 시 OWNER /gemini review 자동 발사.

    ★ ACTIVE=false 골격: 실 gh 네트워크 호출 없이 구조·결선만 제공.
    ★ review-settle: 발사 후 state는 변경하지 않음 (progress trigger 아님).
      /gemini review 발사는 리뷰 요청일 뿐이며 watcher progress 판단에 개입하지 않는다.
    ★ head_changed가 False이면 발사하지 않고 NO_NEW_HEAD 반환.
    FNO_NEW_HEAD)rZ   statusr   NT)rR   rj   rh   ri   rQ   observed_commentrg   routerrequest_onlytoken_providerrf   zFIRE_ERROR: : z/gemini review)	rZ   rn   bodyrR   head_shadeduperq   token_hash_prefixactive)utils.owner_gemini_triggerowner_gemini_triggerfire_owner_gemini_review	Exceptiontyper'   )rQ   rR   rh   ri   rj   rk   r0   re   rf   rg   _ogtr\   excs                r"   #maybe_fire_owner_gemini_on_new_headr      s    ( -88
1..-!! 3 / 
6 M  
$T#Y%7%7$83%@$"( !%

 M
s   "- 	A&%A!!A&)sync_fnfetch_state_fnmax_diff_linesobserved_diff_linesmerge_state_status
pr_workdirmerge_approvedr   r   r   r   c        	         X   |sdddS |dv rdddS |dk7  rdddd	S |	 dd
l m}	 |	||fd}	  || |      }
|
j                  d      rdddd	S ||
||kD  rdddS |	 dd
l m}	 |	j                  }	  || |      }|j                  dd      }|dk(  rdddS dddd	S # t        $ r dddcY S w xY w# t        $ r(}ddt	        |      j
                   d| dcY d
}~S d
}~ww xY w# t        $ r dddcY S w xY w# t        $ r(}ddt	        |      j
                   d| dcY d
}~S d
}~ww xY w)u  결함5: merge 전 base sync 파이프라인.

    - merge_approved False → NOT_APPROVED.
    - merge_state_status가 admin·force 필요 신호(DIRTY/BLOCKED) → ABORT_ADMIN_OR_FORCE_REQUIRED.
      절대 admin override/force 호출 금지.
    - UP_TO_DATE면 sync 불필요 → UP_TO_DATE 반환.
    - BEHIND면 sync_fn(기본=merge_queue_executor.sync_pr_base) 호출.
      conflict → ABORT_CONFLICT.
      diff 초과 → ABORT_DIFF_OVER_LIMIT.
      성공 시 fetch_state_fn으로 재검증 → BEHIND면 ABORT_STILL_BEHIND, 아니면 BASE_SYNCED_REVALIDATED.
    - sync_fn/fetch_state_fn 미주입 시 merge_queue_executor 지연 import(테스트 mock 우선).
    FNOT_APPROVED)merge_okrn   )DIRTYBLOCKEDABORT_ADMIN_OR_FORCE_REQUIREDBEHINDT
UP_TO_DATE)r   rn   base_syncedNr   ABORT_SYNC_FN_UNAVAILABLEc                 *    |j                  d |||      S N)sync_pr_base)_pn_rn_mqe_wd_mss        r"   r   z'base_sync_before_merge.<locals>.sync_fn:  s    $$T3S99r$   zABORT_SYNC_ERROR: rs   conflictABORT_CONFLICTABORT_DIFF_OVER_LIMITABORT_FETCH_FN_UNAVAILABLEzABORT_REVALIDATE_ERROR: mergeStateStatusUNKNOWNABORT_STILL_BEHINDBASE_SYNCED_REVALIDATED)utils.merge_queue_executormerge_queue_executorImportErrorr|   r}   r'   getfetch_merge_state)rR   r   r   rP   r   r   r   r   r   r   sync_resultr   revalidatedrevalidated_statuss                 r"   base_sync_before_merger     s   2 !^<< 11!-LMM X% LOO 	N5
 $(Z=O 	:_i0 z"!-=dSS 	"+.0!-DEE 	O5!33Ne$Y7 %);YGX%!-ABB(ARVWWQ  	N %1LMM	N  _!/A$s)BTBTAUUWX[W\-]^^_&  	O %1MNN	O
  e!/GS	HZHZG[[]^a]b-cddes^   B 	B0 C$ 1	C8 B-,B-0	C!9CC!C!$C54C58	D)D$D)$D))marker_writerexternal_dirtymerge_base_clean	ci_passedlocal_fix_verifiedr   c                     |sddddS |r|r|rddddddd| d}|	  ||       |S |S ddddS # t         $ r(}t        |      j                   d	| |d
<   Y d}~|S d}~ww xY w)u  결함4: external dirty block 존재 시 scope evidence 3개로 authoritative completion 인정.

    ★ .done 파일 직접 생성 코드 절대 작성 금지.
      marker_writer 주입 시 marker_writer(marker dict)를 호출하는 것뿐이며,
      본 함수는 .done 파일을 직접 생성하지 않는다.
    ★ manual_done_forgery=False 항상 유지 (.done 위조 0).

    - external_dirty False → NO_EXTERNAL_DIRTY_BLOCK (authoritative 인정 전제 없음).
    - external_dirty True + scope evidence 3개(merge_base_clean AND ci_passed AND local_fix_verified) 모두 True
      → AUTHORITATIVE_COMPLETION_BY_SCOPE_EVIDENCE, marker_writer 주입 시 호출.
    - evidence 하나라도 빠지면 → EXTERNAL_DIRTY_BLOCK_UNRESOLVED.
    FNO_EXTERNAL_DIRTY_BLOCK)authoritative_completionrn   manual_done_forgeryT*AUTHORITATIVE_COMPLETION_BY_SCOPE_EVIDENCE)r   r   r   )r   rn   evidencer   rQ   Nrs   marker_writer_errorEXTERNAL_DIRTY_BLOCK_UNRESOLVED)r|   r}   r'   )rQ   r   r   r   r   r   markerr   s           r"   authoritative_completion_markerr   a  s    * (-/#(
 	
 I*<(,B$(&*
 $)
"
 $Of% v %*3$ 	  O3793E3E2Fb0N,-Os   0 	A!AA!argvc                 l   t        j                  dd      }|j                  ddd       |j                  dt        d	d
       |j                  ddd       |j                  ddd       |j                  ddd       |j	                  |       }t               }|j                  s|j                  ry	y	)u   argparse 엔트리.

    --dry-run/--once 모드는 실제 gh 폴링 없이 import-safe 하게 동작(네트워크 호출 없이
    0 반환). 실제 live 폴링 루프는 골격만 둔다.
    ci_watch_handoff_runnerzItask-2729 Phase 1 CI_WATCH_HANDOFF progress watcher runner (ACTIVE=false))progdescriptionz	--task-idrc   ztask id (e.g. 2729))defaulthelpz--prr   z	PR number)r}   r   r   z--expected-headzexpected head oidz--once
store_trueu(   단일 평가 후 종료 (폴링 없음))actionr   z	--dry-runu.   네트워크 호출 없이 import-safe dry-run)argparseArgumentParseradd_argumentr-   
parse_argsr   dry_runonce)r   parserargsr0   s       r"   mainr     s     $$&_F R6KL
S!+F
)2<OP
<fg
L?opT"DNE||tyy  r$   __main__r   )4r*   r   sysdataclassesr   pathlibr   typingr   r   r   r   r.   __file__resolveparent
_REPO_ROOTpathinsert*utils.pr_watcher_terminal_state_classifierr	   r
   r   r   r   dispatch.progress_watcher_gater   r   r   REVIEW_SETTLE_QUIET_WINDOW_SECDEFAULT_POLL_INTERVAL_SECDEFAULT_MAX_WATCH_SECr   r+   r7   r;   r>   rA   rC   rE   rG   r-   rL   r]   rd   r   r   r   listr   r'   exitr/   r$   r"   <module>r      s  .  
 !  - -
 h'')00778
SXXHHOOAz"   "%    : : :8 t  L \ 
|  
< L 
L \ 
  
|   ;	
:
: 
: 	
:
 

:( /3!! ++ + 	+
 + + DcN++ c]+ c]+ SM+ 
#s(^+\
  	
  38_J ")- $55 5 	5
 5 5 5 5 c]5 "#5 5 
#s(^5~ "$($()-PXPX PX 	PX
 SMPX PX c]PX SMPX SMPX "#PX 
#s(^PXt $(44 4 	4
 4 4 C=4 
#s(^4nx~  @ zCHHTV r$   