
    "jR                       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	 ddl
mZmZmZ dZg dZdd	gg d
dZg dZg dZg dZg dddgg dg dg ddZ ej*                  d       ej*                  d       ej*                  d      gZdZdZdZd#dZd$dZd$dZd$dZd$d Zd%d!Ze d"k(  r e! e             y)&u  anu_pickup_preflight_check.py — ANU result-pickup 활성화 전 안전성 점검 (read-only).

절대 제약:
  - systemd service 실행/enable/restart 금지 (is-enabled/is-active 조회만)
  - ANU spawn / subprocess claude 호출 금지
  - 파일 수정/생성 금지 (점검 전용)
  - ANU key raw 값 출력 금지

사용:
  python3 anu_pickup_preflight_check.py [--candidate-root <PATH>]
    )annotationsN)Path)DictListTuplez//home/jay/workspace/.worktrees/task-2729+2-dev2)$dispatch/anu_result_pickup_runner.pydispatch/anu_pickup_driver.pyzdeploy/systemd/anu-pickup.pathz!deploy/systemd/anu-pickup.service scripts/anu_pickup_entrypoint.shPathExistsGlob_OR_PathChangedzUnit=)	ExecStartzType=oneshotWorkingDirectory)anu-pickup.pathanu-pickup.service)pickup_onceanu_runner_pickup_and_firePICKUP_WAKE_BUILT)	scan_onceprocess_oneACTIVATION_FLAG_RELis_activated)flockXDG_RUNTIME_DIRp0b_driver_enabledenabled)
idempotentSKIP_DEDUPESKIP_TERMINALdeduper   lock)zpickup.donezpickup.ackedterminal)NOOP_DISABLEDACTIVATION_FLAGr   r   )sealed_key_loaderENV_ANU_KEYCOKACDIR_KEY_ANU
env_loader)idempotencyr   terminal_markerrunaway_guardkey_sealingz (?<!["\w])[0-9a-f]{16,}(?!["\w])z8(?:export\s+|echo\s+)[A-Z_]*KEY\s*=\s*["\']?[0-9a-f]{8,}z--key\s+[0-9a-f]{12,}PASSFAILCAVEATc            
     B   ddg} g }d}| D ]  }dD ]{  }	 t        j                  dd||gddd      }|j                  j                         xs |j                  j                         xs d	}|j                  d| d| d|        |dvszd}}  	 t        j                  g dddd      }|j                  j                         D 	cg c]  }	d|	j                         v s|	 }
}	|
r|j                  d|
        d}n|j                  d       t        j                         dz  dz  dz  }|j                         rt        |j                  d            ng }|r.|j                  d|D cg c]  }t!        |       c}        d}n|j                  d       |rt"        nt$        }||fS # t
        $ r d
}Y Ot         j                  $ r d}Y ft        $ r}d| }Y d}~yd}~ww xY wc c}	w # t        $ r}|j                  d|        Y d}~d}~ww xY wc c}w )uD   systemctl --user is-enabled/is-active 조회. 실 service 미실행.r   r   T)z
is-enabledz	is-active	systemctl--user   )capture_outputtexttimeoutz(empty)systemctl not foundr4   zerror: Nz  systemctl --user  u    → )z	not-foundinactivedisabledstaticr5   F)r/   r0   zlist-unit-filesz
anu-pickupz  list-unit-files: u,     list-unit-files: NOT_INSTALLED (grep 0건)z  list-unit-files: error: z.configsystemduserzanu-pickup*z  ~/.config/systemd/user/: z8  ~/.config/systemd/user/anu-pickup*: NO_USER_UNIT_FILES)
subprocessrunstdoutstripstderrFileNotFoundErrorTimeoutExpired	Exceptionappend
splitlineslowerr   homeexistslistglobstrr+   r-   )unitslinesall_not_installedunitverbrstatuser2lmatchesuser_unit_globfound_filesfverdicts                  1scripts/harness/v36/anu_pickup_preflight_check.pycheck_systemd_unitsr\   J   s+    45EE */ 	*D'NN (D$7#'dA ((..*Kahhnn.>K) LL.tfAdV5IJ 5 5$)!!	**(7^^6dA
 !ii224R	8Q1RRLL.wi89 %LLGH
 YY[9,y86AN>L>S>S>U$~**=9:[]K2K3PqCF3P2QRS!OP'dVGE>I % /.,, #" '"1#' S  71!5667 4QsZ   AF,7G1 G, G,$,G1 +H
,G):G)G)G$$G),G1 1	H:HHc                   g }g }g }t         D ]  }t        |       |z  }|j                  }|j                         s.|j	                  dt
         d| d       |j	                  |       \|j	                  d|        g }|dk(  rt        }n)|dk(  rt        }n|t        v r
