
    $jd                       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mZmZ ddl	m
Z
 ddlmZ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<   dZd	ed<    eeeeeeeh      Zded<    eh d      Zded<   dZd	ed<    ej:                  d      Zd ed!<   d"Zd#ed$<    G d% d&e       Z!d,d'Z"d-d(Z# G d) d*      Z$g d+Z%y).u  utils.ci_watch_handoff_audit — task-2642 CI_WATCH_HANDOFF runner audit JSONL.

회장 verbatim (2026-05-23 19:38 KST) 정책 spec §9 ANU 8 완료 보고 항목 박제:
  1. handoff 생성 여부
  2. watcher 주체
  3. watcher schedule_id
  4. terminal state
  5. 자동수렴 내역
  6. CI/Gemini/phase3 최종 상태
  7. merge-ready 여부
  8. ANU callback 수신 여부

본 모듈 책임 (runner spec §1.1):
  - watcher lifecycle 한 행 = 한 record (PR_OPEN → terminal classification)
  - record 의무 필드: schema / ts_utc / task_id / pr_number / head_sha /
    watcher_owner / watcher_schedule_id / event / terminal_state /
    auto_remediation_attempts / loop_iterations / router_final_state /
    ci_status / callback_prompt_bytes / reason
  - JSONL append-only · atomic via fcntl.flock(LOCK_EX) · UTF-8
  - raw token / Authorization / api_key redact (defense in depth) — PR #144
    owner_gemini_trigger_router_audit 의 redaction doctrine 1:1 정합

one-way isolation: utils/ 외부 import 0 (자기 schema 만 의존). live cokacdir / gh 0.

frozen anchor:
  ANCHOR-1: audit lifecycle event 6종 (HANDOFF_RECEIVED / POLL_TICK /
            AUTO_REMEDIATE / OWNER_NUDGE / TERMINAL_REACHED / CALLBACK_FIRED)
  ANCHOR-2: TERMINAL_REACHED/CALLBACK_FIRED 에는 terminal_state 필수 — 누락 시 AuditError
  ANCHOR-3: token sentinel (Bearer / ghp_ / github_pat_ / ghu_ / ghs_ / ghr_)
            누출 시 AuditError fail-closed
    )annotationsN)datetimetimezone)Path)FinalIterator)ALL_TERMINAL_STATESz1memory/events/ci-watch-handoff-runner-audit.jsonlz
Final[str]AUDIT_REL_PATHzutils.ci_watch_handoff_audit.v1AUDIT_SCHEMAHANDOFF_RECEIVEDEVENT_HANDOFF_RECEIVED	POLL_TICKEVENT_POLL_TICKAUTO_REMEDIATEEVENT_AUTO_REMEDIATEOWNER_NUDGEEVENT_OWNER_NUDGETERMINAL_REACHEDEVENT_TERMINAL_REACHEDCALLBACK_FIREDEVENT_CALLBACK_FIREDzFinal[frozenset[str]]
ALL_EVENTS>   eventreasonschemats_utctask_idhead_sha	ci_status	pr_numberwatcher_ownerterminal_stateloop_iterationsrouter_final_statewatcher_schedule_idcallback_prompt_bytesauto_remediation_attemptsALLOWED_AUDIT_KEYSz
<redacted>REDACTED_PLACEHOLDERz5(?i)(token|authorization|api[_-]?key|secret|password)zFinal[re.Pattern[str]]_REDACT_KEY_RE)zBearer ghp_github_pat_ghu_ghs_ghr_zFinal[tuple[str, ...]]_REDACT_VALUE_SENTINELSc                      e Zd ZdZy)
AuditErroru)   audit record contract / redaction 위반.N)__name__
__module____qualname____doc__     3/home/jay/workspace/utils/ci_watch_handoff_audit.pyr2   r2   h   s    3r8   r2   c                 h    t        j                  t        j                        j	                  d      S )Nseconds)timespec)r   nowr   utc	isoformatr7   r8   r9   _now_isor@   l   s#    <<%///CCr8   c                F   t        | j                               t        z
  }|rt        dt	        |             | D ]/  }t
        j                  t        |            s"t        d|d       t        j                  | d      }t        D ]  }||v st        d|       y)uL   record key 화이트리스트 + raw token / Authorization 값 누출 차단.zdisallowed audit keys: z
