
    #j8                       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m	Z	 ddl
mZmZmZ ddlmZ dZd	Zej$                  j'                  d
dd      Zej$                  j'                  d
dd      ZdZdZdZdZdZdZdZdZd&dZd'dZe G d d             Z d(dZ!d)dZ"d*dZ#d+dZ$d,dZ% G d d e&      Z'd-d!Z(d"edddddd#	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 d.d$Z)g d%Z*y)/u  dispatch.anu_pickup_wake_launcher — task-2729+8 P0-B real wake 결선 (W1).

단일 책임 (single responsibility): WAKE_BUILT argv(이미 sealed ANU key 포함)를
권한 경로로 **실 실행**하는 유일 spawn 책임 모듈. driver=decision-only,
launcher=실행. (real spawn 책임은 오직 이 모듈에만 존재.)

안전 doctrine:
  * raw key 0 — argv 는 stdout/print/log/audit/ledger 어디에도 절대 출력/기록하지
    않는다. argv 의 길이(``argv_len``)만 surface. ``LaunchRecord.to_json()`` 에
    argv/key literal 필드 부재. subprocess args 로만 전달.
  * dry_run 기본 True — 기본 호출은 실행 0(audit-neutral). production ledger/audit
    write 0. audit_path 가 명시 주입된 isolated temp 일 때만 1줄 기록(키/ argv
    미포함). (OWNER_TRIGGER_DRY_RUN_LEDGER_CONTAMINATION 교훈.)
  * fail-closed 우선 — argv None/malformed/non-ANU key/ledger 확인 실패 → launch 0.
    )annotationsN)	dataclass)datetimetimezone)CallableListOptional)CANONICAL_ROOTz+dispatch.anu_pickup_wake_launcher.launch.v1z*dispatch.anu_pickup_wake_launcher.audit.v1memory	p0b_statezwake_launch_ledger.jsonlzwake_launch_audit.jsonlWAKE_LAUNCHEDFAIL_CLOSED_NO_ARGVFAIL_CLOSED_MALFORMEDFAIL_CLOSED_NON_ANU_KEYFAIL_CLOSED_LEDGER_ERRORSKIP_DEDUPEDRY_RUNLAUNCHEDc                 H    t        j                  t        j                        S N)r   nowr   utc     T/home/jay/workspace/.worktrees/task-2729p8-dev5/dispatch/anu_pickup_wake_launcher.py_nowr   4   s    <<%%r   c                z    | j                   | j                  t        j                        } | j	                  d      S )uv  timezone-aware datetime 은 UTC 로 변환한 뒤 Z suffix 부여.

    * dt.tzinfo is not None → dt.astimezone(timezone.utc) 로 UTC 정규화 후 포맷.
    * naive datetime(tzinfo is None) → 명시적 UTC 정책: 이미 UTC 인 것으로 간주하여
      그대로 Z suffix (기본 clock _now() 는 항상 tz-aware UTC 반환). 이 정책을 코드에 고정.
    z%Y-%m-%dT%H:%M:%SZ)tzinfo
astimezoner   r   strftime)dts    r   _isor"   8   s0     
yy]]8<<(;;+,,r   c                  v    e Zd ZU dZded<   ded<   ded<   ded<   ded<   d	Zd
ed<   d	Zded<   d	Zded<   ddZy	)LaunchRecorduV   launcher 결정 결과. ★ argv/key literal 절대 미저장 — argv_len(길이)만.strtstask_idsha256decisionbooldry_runNOptional[str]reasonOptional[int]
returncodeargv_lenc           	         | j                   | j                  | j                  | j                  | j                  | j
                  | j                  | j                  dS )uH   JSON 직렬화. argv/key literal 미포함 보장 (argv_len 만 노출).r&   r'   r(   r)   r+   r-   r/   r0   r2   )selfs    r   to_jsonzLaunchRecord.to_jsonR   sF     ''||kk||kk//	
 		