t        |   }n|dk(  rt        }|s	 |j                  dd	
      |D ]  }	d|	v rz|	j                  d      }
t        fd|
D              }t        fd|
D        d      }|rt        nt
        }|j	                  d| d|
d    d|
d    d|rd|xs dz   dz   nd        n0|	v }|rt        nt
        }|j	                  d| d|	 d|rdnd        |r|j	                  | d|	          |s|rt
        nt        }||fS # t        $ r}|j	                  d|        Y d}~d}~ww xY w)u9   후보 5개 파일 존재 확인 + 결선 키워드 grep.  [] u   : 파일 없음z
  [FOUND] anu_result_pickup_runner.pyanu_pickup_driver.pyanu_pickup_entrypoint.shutf-8replaceencodingerrorsz    read error: N_OR_c              3  &   K   | ]  }|v  
 y wN .0pcontents     r[   	<genexpr>z(check_candidate_files.<locals>.<genexpr>   s     <W<   c              3  ,   K   | ]  }|v s|  y wrj   rk   rl   s     r[   rp   z(check_candidate_files.<locals>.<genexpr>   s     $F1gQ$Fs   	z    [z] keyword 'r   z OR    z': zfound ( )zMISSING (neither)foundMISSINGz::)CANDIDATE_FILESr   namerH   rD   r,   RUNNER_KEYWORDSDRIVER_KEYWORDSUNIT_KEYWORDSENTRYPOINT_KEYWORDS	read_textOSErrorsplitanynextr+   )rootrM   missingkeyword_failsrelpathfpathbasenamekwsrS   kwpartsrv   found_kwrR   rZ   ro   s                  @r[   check_candidate_filesr      s
   EG!M" *=T
W$::||~LL3tfBwi?@NN7#z'+, 44!C//!C&)C33%C//79/M  =R<HHV,E<e<<E#$F$FMH%*TFLLx{58*Dq
#CHIR83>Nabd
 'ME%*TFLL5B4se7YbBc!de!((G9Brd);<!=5*=X dDGE>-  /s34s   9F11	G:GGc           	        g }g }t        t        |       j                  d            }t        t        |       j                  d            }||z   }|D cg c]8  }dt        |      v sdt        |      v rdt        |      vrdt        |      vr|: }}|D ]  }	 |j	                  dd	      }	t        |	j                         d
      D ]x  \  }
}|j                         }|j                  d      r(t        D ]H  }|j                  |      }|st        |j                  |             }|j                  d| d|
         x z  |rW|dd D ]  }|j                  d|         t        |      dkD  r!|j                  dt        |      dz
   d       t        }||fS |j                  d       t         }||fS c c}w # t
        $ r Y 3w xY w)uY   소스에서 평문 key 패턴 검출. 실제 key 값 출력 금지 — 위치만 기록.z*.pyz*.shdispatchscriptsz.git__pycache__rc   rd   re   rs   #zRAW_KEY_EXPOSURE_SUSPECTED at :N   z	  [WARN] u
     ... 외 u   건 더u#     raw key 패턴 검출 0건 (PASS))rI   r   rglobrK   r~   r   	enumeraterE   r?   
startswithRAW_KEY_PATTERNSsearchrelative_torD   lenr-   r+   )r   rM   suspectspy_filessh_files	all_filesrY   target_filesr   ro   linenolinestrippedpatmrelsrZ   s                     r[   check_raw_key_exposurer      s   EHDJ$$V,-HDJ$$V,-H8#I #a& IQ$7#a& Q' 	
L   	oowyoIG &g&8&8&:A> 
	LFDzz|H""3'' JJt$e//56COO&DSE6($ST
	$ #2 	*ALL9QC)	*x=2LL:c(mb&8%9AB
 E> 	:;E>G  		s   =F7F<<	G	G	c                   g }g }g d}d|D ]8  }t        |       |z  }|j                         s"	 |j                  dd      z  : t        j                         D ]t  \  }}t        fd|D              }|rt        nt        }	|D 
