
    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mZ ddlmZ ddlmZ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<   dZd	ed<   dZ d	ed <   d!Z!d	ed"<   eee"   ee"e"f   gejF                  f   Z$eee"ef   gdf   Z%eee"ef   egdf   Z&eg e"f   Z' e
d#$       G d% d&             Z( e
d#$       G d' d(             Z)d@d)Z*dAd*Z+dBd+Z,dCd,Z-efdDd-Z.dEd.Z/dFd/Z0dGd0Z1dHd1Z2dId2Z3dJd3Z4dKd4Z5dLd5Z6dMd6Z7dNd7Z8d8Z9d9ed:<   dOd;Z: G d< d=      Z;dPd>Z<dQd?Z=y)Ru  anu_v2.owner_trigger_pat — Fine-grained OWNER PAT trigger-only 모듈 (task-2553).

회장 §명시 (2026-05-11 dec_1) "Fine-grained OWNER PAT trigger-only doctrine 예외
조건부 승인" 박제. OWNER PAT 일반 사용 허용 X — 오직 GitHub Gemini App 을 깨우기 위한
`/gemini review` 댓글 작성에만 한정한다.

설계 원칙 (회장 §명시 12 필수 + 15 금지):
  - one-way isolation: anu_v2/* 만 import. utils/dispatch/scripts/dashboard 의존성 0.
  - 외부 부수효과는 모두 주입 가능한 callable (gh_runner, audit_writer,
    decision_writer, clock) — 테스트 시 mock 으로 대체.
  - allowed gh args 정확히 `["api", "-X", "POST", "/repos/{owner}/{repo}/issues/
    {pr}/comments", "-f", "body=/gemini review"]` 만. 다른 endpoint / body 는
    정적 차단 (RuntimeError).
  - token raw 0: audit/log/error message 어디에도 토큰 값 노출 X.
    audit 에는 `token_present: bool` + `token_hash: sha256[:12]` 만.
  - default GH_TOKEN fallback 0: 토큰 누락 시 즉시 REJECT (silent skip 금지).
  - dedupe by (pr_number, head_sha) — update-branch 후 새 head 는 새 trigger 허용.

본 모듈 외 OWNER PAT 접근 시도 0 — `OWNER_GEMINI_TRIGGER_PAT` env 이름은 본 모듈
상수에서만 정의된다.
    )annotationsN)asdict	dataclass)datetimetimezonePath)AnyCallableMappingSequenceOWNER_GEMINI_TRIGGER_PATstrOWNER_PAT_ENV_NAMEz/gemini reviewALLOWED_COMMENT_BODYPASSDECISION_PASSREJECTDECISION_REJECTmissing_for_current_head!EVIDENCE_MISSING_FOR_CURRENT_HEADok
OUTCOME_OKrejectedOUTCOME_REJECTEDfailedOUTCOME_FAILEDpendingOUTCOME_PENDINGBODY_NOT_ALLOWEDERR_BODY_NOT_ALLOWEDENDPOINT_NOT_ALLOWEDERR_ENDPOINT_NOT_ALLOWEDzanu_v2.owner_trigger_patACTOR_OWNER_TRIGGER_PATz***REDACTED***REDACTED_PLACEHOLDERT)frozenc                  b    e Zd ZU dZded<   ded<   ded<   ded<   ded<   ded	<   ded
<   ded<   y)OwnerTriggerDecisionu   `owner_trigger_decision.json` schema (frozen, 박제 대상).

    회장 §명시: trigger 전 PASS/REJECT 판정을 본 dataclass 로 박제하고 JSON 으로 atomic
    write. 토큰 값은 본 record 어디에도 포함되지 않는다.
    int	pr_numberr   head_shadecisionreasongemini_evidence_statequeue_position
dedupe_keytsN__name__
__module____qualname____doc____annotations__     //home/jay/workspace/anu_v2/owner_trigger_pat.pyr(   r(   M   s3     NMMKOGr9   r(   c                  0    e Zd ZU dZded<   ded<   ded<   y)TriggerOutcomeum   trigger_gemini_review() 반환 — 호출부가 머지 파이프라인을 진행할지 결정하는 contract.r   outcomer-   decision_pathNr2   r8   r9   r:   r<   r<   _   s    wLKr9   r<   c                 h    t        j                  t        j                        j	                  d      S )u8   결정성 ts (테스트는 clock callable 로 override).seconds)timespec)r   nowr   utc	isoformatr8   r9   r:   _now_isorE   i   s#    <<%///CCr9   c                    |  d| S )u   dedupe key 생성 — `{pr}#{head_sha}`.

    update-branch 후 head_sha 가 바뀌면 자동으로 다른 dedupe key 가 되어 stale 처리.
    회장 §명시: dedupe 단위는 **PR/head** 1조.
    #r8   )r*   r+   s     r:   make_dedupe_keyrH   n   s     [($$r9   c                l    t        j                  | j                  d            j                         dd S )u   token 의 sha256 prefix 12자 — token_present 검증 + audit 박제용.

    토큰 raw 가 audit/log 에 절대 들어가지 않게, hash 만 기록한다.
    utf-8N   )hashlibsha256encode	hexdigest)tokens    r:   _hash_tokenrQ   w   s,    
 >>%,,w/0::<SbAAr9   c                `    | y|st        |       S t        |       j                  |t              S )u  text 안의 token substring 을 모두 `***REDACTED***` 로 치환.

    회장 §명시 token redaction guard — stderr / audit dict / error message 등
    외부 출력 경로 어디에서도 token raw 가 노출되지 않게 한다.

    - token 이 빈 문자열이면 redaction 무의미 → 원본 그대로 반환.
    - text 가 None 이면 빈 문자열 반환 (직렬화 안전성).
    - substring 일치 (대소문자 구분) — fine-grained PAT 은 case-sensitive.
     )r   replacer%   )textrP   s     r:   _redact_tokenrV      s1     |4yt9U$899r9   c                    t         j                  j                  | d      j                         }|st	        d|  d      |S )u|  OWNER PAT env 에서 로드 — **fail-fast**, default GH_TOKEN fallback 금지.

    회장 §15 금지: default GH_TOKEN fallback X. 토큰 누락 시 즉시 `RuntimeError`.
    호출부는 RuntimeError 를 catch 하여 REJECT outcome 으로 변환한다.

    토큰 값 자체는 본 함수 반환값으로만 전달되고 어떤 print/log 에도 노출되지 않는다.
    rS   zOWNER_PAT_MISSING: env z not set or empty)osenvirongetstripRuntimeError)	token_envrP   s     r:   load_owner_patr^      s?     JJNN9b)//1E4YK?PQRRLr9   c                *    t        t        |             S )u   OwnerTriggerDecision → JSON 직렬화 가능 dict.

    frozen dataclass 의 asdict 결과를 그대로 반환. 토큰 필드 0 (구조적으로 차단).
    )dictr   )r,   s    r:   serialize_decisionra      s    
 x !!r9   c                &   t        |      }|j                  j                  dd       |j                  |j                  dz         }t        j                  t        |       ddd      }|j                  |d       t        j                  ||       y	)
u   decision JSON atomic write — `.tmp` → rename.

    중간 실패 시 부분 파일이 남지 않게 same-fs rename 원자성 활용.
    상위 디렉토리는 호출부가 보장하지만 안전을 위해 mkdir(parents, exist_ok).
    Tparentsexist_okz.tmpF   )ensure_ascii	sort_keysindentrJ   encodingN)r	   parentmkdirwith_suffixsuffixjsondumpsr`   
write_textrX   rT   )decision_dictpathtargettmppayloads        r:   write_decision_jsonrx      su     $ZF
MMt4


V]]V3
4Cjjm,5DYZ[GNN7WN-JJsFr9   c                0    t        j                  dd|       S )u#  dedupe_key 를 파일명으로 사용할 수 있게 path-safe 문자만 남긴다.

    `pr_number#head_sha` 형식이지만 `#` 등은 일부 FS 에서 회피하는 편이 안전. SHA 의
    16진수만이라도 그대로 보존되도록 `[A-Za-z0-9._-]` 외 문자는 `_` 로 치환.
    z[^A-Za-z0-9._-]_)resub)r0   s    r:   _safe_dedupe_filenamer}      s     66$c:66r9   c                n    t        |       }t        |      }|j                  d|j                   d| dz  S )u  dedupe 선점 마커 파일 경로 — audit jsonl 와 같은 디렉토리에 박제.

    `<audit_dir>/.<audit_stem>.dedupe.<safe_key>.marker` 형식으로 audit 파일과
    동일 디렉토리에 위치시켜 같은 fs 의 atomic create 성질을 활용한다.
    .z.dedupe.z.marker)r	   r}   rl   stem)audit_log_pathr0   
audit_pathsafe_keys       r:   _dedupe_marker_pathr      s=     n%J$Z0H:??"38H:WMMMr9   c                X    	 | j                          y# t        $ r Y yt        $ r Y yw xY w)u  dedupe 선점 마커를 best-effort 로 제거 (실패해도 silent).

    회장 Codex G1 round 2 High — gh 실패 후 marker 가 남으면 같은 PR/head 의
    재시도가 영구 차단된다. trigger 실패 경로에서만 호출되어 marker 를 정리,
    다음 사이클이 자유롭게 재시도하도록 한다. 마커 미존재나 race 로 사라진
    경우는 무시 (FileNotFoundError) — 본 의도는 "있다면 지운다".

    OSError 외 예외는 trigger 흐름과 무관하므로 silent (예: permission denied
    시에도 trigger 실패 흐름은 계속). 보안 경계 위반 가능성 0 — 마커 정리는
    어떤 endpoint/token 도 건드리지 않는다.
    N)unlinkFileNotFoundErrorOSError)marker_paths    r:   _safe_remove_markerr      s0      s    	)))c                    t        |       }t        ||      }|j                         ry|j                         sy	 |j                  dd      5 }|D ]~  }|j	                         }|s	 t        j                  |      }t        |t              s=|j                  d      |k7  rR|j                  d      }|t        k(  s
|t        k(  sv ddd       y 	 ddd       y# t
        j                  $ r Y w xY w# 1 sw Y   yxY w# t        $ r Y yw xY w)	u  audit jsonl 또는 dedupe 마커 파일에서 같은 dedupe_key 선점 상태 검사.

    회장 §명시: PR/head 단위 1회 only. 새 head_sha 면 다른 dedupe_key 가 되어 자동 stale.

    TOCTOU 보강 (회장 Codex G1 Medium 3):
      - 1차 검사: jsonl 에 outcome == "ok"/"pending" 엔트리 존재 여부.
      - 2차 검사: 같은 디렉토리에 dedupe 마커 파일이 존재하는지 (gh runner 호출
        직전에 O_EXCL 로 박제되는 파일). 둘 중 하나라도 발견되면 중복으로 판정.

    설계:
      - jsonl append-only — 한 줄씩 파싱.
      - 깨진 라인은 skip (안전 fallback). 단, 정상 라인이 1개라도 매칭되면 True.
      - 파일 미존재 = 첫 trigger = False.
    TFrrJ   rj   r0   r=   N)r	   r   existsopenr[   rp   loadsJSONDecodeError
isinstancer`   rZ   r   r   r   )r   r0   rt   r   flinerecordr=   s           r:   is_duplicate_triggerr      s    D%dJ7K;;=YYsWY- 	   zz|!ZZ-F "&$/::l+z9 **Y/j(G,F	  	  	 $  ++ 	 $   sf   D C5(C=A	C5C5D C5D C2/C51C22C55C>:D >D 	DDc                8    d|  d| d| d}ddd|dd	t          gS )
u   허용되는 단 1 가지 gh api 호출 인자 — 정확히 이것만 통과시킨다.

    회장 §명시: comment trigger 1 endpoint only. 다른 endpoint 호출 0.
    /repos///issues/	/commentsapiz-XPOSTz-fbody=)r   )ownerrepor*   endpoints       r:   _build_allowed_gh_argsr     sA    
 qhykCHf*+,	 r9   c                    t        |||      }t        |       }||k(  ry|D ]@  }t        |t              s|j	                  d      s&|dt
         k7  s3t        t               t        t              )uO  gh args 가 허용 list 와 **정확히 일치**하는지 검증. 불일치 시 fail-fast.

    - endpoint path 가 다른 경우 → ENDPOINT_NOT_ALLOWED.
    - body 가 정확히 `/gemini review` 가 아닌 경우 → BODY_NOT_ALLOWED.
    - 다른 어떤 추가 인자도 허용되지 않는다 (예: --silent, --paginate 등).
    Nr   )	r   listr   r   
startswithr   r\   r!   r#   )argsr   r   r*   expected	args_listrP   s          r:   _assert_args_allowlistr     sx     &eT9=HT
IH  5eS!e&6&6w&?EuUiTjMkDk3445 /
00r9   c                4    | t         k7  rt        t              y)u   comment body 가 strict equality `/gemini review` 인지 검증. fail-fast.

    회장 §명시 6 허용 1: comment body 정확히 `/gemini review` only.
    - 앞뒤 공백 / 대소문자 변형 / suffix 추가 등 모두 BODY_NOT_ALLOWED.
    N)r   r\   r!   )bodys    r:   assert_body_allowedr   2  s     ##/00 $r9   c                D    d| d| d| d}| |k7  rt        t              y)u   endpoint path 가 허용된 issue comments POST 1 endpoint 인지 검증.

    회장 §명시 15 금지 1~5: merge/approve/push/close/reopen 호출 0. 본 함수가
    엔드포인트 정적 allowlist 의 단일 진입점.
    r   r   r   r   N)r\   r#   )r   r   r   r*   expected_endpoints        r:   assert_endpoint_allowedr   <  s9     "%$x	{)L$$344 %r9   )z/mergesz/reviewsz/pulls/z
/branches/z	/git/refsztuple[str, ...]_FORBIDDEN_ENDPOINT_FRAGMENTSc                p    | D ]1  }t        |t              st        D ]  }||v st        t               3 y)u,  방어적 2차 차단: args 안에 금지 endpoint fragment 가 섞여 있는지 추가 검사.

    `_assert_args_allowlist` 가 이미 strict equality 로 차단하지만, 호출부가
    내부 헬퍼를 우회해 직접 gh_runner 를 호출하더라도 본 함수가 raise 하도록 한다.
    N)r   r   r   r\   r#   )r   rP   frags      r:    _validate_no_forbidden_fragmentsr   S  sB      =%%1 	=Du}"#;<<	==r9   c                      e Zd ZdZeed	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 d	dZ	 	 	 	 	 	 	 	 	 	 	 	 	 	 d
dZddZ	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 ddZ		 	 	 	 	 	 	 	 	 	 	 	 	 	 ddZ
y)OwnerTriggerPatu  Fine-grained OWNER PAT trigger-only comment writer.

    회장 §명시 (2026-05-11 dec_1) 1:1 강제:
      - OWNER PAT 은 `/gemini review` 댓글 작성 1 endpoint 에서만 사용.
      - merge/approve/push/close/reopen 호출 0 (정적 차단).
      - audit 에 token value 0, token_present + token_hash 만.
      - dedupe by (pr_number, head_sha) — update-branch 후 새 head 는 새 trigger 허용.

    외부 부수효과는 생성자 주입 callable (gh_runner / audit_writer / decision_writer /
    clock) 로 분리되어 테스트에서 mock 으로 교체 가능. 본 모듈은 anu_v2/* 외 어떤 외부
    모듈도 import 하지 않는다 (one-way isolation).
    )clockr]   c                  |rt        |t              st        d      |rt        |t              st        d      t        j                  d|      st        d      t        j                  d|      st        d      |t
        k7  rt        dt
        d|      || _        || _        || _        || _	        || _
        || _        || _        y )Nzowner must be non-empty strzrepo must be non-empty strz[A-Za-z0-9._-]+z!owner contains invalid charactersz repo contains invalid charactersztoken_env must be z; got )r   r   
ValueErrorr{   	fullmatchr   _gh_audit	_decision_owner_repo_clock
_token_env)self	gh_runneraudit_writerdecision_writerr   r   r   r]   s           r:   __init__zOwnerTriggerPat.__init__p  s     Juc2:;;:dC09:: ||.6@AA||.5?@@ **$%7$:&N  "(
"#r9   c               X    t        ||||||t        ||      | j                               S )N)r*   r+   r,   r-   r.   r/   r0   r1   )r(   rH   r   )r   r*   r+   r,   r-   r.   r/   s          r:   _make_decisionzOwnerTriggerPat._make_decision  s6     $"7)&y(;{{}	
 		
r9   c                :    | j                  t        |      |       y)u]   decision_writer 콜백 호출 — REJECT 도 박제하여 회장 §명시 audit trail 유지.N)r   ra   )r   r,   rt   s      r:   _persist_decisionz!OwnerTriggerPat._persist_decision  s    )(3T:r9   c          
     l    |||t        ||      ||t        t        |      |d	}| j                  |       y )N)	r1   r*   r+   r0   r=   r-   actortoken_present
token_hash)rH   r$   boolr   )	r   r*   r+   r=   r-   r   r   r1   r   s	            r:   _append_auditzOwnerTriggerPat._append_audit  s@     " ))X>,!-0$

 	Fr9   c               
   t        t               | j                         }t        |      }t        |      }	|dk7  rid| }
| j	                  ||t
        |
||      }| j                  ||       | j                  ||t        |
dd|       t        t        |
t        |            S |t        k7  rid| }
| j	                  ||t
        |
||      }| j                  ||       | j                  ||t        |
dd|       t        t        |
t        |            S t        ||      }t        |	|      rid	| }
| j	                  ||t
        |
||      }| j                  ||       | j                  ||t        |
dd|       t        t        |
t        |            S 	 t        | j                        }t#        |      }| j	                  ||t$        d||      }| j                  ||       t'        |	|      }	 t)        j*                  t        |      t(        j,                  t(        j.                  z  t(        j0                  z  d      }	 t)        j2                  |t5        j6                  |||t8        |ddd      j;                  d             t)        j<                  |       	 tA        | jB                  | jD                  |      }tG        || jB                  | jD                  |       tI        |       t(        jJ                  jM                         }||d<   ||d<   	 | jO                  ||      }t]        t_        |dd            }tU        ||      }|j`                  dk7  rWtS        |       d|j`                   d|dd  }
| j                  ||tZ        |
d||       t        tZ        |
t        |            S | j                  ||tb        dd||       t        tb        dt        |            S # t         $ rs}d
| }
| j	                  ||t
        |
||      }| j                  ||       | j                  ||t        |
dd|       t        t        |
t        |            cY d}~S d}~ww xY w# t)        j<                  |       w xY w# t>        $ r? d| }
| j                  ||t        |
d||       t        t        |
t        |            cY S w xY w# tP        $ rv}tS        |       tU        tW        |      jX                   d| |      }| j                  ||tZ        d| d||       t        tZ        d| t        |            cY d}~S d}~ww xY w)uf  queue-head + evidence missing + dedupe + token 검증 후 `/gemini review` 댓글 1회 작성.

        회장 §명시 6 허용 1:1:
          1. body 정확히 `/gemini review` (내부 상수, 사용자 입력 받지 않음)
          2. queue_position == 0 (queue-head only)
          3. gemini_evidence_state == "missing_for_current_head"
          4. dedupe 검증 — (pr, head) 1회 only
          5. token 로딩 성공 (없으면 REJECT)
          6. owner_trigger_decision.json 박제 PASS 후에만 실제 호출

        모든 REJECT 케이스도 audit + decision 박제 — 추적성 유지.
        r   znot_queue_head:queue_position=)r*   r+   r,   r-   r.   r/   FrS   )r*   r+   r=   r-   r   r   r1   )r=   r-   r>   zevidence_not_missing:state=zduplicate_trigger:dedupe_key=ztoken_missing:NzDall_gates_pass:queue_head+evidence_missing+dedupe_clean+token_loadedi  )r0   r*   r+   r=   r1   Trg   rh   rJ   z$duplicate_trigger_marker:dedupe_key=GH_TOKENGITHUB_TOKENz: zgh_runner_exception:stderrzgh_api_failed:rc=z:stderr=   zcomment_posted:/gemini reviewcomment_posted)2r   r   r   r	   r   r   r   r   r   r<   r   r   rH   r   r^   r   r\   rQ   r   r   rX   r   O_CREATO_EXCLO_WRONLYwriterp   rq   r   rN   closeFileExistsErrorr   r   r   r   r   rY   copyr   	Exceptionr   rV   typer3   r   _coerce_streamgetattr
returncoder   )r   r*   r+   r/   r.   r   decision_json_pathr1   r>   r   r-   r,   r0   rP   excr   pass_decisiondedupe_marker_pathfdr   envcp	sanitized
stderr_rawstderr_redacteds                            r:   trigger_gemini_reviewz%OwnerTriggerPat.trigger_gemini_review  s   . 	01[[]/0.)
 Q5n5EFF**#!(&;- + H ""8];#!(#   "(!-0  !$EE23H2IJF**#!(&;- + H ""8];#!(#   "(!-0  %Y9

J74ZLAF**#!(&;- + H ""8];#!(#   "(!-0 	"4??3E< !'
 ++"Y"7) , 
 	}m< 1ZH)	&'

RYY&4B
JJ*4)2(0'6"$ &+"&
 fWo . &dkk4::yItT[[$**iH(. jjooJ#N	$$B0 $GB$$?@
'
E:==A 23(xPTQT@U?VWF#!&"%   "&!-0  	2! 	 	
 #m,
 	
C  	 &cU+F**#!(&;- + H ""8];#!(#   "(!-0 -	L  	 <J<HF#!("%   "(!-0 	F  	   23%c););(<Bse&DeLI#!&-i[9"%   "&-i[9!-0 !	sk   O& 2AQ? AQ% Q? S
 &	Q"/A(QQ"Q"%Q<<Q? ?ASS
	U	A+U>U	U	N)r   GhRunnerr   AuditWriterr   DecisionWriterr   r   r   r   r   Clockr]   r   returnNone)r*   r)   r+   r   r,   r   r-   r   r.   r   r/   r)   r   r(   )r,   r(   rt   r	   r   r   )r*   r)   r+   r   r=   r   r-   r   r   r   r   r   r1   r   r   r   )r*   r)   r+   r   r/   r)   r.   r   r   r	   r   r	   r   r<   )r3   r4   r5   r6   rE   r   r   r   r   r   r   r8   r9   r:   r   r   b  s[   *  +$$ $$ "	$$
 ($$ $$ $$ $$ $$ 
$$N
 
 	

 
 
  #
 
 

*;
  	
      
6W
W
 W

 W
  #W
 W
 !W
 
W
r9   r   c                d    | yt        | t              r| j                  dd      S t        |       S )u   subprocess stdout/stderr 정규화 (merge_queue_executor 동일 패턴).

    None → "", bytes → utf-8 decode, str → 그대로. 직렬화 시점에 bytes 가 dict 에
    박히지 않게 한다.
    rS   rJ   rT   )errors)r   bytesdecoder   )values    r:   r   r     s3     }%||GI|66u:r9   c                *    t        |       dfd}|S )u   기본 audit writer factory — jsonl 1줄 append.

    호출부가 명시적으로 path 를 묶어 callable 을 만들 수 있도록 closure 반환.
    테스트에서는 list-collector callable 로 대체.
    c                    j                   j                  dd       t        j                  t	        |       dd      }j                  dd      5 }|j                  |dz          d d d        y # 1 sw Y   y xY w)	NTrc   Fr   arJ   rj   
)rl   rm   rp   rq   r`   r   r   )r   r   fpru   s      r:   _writerz%default_audit_writer.<locals>._writer  sg    D48zz$v,UdK[[w[/ 	"2HHTD[!	" 	" 	"s   A11A:)r   Mapping[str, Any]r   r   r   )r   r   ru   s     @r:   default_audit_writerr     s     .!F" Nr9   )r   r   )r*   r)   r+   r   r   r   )rP   r   r   r   )rU   z
str | NonerP   r   r   r   )r]   r   r   r   )r,   r(   r   zdict[str, Any])rs   r   rt   r	   r   r   )r0   r   r   r   )r   r	   r0   r   r   r	   )r   r	   r   r   )r   r	   r0   r   r   r   )r   r   r   r   r*   r)   r   z	list[str])
r   Sequence[str]r   r   r   r   r*   r)   r   r   )r   r   r   r   )
r   r   r   r   r   r   r*   r)   r   r   )r   r   r   r   )r   r
   r   r   )r   r	   r   r   )>r6   
__future__r   rL   rp   rX   r{   
subprocessdataclassesr   r   r   r   pathlibr	   typingr
   r   r   r   r   r7   r   r   r   r   r   r   r   r   r!   r#   r$   r%   r   CompletedProcessr   r   r   r   r(   r<   rE   rH   rQ   rV   r^   ra   rx   r}   r   r   r   r   r   r   r   r   r   r   r   r   r8   r9   r:   <module>r     s  , #   	 	  ) '  3 3
 5 C 4 - c , s   *D !3 C 
C " # "  !   / c . 6 # 6  :  9 - c , Xc]GCH$56
8S8SSTS)*D01738,d3T9:S $  " $  D
%B:" %7 "7N*.d1(152  =A
 A
J
r9   