
    #j5                       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-2729+8-dev4/dispatch/anu_pickup_wake_launcher.py_nowr   4   s    <<%%r   c                $    | j                  d      S )Nz%Y-%m-%dT%H:%M:%SZ)strftime)dts    r   _isor    8   s    ;;+,,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-   r.   r0   )selfs    r   to_jsonzLaunchRecord.to_jsonJ   sF     ''||kk||kk//	
 		
r   )returndict)	__name__
__module____qualname____doc____annotations__r+   r-   r.   r2   r   r   r   r"   r"   =   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_argvr@   Y   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#   )r?   idxs     r   _extract_key_valuerI   a   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>q   s     0az!S!0s   z--cronrB   T)r;   r<   allrI   strip)r?   key_vals     r   _argv_well_formedrR   l   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_jsonlrj   }   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               6   	 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# t        $ r Y yt        $ r}t        d|       |d}~ww xY w# 1 sw Y   yxY w# t        $ r}t        d|       |d}~wt        $ r}t        d|       |d}~ww xY w)u  launch_ledger 에서 event==WAKE_LAUNCHED AND 동일 (task_id, sha256) 존재 여부.

    파일 부재(FileNotFoundError) → False (중복 없음, 정상 진행).
    OSError 외 예외 또는 명백한 손상으로 확인 불가 → LedgerError(fail-closed)로 raise.
    rrW   rX   Fu   launch ledger 읽기 불가: Neventr%   r&   Tu"   launch ledger 읽기 중 OSError: u$   launch ledger 손상/파싱 실패: )r`   FileNotFoundErrorrg   _LedgerErrorrP   rb   loadsr;   r4   getEVENT_WAKE_LAUNCHED	Exception)launch_ledger_pathr%   r&   ri   exclineentrys          r   _dedupe_launchedrx      s.   K$cG<R 	   zz|

4(ud+		'*.AA		),7		(+v5	  	  	 " /   K:3%@AsJK	 " 	  P?uEFCO RA#GHcQRs   B* C  >CC+C CCC  C!C  *	C5C=CCCC  C   	D)C88DDDc                      e Zd ZdZy)ro   uD   dedupe 확인 실패 신호 — FAIL_CLOSED_LEDGER_ERROR 로 매핑.N)r5   r6   r7   r8   r   r   r   ro   ro      s    Nr   ro   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)   rootrt   
audit_pathanu_key_verifiersubprocess_runnerclockc       	           |	xs t         }	t         |	             }
|xs$ t        j                  j	                  |t
              }| st        |
||t        |dt        |             S t        |       st        |
||t        |dt        |             S t        |       }t        |      r5t        |       }	 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+   r.   uW   argv 구조 이상 (list[str]/--cron/--key+값 검증 실패) — wake 0 (fail-closed).u2   anu_key_verifier 예외 → wake 0 (fail-closed): Nu<   --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)schemarm   Fu>   모든 게이트 통과 → subprocess 실 실행 (real wake).r0   )r   rm   r%   r&   r$   r-   )r   r    r\   r^   joinDEFAULT_LAUNCH_LEDGER_RELr"   DECISION_FAIL_CLOSED_NO_ARGVr@   rR   DECISION_FAIL_CLOSED_MALFORMEDcallablerI   r(   rs    DECISION_FAIL_CLOSED_NON_ANU_KEYrx   DECISION_SKIP_DEDUPEro   !DECISION_FAIL_CLOSED_LEDGER_ERRORDECISION_DRY_RUNSCHEMA_AUDITr2   rj   r~   DECISION_LAUNCHEDSCHEMA_LAUNCHrr   )r?   r%   r&   r)   r   rt   r   r   r   r   r$   ledgerr.   	key_valueverifiedru   rh   
audit_linerunnerr-   s                       r   launch_waker      su   2 MTE	egBP277<<6O#PF 7617F!$'	
 	
 T"763W( "$'
 	
 D!H  !&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<   0F/ #G /	G8GGG	H
'H?H
H
)	r"   r   r   r   r   r   r   r   r   )r3   r   )r   r   r3   r#   )r3   r,   )r?   	List[str]r3   r#   )r3   r(   )rh   r4   r^   r#   r3   None)rt   r#   r%   r#   r&   r#   r3   r(   )r?   r   r3   int)r%   r#   r&   r#   r)   r(   r   r#   rt   r*   r   r*   r   zOptional[Callable[[str], bool]]r   z$Optional[Callable[[List[str]], int]]r   z Optional[Callable[[], datetime]]r3   r"   )+r8   
__future__r   rb   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"   r@   rI   rR   rj   rx   rs   ro   r~   r   __all__r   r   r   <module>r      s   #  	  ! ' + + >; GGLLk5  GGLL;8QR %   5 !8 #<  $> !$   &-
 
 
 
6"DO9 O8 (, $8<>B.2L L 	L
 L L &L L 6L <L ,L L^
r   