cg c]	  }
|
v s|
 }}
|j                  d|	 d| d	|rd
| nd| z          |rd|j                  |       v |rt        nt        }||fS # t        $ r Y w xY wc c}
w )uR   runner/driver/entrypoint 소스에서 안전속성 키워드 존재 여부 확인.)r   r	   r
   rt   rc   rd   re   c              3  &   K   | ]  }|v  
 y wrj   rk   )rm   r   combineds     r[   rp   z(check_safety_keywords.<locals>.<genexpr>  s     52h5rq   r^   r_   z: zfound u   MISSING — expected one of )
r   rH   r~   r   SAFETY_KEYWORDSitemsr   r+   r,   rD   )r   rM   failscheck_filesr   r   propr   	found_anyrR   r   	found_kwsrZ   r   s                @r[   check_safety_keywordsr      s'   EEK H T
W$<<>EOOWYOOO	 %**, 		c555	""%8BxR8	8&D6$'0	{#8TUXTY6Z\	
 LL	 dGE>   9s   C&		C5C5&	C21C2c                   g }g }t        |       dz  dz  }|j                         rV|j                  dd      }d|v xs
 d|v xr d|v }|j                  d	|rt        nt
         d
       |j                  |       n*|j                  d	t         d       |j                  d       t        |       dz  dz  }|j                         r|j                  dd      }d|v xr d|v }|j                  d	|rt        nt
         d       |j                  |       d|vxr d|v}	|j                  d	|	rt        nt
         d       |j                  |	       d|v xs d|v }
|j                  d	|
rt        nt
         d       |j                  |
       n|j                  d	t         d       t        |       dz  dz  }|j                         r|j                  dd      }d|vxr d|v}|j                  d	|rt        nt
         d       |j                  |       d|v xr
 d|v xr d|v}|j                  d	|rt        nt         d       |j                  d       t        |      rt        nt
        }||fS )u  driver/entrypoint 의 실 ANU spawn 0 가드 확인.
    - activation flag 가드 (default DISABLED)
    - pickup_fn 주입 가능 (mock 교체 가능)
    - entrypoint FLAG_VALUE != 'enabled' → exit 0 가드
    - driver 는 argv 를 실행하지 않음 (P0-a dry_run 주석)
    r   rb   rc   rd   re   zFLAG_VALUE" != "enabled"
