
     jZ                       U 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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ZdZ eh d      Zd)d*dZdd	 	 	 	 	 d+dZd,dZdd	 	 	 	 	 d+dZ dddddd	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 d-d Z!y).uZ  Silent corruption guard for task done preconditions.

task-2472 보강: 기존 3 check에 3개 추가 → 총 6 check.

기존 3 check:
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회 교차 검증.

task-2472 신규 3 check:
4. ``check_done_escalated_coexistence`` — .done + .done.escalated 동시 존재 차단.
5. ``check_escalated_payload`` — .done.escalated 0-byte/비-JSON fail-closed.
6. ``check_state_file_present`` — .tasks/state/{task_id}.json 존재 + checksum 검증.

각 함수는 ``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        4/home/jay/workspace/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   B   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@   h   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   zmemory/eventsz.tasks/state>   tsr<   sourcetask_idevidence_pathblocking_conditionc                \    | xs) t        t        j                  j                  dd            S )u   workspace root Path 반환.WORKSPACE_ROOTz/home/jay/workspace)r   osenvironrH   	workspaces    r"   _workspace_root_pathrr   H  s#    URZZ^^,<>STUUrA   rp   c                   t        |      }|t        z  }||  dz  }||  dz  }|j                         }|j                         }||t        |      t        |      d}|r|r	dd|  |dS dd|dS )	u=  .done + .done.escalated 동시 존재 시 fail.

    task-2472 추가 11: DONE 인정 금지 조건.
    두 마커가 동시 존재하면 silent corruption 가능성 → fail-closed.

    Returns
    -------
    dict
        {"ok": bool, "reason": str, "detail": {"done_exists": bool, "escalated_exists": bool}}
    z.done.done.escalated)done_existsescalated_exists	done_pathescalated_pathFua   .done + .done.escalated 동시 존재 → DONE 인정 불가 (silent corruption 의심). task_id=r:   Tu7   .done + .done.escalated 동시 존재 없음 — 정상)rr   _EVENTS_DIR_RELexistsr   )	ri   rq   	work_root
events_dirrw   rx   ru   rv   r=   s	            r"    check_done_escalated_coexistencer}   M  s     %Y/I_,Jy..IWI_"==N""$K%,,. #,^n-	F '")% 
 	
 K rA   c                8   ddg d}| j                         s	dd|  |dS 	 | j                         j                  }||d<   |dk(  r	dd	|  |dS 	 | j	                  d
      }t        j                  |      }d|d<   t        t        t        |j                               z
        }||d<   |r	dd| |dS dd|dS # t        $ r}dd| |dcY d}~S d}~ww xY w# t
        j                  $ r}dd| |dcY d}~S d}~wt        $ r}dd| |dcY d}~S d}~ww xY w)uf  .done.escalated 파일이 0-byte이거나 비-JSON이면 fail-closed.

    task-2472 추가 10: 0-byte escalation marker 차단.
    필수 JSON 필드: reason, ts, task_id, source, blocking_condition, evidence_path

    Returns
    -------
    dict
        {"ok": bool, "reason": str, "detail": {"size": N, "json_valid": bool, "missing_fields": [...]}}
    r   F)size
json_validmissing_fieldsu   .done.escalated 파일 없음: r:   u   stat 실패: Nr   uO   .done.escalated 파일이 0-byte → fail-closed (raw shell emit 의심). path=utf-8encodingTr   u*   .done.escalated 비-JSON → fail-closed: u   .done.escalated 읽기 실패: r   u&   .done.escalated 필수 필드 누락: uD   .done.escalated payload 검증 통과 (JSON valid, 필수 필드 OK))rz   statst_size	Exception	read_textr,   r-   r.   sorted_ESCALATED_REQUIRED_FIELDSsetkeys)rx   r=   r   r!   contentr4   missings          r"   check_escalated_payloadr   }  s    UbIF  "77GH
 	

""$,, F6Nqyghvgwx
 	

 **G*<**W%#| /#glln2EEFG&F>wiH
 	
 X Y  
%cU+
 	

(  
B3%H
 	

  
7u=
 	

sM   B: ,C :	CCCCD,C:4D:DDDDc               f   t        |      }|t        z  |  dz  }t        |      ddddd}|j                         s
dd| d|dS d|d<   	 |j	                  d	
      }t        j                  |      }d|d<   |j                  d      }|sdd|dS d|d<   |j                         D 	
ci c]  \  }	}
|	dk7  s|	|
 }}	}
t        j                  t        j                  |dd      j                  d	            j                         }||k7  r|dd dz   |d<   |dd dz   |d<   dd|dS d|d<   |t        z  d|  z  }|j                         rt        |      |d<   dd| d|dS dd|dS # t
        j                  $ r}dd| |dcY d}~S d}~wt        $ r}dd| |dcY d}~S d}~ww xY wc c}
}	w )u   .tasks/state/{task_id}.json 존재 + JSON parse + _checksum field 검증.

    task-2472 추가 12: state file missing 상태에서 done/merge 차단.

    Returns
    -------
    dict
        {"ok": bool, "reason": str, "detail": {...}}
    z.jsonF)
