
    j,              
         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m	Z	 ddl
mZ ddlmZmZ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<   dZded<   dZded<    eeeeeeeeeh      Zded<   dZded <    eh d!      Zded"<    G d# d$e      Z  ejB                  d%      Z"d&ed'<   d(Z#ded)<   d*Z$ded+<   dd,d8d-Z%d9d.Z&d:d/Z'd0d1d;d2Z(d<d3Z)d=d4Z* G d5 d6      Z+g d7Z,y)>u  anu_v2.owner_gemini_trigger_router_audit — task-2641 신규 audit log.

회장 verbatim 12 (2026-05-23) 1:1 박제 — OWNER_GEMINI_TRIGGER_UI_FALLBACK_MISROUTE
재발 방지 router audit.

본 모듈 책임 (spec §3.3, task md §재발 방지):
  - 신규 audit JSONL schema v1: ``anu_v2.owner_gemini_trigger_router_audit.v1``
  - router lifecycle 한 행 = 한 record (freshness 평가 → nudge 결과 → final state)
  - 회장 verbatim §6 raw token 출력 금지: token_hash_prefix (12 hex char) 만
  - 회장 verbatim §8 403 발생 시 X-Accepted-GitHub-Permissions / X-Accepted-OAuth-Scopes /
    X-OAuth-Scopes / X-RateLimit-Remaining record (raw token redact)
  - dedupe 보조 helper: nudge_count_for_pr_head — owner_trigger_only validate_decision
    의 ``nudge_count_for_pr_head=0`` 검사를 router 진입 시 미리 컴퓨트

one-way isolation: anu_v2/ 외부 import 금지. live cokacdir / gh / merge 호출 0.
    )annotationsN)datetimetimezone)Path)AnyFinalIteratorz5memory/events/owner-gemini-trigger-router-audit.jsonlz
Final[str]AUDIT_REL_PATHz+anu_v2.owner_gemini_trigger_router_audit.v1AUDIT_SCHEMAFRESHSTATE_FRESHNUDGE_POSTEDSTATE_NUDGE_POSTEDNUDGE_DEDUPEDSTATE_NUDGE_DEDUPEDNUDGE_FAILEDSTATE_NUDGE_FAILEDGEMINI_EXTERNAL_TRIGGER_STALE#STATE_GEMINI_EXTERNAL_TRIGGER_STALECHAIR_UI_FALLBACK_REQUIRED STATE_CHAIR_UI_FALLBACK_REQUIREDNUDGE_PERMISSION_DENIEDSTATE_NUDGE_PERMISSION_DENIEDNOT_GEMINI_TRIGGERSTATE_NOT_GEMINI_TRIGGERzFinal[frozenset[str]]
ALL_STATES)zx-accepted-github-permissionszx-accepted-oauth-scopeszx-oauth-scopeszx-ratelimit-remainingzFinal[tuple[str, ...]]RECORDED_403_HEADERS>   reasonschemats_utctask_id	pr_numberfinal_statenudge_resulttoken_presentfreshness_statenudge_attemptedcurrent_head_shatoken_hash_prefixtoken_value_loggedgemini_commit_id_observedpermission_header_diagnosticsALLOWED_AUDIT_KEYSc                      e Zd ZdZy)RouterAuditRedactionErroruC   audit record 에 raw token / Authorization / api_key 누출 의심.N)__name__
__module____qualname____doc__     J/home/jay/workspace/scripts/../anu_v2/owner_gemini_trigger_router_audit.pyr/   r/   W   s    Mr5   r/   z5(?i)(token|authorization|api[_-]?key|secret|password)zFinal[re.Pattern[str]]_REDACT_KEY_REzBearer ghp_github_pat_ghu_ghs_ghr__REDACT_VALUE_SENTINELSz
<redacted>REDACTED_PLACEHOLDER_depthc                  dk\  rt         S | t        | t        t        t        f      r| S t        | t
              r7| j                         }t        D ]  }|j                         |v st         c S  | S t        | t              rYi }| j                         D ]B  \  }}t        |      }t        j                  |      r