r   )returndict)	__name__
__module____qualname____doc____annotations__r-   r/   r0   r4   r   r   r   r$   r$   E   sA    `GLKMM FM  $J$"Hm"
r   r$   c                :    t        | t              rt        |       S y)u   argv 를 redaction — 길이(정수)만 반환. argv None/비-list → None.
    ★ argv/key literal 은 어디에도 노출하지 않는다.N)
isinstancelistlenargvs    r   _redact_argvrB   a   s     $4yr   c                    	 | j                  d      }|dz   t        |       k  rt	        | |dz            S y# t        t        f$ r Y yw xY w)uH   argv 에서 ``--key`` 인덱스 다음 원소(값) 반환. 없으면 "".--key    )index
ValueErrorAttributeErrorr?   r%   )rA   idxs     r   _extract_key_valuerK   i   sV    jj! QwT4a=!!	 ' s   6 AAc                    t        | t              r| syt        d | D              syd| vryd| vryt        |       }|j	                         syy)u   argv 구조 검증: list[str] AND ``--cron`` 포함 AND ``--key`` 포함 +
    그 값(--key 다음 원소)이 비어있지 않음.Fc              3  <   K   | ]  }t        |t                y wr   )r=   r%   ).0xs     r   	<genexpr>z$_argv_well_formed.<locals>.<genexpr>y   s     0az!S!0s   z--cronrD   T)r=   r>   allrK   strip)rA   key_vals     r   _argv_well_formedrT   t   sS     dD!0400td &G==?r   c                   	 t        j                  t         j                  j                  |      xs dd       t	        |dd      5 }|j                  t        j                  | d      d	z          |j                          t        j                  |j                                d
d
d
       y
# 1 sw Y   y
xY w# t        $ r Y y
w xY w)u   JSONL 1줄 append (ensure_ascii=False, flush+fsync atomic-ish).
    ★ argv/key literal 절대 미기록 (호출부가 redacted record 만 전달).
    OSError 는 비치명(fail-safe) — 단, dedupe 확인 실패는 fail-closed(별도 처리)..T)exist_okautf-8encodingF)ensure_ascii
N)osmakedirspathdirnameopenwritejsondumpsflushfsyncfilenoOSError)recordr`   fhs      r   _append_jsonlrl      s    
BGGOOD)0S4@$g. 	""HHTZZU;dBCHHJHHRYY[!	" 	" 	"  s1   AB: AB.%B: .B73B: 7B: :	CCc                  	 t        | dd      5 }|D ]  }|j                         }|st        j                  |      }t	        |t
              s<|j                  d      t        k(  sU|j                  d      |k(  sj|j                  d      |k(  s ddd       y 	 ddd       y	# 1 sw Y   y	xY w# t        $ r Y y	t        $ r}t        d
|       |d}~wt        $ r}t        d|       |d}~ww xY w)up  launch_ledger 에서 event==WAKE_LAUNCHED AND 동일 (task_id, sha256) 존재 여부.

    파일 부재(FileNotFoundError) → False (중복 없음, 정상 진행).
    OSError 외 예외 또는 명백한 손상으로 확인 불가 → LedgerError(fail-closed)로 raise.
    ★ try-block hygiene: 단일 ``with open(...)`` 으로 파일 핸들 수명 일원화.
    rrY   rZ   eventr'   r(   NTFu   launch ledger 읽기 불가: u$   launch ledger 손상/파싱 실패: )rb   rR   rd   loadsr=   r6   getEVENT_WAKE_LAUNCHEDFileNotFoundErrorri   _LedgerError	Exception)launch_ledger_pathr'   r(   rk   lineentryexcs          r   _dedupe_launchedrz      s    R$cG< 	   zz|

4(ud+		'*.AA		),7		(+v5	  	  	 ( )	 (    K:3%@AsJ RA#GHcQRsj   B3 >B'B'(B'=B'B'B3 B'B3 'B0,B3 0B3 3	C5>C5CC5!C00C5c                      e Zd ZdZy)rt   uD   dedupe 확인 실패 신호 — FAIL_CLOSED_LEDGER_ERROR 로 매핑.N)r7   r8   r9   r:   r   r   r   rt   rt      s    Nr   rt   c                D    t        j                  | d      j                  S )u  기본 subprocess_runner: argv 를 실행하고 returncode 반환.

    ★ capture_output 하지 않는다 (키 누출 방지 — 출력 미수집). 실제 호출은
    dry_run=False 일 때만 일어난다. 테스트는 mock runner 를 주입한다.
    F)check)
subprocessrunr/   r@   s    r   _default_subprocess_runnerr      s     >>$e,777r   T)r+   rootrv   
audit_pathanu_key_verifiersubprocess_runnerclockc       	        J   |	xs t         }	t         |	             }
|xs$ t        j                  j	                  |t
              }| st        |