audit key uX    matches token/authorization sentinel — fail-closed (회장 verbatim 안전 불변식)F)ensure_asciiz%audit record contains token sentinel N)setkeysr(   r2   sortedr*   searchstrjsondumpsr0   )recordextrakey
serialisedsentinels        r9   _ensure_no_secret_leakrO   p   s    !33E26%=/BCC   S*SG $A A  F7J+ z!7|D r8   c                  J    e Zd ZdZd	dZed
d       ZddZddZddZ	ddZ
y)CiWatchHandoffAuditu   append-only JSONL audit for watcher lifecycle. atomic via fcntl.flock.

    파일 위치: ``<workspace_root>/memory/events/ci-watch-handoff-runner-audit.jsonl``.
    pytest 회귀는 ``tmp_path`` 를 workspace_root 로 주입하여 격리.
    c                `    t        |      j                         }|| _        |t        z  | _        y N)r   resolve_workspace_rootr
   _path)selfworkspace_rootroots      r9   __init__zCiWatchHandoffAudit.__init__   s)    N#++-#N*
r8   c                    | j                   S rS   )rV   rW   s    r9   pathzCiWatchHandoffAudit.path   s    zzr8   c                R    | j                   j                  j                  dd       y )NT)parentsexist_ok)rV   parentmkdirr\   s    r9   _ensure_parentz"CiWatchHandoffAudit._ensure_parent   s    

t<r8   c              #  P  K   | j                   j                         sy 	 t        | j                   dd      5 }|D ]+  }|j                         s	 t	        j
                  |       - 	 d d d        y # t        j                  $ r Y Mw xY w# 1 sw Y   y xY w# t        $ r Y y w xY ww)Nrutf-8encoding)rV   existsopenstriprH   loadsJSONDecodeErrorFileNotFoundError)rW   fhlines      r9   _iter_rows_forwardz&CiWatchHandoffAudit._iter_rows_forward   s     zz  "
	djj#8 !B !D::< !"jj..	!! !  // ! !! ! ! 		so   B&B BA2&B)B 1B&2BBBBBB B&B 	B# B&"B##B&c                   t        |      }|j                  dt               |j                  dt                      |j	                  d      }|t
        vrt        dt        t
               d|      |t        t        fv r:|j	                  d      }|t        vr!t        dt        t               d| d|      |j	                  d	      }t        |t              r!t        |      d
k(  r|j                         |d	<   t        |       | j!                          t#        | j$                  dd      5 }t'        j(                  |j+                         t&        j,                         	 |j/                  t1        j2                  |dd      dz          |j5                          t7        j8                  |j+                                t'        j(                  |j+                         t&        j:                         	 ddd       y# t'        j(                  |j+                         t&        j:                         w xY w# 1 sw Y   yxY w)uH  단일 record append. event/terminal_state 강제 검증 + redaction guard.

        Args:
          record: audit record dict (event 필수, schema/ts_utc 자동 채움).

        Raises:
          AuditError: event enum 위반 / terminal_state 누락 (TERMINAL_*) /
            disallowed key / token sentinel 누출.
        r   r   r   zevent must be one of z, got r"   zterminal_state must be one of z for event=r   (   arf   rg   FT)rB   	sort_keys
N)dict
setdefaultr   r@   getr   r2   rE   r   r   r	   
isinstancerG   lenlowerrO   rc   rj   rV   fcntlflockfilenoLOCK_EXwriterH   rI   flushosfsyncLOCK_UN)rW   rJ   recr   tshead_valro   s          r9   appendzCiWatchHandoffAudit.append   s    6lx.x, 
"'z(:';6%K  +-ABB)*B,, 4V<O5P4Q R!!&vbV5 
 77:&h$X")<&nn.C
Os#$**cG4 		8KK		U]]38JJs$G$N 
%BIIK7		8 		8 BIIK7		8 		8s%   03H4$AG=2H4=4H11H44H=c                  t        |t              r|j                         nd}| j                         D cg c]M  }|j	                  d      |k(  r7t        |j	                  d      t              r|d   j                         |k(  r|O c}S c c}w )u   전체 audit JSONL 에서 (pr_number, head_sha) 매칭 record list 반환.

        Streaming line-by-line scan (메모리 로드 0). 회귀에서 lifecycle 추적용.
         r    r   )rz   rG   r|   rq   ry   )rW   r    head	head_normre   s        r9   records_for_pr_headz'CiWatchHandoffAudit.records_for_pr_head   s~    
 %/tS$9DJJLr	 ,,.
uu[!Y.155,c2*##%2	 
 	
 
s   AB
N)rX   z
str | PathreturnNone)r   r   )r   r   )r   zIterator[dict]rJ   rw   r   r   )r    intr   rG   r   z
list[dict])r3   r4   r5   r6   rZ   propertyr]   rc   rq   r   r   r7   r8   r9   rQ   rQ      s5    +
  =,8\
r8   rQ   )r
   r   r   r   r   r   r   r   r   r(   r)   r2   rQ   )r   rG   r   )&r6   
__future__r   r}   rH   r   rer   r   pathlibr   typingr   r   utils.ci_watch_handoff_schemar	   r
   __annotations__r   r   r   r   r   r   r   	frozensetr   r(   r)   compiler*   r0   RuntimeErrorr2   r@   rO   rQ   __all__r7   r8   r9   <module>r      s%  > #   	 	 '  " = Q
 P<j <%7 
 7) )#3 j 3 - : -%7 
 7#3 j 3$-	%
! 	 -6- ) ( $0 j /)3<*& 
3 / 4 4D&\
 \
~r8   