state_pathrz   r   checksum_presentchecksum_matchu   state 파일 없음: u$    → done/merge 차단 (fail-closed)r:   Trz   r   r   r   u!   state 파일 JSON 파싱 실패: Nu   state 파일 읽기 실패: 	_checksumuK   state 파일 _checksum 필드 없음 → 수동 편집 의심 (fail-closed)r   )	sort_keysensure_ascii   z...stored_checksumcomputed_checksumuy   state 파일 checksum mismatch → 수동 편집 감지 (fail-closed). taskctl state repair 명령으로만 복구 가능r   z.verify-pending-verify_pending_markerud   state repair 후 verify_consistency 미실행 → done/merge 차단 (.verify-pending 마커 잔존: )uQ   state 파일 존재 + JSON valid + checksum 일치 + verify-pending 마커 없음)rr   _STATE_DIR_RELr   rz   r   r,   r-   r.   r   rH   itemshashlibsha256dumpsencode	hexdigest)ri   rq   r{   r   r=   r   stater!   r   kvcleancomputedr   s                 r"   check_state_file_presentr     sa    %Y/I^+	.??J *o!F -j\9]^
 	
 F8
&&&8

7##| ii,Oc
 	
 "&F $kkm@daqK/?QT@E@~~

5Du=DDWMik  ?"$3CR$85$@ !&.sme&;"#E 
 	
  $F &6;KG99UU##%*-.C*D&'33H2IL 
 	
 e s  
9#?
 	

  
4SE:
 	

$ As<   ,E* )F-7F-*F*=FF*F*F%F*%F*)ri   rQ   r%   r   rq   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 |rt        ||      }||d<   |d   sdd|d    d| |||ddS t	        |      }|t
        z  | dz  }|j                         r't        |      }||d<   |d   sdd|d    d| |||ddS ddi d|d<   t        ||      }||d<   |d   sdd|d    d| |||ddS dd|
|| ||||ddS dd|
|| |||d dS )!u  6 check 통합 (task-2472 보강: 기존 3 → 6). 하나라도 FAIL 시 ``ok=False``.

    기존 3 check (순서 유지):
    1. merged_at
    2. merge_commit_oid
    3. ancestry

    task-2472 신규 3 check (task_id 필요):
    4. done_escalated_coexistence
    5. escalated_payload (escalated 존재 시에만)
    6. state_file_present

    Backward-compatible: task_id=None 시 신규 3 check 생략 (기존 동작 유지).

    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: )r   r/   r0   rE   r   rQ   r   rp   done_escalated_coexistencez)done_escalated_coexistence check failed: )r   r/   r0   ri   r   rt   escalated_payloadz escalated_payload check failed: Tu7   .done.escalated 파일 없음 — payload 검증 생략state_file_presentz!state_file_present check failed: u8   all 6 silent_corruption checks passed (task-2472 보강))rE   r   r/   r0   rQ   ri   r   z%all 3 silent_corruption checks passed)rE   r   r/   r0   rQ   r   )	r@   rI   rf   r}   rr   ry   rz   r   r   )r/   r0   ri   rQ   r%   r   rq   r   merged_at_result
oid_resultrE   r   ancestry_resultcoexistence_resultr{   rx   payload_resultstate_results                     r"   verify_done_preconditionsr   ,  s   @ !F *)T&cR*F;D!01A(1K0LM +& 		
 		
 +9d6sSJ!+Fd7
88L7MN 2& 		
 		
 "(+,>? *:6I 1ksO )F:4 /0I/JK *&$4&* 
 	
 =y
 0B+,!$'EFXYaFbEcd$@!* &$
 
 )3	"_4'/7RR  "4^DN*8F&'!$' @PXAY@Z[(;%. $#*"(
 
 S+F&' 09M'3#$D!=l8>T=UV$8!* &$
 
 P$4&&*" 
 	
 9 0""&
 rA   )r   r   r   Optional[Path]r   intreturnztuple[int, str, str])r/   r   r0   r   r1   r   r%   Optional[list[str]]r   r   r   ztuple[bool, object, str])
r/   r   r0   r   r%   r   r   r   r   rG   )rQ   r   r   r   r   ztuple[bool, str])rQ   r   r   r   r   Optional[str])rE   r   rQ   r   r   r   r   bool)r[   )rE   r   rQ   r   r   r   r   rG   )N)rq   r   r   r   )ri   r   rq   r   r   rG   )rx   r   r   rG   )r/   r   r0   r   ri   r   rQ   r   r%   r   r   r   rq   r   r   rG   )"__doc__
__future__r   r   r,   rn   r   rb   pathlibr   typingr   r*   rP   rd   r	   __annotations__r#   r5   r@   rI   rT   rX   rZ   rf   ry   r   	frozensetr   rr   r}   r   r   r    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D "   'P 
V !%-- - 
	-`FX !%cc c 
	cT ""& $mm
m 	m
 m  m 
m m 
mrA   