t         ||<   0t        |dz         ||<   D |S t        | t              r| D cg c]  }t        |dz          c}S t        | t              rt        fd| D              S 	 t        |       }	|	j                         }
t        D ]  }|j                         |
v st         c S  |	S c c}w # t        $ r	 t         cY S w xY w)uD   dict/list/tuple/str 재귀 redact (token / Authorization / api_key).      r@   c              3  >   K   | ]  }t        |d z           yw)rD   r@   N_redact).0vrA   s     r6   	<genexpr>z_redact.<locals>.<genexpr>   s     AqWQvz22As   )r?   
isinstanceboolintfloatstrlowerr>   dictitemsr7   searchrG   listtuple	Exception)datarA   rP   sentinelresultkeyvaluekey_strrI   text
text_lowers    `         r6   rG   rG   m   sr   {##|z$sE(:;$

/ 	,H~~5(++	, $**, 	DJC#hG$$W-"6w")%
"Cw	D $7;<!&1*-<<$ADAAA$4y J+ (>>z)''( K =
  $##$s   0E1/E6 6FFc                    t        |       S )u9   public alias — router 코드가 명시적으로 호출.rF   )rW   s    r6   redact_diagnosticsr`      s    4=r5   c                   t        | t              si S | j                         D ci c]  \  }}t        |      j	                         |! }}}i }t
        D ]  }||v s||   ||<    t        |      }t        |t              si S |S c c}}w )uF  403 응답 header dict 에서 RECORDED_403_HEADERS 만 추출 (case-insensitive).

    회장 verbatim §8 + spec §2.7 1:1: ``X-Accepted-GitHub-Permissions`` /
    ``X-Accepted-OAuth-Scopes`` / ``X-OAuth-Scopes`` / ``X-RateLimit-Remaining`` 만
    화이트리스트로 캡처. token / Authorization / api_key 절대 0.
    )rK   rQ   rR   rO   rP   r   rG   )response_headerskrI   loweredout
header_keyredacteds          r6   extract_403_headersrh      s     &-	-=-C-C-EFTQs1v||~q FGFC* 2
 %j1C
O2 s|Hh%	O Gs   $B   )lengthc                   t        | t              r| st        d      t        j                  | j                  d            j                         }|d| S )uO   token → SHA256 16진수 앞 ``length`` 자리. spec §3.3 1:1 (12 hex chars).z token must be a non-empty stringutf-8N)rK   rO   
ValueErrorhashlibsha256encode	hexdigest)tokenrj   digests      r6   r)   r)      sF    eS!;<<^^ELL12<<>F'6?r5   c                 h    t        j                  t        j                        j	                  d      S )Nseconds)timespec)r   nowr   utc	isoformatr4   r5   r6   _now_isorz      s#    <<%///CCr5   c                   t        | j                               t        z
  }|rt        dt	        |             | j                  d      durt        d      t        j                  | d      }d}|D ]  }||v st        d|       y)	uD   record 에 raw token / Authorization 값 누출 차단 (spec §3.3).zdisallowed audit keys: r*   Fz token_value_logged must be False)ensure_asciir8   z%audit record contains token sentinel N)setkeysr-   r/   sortedgetjsondumps)recordextra