||t        |dt        |             S t        |       st        |
||t        |dt        |             S t        |       }|Vt        |      st        |
||t        |d|      S t        |       }	 t         ||            }|st        |
||t        |d|      S 	 t!        |||      rt        |
||t"        |d	|      S 	 |r?t        |
||t(        dd|      }|%t*        dd|j-                         }t/        ||       |S |xs t0        } ||       }t        |
||t2        dd||      }t/        t4        t6        |||
|d|       |'t/        t*        t6        d|j-                         |       |S # t        $ r#}t        |
||t        |d| |      cY d}~S d}~ww xY w# t$        $ r#}t        |
||t&        |d
| |      cY d}~S d}~ww xY w)u  WAKE_BUILT argv 를 안전하게 실 실행(또는 dry-run).

    결정 순서 (fail-closed 우선, 설계 W1 6단계):
      (1) argv None/empty            → FAIL_CLOSED_NO_ARGV
      (2) argv 구조 검증 실패         → FAIL_CLOSED_MALFORMED
      (3) anu_key_verifier 검증 실패  → FAIL_CLOSED_NON_ANU_KEY
      (4) dedupe(WAKE_LAUNCHED) 존재  → SKIP_DEDUPE / 확인 실패 → FAIL_CLOSED_LEDGER_ERROR
      (5) dry_run=True               → DRY_RUN (production write 0)
      (6) dry_run=False              → subprocess 실행 → LAUNCHED

    ★ argv/key 는 어떤 기록에도 미포함 — argv_len(길이)만.
    u1   argv None/빈 리스트 — wake 0 (fail-closed).)r&   r'   r(   r)   r+   r-   r0   uW   argv 구조 이상 (list[str]/--cron/--key+값 검증 실패) — wake 0 (fail-closed).NuM   anu_key_verifier 가 callable 아님(검증 불가) — wake 0 (fail-closed).u2   anu_key_verifier 예외 → wake 0 (fail-closed): u<   --key 값이 ANU key 검증 실패 — wake 0 (fail-closed).)r'   r(   uU   동일 (task_id, sha256) WAKE_LAUNCHED 기록 존재 — 중복 wake 0 (SKIP_DEDUPE).u/   dedupe 확인 실패 → wake 0 (fail-closed): Tu>   dry_run=True — 실행 0, audit-neutral (production write 0).WAKE_DRY_RUN)schemaro   Fu>   모든 게이트 통과 → subprocess 실 실행 (real wake).r2   )r   ro   r'   r(   r&   r/   )r   r"   r^   r`   joinDEFAULT_LAUNCH_LEDGER_RELr$   DECISION_FAIL_CLOSED_NO_ARGVrB   rT   DECISION_FAIL_CLOSED_MALFORMEDcallable DECISION_FAIL_CLOSED_NON_ANU_KEYrK   r*   ru   rz   DECISION_SKIP_DEDUPErt   !DECISION_FAIL_CLOSED_LEDGER_ERRORDECISION_DRY_RUNSCHEMA_AUDITr4   rl   r   DECISION_LAUNCHEDSCHEMA_LAUNCHrr   )rA   r'   r(   r+   r   rv   r   r   r   r   r&   ledgerr0   	key_valueverifiedry   rj   
audit_linerunnerr/   s                       r   launch_waker      s   2 MTE	egBP277<<6O#PF 7617F!$'	
 	
 T"763W( "$'
 	
 D!H #()wv97f!	  't,		,Y78H wv97U!	 
FGFCwv-w3 "  D( 76%tS	
 ! '' .."J
 *j1 <"<FJwv"EOF #($	
 	
 #.AVV^^EUV	
 Me  	wv97KC5Q!	 	6  
766DSEJ	
 	

s<   G 2#G6 	G3G.(G3.G36	H"?HH"H")	r$   r   r   r   r   r   r   r   r   )r5   r   )r!   r   r5   r%   )r5   r.   )rA   	List[str]r5   r%   )r5   r*   )rj   r6   r`   r%   r5   None)rv   r%   r'   r%   r(   r%   r5   r*   )rA   r   r5   int)r'   r%   r(   r%   r+   r*   r   r%   rv   r,   r   r,   r   zOptional[object]r   z$Optional[Callable[[List[str]], int]]r   z Optional[Callable[[], datetime]]r5   r$   )+r:   
__future__r   rd   r^   r~   dataclassesr   r   r   typingr   r   r	   'dispatch.anu_owned_callback_enforcementr
   r   r   r`   r   r   DEFAULT_AUDIT_RELrr   r   r   r   r   r   r   r   r   r"   r$   rB   rK   rT   rl   rz   ru   rt   r   r   __all__r   r   r   <module>r      s   #  	  ! ' + + >; GGLLk5  GGLL;8QR %   5 !8 #<  $> !$   &	- 
 
 
6">O9 O8 (, $)->B.2T T 	T
 T T &T T 'T <T ,T Tn
r   