
    üi.                    j   U d Z ddlmZ ddlZddlZddlZddlmZ ddlm	Z	 dZ
dZdZdgZd	ed
<   de
d	 	 	 	 	 	 	 ddZddd	 	 	 	 	 	 	 	 	 	 	 ddZddd	 	 	 	 	 	 	 	 	 ddZddd	 	 	 	 	 	 	 	 	 ddZddddZddddZdd	 	 	 	 	 	 	 ddZ	 ddd	 	 	 	 	 	 	 d dZdddd	 	 	 	 	 	 	 	 	 	 	 d!dZy)"uU  Silent corruption guard for task done preconditions.

Three hardcoded fail-closed checks:

1. ``check_pr_merged_at`` — ``gh pr view <PR> --json mergedAt`` not null.
2. ``check_pr_merge_commit_oid`` — ``gh pr view <PR> --json mergeCommit`` oid not null.
3. ``check_origin_main_ancestry`` — race-safe fetch + 2회 교차 검증으로
   ``origin/<base>`` 가 merge commit 의 ancestor 인지 확인.

각 함수는 ``GuardResult-like`` dict 를 반환 한다::

    {"ok": bool, "reason": str, "detail": dict}

외부 호출 (subprocess, gh, git) 실패 시 항상 fail-closed (``ok=False``).
    )annotationsN)Path)Optional   g      ?gh	list[str]DEFAULT_GH_CMDcwdtimeoutc          	     ,   	 t        j                  | |rt        |      nddd|dd      }|j                  |j                  |j
                  fS # t         j                  t        t        f$ r'}ddt        |      j                   d| fcY d}~S d}~ww xY w)zRun subprocess with shell=False, capture_output, timeout.

    Returns ``(returncode, stdout, stderr)``. On any exception returns
    ``(-1, "", str(exc))`` so callers can fail-closed without try/except noise.
    NTF)r   capture_outputtextr   shellcheck z: )
subprocessrunstr
returncodestdoutstderrTimeoutExpiredFileNotFoundErrorOSErrortype__name__)cmdr   r   procexcs        N/home/jay/workspace/.worktrees/task-2487-dev2/utils/silent_corruption_guard.py_runr#   !   s    6~~CT
 T[[88%%'8'B 62$s),,-Ru5556s   AA B,BBBgh_cmdr   c               r   |rt        |      nt        t              }|ddt        |       d|ddgz   }t        ||t              \  }}}	|dk7  rdd	|	j                         xs d
| fS 	 t        j                  |      }
||
vrdd	d| fS d|
|   dfS # t        j                  $ r}dd	d| fcY d	}~S d	}~ww xY w)zRun ``gh pr view <PR> --repo <repo> --json mergedAt,mergeCommit -q '<expr>'``.

    Returns ``(ok, value, raw_stderr_or_msg)``. ``value`` is whatever JSON the
    ``-q`` selector emits (string for mergedAt, object/None for mergeCommit).
    prviewz--repoz--jsonzmergedAt,mergeCommitr
   r   FNzgh exited rc=zgh JSON decode failed: zmissing field Tr   )	listr	   r   r#   _GH_TIMEOUT_SECstripjsonloadsJSONDecodeError)	pr_numberrepofieldr%   r   baser   rcr   r   payloadr!   s               r"   _gh_view_fieldr5   ;   s     "4<tN';D
I C csODB	QwdFLLNBbT.BBB<**V$ GdnUG444##  <d5cU;;;<s   (B B6$B1+B61B6c               ~    t        | |d||      \  }}}|sdd| | |dddS ||dk(  r
dd| ||d	dS d
d| ||d	dS )u/   ``gh pr view --json mergedAt`` not null 검증.mergedAtr$   Fgh pr view failed: r/   r0   r1   okreasondetailr   z mergedAt is null (PR not merged))r/   r0   r7   TzmergedAt present)r5   )r/   r0   r%   r   r;   valuemsgs          r"   check_pr_merged_atr@   a   s     $4FNBs +C51$-tjQ
 	
 }8$-tO
 	
 $ )4UK     c                   t        | |d||      \  }}}|sdd| | |dddS t        |t              s
dd| ||ddS |j                  d	      }|rt        |t              s
dd
| ||ddS dd| ||ddS )u6   ``gh pr view --json mergeCommit`` oid not null 검증.mergeCommitr$   Fr8   r9   r:   zmergeCommit is null/not-object)r/   r0   rC   oidzmergeCommit.oid missingTzmergeCommit.oid present)r/   r0   merge_commit_sha)r5   
isinstancedictgetr   )r/   r0   r%   r   r;   r>   r?   rD   s           r"   check_pr_merge_commit_oidrI      s     $4v3NBs +C51$-tmT
 	
 eT"6$-tER
 	
 ))E
Cjc*/$-tER
 	
 + )4SQ rA   r   c                   d|  d|  }t        dddd|g|t              \  }}}|dk7  rd	|j                         xs d
| fS y)zM``git fetch origin --no-tags +refs/heads/<base>:refs/remotes/origin/<base>``.z+refs/heads/z:refs/remotes/origin/gitfetchoriginz	--no-tagsr
   r   Fzgit fetch rc=)Tr   r#   _GIT_TIMEOUT_SECr+   )base_branchr   refspecr3   _r   s         r"   _git_fetch_baserT      sb    [M)>{mLG	;8 MB6
 