serialised	sentinelsss        r6   _ensure_no_secret_leakr      s    !33E'%fUm_5
 	
 zz&'u4'(JKKF7JJI 
?+7u= r5   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)OwnerGeminiTriggerRouterAuditu   append-only audit JSONL for router lifecycle.

    한 router.route_for_pr 호출 = 한 record. atomic via ``fcntl.flock(LOCK_EX)``.
    c                `    t        |      j                         }|| _        |t        z  | _        y N)r   resolve_workspace_rootr
   _path)selfworkspace_rootroots      r6   __init__z&OwnerGeminiTriggerRouterAudit.__init__   s)    N#++-#N*
r5   c                    | j                   S r   )r   r   s    r6   pathz"OwnerGeminiTriggerRouterAudit.path   s    zzr5   c                R    | j                   j                  j                  dd       y )NT)parentsexist_ok)r   parentmkdirr   s    r6   _ensure_parentz,OwnerGeminiTriggerRouterAudit._ensure_parent   s    

t<r5   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)u  Stream audit records line-by-line (전체 파일 메모리 로드 0).

        Gemini PR #144 HIGH (memory exhaustion + cross-PR drift) 자동수렴:
        전체 audit 정확 스캔 — 다른 PR 활동 누적되어도 §9 hard limit 정확성 유지.
        Nrrl   encoding)r   existsopenstripr   loadsJSONDecodeErrorFileNotFoundError)r   fhlines      r6   _iter_rows_forwardz0OwnerGeminiTriggerRouterAudit._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               B   t        |t              r|j                         nd}d}| j                         D ]g  }|j	                  d      |k(  st        |j	                  d      t              s8|d   j                         |k(  sO|j	                  d      du sc|dz  }i |S )u  기존 audit record 에서 (pr, head) 에 대한 nudge_attempted=True 카운트.

        owner_trigger_only.validate_decision 의 ``nudge_count_for_pr_head=0`` 정합
        체크에 사용. spec §2.8: PR/head 당 1회 hard limit.

        전체 audit JSONL 을 line-by-line streaming 으로 스캔. cross-PR 활동 누적과
        무관하게 (pr, head) 매칭 record 전부 카운트 (Gemini PR #144 HIGH 자동수렴).
         r   r"   r(   r'   TrD   )rK   rO   rP   r   r   )r   r"   head	head_normcountrows         r6   nudge_count_for_pr_headz5OwnerGeminiTriggerRouterAudit.nudge_count_for_pr_head   s     %/tS$9DJJLr	**, 	C$	1sww'9:C@*+113y@GG-.$6
	 r5   c                v   t        |      }|j                  dt               |j                  dt                      |j                  dd       |j                  dd       |j                  dd       |j	                  d      t
        vr-t        d	t        t
               d
|j	                  d            t        |       |j	                  d      }t        |t              r!t        |      dk(  r|j                         |d<   | j                          t        | j                  dd      5 }t!        j"                  |j%                         t         j&                         	 |j)                  t+        j,                  |dd      dz          |j/                          t1        j2                  |j%                                t!        j"                  |j%                         t         j4                         	 ddd       y# t!        j"                  |j%                         t         j4                         w xY w# 1 sw Y   yxY w)u@   단일 record append. token 누출 가드 + atomic flock 보호.r   r    r*   Fr'   r,   Nr#   zfinal_state must be one of z, got r(   (   arl   r   T)r|   	sort_keys
)rQ   
setdefaultr   rz   r   r   r/   r   r   rK   rO   lenrP   r   r   r   fcntlflockfilenoLOCK_EXwriter   r   flushosfsyncLOCK_UN)r   r   rechead_valr   s        r6   appendz$OwnerGeminiTriggerRouterAudit.append  s   6lx.x,+U3(%06=77=!3+-fZ.@-A77=),.  	s#77-.h$X")<&.nn&6C"#$**cG4 		8KK		U]]38JJs$G$N 
%BIIK7		8 		8 BIIK7		8 		8s%   +3H/AG8<2H/84H,,H//H8N)r   z
str | PathreturnNone)r   r   )r   r   )r   zIterator[dict])r"   rM   r   rO   r   rM   r   rQ   r   r   )r0   r1   r2   r3   r   propertyr   r   r   r   r   r4   r5   r6   r   r      s4    
+
  =(* 8r5   r   )r
   r   r   r   r   r   r   r   r   r   r   r   r-   r?   r/   rh   r`   r)   r   )rW   r   rA   rM   r   r   )rW   r   r   r   )rb   r   r   rQ   )rr   rO   rj   rM   r   rO   )r   rO   r   )-r3   
__future__r   r   rn   r   r   rer   r   pathlibr   typingr   r   r	   r
   __annotations__r   r   r   r   r   r   r   r   r   	frozensetr   r   r-   RuntimeErrorr/   compiler7   r>   r?   rG   r`   rh   r)   rz   r   r   __all__r4   r5   r6   <module>r      sy  " #    	 	 '  ' ' U
 THj H "Z !!/ J /"1 Z 1!/ J /2Q #Z Q/K  * K,E z E'; * ;$-+(% 	%
! 0 ,  -6- ) *N N *4<*& 3 /  $0 j / )* !H
* 46 D$[8 [8|r5   