FLAG_VALUEzexit 0r^   z=] entrypoint: activation flag guard (exit 0 when not enabled)u:   ] anu_pickup_entrypoint.sh 파일 없음 — 점검 스킵Tr   ra   VERDICT_NOOP_DISABLEDr   z4] driver scan_once: NOOP_DISABLED when not activatedzimport subprocesszsubprocess.u:   ] driver: subprocess import/호출 0건 (실 spawn 없음)zpickup_fn=Nonezpickup_fn =u9   ] driver: pickup_fn 주입 가능 (테스트/mock 교체)u/   ] anu_pickup_driver.py 없음 — 점검 스킵r`   u:   ] runner: subprocess import/호출 0건 (실 spawn 없음)argvzwake_built=Trueu   ] runner: WAKE_BUILT 는 argv dict 반환만 (실 실행 없음 — '실 발사는 권한 있는 ANU 세션이 수행' 주석 확인))r   rH   r~   rD   r+   r,   r-   all)r   rM   checksepep_texthas_flag_guarddriverdrv_texthas_noophas_no_subprocesshas_injectionrunnerrun_texthas_no_subprocess_runnerhas_argv_onlyrZ   s                   r[   check_dry_run_guardr     s    EF 
di	"<	<B	yy{,,	,B4? M)W4LW9L 	s>4t<<yz{n%s6("\]^d $Z*$'==F}}##WY#G*h6U>X;Us8466jklh  x/ .X- 	 	s#44$??yz{'( )H4Q8Qs=4d;;tuvm$s6("QRS $Z*$'DDF}}##WY#G  x/ .X- 	! 	s#;4F  GA  B  	C./ h 4!X-4#83 	
 	-$V4 5g h	
 	d&kdtGE>    c                    t        j                  dd      } | j                  dt        dt         d       | j	                         }|j
                  }t        d|        t        d	t        j                  j                  |              t                i }t        d
       t               \  }}|D ]  }t        |        t        d|        ||d|d<   t                t        d       t        |      \  }}|D ]  }t        |        t        d|        ||d|d<   t                t        d       t        |      \  }	}
|
D ]  }t        |        t        d|	        |	|
d|d<   t                t        d       t        |      \  }}|D ]  }t        |        t        d|        ||d|d<   t                t        d       t        |      \  }}|D ]  }t        |        t        d|        ||d|d<   t                t        d       ||j                         D ci c]  \  }}||d    c}}t         d}t#        d |j%                         D              }t#        d |j%                         D              }|r
t&        |d<   n|r	t(        |d<   t        t+        j,                  |dd             t                |d   }|t&        k(  rt        d       y |t(        k(  rt        d!       y"t        d#       y"c c}}w )$Nanu_pickup_preflight_checkuS   ANU result-pickup 활성화 전 안전성 점검 (read-only, 실 service 미실행))progdescriptionz--candidate-rootu'   점검 대상 worktree 루트 (기본: ru   )defaulthelpz[preflight] candidate-root: z[preflight] root exists: u0   === [1] systemd user unit 상태 (read-only) ===z  => )rZ   detailsystemd_unitsu8   === [2] 후보 파일 존재 + 결선 키워드 grep ===candidate_filesu,   === [3] ANU key raw 노출 정적 스캔 ===raw_key_exposureuR   === [4] 안전속성 키워드 (idempotency/lock/terminal/runaway/key-sealing) ===safety_keywordsu3   === [5] dry-run / 실 ANU spawn 0 가드 확인 ===dry_run_guardu   === 종합 preflight 결과 ===rZ   )candidate_rootr   overallc              3  4   K   | ]  }|d    t         k(    ywrZ   N)r,   rm   vs     r[   rp   zmain.<locals>.<genexpr>  s     BA1Y<4'B   c              3  4   K   | ]  }|d    t         k(    ywr   )r-   r   s     r[   rp   zmain.<locals>.<genexpr>  s     FQy\V+Fr   r   F   )ensure_asciiindentuG   [preflight] OVERALL: FAIL — 활성화 전 필수 항목 해결 필요rs   uI   [preflight] OVERALL: CAVEAT — 경고 항목 확인 후 활성화 검토r   u2   [preflight] OVERALL: PASS — 모든 항목 통과)argparseArgumentParseradd_argumentDEFAULT_CANDIDATE_ROOT
parse_argsr   printospathisdirr\   r   r   r   r   r   r+   r   valuesr,   r-   jsondumps)apargsr   resultsv1l1r   v2l2v3l3v4l4v5l5kr   summaryany_fail
any_caveatr   s                        r[   mainr   a  s   		 	 )i
B OO&67M6NaP  
 ==?DD	(
/0	%bggmmD&9%:
;<	G!G 

<= "FB d	E",+-<GO	G 

DE"4(FB d	E",-/2!>G	G 

89#D)FB d	E",.0B"?G	G 

^_"4(FB d	E",-/2!>G	G 

?@ &FB d	E",+-<GO	G 

+,/6}}?tq!1a	l??G B1ABBHFW^^5EFFJ!		#		$**W5
;<	Gi G$WX	F	YZBC/ @s   .L__main__)returnTuple[str, List[str]])r   rK   r   r   )r   int)"__doc__
__future__r   r   r   r   rer<   pathlibr   typingr   r   r   r   rx   r|   rz   r{   r}   r   compiler   r+   r,   r-   r\   r   r   r   r   r   __name__
SystemExitrk   r   r[   <module>r	     s  
 #   	 	   $ $ K  ;GDK
 UUS  P(Ba] BJJ23BJJJKBJJ'(  	2p3r-f!NFX[| z
TV
 r   