Qwflln<-t(<<<rA   c               t    t        dddd|  g|t              \  }}}|dk7  ry|j                         }|xs dS )z)``git rev-parse --verify origin/<base>``.rL   z	rev-parsez--verifyrefs/remotes/origin/r
   r   NrO   )rQ   r   r3   r   rS   shas         r"   _git_rev_parse_remoterX      sM    	Z+?})MN MB
 
Qw
,,.C;$rA   c               H    t        ddd| d| g|t              \  }}}|dk(  S )NrL   z
merge-basez--is-ancestorrV   r
   r   )r#   rP   )rE   rQ   r   r3   rS   s        r"   _git_is_ancestorrZ      sB     ";-0	
  
HB1 7NrA   mainc          	         t        | t              r| j                         s	dd| |ddS t        ||      \  }}|sdd| | |ddS t	        ||      }|s	dd| |ddS t        j                  t               t	        ||      }|s	dd| |ddS ||k7  rXt        ||       t	        ||      }t        j                  t               t	        ||      }|r|r||k7  rdd	| |||||d
dS |}t        | ||      s
dd| ||ddS dd| ||ddS )u  Race-safe ancestry verification (fetch + 2회 교차 검증).

    Sequence
    --------
    1. ``git fetch origin --no-tags +refs/heads/<base>:refs/remotes/origin/<base>``
    2. 1차 ``git rev-parse origin/<base>`` -> ``sha_a``
    3. ``time.sleep(0.5)``
    4. 2차 ``git rev-parse origin/<base>`` -> ``sha_b``
    5. ``sha_a == sha_b`` -> ``git merge-base --is-ancestor`` 통과 검증
       그렇지 않으면 1회 fetch+재조회 후 그래도 불일치면 fail-closed.
    Fzmerge_commit_sha empty)rE   rQ   r:   rJ   zgit fetch failed: z$rev-parse origin/<base> failed (1st)z$rev-parse origin/<base> failed (2nd)z;origin/<base> SHA unstable across 2 fetches (race detected))rE   rQ   sha_asha_bsha_csha_dz*merge_commit not ancestor of origin/<base>)rE   rQ   
origin_shaTzancestry verified)	rF   r   r+   rT   rX   timesleep_FETCH_RETRY_SLEEP_SECrZ   )	rE   rQ   r   fetchedr?   r]   r^   r_   r`   s	            r"   check_origin_main_ancestryrf      s   " &,4D4J4J4L.+;KX
 	
 #;C8LGS*3%0+;KX
 	
 "+37E<+;KX
 	
 	JJ%&!+37E<+;KX
 	
 ~-%ks;

)*%ks;EUe^W(8#.""""  ,ksCB$4*#
 	
 % 0&
 rA   )rQ   r%   r   c          
     @   i }t        | |||      }||d<   |d   sdd|d    d| ||ddS t        | |||      }||d	<   |d   sdd
|d    d	| ||ddS |d   d   }|d   d   }	t        |||      }
|
|d<   |
d   sdd|
d    d| |||	||ddS dd||	| |||ddS )uR  3 check 통합. 하나라도 FAIL 시 ``ok=False``.

    Returns
    -------
    dict
        ``{"ok": bool, "reason": str, "detail": {...}}``.

        성공 시 ``detail`` 은 ``merge_commit_sha``, ``merged_at``, ``checks`` 키를 포함.
        실패 시 어느 check 가 실패 했는지 ``detail["failed_check"]`` 에 명시.
    r$   	merged_atr;   Fzmerged_at check failed: r<   )failed_checkr/   r0   checksr:   merge_commit_oidzmerge_commit_oid check failed: r=   rE   r7   )rQ   r   ancestryzancestry check failed: )ri   r/   r0   rE   rh   rQ   rj   Tz%all 3 silent_corruption checks passed)rE   rh   r/   r0   rQ   rj   )r@   rI   rf   )r/   r0   rQ   r%   r   rj   merged_at_result
oid_resultrE   rh   ancestry_results              r"   verify_done_preconditionsrp   1  sU   $ !F))T&cR*F;D!01A(1K0LM +& 		
 		
 +9d6sSJ!+Fd7
88L7MN 2& 		
 		
 "(+,>? *:6I0ksO )F:4 /0I/JK *&$4&* 
 	
 9 0""&
 rA   )r   r   r   Optional[Path]r   intreturnztuple[int, str, str])r/   rr   r0   r   r1   r   r%   Optional[list[str]]r   rq   rs   ztuple[bool, object, str])
r/   rr   r0   r   r%   rt   r   rq   rs   rG   )rQ   r   r   rq   rs   ztuple[bool, str])rQ   r   r   rq   rs   zOptional[str])rE   r   rQ   r   r   rq   rs   bool)r[   )rE   r   rQ   r   r   rq   rs   rG   )r/   rr   r0   r   rQ   r   r%   rt   r   rq   rs   rG   )__doc__
__future__r   r,   r   rb   pathlibr   typingr   r*   rP   rd   r	   __annotations__r#   r5   r@   rI   rT   rX   rZ   rf   rp    rA   r"   <module>r|      s     #        !F	 " "	6	6 
6 	6
 6> #'#$#$
#$ #$
  #$ 
#$ #$T #'
  	
 
 
H #'%%
%  	%
 
% 
%P @D 
 FJ 
 GK(+5C	& Z 	ZZZ 
	Z
 
ZB "&QQ
Q 	Q
  Q 
Q 
QrA   